Merge "Move FloatingToolbarPopup out from FloatingToolbar"
diff --git a/ApiDocs.bp b/ApiDocs.bp
index c181646..1e5ae7b 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -127,6 +127,7 @@
":framework-sdkextensions-sources",
":framework-statsd-sources",
":framework-tethering-srcs",
+ ":framework-uwb-updatable-sources",
":framework-wifi-updatable-sources",
":ike-srcs",
":updatable-media-srcs",
@@ -180,6 +181,7 @@
":framework-sdkextensions{.public.stubs.source}",
":framework-statsd{.public.stubs.source}",
":framework-tethering{.public.stubs.source}",
+ ":framework-uwb{.public.stubs.source}",
":framework-wifi{.public.stubs.source}",
],
aidl: {
@@ -192,6 +194,43 @@
},
}
+// This produces the same annotations.zip as framework-doc-stubs, but by using
+// outputs from individual modules instead of all the source code.
+genrule {
+ name: "sdk-annotations.zip",
+ srcs: [
+ ":android-non-updatable-doc-stubs{.annotations.zip}",
+
+ // Conscrypt and i18n currently do not enable annotations
+ // ":conscrypt.module.public.api{.public.annotations.zip}",
+ // ":i18n.module.public.api{.public.annotations.zip}",
+
+ // Modules that enable annotations below
+ ":android.net.ipsec.ike{.public.annotations.zip}",
+ ":art.module.public.api{.public.annotations.zip}",
+ ":framework-appsearch{.public.annotations.zip}",
+ ":framework-connectivity{.public.annotations.zip}",
+ ":framework-graphics{.public.annotations.zip}",
+ ":framework-media{.public.annotations.zip}",
+ ":framework-mediaprovider{.public.annotations.zip}",
+ ":framework-permission{.public.annotations.zip}",
+ ":framework-permission-s{.public.annotations.zip}",
+ ":framework-scheduling{.public.annotations.zip}",
+ ":framework-sdkextensions{.public.annotations.zip}",
+ ":framework-statsd{.public.annotations.zip}",
+ ":framework-tethering{.public.annotations.zip}",
+ ":framework-uwb{.public.annotations.zip}",
+ ":framework-wifi{.public.annotations.zip}",
+ ],
+ out: ["annotations.zip"],
+ tools: [
+ "merge_annotation_zips",
+ "soong_zip",
+ ],
+ cmd: "$(location merge_annotation_zips) $(genDir)/out $(in) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out",
+}
+
/////////////////////////////////////////////////////////////////////
// API docs are created from the generated stub source files
// using droiddoc
diff --git a/apct-tests/perftests/autofill/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml
index 57595a2..de2a3f2 100644
--- a/apct-tests/perftests/autofill/AndroidManifest.xml
+++ b/apct-tests/perftests/autofill/AndroidManifest.xml
@@ -16,6 +16,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.autofill">
+ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 646a027..dfe2101 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -29,6 +29,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ComponentName;
@@ -63,6 +66,25 @@
public class JobInfo implements Parcelable {
private static String TAG = "JobInfo";
+ /**
+ * Disallow setting a deadline (via {@link Builder#setOverrideDeadline(long)}) for prefetch
+ * jobs ({@link Builder#setPrefetch(boolean)}. Prefetch jobs are meant to run close to the next
+ * app launch, so there's no good reason to allow them to have deadlines.
+ *
+ * We don't drop or cancel any previously scheduled prefetch jobs with a deadline.
+ * There's no way for an app to keep a perpetually scheduled prefetch job with a deadline.
+ * Prefetch jobs with a deadline will run and apps under this restriction won't be able to
+ * schedule new prefetch jobs with a deadline. If a job is rescheduled (by providing
+ * {@code true} via {@link JobService#jobFinished(JobParameters, boolean)} or
+ * {@link JobService#onStopJob(JobParameters)}'s return value),the deadline is dropped.
+ * Periodic jobs require all constraints to be met, so there's no issue with their deadlines.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long DISALLOW_DEADLINES_FOR_PREFETCH_JOBS = 194532703L;
+
/** @hide */
@IntDef(prefix = { "NETWORK_TYPE_" }, value = {
NETWORK_TYPE_NONE,
@@ -185,62 +207,65 @@
public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;
/**
- * Default of {@link #getPriority}.
+ * Default of {@link #getBias}.
* @hide
*/
- public static final int PRIORITY_DEFAULT = 0;
+ public static final int BIAS_DEFAULT = 0;
/**
- * Value of {@link #getPriority} for expedited syncs.
+ * Value of {@link #getBias} for expedited syncs.
* @hide
*/
- public static final int PRIORITY_SYNC_EXPEDITED = 10;
+ public static final int BIAS_SYNC_EXPEDITED = 10;
/**
- * Value of {@link #getPriority} for first time initialization syncs.
+ * Value of {@link #getBias} for first time initialization syncs.
* @hide
*/
- public static final int PRIORITY_SYNC_INITIALIZATION = 20;
+ public static final int BIAS_SYNC_INITIALIZATION = 20;
/**
- * Value of {@link #getPriority} for a BFGS app (overrides the supplied
- * JobInfo priority if it is smaller).
+ * Value of {@link #getBias} for a BFGS app (overrides the supplied
+ * JobInfo bias if it is smaller).
* @hide
*/
- public static final int PRIORITY_BOUND_FOREGROUND_SERVICE = 30;
+ public static final int BIAS_BOUND_FOREGROUND_SERVICE = 30;
/** @hide For backward compatibility. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int PRIORITY_FOREGROUND_APP = PRIORITY_BOUND_FOREGROUND_SERVICE;
+ public static final int PRIORITY_FOREGROUND_APP = BIAS_BOUND_FOREGROUND_SERVICE;
/**
- * Value of {@link #getPriority} for a FG service app (overrides the supplied
- * JobInfo priority if it is smaller).
+ * Value of {@link #getBias} for a FG service app (overrides the supplied
+ * JobInfo bias if it is smaller).
* @hide
*/
+ public static final int BIAS_FOREGROUND_SERVICE = 35;
+
+ /** @hide For backward compatibility. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int PRIORITY_FOREGROUND_SERVICE = 35;
+ public static final int PRIORITY_FOREGROUND_SERVICE = BIAS_FOREGROUND_SERVICE;
/**
- * Value of {@link #getPriority} for the current top app (overrides the supplied
- * JobInfo priority if it is smaller).
+ * Value of {@link #getBias} for the current top app (overrides the supplied
+ * JobInfo bias if it is smaller).
* @hide
*/
- public static final int PRIORITY_TOP_APP = 40;
+ public static final int BIAS_TOP_APP = 40;
/**
- * Adjustment of {@link #getPriority} if the app has often (50% or more of the time)
+ * Adjustment of {@link #getBias} if the app has often (50% or more of the time)
* been running jobs.
* @hide
*/
- public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
+ public static final int BIAS_ADJ_OFTEN_RUNNING = -40;
/**
- * Adjustment of {@link #getPriority} if the app has always (90% or more of the time)
+ * Adjustment of {@link #getBias} if the app has always (90% or more of the time)
* been running jobs.
* @hide
*/
- public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
+ public static final int BIAS_ADJ_ALWAYS_RUNNING = -80;
/**
* Indicates that the implementation of this job will be using
@@ -333,7 +358,7 @@
private final long flexMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
- private final int priority;
+ private final int mBias;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final int flags;
@@ -381,8 +406,8 @@
}
/** @hide */
- public int getPriority() {
- return priority;
+ public int getBias() {
+ return mBias;
}
/** @hide */
@@ -718,7 +743,7 @@
if (backoffPolicy != j.backoffPolicy) {
return false;
}
- if (priority != j.priority) {
+ if (mBias != j.mBias) {
return false;
}
if (flags != j.flags) {
@@ -765,7 +790,7 @@
hashCode = 31 * hashCode + Long.hashCode(flexMillis);
hashCode = 31 * hashCode + Long.hashCode(initialBackoffMillis);
hashCode = 31 * hashCode + backoffPolicy;
- hashCode = 31 * hashCode + priority;
+ hashCode = 31 * hashCode + mBias;
hashCode = 31 * hashCode + flags;
return hashCode;
}
@@ -804,7 +829,7 @@
backoffPolicy = in.readInt();
hasEarlyConstraint = in.readInt() == 1;
hasLateConstraint = in.readInt() == 1;
- priority = in.readInt();
+ mBias = in.readInt();
flags = in.readInt();
}
@@ -835,7 +860,7 @@
backoffPolicy = b.mBackoffPolicy;
hasEarlyConstraint = b.mHasEarlyConstraint;
hasLateConstraint = b.mHasLateConstraint;
- priority = b.mPriority;
+ mBias = b.mBias;
flags = b.mFlags;
}
@@ -880,7 +905,7 @@
out.writeInt(backoffPolicy);
out.writeInt(hasEarlyConstraint ? 1 : 0);
out.writeInt(hasLateConstraint ? 1 : 0);
- out.writeInt(priority);
+ out.writeInt(mBias);
out.writeInt(this.flags);
}
@@ -998,7 +1023,7 @@
private Bundle mTransientExtras = Bundle.EMPTY;
private ClipData mClipData;
private int mClipGrantFlags;
- private int mPriority = PRIORITY_DEFAULT;
+ private int mBias = BIAS_DEFAULT;
private int mFlags;
// Requirements.
private int mConstraintFlags;
@@ -1052,7 +1077,7 @@
mTransientExtras = job.getTransientExtras();
mClipData = job.getClipData();
mClipGrantFlags = job.getClipGrantFlags();
- mPriority = job.getPriority();
+ mBias = job.getBias();
mFlags = job.getFlags();
mConstraintFlags = job.getConstraintFlags();
mNetworkRequest = job.getRequiredNetwork();
@@ -1078,9 +1103,17 @@
}
/** @hide */
+ @NonNull
+ public Builder setBias(int bias) {
+ mBias = bias;
+ return this;
+ }
+
+ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Builder setPriority(int priority) {
- mPriority = priority;
+ // No-op for invalid calls. This wasn't a supported API before Tiramisu, so anyone
+ // calling this that isn't targeting T isn't guaranteed a behavior change.
return this;
}
@@ -1445,7 +1478,9 @@
/**
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
- * only the guarantee that it will be executed at most once within this interval.
+ * only the guarantee that it will be executed at most once within this interval, as long
+ * as the constraints are satisfied. If the constraints are not satisfied within this
+ * interval, the job will wait until the constraints are satisfied.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
@@ -1641,6 +1676,9 @@
* the specific user of this device. For example, fetching top headlines
* of interest to the current user.
* <p>
+ * Starting with Android version {@link Build.VERSION_CODES#TIRAMISU}, prefetch jobs are
+ * not allowed to have deadlines (set via {@link #setOverrideDeadline(long)}.
+ * <p>
* The system may use this signal to relax the network constraints you
* originally requested, such as allowing a
* {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
@@ -1675,6 +1713,11 @@
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
+ return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS));
+ }
+
+ /** @hide */
+ public JobInfo build(boolean disallowPrefetchDeadlines) {
// This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
// check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -1683,7 +1726,7 @@
" setRequiresDeviceIdle is an error.");
}
JobInfo jobInfo = new JobInfo(this);
- jobInfo.enforceValidity();
+ jobInfo.enforceValidity(disallowPrefetchDeadlines);
return jobInfo;
}
@@ -1701,7 +1744,7 @@
/**
* @hide
*/
- public final void enforceValidity() {
+ public final void enforceValidity(boolean disallowPrefetchDeadlines) {
// Check that network estimates require network type and are reasonable values.
if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
&& networkRequest == null) {
@@ -1725,9 +1768,10 @@
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
+ final boolean hasDeadline = maxExecutionDelayMillis != 0L;
// Check that a deadline was not set on a periodic job.
if (isPeriodic) {
- if (maxExecutionDelayMillis != 0L) {
+ if (hasDeadline) {
throw new IllegalArgumentException(
"Can't call setOverrideDeadline() on a periodic job.");
}
@@ -1741,6 +1785,12 @@
}
}
+ // Prefetch jobs should not have deadlines
+ if (disallowPrefetchDeadlines && hasDeadline && (flags & FLAG_PREFETCH) != 0) {
+ throw new IllegalArgumentException(
+ "Can't call setOverrideDeadline() on a prefetch job.");
+ }
+
if (isPersisted) {
// We can't serialize network specifiers
if (networkRequest != null
@@ -1790,27 +1840,27 @@
}
/**
- * Convert a priority integer into a human readable string for debugging.
+ * Convert a bias integer into a human readable string for debugging.
* @hide
*/
- public static String getPriorityString(int priority) {
- switch (priority) {
- case PRIORITY_DEFAULT:
- return PRIORITY_DEFAULT + " [DEFAULT]";
- case PRIORITY_SYNC_EXPEDITED:
- return PRIORITY_SYNC_EXPEDITED + " [SYNC_EXPEDITED]";
- case PRIORITY_SYNC_INITIALIZATION:
- return PRIORITY_SYNC_INITIALIZATION + " [SYNC_INITIALIZATION]";
- case PRIORITY_BOUND_FOREGROUND_SERVICE:
- return PRIORITY_BOUND_FOREGROUND_SERVICE + " [BFGS_APP]";
- case PRIORITY_FOREGROUND_SERVICE:
- return PRIORITY_FOREGROUND_SERVICE + " [FGS_APP]";
- case PRIORITY_TOP_APP:
- return PRIORITY_TOP_APP + " [TOP_APP]";
+ public static String getBiasString(int bias) {
+ switch (bias) {
+ case BIAS_DEFAULT:
+ return BIAS_DEFAULT + " [DEFAULT]";
+ case BIAS_SYNC_EXPEDITED:
+ return BIAS_SYNC_EXPEDITED + " [SYNC_EXPEDITED]";
+ case BIAS_SYNC_INITIALIZATION:
+ return BIAS_SYNC_INITIALIZATION + " [SYNC_INITIALIZATION]";
+ case BIAS_BOUND_FOREGROUND_SERVICE:
+ return BIAS_BOUND_FOREGROUND_SERVICE + " [BFGS_APP]";
+ case BIAS_FOREGROUND_SERVICE:
+ return BIAS_FOREGROUND_SERVICE + " [FGS_APP]";
+ case BIAS_TOP_APP:
+ return BIAS_TOP_APP + " [TOP_APP]";
- // PRIORITY_ADJ_* are adjustments and not used as real priorities.
+ // BIAS_ADJ_* are adjustments and not used as real priorities.
// No need to convert to strings.
}
- return priority + " [UNKNOWN]";
+ return bias + " [UNKNOWN]";
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 143c0f1..18f63b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -86,7 +86,7 @@
/**
* Set of possible execution types that a job can have. The actual type(s) of a job are based
- * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before
+ * on the {@link JobStatus#lastEvaluatedBias}, which is typically evaluated right before
* execution (when we're trying to determine which jobs to run next) and won't change after the
* job has started executing.
*
@@ -527,9 +527,8 @@
/**
* Takes jobs from pending queue and runs them on available contexts.
- * If no contexts are available, preempts lower priority jobs to
- * run higher priority ones.
- * Lock on mJobs before calling this function.
+ * If no contexts are available, preempts lower bias jobs to run higher bias ones.
+ * Lock on mLock before calling this function.
*/
@GuardedBy("mLock")
void assignJobsToContextsLocked() {
@@ -596,9 +595,9 @@
}
// Find an available slot for nextPending. The context should be available OR
- // it should have lowest priority among all running jobs
+ // it should have the lowest bias among all running jobs
// (sharing the same Uid as nextPending)
- int minPriorityForPreemption = Integer.MAX_VALUE;
+ int minBiasForPreemption = Integer.MAX_VALUE;
int selectedContextId = -1;
int allWorkTypes = getJobWorkTypes(nextPending);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
@@ -631,7 +630,7 @@
// Maybe stop the job if it has had its day in the sun. Don't let a different
// app preempt jobs started for TOP apps though.
final String reason = shouldStopJobReason[j];
- if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP
+ if (job.lastEvaluatedBias < JobInfo.BIAS_TOP_APP
&& reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
// Right now, the way the code is set up, we don't need to explicitly
@@ -643,19 +642,19 @@
continue;
}
- final int jobPriority = mService.evaluateJobPriorityLocked(job);
- if (jobPriority >= nextPending.lastEvaluatedPriority) {
+ final int jobBias = mService.evaluateJobBiasLocked(job);
+ if (jobBias >= nextPending.lastEvaluatedBias) {
continue;
}
- if (minPriorityForPreemption > jobPriority) {
+ if (minBiasForPreemption > jobBias) {
// Step down the preemption threshold - wind up replacing
- // the lowest-priority running job
- minPriorityForPreemption = jobPriority;
+ // the lowest-bias running job
+ minBiasForPreemption = jobBias;
selectedContextId = j;
- preemptReason = "higher priority job found";
+ preemptReason = "higher bias job found";
preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
- // In this case, we're just going to preempt a low priority job, we're not
+ // In this case, we're just going to preempt a low bias job, we're not
// actually starting a job, so don't set startingJob.
}
}
@@ -749,7 +748,7 @@
continue;
}
- pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending);
+ pending.lastEvaluatedBias = mService.evaluateJobBiasLocked(pending);
if (updateCounter) {
mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
@@ -773,7 +772,7 @@
@GuardedBy("mLock")
private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
- if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+ if (jobStatus.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
// Don't restrict top apps' concurrency. The work type limits will make sure
// background jobs have slots to run if the system has resources.
return false;
@@ -866,9 +865,9 @@
// Preemption case needs special care.
updateNonRunningPrioritiesLocked(pendingJobs, false);
- JobStatus highestPriorityJob = null;
- int highPriWorkType = workType;
- int highPriAllWorkTypes = workType;
+ JobStatus highestBiasJob = null;
+ int highBiasWorkType = workType;
+ int highBiasAllWorkTypes = workType;
JobStatus backupJob = null;
int backupWorkType = WORK_TYPE_NONE;
int backupAllWorkTypes = WORK_TYPE_NONE;
@@ -893,40 +892,39 @@
}
// Only bypass the concurrent limit if we had preempted the job due to a higher
- // priority job.
- if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority
+ // bias job.
+ if (nextPending.lastEvaluatedBias <= jobStatus.lastEvaluatedBias
&& isPkgConcurrencyLimitedLocked(nextPending)) {
continue;
}
- if (highestPriorityJob == null
- || highestPriorityJob.lastEvaluatedPriority
- < nextPending.lastEvaluatedPriority) {
- highestPriorityJob = nextPending;
+ if (highestBiasJob == null
+ || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
+ highestBiasJob = nextPending;
} else {
continue;
}
// In this path, we pre-empted an existing job. We don't fully care about the
- // reserved slots. We should just run the highest priority job we can find,
+ // reserved slots. We should just run the highest bias job we can find,
// though it would be ideal to use an available WorkType slot instead of
// overloading slots.
- highPriAllWorkTypes = getJobWorkTypes(nextPending);
- final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
+ highBiasAllWorkTypes = getJobWorkTypes(nextPending);
+ final int workAsType = mWorkCountTracker.canJobStart(highBiasAllWorkTypes);
if (workAsType == WORK_TYPE_NONE) {
// Just use the preempted job's work type since this new one is technically
// replacing it anyway.
- highPriWorkType = workType;
+ highBiasWorkType = workType;
} else {
- highPriWorkType = workAsType;
+ highBiasWorkType = workAsType;
}
}
- if (highestPriorityJob != null) {
+ if (highestBiasJob != null) {
if (DEBUG) {
Slog.d(TAG, "Running job " + jobStatus + " as preemption");
}
- mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
- startJobLocked(worker, highestPriorityJob, highPriWorkType);
+ mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
+ startJobLocked(worker, highestBiasJob, highBiasWorkType);
} else {
if (DEBUG) {
Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
@@ -944,11 +942,10 @@
updateCounterConfigLocked();
updateNonRunningPrioritiesLocked(pendingJobs, false);
- // This slot is now free and we have pending jobs. Start the highest priority job we
- // find.
- JobStatus highestPriorityJob = null;
- int highPriWorkType = workType;
- int highPriAllWorkTypes = workType;
+ // This slot is now free and we have pending jobs. Start the highest bias job we find.
+ JobStatus highestBiasJob = null;
+ int highBiasWorkType = workType;
+ int highBiasAllWorkTypes = workType;
for (int i = 0; i < pendingJobs.size(); i++) {
final JobStatus nextPending = pendingJobs.get(i);
@@ -965,23 +962,22 @@
if (workAsType == WORK_TYPE_NONE) {
continue;
}
- if (highestPriorityJob == null
- || highestPriorityJob.lastEvaluatedPriority
- < nextPending.lastEvaluatedPriority) {
- highestPriorityJob = nextPending;
- highPriWorkType = workAsType;
- highPriAllWorkTypes = allWorkTypes;
+ if (highestBiasJob == null
+ || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
+ highestBiasJob = nextPending;
+ highBiasWorkType = workAsType;
+ highBiasAllWorkTypes = allWorkTypes;
}
}
- if (highestPriorityJob != null) {
+ if (highestBiasJob != null) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
if (DEBUG) {
Slog.d(TAG, "About to run job: " + jobStatus);
}
- mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
- startJobLocked(worker, highestPriorityJob, highPriWorkType);
+ mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
+ startJobLocked(worker, highestBiasJob, highBiasWorkType);
}
}
@@ -1255,9 +1251,9 @@
int classification = 0;
if (shouldRunAsFgUserJob(js)) {
- if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+ if (js.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
classification |= WORK_TYPE_TOP;
- } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) {
+ } else if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
classification |= WORK_TYPE_FGS;
} else {
classification |= WORK_TYPE_BG;
@@ -1267,7 +1263,7 @@
classification |= WORK_TYPE_EJ;
}
} else {
- if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE
+ if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
|| js.shouldTreatAsExpeditedJob()) {
classification |= WORK_TYPE_BGUSER_IMPORTANT;
}
@@ -1630,7 +1626,7 @@
for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
int wt = mNumActuallyReservedSlots.keyAt(i);
if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
- // Try to give this slot to the highest priority one within its limits.
+ // Try to give this slot to the highest bias one within its limits.
int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
+ mNumPendingJobs.get(wt);
if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
index 1f0900d..bdaaf39 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -481,7 +481,7 @@
final long now = sUptimeMillisClock.millis();
job.madeActive = now;
rebatchIfNeeded(now);
- if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+ if (job.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
} else {
mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
@@ -492,14 +492,14 @@
public void noteInactive(JobStatus job, int stopReason, String debugReason) {
final long now = sUptimeMillisClock.millis();
- if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+ if (job.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
stopReason);
} else {
mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
}
rebatchIfNeeded(now);
- addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB,
+ addEvent(job.getJob().isPeriodic() ? EVENT_STOP_PERIODIC_JOB : EVENT_STOP_JOB,
job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason, debugReason);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a23f6e1..e4b1e3e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -29,6 +29,7 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -320,9 +321,9 @@
private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY];
/**
- * A mapping of which uids are currently in the foreground to their effective priority.
+ * A mapping of which uids are currently in the foreground to their effective bias.
*/
- final SparseIntArray mUidPriorityOverride = new SparseIntArray();
+ final SparseIntArray mUidBiasOverride = new SparseIntArray();
/**
* Which uids are currently performing backups, so we shouldn't allow their jobs to run.
@@ -1430,27 +1431,26 @@
void updateUidState(int uid, int procState) {
synchronized (mLock) {
- final int prevPriority = mUidPriorityOverride.get(uid, JobInfo.PRIORITY_DEFAULT);
+ final int prevBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
if (procState == ActivityManager.PROCESS_STATE_TOP) {
// Only use this if we are exactly the top app. All others can live
- // with just the foreground priority. This means that persistent processes
- // can never be the top app priority... that is fine.
- mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
+ // with just the foreground bias. This means that persistent processes
+ // can never have the top app bias... that is fine.
+ mUidBiasOverride.put(uid, JobInfo.BIAS_TOP_APP);
} else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE);
+ mUidBiasOverride.put(uid, JobInfo.BIAS_FOREGROUND_SERVICE);
} else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
- mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE);
+ mUidBiasOverride.put(uid, JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
} else {
- mUidPriorityOverride.delete(uid);
+ mUidBiasOverride.delete(uid);
}
- final int newPriority = mUidPriorityOverride.get(uid, JobInfo.PRIORITY_DEFAULT);
- if (prevPriority != newPriority) {
+ final int newBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
+ if (prevBias != newBias) {
if (DEBUG) {
- Slog.d(TAG, "UID " + uid + " priority changed from " + prevPriority
- + " to " + newPriority);
+ Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias);
}
for (int c = 0; c < mControllers.size(); ++c) {
- mControllers.get(c).onUidPriorityChangedLocked(uid, newPriority);
+ mControllers.get(c).onUidBiasChangedLocked(uid, newBias);
}
}
}
@@ -2201,17 +2201,17 @@
/**
* Check if a job is restricted by any of the declared {@link JobRestriction}s.
- * Note, that the jobs with {@link JobInfo#PRIORITY_FOREGROUND_APP} priority or higher may not
+ * Note, that the jobs with {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher may not
* be restricted, thus we won't even perform the check, but simply return null early.
*
* @param job to be checked
* @return the first {@link JobRestriction} restricting the given job that has been found; null
- * - if passes all the restrictions or has priority {@link JobInfo#PRIORITY_FOREGROUND_APP}
+ * - if passes all the restrictions or has {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias
* or higher.
*/
private JobRestriction checkIfRestricted(JobStatus job) {
- if (evaluateJobPriorityLocked(job) >= JobInfo.PRIORITY_FOREGROUND_APP) {
- // Jobs with PRIORITY_FOREGROUND_APP or higher should not be restricted
+ if (evaluateJobBiasLocked(job) >= JobInfo.BIAS_FOREGROUND_SERVICE) {
+ // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted
return null;
}
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
@@ -2682,28 +2682,28 @@
reportActiveLocked();
}
- private int adjustJobPriority(int curPriority, JobStatus job) {
- if (curPriority < JobInfo.PRIORITY_TOP_APP) {
+ private int adjustJobBias(int curBias, JobStatus job) {
+ if (curBias < JobInfo.BIAS_TOP_APP) {
float factor = mJobPackageTracker.getLoadFactor(job);
if (factor >= mConstants.HEAVY_USE_FACTOR) {
- curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
+ curBias += JobInfo.BIAS_ADJ_ALWAYS_RUNNING;
} else if (factor >= mConstants.MODERATE_USE_FACTOR) {
- curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
+ curBias += JobInfo.BIAS_ADJ_OFTEN_RUNNING;
}
}
- return curPriority;
+ return curBias;
}
- int evaluateJobPriorityLocked(JobStatus job) {
- int priority = job.getPriority();
- if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) {
- return adjustJobPriority(priority, job);
+ int evaluateJobBiasLocked(JobStatus job) {
+ int bias = job.getBias();
+ if (bias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
+ return adjustJobBias(bias, job);
}
- int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
+ int override = mUidBiasOverride.get(job.getSourceUid(), 0);
if (override != 0) {
- return adjustJobPriority(override, job);
+ return adjustJobBias(override, job);
}
- return adjustJobPriority(priority, job);
+ return adjustJobBias(bias, job);
}
final class LocalService implements JobSchedulerInternal {
@@ -2932,7 +2932,9 @@
}
private void validateJobFlags(JobInfo job, int callingUid) {
- job.enforceValidity();
+ job.enforceValidity(
+ CompatChanges.isChangeEnabled(
+ JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid));
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
@@ -3573,17 +3575,17 @@
}
boolean overridePrinted = false;
- for (int i = 0; i < mUidPriorityOverride.size(); i++) {
- int uid = mUidPriorityOverride.keyAt(i);
+ for (int i = 0; i < mUidBiasOverride.size(); i++) {
+ int uid = mUidBiasOverride.keyAt(i);
if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
if (!overridePrinted) {
overridePrinted = true;
pw.println();
- pw.println("Uid priority overrides:");
+ pw.println("Uid bias overrides:");
pw.increaseIndent();
}
pw.print(UserHandle.formatUid(uid));
- pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
+ pw.print(": "); pw.println(mUidBiasOverride.valueAt(i));
}
}
if (overridePrinted) {
@@ -3654,9 +3656,9 @@
pw.increaseIndent();
job.dump(pw, false, nowElapsed);
- int priority = evaluateJobPriorityLocked(job);
- pw.print("Evaluated priority: ");
- pw.println(JobInfo.getPriorityString(priority));
+ int bias = evaluateJobBiasLocked(job);
+ pw.print("Evaluated bias: ");
+ pw.println(JobInfo.getBiasString(bias));
pw.print("Tag: "); pw.println(job.getTag());
pw.print("Enq: ");
@@ -3690,8 +3692,8 @@
job.dump(pw, false, nowElapsed);
pw.decreaseIndent();
- pw.print("Evaluated priority: ");
- pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority));
+ pw.print("Evaluated bias: ");
+ pw.println(JobInfo.getBiasString(job.lastEvaluatedBias));
pw.print("Active at ");
TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
@@ -3829,13 +3831,13 @@
controller.dumpControllerStateLocked(
proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate);
}
- for (int i = 0; i < mUidPriorityOverride.size(); i++) {
- int uid = mUidPriorityOverride.keyAt(i);
+ for (int i = 0; i < mUidBiasOverride.size(); i++) {
+ int uid = mUidBiasOverride.keyAt(i);
if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES);
proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid);
proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE,
- mUidPriorityOverride.valueAt(i));
+ mUidBiasOverride.valueAt(i));
proto.end(pToken);
}
}
@@ -3856,7 +3858,7 @@
job.writeToShortProto(proto, PendingJob.INFO);
job.dump(proto, PendingJob.DUMP, false, nowElapsed);
- proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job));
+ proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobBiasLocked(job));
proto.write(PendingJob.PENDING_DURATION_MS, nowUptime - job.madePending);
proto.end(pjToken);
@@ -3889,7 +3891,7 @@
job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
- evaluateJobPriorityLocked(job));
+ evaluateJobBiasLocked(job));
proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
nowUptime - job.madeActive);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d1afc80..b1ea14d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -49,7 +49,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.server.IoThread;
-import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
@@ -531,7 +530,8 @@
}
}
- /** Write out a tag with data comprising the required fields and priority of this job and
+ /**
+ * Write out a tag with data comprising the required fields and bias of this job and
* its client.
*/
private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
@@ -547,7 +547,7 @@
}
out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
- out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
+ out.attribute(null, "bias", String.valueOf(jobStatus.getBias()));
out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
if (jobStatus.getInternalFlags() != 0) {
out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
@@ -820,15 +820,18 @@
long lastFailedRunTime;
int internalFlags = 0;
- // Read out job identifier attributes and priority.
+ // Read out job identifier attributes and bias.
try {
jobBuilder = buildBuilderFromXml(parser);
jobBuilder.setPersisted(true);
uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
- String val = parser.getAttributeValue(null, "priority");
+ String val = parser.getAttributeValue(null, "bias");
+ if (val == null) {
+ val = parser.getAttributeValue(null, "priority");
+ }
if (val != null) {
- jobBuilder.setPriority(Integer.parseInt(val));
+ jobBuilder.setBias(Integer.parseInt(val));
}
val = parser.getAttributeValue(null, "flags");
if (val != null) {
@@ -978,10 +981,17 @@
final JobInfo builtJob;
try {
- builtJob = jobBuilder.build();
+ // Don't perform prefetch-deadline check here. Apps targeting S- shouldn't have
+ // any prefetch-with-deadline jobs accidentally dropped. It's not worth doing
+ // target SDK version checks here for apps targeting T+. There's no way for an
+ // app to keep a perpetually scheduled prefetch job with a deadline. Prefetch jobs
+ // with a deadline would run and then any newly scheduled prefetch jobs wouldn't
+ // have a deadline. If a job is rescheduled (via jobFinished(true) or onStopJob()'s
+ // return value), the deadline is dropped. Periodic jobs require all constraints
+ // to be met, so there's no issue with their deadlines.
+ builtJob = jobBuilder.build(false);
} catch (Exception e) {
- Slog.w(TAG, "Unable to build job from XML, ignoring: "
- + jobBuilder.summarize());
+ Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
return null;
}
@@ -997,11 +1007,10 @@
}
// And now we're done
- JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
sourceUserId, elapsedNow);
JobStatus js = new JobStatus(
- jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+ builtJob, uid, sourcePackageName, sourceUserId,
appBucket, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 98a39a6..aca381f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -113,7 +113,6 @@
userFilter.addAction(Intent.ACTION_USER_STOPPED);
mContext.registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
-
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 607ed3c..524d68f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -176,8 +176,8 @@
}
// Prioritize the top app. If neither are top apps, then use a later prioritization
// check.
- final int topPriority = prioritizeExistenceOver(JobInfo.PRIORITY_TOP_APP - 1,
- us1.basePriority, us2.basePriority);
+ final int topPriority = prioritizeExistenceOver(JobInfo.BIAS_TOP_APP - 1,
+ us1.baseBias, us2.baseBias);
if (topPriority != 0) {
return topPriority;
}
@@ -190,8 +190,8 @@
// They both have runnable EJs.
// Prioritize an FGS+ app. If neither are FGS+ apps, then use a later prioritization
// check.
- final int fgsPriority = prioritizeExistenceOver(JobInfo.PRIORITY_FOREGROUND_SERVICE - 1,
- us1.basePriority, us2.basePriority);
+ final int fgsPriority = prioritizeExistenceOver(JobInfo.BIAS_FOREGROUND_SERVICE - 1,
+ us1.baseBias, us2.baseBias);
if (fgsPriority != 0) {
return fgsPriority;
}
@@ -202,8 +202,8 @@
return 1;
}
// Order by any latent important proc states.
- if (us1.basePriority != us2.basePriority) {
- return us2.basePriority - us1.basePriority;
+ if (us1.baseBias != us2.baseBias) {
+ return us2.baseBias - us1.baseBias;
}
// Order by enqueue time.
if (us1.earliestEnqueueTime < us2.earliestEnqueueTime) {
@@ -527,10 +527,10 @@
@GuardedBy("mLock")
@Override
- public void onUidPriorityChangedLocked(int uid, int newPriority) {
+ public void onUidBiasChangedLocked(int uid, int newBias) {
UidStats uidStats = mUidStats.get(uid);
- if (uidStats != null && uidStats.basePriority != newPriority) {
- uidStats.basePriority = newPriority;
+ if (uidStats != null && uidStats.baseBias != newBias) {
+ uidStats.baseBias = newBias;
postAdjustCallbacks();
}
}
@@ -928,7 +928,7 @@
final int unbypassableBlockedReasons;
// TOP will probably have fewer reasons, so we may not have to worry about returning
// BG_BLOCKED for a TOP app. However, better safe than sorry.
- if (uidStats.basePriority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE
+ if (uidStats.baseBias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
|| (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
if (DEBUG) {
Slog.d(TAG, "Using FG bypass for " + jobStatus.getSourceUid());
@@ -1281,7 +1281,7 @@
private static class UidStats {
public final int uid;
- public int basePriority;
+ public int baseBias;
public final ArraySet<JobStatus> runningJobs = new ArraySet<>();
public int numReadyWithConnectivity;
public int numRequestedNetworkAvailable;
@@ -1298,7 +1298,7 @@
private void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
pw.print("UidStats{");
pw.print("uid", uid);
- pw.print("pri", basePriority);
+ pw.print("pri", baseBias);
pw.print("#run", runningJobs.size());
pw.print("#readyWithConn", numReadyWithConnectivity);
pw.print("#netAvail", numRequestedNetworkAvailable);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index d35c03d..dee716f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -328,8 +328,8 @@
public Network network;
public ServiceInfo serviceInfo;
- /** The evaluated priority of the job when it started running. */
- public int lastEvaluatedPriority;
+ /** The evaluated bias of the job when it started running. */
+ public int lastEvaluatedBias;
/**
* Whether or not this particular JobStatus instance was treated as an EJ when it started
@@ -555,7 +555,9 @@
requestBuilder.setUids(
Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
builder.setRequiredNetwork(requestBuilder.build());
- job = builder.build();
+ // Don't perform prefetch-deadline check at this point. We've already passed the
+ // initial validation check.
+ job = builder.build(false);
}
final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
@@ -922,8 +924,8 @@
return tag;
}
- public int getPriority() {
- return job.getPriority();
+ public int getBias() {
+ return job.getBias();
}
public int getFlags() {
@@ -1945,9 +1947,9 @@
if (job.isPersisted()) {
pw.println("PERSISTED");
}
- if (job.getPriority() != 0) {
- pw.print("Priority: ");
- pw.println(JobInfo.getPriorityString(job.getPriority()));
+ if (job.getBias() != 0) {
+ pw.print("Bias: ");
+ pw.println(JobInfo.getBiasString(job.getBias()));
}
if (job.getFlags() != 0) {
pw.print("Flags: ");
@@ -2215,7 +2217,7 @@
proto.write(JobStatusDumpProto.JobInfo.PERIOD_FLEX_MS, job.getFlexMillis());
proto.write(JobStatusDumpProto.JobInfo.IS_PERSISTED, job.isPersisted());
- proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getPriority());
+ proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getBias());
proto.write(JobStatusDumpProto.JobInfo.FLAGS, job.getFlags());
proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags());
// Foreground exemption can be determined from internal flags value.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
new file mode 100644
index 0000000..78a77fe
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import java.util.Objects;
+
+/** Wrapper class to represent a userId-pkgName combo. */
+final class Package {
+ public final String packageName;
+ public final int userId;
+
+ Package(int userId, String packageName) {
+ this.userId = userId;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return packageToString(userId, packageName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Package)) {
+ return false;
+ }
+ Package other = (Package) obj;
+ return userId == other.userId && Objects.equals(packageName, other.packageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode() + userId;
+ }
+
+ /**
+ * Standardize the output of userId-packageName combo.
+ */
+ static String packageToString(int userId, String packageName) {
+ return "<" + userId + ">" + packageName;
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 725092c..6232dfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -20,9 +20,13 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
+import static com.android.server.job.controllers.Package.packageToString;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -36,6 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.job.JobSchedulerService;
+import com.android.server.utils.AlarmQueue;
import java.util.function.Predicate;
@@ -57,6 +62,7 @@
*/
@GuardedBy("mLock")
private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
+ private final ThresholdAlarmListener mThresholdAlarmListener;
/**
* The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
@@ -69,6 +75,8 @@
public PrefetchController(JobSchedulerService service) {
super(service);
mPcConstants = new PcConstants();
+ mThresholdAlarmListener = new ThresholdAlarmListener(
+ mContext, JobSchedulerBackgroundThread.get().getLooper());
}
@Override
@@ -82,9 +90,13 @@
jobs = new ArraySet<>();
mTrackedJobs.add(userId, pkgName, jobs);
}
- jobs.add(jobStatus);
- updateConstraintLocked(jobStatus,
- sSystemClock.millis(), sElapsedRealtimeClock.millis());
+ final long now = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (jobs.add(jobStatus) && jobs.size() == 1
+ && !willBeLaunchedSoonLocked(userId, pkgName, now)) {
+ updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
+ }
+ updateConstraintLocked(jobStatus, now, nowElapsed);
}
}
@@ -92,10 +104,11 @@
@GuardedBy("mLock")
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- if (jobs != null) {
- jobs.remove(jobStatus);
+ final int userId = jobStatus.getSourceUserId();
+ final String pkgName = jobStatus.getSourcePackageName();
+ final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+ if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
}
}
@@ -109,6 +122,7 @@
final int userId = UserHandle.getUserId(uid);
mTrackedJobs.delete(userId, packageName);
mEstimatedLaunchTimes.delete(userId, packageName);
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
}
@Override
@@ -116,6 +130,7 @@
public void onUserRemovedLocked(int userId) {
mTrackedJobs.delete(userId);
mEstimatedLaunchTimes.delete(userId);
+ mThresholdAlarmListener.removeAlarmsForUserId(userId);
}
/** Return the app's next estimated launch time. */
@@ -124,8 +139,14 @@
public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
final int userId = jobStatus.getSourceUserId();
final String pkgName = jobStatus.getSourcePackageName();
+ return getNextEstimatedLaunchTimeLocked(userId, pkgName, sSystemClock.millis());
+ }
+
+ @GuardedBy("mLock")
+ @CurrentTimeMillisLong
+ private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
+ @CurrentTimeMillisLong long now) {
Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
- final long now = sSystemClock.millis();
if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
// TODO(194532703): get estimated time from UsageStats
nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
@@ -135,8 +156,8 @@
}
@GuardedBy("mLock")
- private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
- String pkgName) {
+ private boolean maybeUpdateConstraintForPkgLocked(@CurrentTimeMillisLong long now,
+ @ElapsedRealtimeLong long nowElapsed, int userId, String pkgName) {
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
if (jobs == null) {
return false;
@@ -150,10 +171,43 @@
}
@GuardedBy("mLock")
- private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
- long nowElapsed) {
+ private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
+ @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
- getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
+ willBeLaunchedSoonLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now));
+ }
+
+ @GuardedBy("mLock")
+ private void updateThresholdAlarmLocked(int userId, @NonNull String pkgName,
+ @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
+ final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+ if (jobs == null || jobs.size() == 0) {
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ return;
+ }
+
+ final long nextEstimatedLaunchTime = getNextEstimatedLaunchTimeLocked(userId, pkgName, now);
+ if (nextEstimatedLaunchTime - now > mLaunchTimeThresholdMs) {
+ // Set alarm to be notified when this crosses the threshold.
+ final long timeToCrossThresholdMs =
+ nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
+ mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
+ nowElapsed + timeToCrossThresholdMs);
+ } else {
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ }
+ }
+
+ /**
+ * Returns true if the app is expected to be launched soon, where "soon" is within the next
+ * {@link #mLaunchTimeThresholdMs} time.
+ */
+ @GuardedBy("mLock")
+ private boolean willBeLaunchedSoonLocked(int userId, @NonNull String pkgName,
+ @CurrentTimeMillisLong long now) {
+ return getNextEstimatedLaunchTimeLocked(userId, pkgName, now)
+ <= now + mLaunchTimeThresholdMs;
}
@Override
@@ -186,6 +240,9 @@
now, nowElapsed, userId, packageName)) {
changedJobs.addAll(mTrackedJobs.valueAt(u, p));
}
+ if (!willBeLaunchedSoonLocked(userId, packageName, now)) {
+ updateThresholdAlarmLocked(userId, packageName, now, nowElapsed);
+ }
}
}
}
@@ -196,6 +253,42 @@
}
}
+ /** Track when apps will cross the "will run soon" threshold. */
+ private class ThresholdAlarmListener extends AlarmQueue<Package> {
+ private ThresholdAlarmListener(Context context, Looper looper) {
+ super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
+ PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
+ }
+
+ @Override
+ protected boolean isForUser(@NonNull Package key, int userId) {
+ return key.userId == userId;
+ }
+
+ @Override
+ protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+ synchronized (mLock) {
+ final long now = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ for (int i = 0; i < expired.size(); ++i) {
+ Package p = expired.valueAt(i);
+ if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
+ Slog.e(TAG, "Alarm expired for "
+ + packageToString(p.userId, p.packageName) + " at the wrong time");
+ updateThresholdAlarmLocked(p.userId, p.packageName, now, nowElapsed);
+ } else if (maybeUpdateConstraintForPkgLocked(
+ now, nowElapsed, p.userId, p.packageName)) {
+ changedJobs.addAll(mTrackedJobs.get(p.userId, p.packageName));
+ }
+ }
+ }
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
+ }
+ }
+ }
+
@VisibleForTesting
class PcConstants {
private boolean mShouldReevaluateConstraints = false;
@@ -225,6 +318,9 @@
if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
mShouldReevaluateConstraints = true;
+ // Give a leeway of 10% of the launch time threshold between alarms.
+ mThresholdAlarmListener.setMinTimeBetweenAlarmsMs(
+ mLaunchTimeThresholdMs / 10);
}
break;
}
@@ -294,6 +390,9 @@
pw.println();
}
});
+
+ pw.println();
+ mThresholdAlarmListener.dump(pw);
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 1016294..31da526 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -27,6 +27,7 @@
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.controllers.Package.packageToString;
import android.Manifest;
import android.annotation.NonNull;
@@ -79,7 +80,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -123,52 +123,6 @@
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;
- /**
- * Standardize the output of userId-packageName combo.
- */
- private static String string(int userId, String packageName) {
- return "<" + userId + ">" + packageName;
- }
-
- private static final class Package {
- public final String packageName;
- public final int userId;
-
- Package(int userId, String packageName) {
- this.userId = userId;
- this.packageName = packageName;
- }
-
- @Override
- public String toString() {
- return string(userId, packageName);
- }
-
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
- proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);
-
- proto.end(token);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Package) {
- Package other = (Package) obj;
- return userId == other.userId && Objects.equals(packageName, other.packageName);
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return packageName.hashCode() + userId;
- }
- }
-
private static int hashLong(long val) {
return (int) (val ^ (val >>> 32));
}
@@ -1741,7 +1695,6 @@
return;
}
- final String pkgString = string(userId, packageName);
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
@@ -1755,7 +1708,8 @@
if (inRegularQuota && remainingEJQuota > 0) {
// Already in quota. Why was this method called?
if (DEBUG) {
- Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
+ Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
+ + packageToString(userId, packageName)
+ " even though it already has "
+ getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+ "ms in its quota.");
@@ -1811,8 +1765,8 @@
// In some strange cases, an app may end be in the NEVER bucket but could have run
// some regular jobs. This results in no EJ timing sessions and QC having a bad
// time.
- Slog.wtf(TAG,
- string(userId, packageName) + " has 0 EJ quota without running anything");
+ Slog.wtf(TAG, packageToString(userId, packageName)
+ + " has 0 EJ quota without running anything");
return;
}
}
@@ -2272,7 +2226,6 @@
public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
final long token = proto.start(fieldId);
- mPkg.dumpDebug(proto, StateControllerProto.QuotaController.Timer.PKG);
proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
mStartTimeElapsed);
@@ -2381,7 +2334,6 @@
public void dump(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- mPkg.dumpDebug(proto, StateControllerProto.QuotaController.TopAppTimer.PKG);
proto.write(StateControllerProto.QuotaController.TopAppTimer.IS_ACTIVE, isActive());
proto.write(StateControllerProto.QuotaController.TopAppTimer.START_TIME_ELAPSED,
mStartTimeElapsed);
@@ -2413,7 +2365,7 @@
void updateStandbyBucket(
final int userId, final @NonNull String packageName, final int bucketIndex) {
if (DEBUG) {
- Slog.i(TAG, "Moving pkg " + string(userId, packageName)
+ Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName)
+ " to bucketIndex " + bucketIndex);
}
List<JobStatus> restrictedChanges = new ArrayList<>();
@@ -2641,7 +2593,7 @@
String packageName = (String) msg.obj;
int userId = msg.arg1;
if (DEBUG) {
- Slog.d(TAG, "Checking pkg " + string(userId, packageName));
+ Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
}
if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
userId, packageName)) {
@@ -2722,7 +2674,7 @@
final String pkgName = event.getPackageName();
if (DEBUG) {
Slog.d(TAG, "Processing event " + event.getEventType()
- + " for " + string(userId, pkgName));
+ + " for " + packageToString(userId, pkgName));
}
switch (event.getEventType()) {
case UsageEvents.Event.ACTIVITY_RESUMED:
@@ -4119,7 +4071,7 @@
final String pkgName = mExecutionStatsCache.keyAt(u, p);
ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
- pw.println(string(userId, pkgName));
+ pw.println(packageToString(userId, pkgName));
pw.increaseIndent();
for (int i = 0; i < stats.length; ++i) {
ExecutionStats executionStats = stats[i];
@@ -4143,7 +4095,7 @@
final String pkgName = mEJStats.keyAt(u, p);
ShrinkableDebits debits = mEJStats.valueAt(u, p);
- pw.print(string(userId, pkgName));
+ pw.print(packageToString(userId, pkgName));
pw.print(": ");
debits.dumpLocked(pw);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 3fe8df2..5e73f42 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -133,11 +133,11 @@
}
/**
- * Called when a UID's base priority has changed. The more positive the priority, the more
+ * Called when a UID's base bias has changed. The more positive the bias, the more
* important the UID is.
*/
@GuardedBy("mLock")
- public void onUidPriorityChangedLocked(int uid, int newPriority) {
+ public void onUidBiasChangedLocked(int uid, int newBias) {
}
protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 5193179..3387b1d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -29,8 +29,8 @@
* should be scheduled or not based on the state of the system/device.
* Every restriction is associated with exactly one stop reason, which could be retrieved using
* {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
- * Note, that this is not taken into account for the jobs that have priority
- * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
+ * Note, that this is not taken into account for the jobs that have
+ * {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
*/
public abstract class JobRestriction {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index e9fa926..a39fd47 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -537,10 +537,11 @@
private void setupHeavyWork() {
synchronized (mLock) {
loadInstalledPackageListLocked();
- // TODO: base on if we have anything persisted
- final boolean isFirstSetup = true;
+ final boolean isFirstSetup = !mScribe.recordExists();
if (isFirstSetup) {
mAgent.grantBirthrightsLocked();
+ } else {
+ mScribe.loadFromDiskLocked();
}
scheduleUnusedWealthReclamationLocked();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index f2b78c0..a234ae6 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -61,6 +61,11 @@
Ledger() {
}
+ Ledger(long currentBalance, @NonNull List<Transaction> transactions) {
+ mCurrentBalance = currentBalance;
+ mTransactions.addAll(transactions);
+ }
+
long getCurrentBalance() {
return mCurrentBalance;
}
@@ -73,6 +78,11 @@
return null;
}
+ @NonNull
+ List<Transaction> getTransactions() {
+ return mTransactions;
+ }
+
void recordTransaction(@NonNull Transaction transaction) {
mTransactions.add(transaction);
mCurrentBalance += transaction.delta;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 2c133dcb..d4c6c8c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -21,11 +21,34 @@
import static com.android.server.tare.TareUtils.appToString;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseArrayMap;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/**
* Maintains the current TARE state and handles writing it to disk and reading it back from disk.
@@ -44,40 +67,76 @@
*/
private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS;
+ private static final String XML_TAG_HIGH_LEVEL_STATE = "irs-state";
+ private static final String XML_TAG_LEDGER = "ledger";
+ private static final String XML_TAG_TARE = "tare";
+ private static final String XML_TAG_TRANSACTION = "transaction";
+ private static final String XML_TAG_USER = "user";
+
+ private static final String XML_ATTR_DELTA = "delta";
+ private static final String XML_ATTR_EVENT_ID = "eventId";
+ private static final String XML_ATTR_TAG = "tag";
+ private static final String XML_ATTR_START_TIME = "startTime";
+ private static final String XML_ATTR_END_TIME = "endTime";
+ private static final String XML_ATTR_PACKAGE_NAME = "pkgName";
+ private static final String XML_ATTR_CURRENT_BALANCE = "currentBalance";
+ private static final String XML_ATTR_USER_ID = "userId";
+ private static final String XML_ATTR_VERSION = "version";
+ private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
+
+ /** Version of the file schema. */
+ private static final int STATE_FILE_VERSION = 0;
+ /** Minimum amount of time between consecutive writes. */
+ private static final long WRITE_DELAY = 30_000L;
+
+ private final AtomicFile mStateFile;
private final InternalResourceService mIrs;
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
private long mNarcsInCirculation;
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
private final Runnable mCleanRunnable = this::cleanupLedgers;
+ private final Runnable mWriteRunnable = this::writeState;
Scribe(InternalResourceService irs) {
- mIrs = irs;
+ this(irs, Environment.getDataSystemDirectory());
}
- @GuardedBy("mIrs.mLock")
+ @VisibleForTesting
+ Scribe(InternalResourceService irs, File dataDir) {
+ mIrs = irs;
+
+ final File tareDir = new File(dataDir, "tare");
+ //noinspection ResultOfMethodCallIgnored
+ tareDir.mkdirs();
+ mStateFile = new AtomicFile(new File(tareDir, "state.xml"), "tare");
+ }
+
+ @GuardedBy("mIrs.getLock()")
void adjustNarcsInCirculationLocked(long delta) {
if (delta != 0) {
// No point doing any work if the change is 0.
mNarcsInCirculation += delta;
+ postWrite();
}
}
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
void discardLedgerLocked(final int userId, @NonNull final String pkgName) {
mLedgers.delete(userId, pkgName);
+ postWrite();
}
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
long getLastReclamationTimeLocked() {
return mLastReclamationTime;
}
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
@NonNull
Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) {
Ledger ledger = mLedgers.get(userId, pkgName);
@@ -89,33 +148,118 @@
}
/** Returns the total amount of narcs currently allocated to apps. */
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
long getNarcsInCirculationLocked() {
return mNarcsInCirculation;
}
- @GuardedBy("mIrs.mLock")
- void setLastReclamationTimeLocked(long time) {
- mLastReclamationTime = time;
+ @GuardedBy("mIrs.getLock()")
+ void loadFromDiskLocked() {
+ mLedgers.clear();
+ mNarcsInCirculation = 0;
+ if (!recordExists()) {
+ return;
+ }
+
+ final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
+ final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
+ for (int i = 0; i < installedPackages.size(); ++i) {
+ final PackageInfo packageInfo = installedPackages.get(i);
+ if (packageInfo.applicationInfo != null) {
+ final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+ ArraySet<String> pkgsForUser = installedPackagesPerUser.get(userId);
+ if (pkgsForUser == null) {
+ pkgsForUser = new ArraySet<>();
+ installedPackagesPerUser.put(userId, pkgsForUser);
+ }
+ pkgsForUser.add(packageInfo.packageName);
+ }
+ }
+
+ try (FileInputStream fis = mStateFile.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(fis);
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ if (DEBUG) {
+ Slog.w(TAG, "No persisted state.");
+ }
+ return;
+ }
+
+ String tagName = parser.getName();
+ if (XML_TAG_TARE.equals(tagName)) {
+ final int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+ if (version < 0 || version > STATE_FILE_VERSION) {
+ Slog.e(TAG, "Invalid version number (" + version + "), aborting file read");
+ return;
+ }
+ }
+
+ final long endTimeCutoff = System.currentTimeMillis() - MAX_TRANSACTION_AGE_MS;
+ long earliestEndTime = Long.MAX_VALUE;
+ for (eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ if (eventType != XmlPullParser.START_TAG) {
+ continue;
+ }
+ tagName = parser.getName();
+ if (tagName == null) {
+ continue;
+ }
+
+ switch (tagName) {
+ case XML_TAG_HIGH_LEVEL_STATE:
+ mLastReclamationTime =
+ parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
+ break;
+ case XML_TAG_USER:
+ earliestEndTime = Math.min(earliestEndTime,
+ readUserFromXmlLocked(
+ parser, installedPackagesPerUser, endTimeCutoff));
+ break;
+ default:
+ Slog.e(TAG, "Unexpected tag: " + tagName);
+ break;
+ }
+ }
+ scheduleCleanup(earliestEndTime);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Error reading state from disk", e);
+ }
}
- @GuardedBy("mIrs.mLock")
+ @VisibleForTesting
+ void postWrite() {
+ TareHandlerThread.getHandler().postDelayed(mWriteRunnable, WRITE_DELAY);
+ }
+
+ boolean recordExists() {
+ return mStateFile.exists();
+ }
+
+ @GuardedBy("mIrs.getLock()")
+ void setLastReclamationTimeLocked(long time) {
+ mLastReclamationTime = time;
+ postWrite();
+ }
+
+ @GuardedBy("mIrs.getLock()")
void tearDownLocked() {
+ TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
+ TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
mLedgers.clear();
mNarcsInCirculation = 0;
mLastReclamationTime = 0;
}
- private void scheduleCleanup(long earliestEndTime) {
- if (earliestEndTime == Long.MAX_VALUE) {
- return;
- }
- // This is just cleanup to manage memory. We don't need to do it too often or at the exact
- // intended real time, so the delay that comes from using the Handler (and is limited
- // to uptime) should be fine.
- final long delayMs = Math.max(HOUR_IN_MILLIS,
- earliestEndTime + MAX_TRANSACTION_AGE_MS - System.currentTimeMillis());
- TareHandlerThread.getHandler().postDelayed(mCleanRunnable, delayMs);
+ @VisibleForTesting
+ void writeImmediatelyForTesting() {
+ mWriteRunnable.run();
}
private void cleanupLedgers() {
@@ -139,7 +283,226 @@
}
}
- @GuardedBy("mIrs.mLock")
+ /**
+ * @param parser Xml parser at the beginning of a "<ledger/>" tag. The next "parser.next()" call
+ * will take the parser into the body of the ledger tag.
+ * @return Newly instantiated ledger holding all the information we just read out of the xml
+ * tag, and the package name associated with the ledger.
+ */
+ @Nullable
+ private static Pair<String, Ledger> readLedgerFromXml(TypedXmlPullParser parser,
+ ArraySet<String> validPackages, long endTimeCutoff)
+ throws XmlPullParserException, IOException {
+ final String pkgName;
+ final long curBalance;
+ final List<Ledger.Transaction> transactions = new ArrayList<>();
+
+ pkgName = parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME);
+ curBalance = parser.getAttributeLong(null, XML_ATTR_CURRENT_BALANCE);
+
+ final boolean isInstalled = validPackages.contains(pkgName);
+ if (!isInstalled) {
+ // Don't return early since we need to go through all the transaction tags and get
+ // to the end of the ledger tag.
+ Slog.w(TAG, "Invalid pkg " + pkgName + " is saved to disk");
+ }
+
+ for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ final String tagName = parser.getName();
+ if (eventType == XmlPullParser.END_TAG) {
+ if (XML_TAG_LEDGER.equals(tagName)) {
+ // We've reached the end of the ledger tag.
+ break;
+ }
+ continue;
+ }
+ if (eventType != XmlPullParser.START_TAG || !XML_TAG_TRANSACTION.equals(tagName)) {
+ // Expecting only "transaction" tags.
+ Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
+ return null;
+ }
+ if (!isInstalled) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Starting ledger tag: " + tagName);
+ }
+ final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
+ final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
+ final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
+ final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
+ final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
+ if (endTime <= endTimeCutoff) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping event because it's too old.");
+ }
+ continue;
+ }
+ transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta));
+ }
+
+ if (!isInstalled) {
+ return null;
+ }
+ return Pair.create(pkgName, new Ledger(curBalance, transactions));
+ }
+
+ /**
+ * @param parser Xml parser at the beginning of a "<user>" tag. The next "parser.next()" call
+ * will take the parser into the body of the user tag.
+ * @return The earliest valid transaction end time found for the user.
+ */
+ @GuardedBy("mIrs.getLock()")
+ private long readUserFromXmlLocked(TypedXmlPullParser parser,
+ SparseArray<ArraySet<String>> installedPackagesPerUser,
+ long endTimeCutoff) throws XmlPullParserException, IOException {
+ int curUser = parser.getAttributeInt(null, XML_ATTR_USER_ID);
+ final ArraySet<String> installedPackages = installedPackagesPerUser.get(curUser);
+ if (installedPackages == null) {
+ Slog.w(TAG, "Invalid user " + curUser + " is saved to disk");
+ curUser = UserHandle.USER_NULL;
+ // Don't return early since we need to go through all the ledger tags and get to the end
+ // of the user tag.
+ }
+ long earliestEndTime = Long.MAX_VALUE;
+
+ for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ final String tagName = parser.getName();
+ if (eventType == XmlPullParser.END_TAG) {
+ if (XML_TAG_USER.equals(tagName)) {
+ // We've reached the end of the user tag.
+ break;
+ }
+ continue;
+ }
+ if (XML_TAG_LEDGER.equals(tagName)) {
+ if (curUser == UserHandle.USER_NULL) {
+ continue;
+ }
+ final Pair<String, Ledger> ledgerData =
+ readLedgerFromXml(parser, installedPackages, endTimeCutoff);
+ if (ledgerData == null) {
+ continue;
+ }
+ final Ledger ledger = ledgerData.second;
+ if (ledger != null) {
+ mLedgers.add(curUser, ledgerData.first, ledger);
+ mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance());
+ final Ledger.Transaction transaction = ledger.getEarliestTransaction();
+ if (transaction != null) {
+ earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
+ }
+ }
+ } else {
+ Slog.e(TAG, "Unknown tag: " + tagName);
+ }
+ }
+
+ return earliestEndTime;
+ }
+
+ private void scheduleCleanup(long earliestEndTime) {
+ if (earliestEndTime == Long.MAX_VALUE) {
+ return;
+ }
+ // This is just cleanup to manage memory. We don't need to do it too often or at the exact
+ // intended real time, so the delay that comes from using the Handler (and is limited
+ // to uptime) should be fine.
+ final long delayMs = Math.max(HOUR_IN_MILLIS,
+ earliestEndTime + MAX_TRANSACTION_AGE_MS - System.currentTimeMillis());
+ TareHandlerThread.getHandler().postDelayed(mCleanRunnable, delayMs);
+ }
+
+ private void writeState() {
+ synchronized (mIrs.getLock()) {
+ TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
+ // Remove mCleanRunnable callbacks since we're going to clean up the ledgers before
+ // writing anyway.
+ TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
+ if (!mIrs.isEnabled()) {
+ // If it's no longer enabled, we would have cleared all the data in memory and would
+ // accidentally write an empty file, thus deleting all the history.
+ return;
+ }
+ long earliestStoredEndTime = Long.MAX_VALUE;
+ try (FileOutputStream fos = mStateFile.startWrite()) {
+ TypedXmlSerializer out = Xml.resolveSerializer(fos);
+ out.startDocument(null, true);
+
+ out.startTag(null, XML_TAG_TARE);
+ out.attributeInt(null, XML_ATTR_VERSION, STATE_FILE_VERSION);
+
+ out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
+ out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
+ out.endTag(null, XML_TAG_HIGH_LEVEL_STATE);
+
+ for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
+ final int userId = mLedgers.keyAt(uIdx);
+ earliestStoredEndTime = Math.min(earliestStoredEndTime,
+ writeUserLocked(out, userId));
+ }
+
+ out.endTag(null, XML_TAG_TARE);
+
+ out.endDocument();
+ mStateFile.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error writing state to disk", e);
+ }
+ scheduleCleanup(earliestStoredEndTime);
+ }
+ }
+
+ @GuardedBy("mIrs.getLock()")
+ private long writeUserLocked(@NonNull TypedXmlSerializer out, final int userId)
+ throws IOException {
+ final int uIdx = mLedgers.indexOfKey(userId);
+ long earliestStoredEndTime = Long.MAX_VALUE;
+
+ out.startTag(null, XML_TAG_USER);
+ out.attributeInt(null, XML_ATTR_USER_ID, userId);
+ for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
+ final String pkgName = mLedgers.keyAt(uIdx, pIdx);
+ final Ledger ledger = mLedgers.get(userId, pkgName);
+ // Remove old transactions so we don't waste space storing them.
+ ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS);
+
+ out.startTag(null, XML_TAG_LEDGER);
+ out.attribute(null, XML_ATTR_PACKAGE_NAME, pkgName);
+ out.attributeLong(null,
+ XML_ATTR_CURRENT_BALANCE, ledger.getCurrentBalance());
+
+ final List<Ledger.Transaction> transactions = ledger.getTransactions();
+ for (int t = 0; t < transactions.size(); ++t) {
+ Ledger.Transaction transaction = transactions.get(t);
+ if (t == 0) {
+ earliestStoredEndTime = Math.min(earliestStoredEndTime, transaction.endTimeMs);
+ }
+ writeTransaction(out, transaction);
+ }
+ out.endTag(null, XML_TAG_LEDGER);
+ }
+ out.endTag(null, XML_TAG_USER);
+
+ return earliestStoredEndTime;
+ }
+
+ private static void writeTransaction(@NonNull TypedXmlSerializer out,
+ @NonNull Ledger.Transaction transaction) throws IOException {
+ out.startTag(null, XML_TAG_TRANSACTION);
+ out.attributeLong(null, XML_ATTR_START_TIME, transaction.startTimeMs);
+ out.attributeLong(null, XML_ATTR_END_TIME, transaction.endTimeMs);
+ out.attributeInt(null, XML_ATTR_EVENT_ID, transaction.eventId);
+ if (transaction.tag != null) {
+ out.attribute(null, XML_ATTR_TAG, transaction.tag);
+ }
+ out.attributeLong(null, XML_ATTR_DELTA, transaction.delta);
+ out.endTag(null, XML_TAG_TRANSACTION);
+ }
+
+ @GuardedBy("mIrs.getLock()")
void dumpLocked(IndentingPrintWriter pw) {
pw.println("Ledgers:");
pw.increaseIndent();
diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java
index cb6e1a0..7d07eb3 100644
--- a/apex/media/framework/java/android/media/MediaSession2.java
+++ b/apex/media/framework/java/android/media/MediaSession2.java
@@ -302,8 +302,9 @@
parcel.setDataPosition(0);
Bundle out = parcel.readBundle(null);
- // Calling Bundle#size() will trigger Bundle#unparcel().
- out.size();
+ for (String key : out.keySet()) {
+ out.get(key);
+ }
} catch (BadParcelableException e) {
Log.d(TAG, "Custom parcelable in bundle.", e);
return true;
diff --git a/core/api/current.txt b/core/api/current.txt
index 4dd311c..f989d06 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -100,7 +100,7 @@
field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERNET = "android.permission.INTERNET";
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
- field public static final String LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK";
+ field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -114,6 +114,7 @@
field public static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
field public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
field public static final String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
+ field public static final String NEARBY_WIFI_DEVICES = "android.permission.NEARBY_WIFI_DEVICES";
field public static final String NFC = "android.permission.NFC";
field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO";
field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
@@ -168,6 +169,7 @@
field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+ field public static final String START_VIEW_APP_FEATURES = "android.permission.START_VIEW_APP_FEATURES";
field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -2039,6 +2041,10 @@
field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
+ field public static final int accessibilityActionSwipeDown;
+ field public static final int accessibilityActionSwipeLeft;
+ field public static final int accessibilityActionSwipeRight;
+ field public static final int accessibilityActionSwipeUp;
field public static final int accessibilitySystemActionBack = 16908363; // 0x102004b
field public static final int accessibilitySystemActionHome = 16908364; // 0x102004c
field public static final int accessibilitySystemActionLockScreen = 16908370; // 0x1020052
@@ -4380,6 +4386,7 @@
method @Nullable public android.graphics.Rect getLaunchBounds();
method public int getLaunchDisplayId();
method public boolean getLockTaskMode();
+ method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.ActivityOptions makeBasic();
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4393,6 +4400,7 @@
method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
method public android.app.ActivityOptions setLaunchDisplayId(int);
method public android.app.ActivityOptions setLockTaskEnabled(boolean);
+ method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -6257,6 +6265,7 @@
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
+ method @WorkerThread public boolean matchesCallFilter(@NonNull android.net.Uri);
method public void notify(int, android.app.Notification);
method public void notify(String, int, android.app.Notification);
method public void notifyAsPackage(@NonNull String, @Nullable String, int, @NonNull android.app.Notification);
@@ -6728,6 +6737,7 @@
}
public class TaskInfo {
+ method public boolean isVisible();
field @Nullable public android.content.ComponentName baseActivity;
field @NonNull public android.content.Intent baseIntent;
field public boolean isRunning;
@@ -6769,7 +6779,7 @@
public final class UiAutomation {
method public void adoptShellPermissionIdentity();
method public void adoptShellPermissionIdentity(@Nullable java.lang.String...);
- method public void clearWindowAnimationFrameStats();
+ method @Deprecated public void clearWindowAnimationFrameStats();
method public boolean clearWindowContentFrameStats(int);
method public void dropShellPermissionIdentity();
method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
@@ -6778,7 +6788,7 @@
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
- method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
+ method @Deprecated public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
method public android.view.WindowContentFrameStats getWindowContentFrameStats(int);
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method @NonNull public android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
@@ -13300,6 +13310,7 @@
method public int describeContents();
method public int diff(android.content.res.Configuration);
method public boolean equals(android.content.res.Configuration);
+ method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
method public int getLayoutDirection();
method @NonNull public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
@@ -13453,7 +13464,7 @@
method public float getFloat(@DimenRes int);
method @NonNull public android.graphics.Typeface getFont(@FontRes int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(@FractionRes int, int, int);
- method public int getIdentifier(String, String, String);
+ method @Discouraged(message="Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier(\"bar\", \"foo\", null)`).") public int getIdentifier(String, String, String);
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
@@ -13473,7 +13484,7 @@
method public CharSequence getText(@StringRes int, CharSequence);
method @NonNull public CharSequence[] getTextArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public void getValue(@AnyRes int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
- method public void getValue(String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
+ method @Discouraged(message="Use of this function is discouraged because it makes internal calls to `getIdentifier()`, which uses resource reflection. Reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resource values by identifier (e.g. `getValue(R.foo.bar, outValue, true)`) than by name (e.g. `getValue(\"foo\", outvalue, true)`).") public void getValue(String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
method public void getValueForDensity(@AnyRes int, int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getXml(@XmlRes int) throws android.content.res.Resources.NotFoundException;
method public final android.content.res.Resources.Theme newTheme();
@@ -18010,8 +18021,10 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> DISTORTION_CORRECTION_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_DEFAULT_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_MAXIMUM_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
- field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18706,7 +18719,7 @@
method public android.util.Rational getElement(int, int);
}
- public final class DeviceStateOrientationMap {
+ public final class DeviceStateSensorOrientationMap {
method public int getSensorOrientation(long);
field public static final long FOLDED = 4L; // 0x4L
field public static final long NORMAL = 0L; // 0x0L
@@ -22659,6 +22672,7 @@
field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
field public static final String KEY_MAX_HEIGHT = "max-height";
field public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
+ field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel_count";
field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder";
field public static final String KEY_MAX_WIDTH = "max-width";
field public static final String KEY_MIME = "mime";
@@ -25659,6 +25673,7 @@
field public static final String COLUMN_CONTENT_ID = "content_id";
field public static final String COLUMN_CONTENT_RATING = "content_rating";
field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+ field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final String COLUMN_EPISODE_TITLE = "episode_title";
field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25689,6 +25704,7 @@
field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
field public static final String COLUMN_SPLIT_ID = "split_id";
field public static final String COLUMN_STARTING_PRICE = "starting_price";
+ field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final String COLUMN_TITLE = "title";
@@ -25851,6 +25867,7 @@
field public static final String COLUMN_CONTENT_ID = "content_id";
field public static final String COLUMN_CONTENT_RATING = "content_rating";
field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+ field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final String COLUMN_EPISODE_TITLE = "episode_title";
field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25882,6 +25899,7 @@
field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
field public static final String COLUMN_SPLIT_ID = "split_id";
field public static final String COLUMN_STARTING_PRICE = "starting_price";
+ field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final String COLUMN_TITLE = "title";
@@ -31566,6 +31584,7 @@
method public int dataSize();
method public void enforceInterface(@NonNull String);
method public boolean hasFileDescriptors();
+ method public boolean hasFileDescriptors(int, int);
method public byte[] marshall();
method @NonNull public static android.os.Parcel obtain();
method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
@@ -31804,9 +31823,9 @@
method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
method public boolean isBatteryDischargePredictionPersonalized();
method public boolean isDeviceIdleMode();
+ method public boolean isDeviceLightIdleMode();
method public boolean isIgnoringBatteryOptimizations(String);
method public boolean isInteractive();
- method public boolean isLightDeviceIdleMode();
method public boolean isPowerSaveMode();
method public boolean isRebootingUserspaceSupported();
method @Deprecated public boolean isScreenOn();
@@ -31817,7 +31836,7 @@
method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
- field public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+ field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -35354,7 +35373,7 @@
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
- field public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK = "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK";
+ field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
@@ -35395,8 +35414,8 @@
field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
- field public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI";
- field public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY";
+ field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
+ field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
@@ -45767,7 +45786,7 @@
method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter);
method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable String[], @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter);
method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable String[], @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter, @Nullable java.util.function.Function<java.lang.String,android.text.style.URLSpan>);
- field public static final int ALL = 15; // 0xf
+ field @Deprecated public static final int ALL = 15; // 0xf
field public static final int EMAIL_ADDRESSES = 2; // 0x2
field @Deprecated public static final int MAP_ADDRESSES = 8; // 0x8
field public static final int PHONE_NUMBERS = 4; // 0x4
@@ -51173,6 +51192,10 @@
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_DOWN;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_LEFT;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_RIGHT;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_UP;
field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> CREATOR;
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 64d4456..cc30db3 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -55,6 +55,15 @@
}
+package android.app.admin {
+
+ public class DevicePolicyManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+ field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
+ }
+
+}
+
package android.app.usage {
public class NetworkStatsManager {
@@ -323,6 +332,10 @@
package android.provider {
+ public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns {
+ method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static java.util.Map<java.lang.String,java.util.List<android.content.ContentValues>> queryRawContactEntity(@NonNull android.content.ContentResolver, long);
+ }
+
public final class DeviceConfig {
field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
field public static final String NAMESPACE_APP_STANDBY = "app_standby";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5ef3d04..fcda5b2 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -26,7 +26,7 @@
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
- field public static final String ALLOW_PLACE_IN_TWO_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS";
+ field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
@@ -242,6 +242,7 @@
field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
+ field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING";
field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -439,6 +440,11 @@
method public void onUidImportance(int, int);
}
+ public class ActivityOptions {
+ method public int getLaunchTaskId();
+ method @RequiresPermission("android.permission.START_TASKS_FROM_RECENTS") public void setLaunchTaskId(int);
+ }
+
public class AlarmManager {
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource);
@@ -687,9 +693,11 @@
}
public class BroadcastOptions {
+ method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
method public void setDontSendToRestrictedApps(boolean);
+ method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
method public android.os.Bundle toBundle();
@@ -2133,6 +2141,7 @@
field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET;
field @NonNull public static final android.os.ParcelUuid BASE_UUID;
field @NonNull public static final android.os.ParcelUuid BNEP;
+ field @NonNull public static final android.os.ParcelUuid CAP;
field @NonNull public static final android.os.ParcelUuid COORDINATED_SET;
field @NonNull public static final android.os.ParcelUuid DIP;
field @NonNull public static final android.os.ParcelUuid GENERIC_MEDIA_CONTROL;
@@ -2289,6 +2298,10 @@
package android.companion {
+ public final class AssociationRequest implements android.os.Parcelable {
+ field public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING";
+ }
+
public final class CompanionDeviceManager {
method @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public void associate(@NonNull String, @NonNull android.net.MacAddress, @NonNull byte[]);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -2447,6 +2460,7 @@
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
+ field @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES) public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES";
field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
field public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE";
@@ -3354,6 +3368,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerControlMode();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerStateChangeOnActiveSourceLost();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getRoutingControl();
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSadPresenceInQuery(@NonNull String);
method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioControl();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioModeMuting();
@@ -3372,6 +3387,8 @@
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setPowerControlMode(@NonNull String);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setPowerStateChangeOnActiveSourceLost(@NonNull String);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setRoutingControl(@NonNull int);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadPresenceInQuery(@NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadsPresenceInQuery(@NonNull java.util.List<java.lang.String>, int);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioControl(@NonNull int);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioModeMuting(@NonNull int);
@@ -3383,6 +3400,21 @@
field public static final String CEC_SETTING_NAME_HDMI_CEC_VERSION = "hdmi_cec_version";
field public static final String CEC_SETTING_NAME_POWER_CONTROL_MODE = "power_control_mode";
field public static final String CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST = "power_state_change_on_active_source_lost";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_AAC = "query_sad_aac";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_ATRAC = "query_sad_atrac";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_DD = "query_sad_dd";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_DDP = "query_sad_ddp";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_DST = "query_sad_dst";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_DTS = "query_sad_dts";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_DTSHD = "query_sad_dtshd";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_LPCM = "query_sad_lpcm";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_MAX = "query_sad_max";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_MP3 = "query_sad_mp3";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_MPEG1 = "query_sad_mpeg1";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_MPEG2 = "query_sad_mpeg2";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO = "query_sad_onebitaudio";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_TRUEHD = "query_sad_truehd";
+ field public static final String CEC_SETTING_NAME_QUERY_SAD_WMAPRO = "query_sad_wmapro";
field public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL = "system_audio_control";
field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING = "system_audio_mode_muting";
@@ -3450,6 +3482,8 @@
field public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; // 0x2
field public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; // 0x3
field public static final int POWER_STATUS_UNKNOWN = -1; // 0xffffffff
+ field public static final int QUERY_SAD_DISABLED = 0; // 0x0
+ field public static final int QUERY_SAD_ENABLED = 1; // 0x1
field @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; // 0x4
field public static final int RESULT_COMMUNICATION_FAILED = 7; // 0x7
field public static final int RESULT_EXCEPTION = 5; // 0x5
@@ -5448,10 +5482,12 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
+ method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
@@ -5461,6 +5497,7 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener);
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2
@@ -5477,6 +5514,10 @@
method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
}
+ public static interface Spatializer.OnSpatializerOutputChangedListener {
+ method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int);
+ }
+
}
package android.media.audiofx {
@@ -6041,6 +6082,8 @@
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+ method public boolean hasUnusedFrontend(int);
+ method public boolean isLowestPriority(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -9014,6 +9057,7 @@
public final class PermissionManager {
method public int checkDeviceIdentifierAccess(@Nullable String, @Nullable String, @Nullable String, int, int);
method public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
+ method public int checkPermissionForDataDeliveryFromDataSource(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
@@ -9217,6 +9261,7 @@
field public static final String NAMESPACE_BIOMETRICS = "biometrics";
field public static final String NAMESPACE_BLOBSTORE = "blobstore";
field public static final String NAMESPACE_BLUETOOTH = "bluetooth";
+ field public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
field public static final String NAMESPACE_CLIPBOARD = "clipboard";
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
@@ -9230,6 +9275,7 @@
field public static final String NAMESPACE_MEDIA = "media";
field public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+ field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
field public static final String NAMESPACE_OTA = "ota";
field public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service";
field public static final String NAMESPACE_PERMISSIONS = "permissions";
@@ -9253,6 +9299,7 @@
field public static final String NAMESPACE_SYSTEMUI = "systemui";
field public static final String NAMESPACE_SYSTEM_TIME = "system_time";
field public static final String NAMESPACE_TELEPHONY = "telephony";
+ field public static final String NAMESPACE_TETHERING = "tethering";
field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b225f5b..e707b03 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -146,7 +146,6 @@
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
- method public void setLaunchTaskId(int);
method public void setLaunchWindowingMode(int);
method public void setLaunchedFromBubble(boolean);
method public void setTaskAlwaysOnTop(boolean);
@@ -305,10 +304,10 @@
public class NotificationManager {
method public void allowAssistantAdjustment(String);
+ method public void cleanUpCallersAfter(long);
method public void disallowAssistantAdjustment(String);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
- method public boolean matchesCallFilter(android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 5f2c456..e08a493 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -158,10 +158,7 @@
"android/util/LocalLog.java",
// This should be android.util.IndentingPrintWriter, but it's not available in all branches.
"com/android/internal/util/IndentingPrintWriter.java",
- "com/android/internal/util/IState.java",
"com/android/internal/util/MessageUtils.java",
- "com/android/internal/util/State.java",
- "com/android/internal/util/StateMachine.java",
"com/android/internal/util/WakeupMessage.java",
],
}
diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java
index 63e005f..983dcd8 100644
--- a/core/java/android/accounts/ChooseAccountTypeActivity.java
+++ b/core/java/android/accounts/ChooseAccountTypeActivity.java
@@ -138,7 +138,6 @@
if (sequence != null) {
name = sequence.toString();
}
- name = sequence.toString();
} catch (PackageManager.NameNotFoundException e) {
// Nothing we can do much here, just log
if (Log.isLoggable(TAG, Log.WARN)) {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 860224c..a5facd9 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -69,7 +69,7 @@
* {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
* Context.startActivity(Intent, Bundle)} and related methods.
*/
-public class ActivityOptions {
+public class ActivityOptions extends ComponentOptions {
private static final String TAG = "ActivityOptions";
/**
@@ -168,14 +168,6 @@
public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
/**
- * PendingIntent caller allows activity start even if PendingIntent creator is in background.
- * This only works if the PendingIntent caller is allowed to start background activities,
- * for example if it's in the foreground, or has BAL permission.
- * @hide
- */
- public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
- "android.pendingIntent.backgroundActivityAllowed";
- /**
* Callback for when the last frame of the animation is played.
* @hide
*/
@@ -389,12 +381,6 @@
/** @hide */
public static final int ANIM_REMOTE_ANIMATION = 13;
- /**
- * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
- * @hide
- **/
- public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
-
private String mPackageName;
private Rect mLaunchBounds;
private int mAnimationType = ANIM_UNDEFINED;
@@ -446,7 +432,6 @@
private String mSplashScreenThemeResName;
@SplashScreen.SplashScreenStyle
private int mSplashScreenStyle;
- private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
private boolean mRemoveWithTaskOrganizer;
private boolean mLaunchedFromBubble;
private boolean mTransientLaunch;
@@ -1096,13 +1081,12 @@
}
private ActivityOptions() {
+ super();
}
/** @hide */
public ActivityOptions(Bundle opts) {
- // If the remote side sent us bad parcelables, they won't get the
- // results they want, which is their loss.
- opts.setDefusable(true);
+ super(opts);
mPackageName = opts.getString(KEY_PACKAGE_NAME);
try {
@@ -1200,8 +1184,6 @@
mRemoteTransition = opts.getParcelable(KEY_REMOTE_TRANSITION);
mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME);
- mPendingIntentBalAllowed = opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
- PENDING_INTENT_BAL_ALLOWED_DEFAULT);
mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
@@ -1426,24 +1408,6 @@
}
/**
- * Set PendingIntent activity is allowed to be started in the background if the caller
- * can start background activities.
- * @hide
- */
- public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
- mPendingIntentBalAllowed = allowed;
- }
-
- /**
- * Get PendingIntent activity is allowed to be started in the background if the caller
- * can start background activities.
- * @hide
- */
- public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
- return mPendingIntentBalAllowed;
- }
-
- /**
* Sets whether the activity is to be launched into LockTask mode.
*
* Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1565,7 +1529,8 @@
* Sets the task the activity will be launched in.
* @hide
*/
- @TestApi
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ @SystemApi
public void setLaunchTaskId(int taskId) {
mLaunchTaskId = taskId;
}
@@ -1573,6 +1538,7 @@
/**
* @hide
*/
+ @SystemApi
public int getLaunchTaskId() {
return mLaunchTaskId;
}
@@ -1868,8 +1834,9 @@
* object; you must not modify it, but can supply it to the startActivity
* methods that take an options Bundle.
*/
+ @Override
public Bundle toBundle() {
- Bundle b = new Bundle();
+ Bundle b = super.toBundle();
if (mPackageName != null) {
b.putString(KEY_PACKAGE_NAME, mPackageName);
}
@@ -2016,7 +1983,6 @@
if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) {
b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName);
}
- b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
if (mRemoveWithTaskOrganizer) {
b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer);
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a17a89b..773694c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1292,6 +1292,9 @@
/** @hide */
public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
+ /** @hide */
+ public static final int OP_NEARBY_WIFI_DEVICES = AppProtoEnums.APP_OP_NEARBY_WIFI_DEVICES;
+
/**
* Activity recognition being accessed by an activity recognition source, which
* is a component that already has access since it is the one that detects
@@ -1312,7 +1315,7 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 116;
+ public static final int _NUM_OP = 117;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1731,6 +1734,8 @@
public static final String OPSTR_MANAGE_MEDIA = "android:manage_media";
/** @hide */
public static final String OPSTR_UWB_RANGING = "android:uwb_ranging";
+ /** @hide */
+ public static final String OPSTR_NEARBY_WIFI_DEVICES = "android:nearby_wifi_devices";
/**
* Activity recognition being accessed by an activity recognition source, which
@@ -1819,6 +1824,7 @@
OP_BLUETOOTH_CONNECT,
OP_BLUETOOTH_ADVERTISE,
OP_UWB_RANGING,
+ OP_NEARBY_WIFI_DEVICES,
// Notifications
OP_POST_NOTIFICATION,
@@ -1965,6 +1971,7 @@
OP_ACTIVITY_RECOGNITION, // OP_ACTIVITY_RECOGNITION_SOURCE
OP_BLUETOOTH_ADVERTISE, // OP_BLUETOOTH_ADVERTISE
OP_RECORD_INCOMING_PHONE_AUDIO, // OP_RECORD_INCOMING_PHONE_AUDIO
+ OP_NEARBY_WIFI_DEVICES, // OP_NEARBY_WIFI_DEVICES
};
/**
@@ -2087,6 +2094,7 @@
OPSTR_ACTIVITY_RECOGNITION_SOURCE,
OPSTR_BLUETOOTH_ADVERTISE,
OPSTR_RECORD_INCOMING_PHONE_AUDIO,
+ OPSTR_NEARBY_WIFI_DEVICES,
};
/**
@@ -2210,6 +2218,7 @@
"ACTIVITY_RECOGNITION_SOURCE",
"BLUETOOTH_ADVERTISE",
"RECORD_INCOMING_PHONE_AUDIO",
+ "NEARBY_WIFI_DEVICES"
};
/**
@@ -2334,6 +2343,7 @@
null, // no permission for OP_ACTIVITY_RECOGNITION_SOURCE,
Manifest.permission.BLUETOOTH_ADVERTISE,
null, // no permission for OP_RECORD_INCOMING_PHONE_AUDIO,
+ Manifest.permission.NEARBY_WIFI_DEVICES,
};
/**
@@ -2458,6 +2468,7 @@
null, // ACTIVITY_RECOGNITION_SOURCE
null, // BLUETOOTH_ADVERTISE
null, // RECORD_INCOMING_PHONE_AUDIO
+ null, // NEARBY_WIFI_DEVICES
};
/**
@@ -2581,6 +2592,7 @@
null, // ACTIVITY_RECOGNITION_SOURCE
null, // BLUETOOTH_ADVERTISE
null, // RECORD_INCOMING_PHONE_AUDIO
+ null, // NEARBY_WIFI_DEVICES
};
/**
@@ -2703,6 +2715,7 @@
AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION_SOURCE
AppOpsManager.MODE_ALLOWED, // BLUETOOTH_ADVERTISE
AppOpsManager.MODE_ALLOWED, // RECORD_INCOMING_PHONE_AUDIO
+ AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES
};
/**
@@ -2829,6 +2842,7 @@
false, // ACTIVITY_RECOGNITION_SOURCE
false, // BLUETOOTH_ADVERTISE
false, // RECORD_INCOMING_PHONE_AUDIO
+ false, // NEARBY_WIFI_DEVICES
};
/**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 4e19abf..5b8cc70 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -34,7 +34,7 @@
* {@hide}
*/
@SystemApi
-public class BroadcastOptions {
+public class BroadcastOptions extends ComponentOptions {
private long mTemporaryAppAllowlistDuration;
private @TempAllowListType int mTemporaryAppAllowlistType;
private @ReasonCode int mTemporaryAppAllowlistReasonCode;
@@ -43,7 +43,6 @@
private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
private boolean mDontSendToRestrictedApps = false;
private boolean mAllowBackgroundActivityStarts;
- private boolean mPendingIntentBalAllowed = ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
/**
* How long to temporarily put an app on the power allowlist when executing this broadcast
@@ -80,16 +79,6 @@
"android:broadcast.dontSendToRestrictedApps";
/**
- * PendingIntent caller allows activity start even if PendingIntent creator is in background.
- * This only works if the PendingIntent caller is allowed to start background activities,
- * for example if it's in the foreground, or has BAL permission.
- * TODO: Merge it with ActivityOptions.
- * @hide
- */
- public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
- "android.pendingIntent.backgroundActivityAllowed";
-
- /**
* Corresponds to {@link #setBackgroundActivityStartsAllowed}.
*/
private static final String KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS =
@@ -119,12 +108,14 @@
}
private BroadcastOptions() {
+ super();
resetTemporaryAppAllowlist();
}
/** @hide */
@TestApi
public BroadcastOptions(@NonNull Bundle opts) {
+ super(opts);
// Match the logic in toBundle().
if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) {
mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION);
@@ -141,8 +132,6 @@
mDontSendToRestrictedApps = opts.getBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, false);
mAllowBackgroundActivityStarts = opts.getBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS,
false);
- mPendingIntentBalAllowed = opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
- ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT);
}
/**
@@ -204,6 +193,26 @@
}
/**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return super.isPendingIntentBackgroundActivityLaunchAllowed();
+ }
+
+ /**
* Return {@link #setTemporaryAppAllowlist}.
* @hide
*/
@@ -314,26 +323,6 @@
}
/**
- * Set PendingIntent activity is allowed to be started in the background if the caller
- * can start background activities.
- * TODO: Merge it with ActivityOptions.
- * @hide
- */
- public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
- mPendingIntentBalAllowed = allowed;
- }
-
- /**
- * Get PendingIntent activity is allowed to be started in the background if the caller
- * can start background activities.
- * TODO: Merge it with ActivityOptions.
- * @hide
- */
- public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
- return mPendingIntentBalAllowed;
- }
-
- /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
@@ -341,8 +330,9 @@
* object; you must not modify it, but can supply it to the sendBroadcast
* methods that take an options Bundle.
*/
+ @Override
public Bundle toBundle() {
- Bundle b = new Bundle();
+ Bundle b = super.toBundle();
if (isTemporaryAppAllowlistSet()) {
b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType);
@@ -361,8 +351,6 @@
if (mAllowBackgroundActivityStarts) {
b.putBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS, true);
}
- // TODO: Add API for BroadcastOptions and have a shared base class with ActivityOptions.
- b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
new file mode 100644
index 0000000..d50a73a
--- /dev/null
+++ b/core/java/android/app/ComponentOptions.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+public class ComponentOptions {
+
+ /**
+ * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
+ * @hide
+ **/
+ public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
+
+ /**
+ * PendingIntent caller allows activity start even if PendingIntent creator is in background.
+ * This only works if the PendingIntent caller is allowed to start background activities,
+ * for example if it's in the foreground, or has BAL permission.
+ * @hide
+ */
+ public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
+ "android.pendingIntent.backgroundActivityAllowed";
+
+ private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+
+ ComponentOptions() {
+ }
+
+ ComponentOptions(Bundle opts) {
+ // If the remote side sent us bad parcelables, they won't get the
+ // results they want, which is their loss.
+ opts.setDefusable(true);
+ setPendingIntentBackgroundActivityLaunchAllowed(
+ opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ PENDING_INTENT_BAL_ALLOWED_DEFAULT));
+ }
+
+ /**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ */
+ public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ mPendingIntentBalAllowed = allowed;
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ */
+ public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return mPendingIntentBalAllowed;
+ }
+
+ public Bundle toBundle() {
+ Bundle b = new Bundle();
+ b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+ return b;
+ }
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 098492c..1312454 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -114,9 +114,7 @@
NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
ParceledListSlice getNotificationChannelGroups(String pkg);
boolean onlyHasDefaultChannel(String pkg, int uid);
- int getBlockedAppCount(int userId);
boolean areChannelsBypassingDnd();
- int getAppsBypassingDndCount(int uid);
ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
boolean isPackagePaused(String pkg);
void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
@@ -175,6 +173,7 @@
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
+ void cleanUpCallersAfter(long timeThreshold);
boolean isSystemConditionProviderEnabled(String path);
boolean isNotificationListenerAccessGranted(in ComponentName listener);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index ccf1edb..9be4adc 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -25,6 +25,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.WorkerThread;
import android.app.Notification.Builder;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -1079,7 +1080,6 @@
/**
* @hide
*/
- @TestApi
public boolean matchesCallFilter(Bundle extras) {
INotificationManager service = getService();
try {
@@ -1092,6 +1092,19 @@
/**
* @hide
*/
+ @TestApi
+ public void cleanUpCallersAfter(long timeThreshold) {
+ INotificationManager service = getService();
+ try {
+ service.cleanUpCallersAfter(timeThreshold);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
public boolean isSystemConditionProviderEnabled(String path) {
INotificationManager service = getService();
try {
@@ -2544,6 +2557,46 @@
}
}
+ /**
+ * Returns whether a call from the provided URI is permitted to notify the user.
+ * <p>
+ * A true return value indicates one of the following: Do Not Disturb is not currently active;
+ * or the caller is a repeat caller and the current policy allows interruptions from repeat
+ * callers; or the caller is in the user's set of contacts whose calls are allowed to interrupt
+ * Do Not Disturb.
+ * </p>
+ * <p>
+ * If Do Not Disturb is enabled and either no interruptions or only alarms are allowed, this
+ * method will return false regardless of input.
+ * </p>
+ * <p>
+ * The provided URI must meet the requirements for a URI associated with a
+ * {@link Person}: it may be the {@code String} representation of a
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, or a
+ * <code>mailto:</code> or <code>tel:</code> schema URI matching an entry in the
+ * Contacts database. See also {@link Person.Builder#setUri} and
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}
+ * for more information.
+ * </p>
+ * <p>
+ * NOTE: This method calls into Contacts, which may take some time, and should not be called
+ * on the main thread.
+ * </p>
+ *
+ * @param uri A URI representing a caller. Must not be null.
+ * @return A boolean indicating whether a call from the URI provided would be allowed to
+ * interrupt the user given the current filter.
+ */
+ @WorkerThread
+ public boolean matchesCallFilter(@NonNull Uri uri) {
+ Bundle extras = new Bundle();
+ ArrayList<Person> pList = new ArrayList<>();
+ pList.add(new Person.Builder().setUri(uri.toString()).build());
+ extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, pList);
+
+ return matchesCallFilter(extras);
+ }
+
/** @hide */
public static int zenModeToInterruptionFilter(int zen) {
switch (zen) {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e0e9b62..4da51c1 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -28,6 +28,7 @@
per-file Service* = file:/services/core/java/com/android/server/am/OWNERS
per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
+per-file UiAutomation.java = file:/services/accessibility/OWNERS
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8ac8229..8ea1f83 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -26,6 +26,8 @@
import android.app.admin.IDevicePolicyManager;
import android.app.appsearch.AppSearchManagerFrameworkInitializer;
import android.app.blob.BlobStoreManagerFrameworkInitializer;
+import android.app.communal.CommunalManager;
+import android.app.communal.ICommunalManager;
import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.job.JobSchedulerFrameworkInitializer;
@@ -1460,7 +1462,23 @@
@Override
public DisplayHashManager createService(ContextImpl ctx) {
return new DisplayHashManager();
- }});
+ }
+ });
+
+ registerService(Context.COMMUNAL_MANAGER_SERVICE, CommunalManager.class,
+ new CachedServiceFetcher<CommunalManager>() {
+ @Override
+ public CommunalManager createService(ContextImpl ctx) {
+ if (!ctx.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_COMMUNAL_MODE)) {
+ return null;
+ }
+ IBinder iBinder =
+ ServiceManager.getService(Context.COMMUNAL_MANAGER_SERVICE);
+ return iBinder != null ? new CommunalManager(
+ ICommunalManager.Stub.asInterface(iBinder)) : null;
+ }
+ });
sInitializing = true;
try {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index cac7639..bd9b6e9 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -265,6 +265,13 @@
}
/**
+ * Whether this task is visible.
+ */
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ /**
* @param isLowResolution
* @return
* @hide
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index e0b484c..65f71d0 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1174,7 +1174,9 @@
* @see android.view.WindowAnimationFrameStats
* @see #getWindowAnimationFrameStats()
* @see android.R.styleable#WindowAnimation
+ * @deprecated animation-frames are no-longer used.
*/
+ @Deprecated
public void clearWindowAnimationFrameStats() {
try {
if (DEBUG) {
@@ -1213,7 +1215,9 @@
* @see android.view.WindowAnimationFrameStats
* @see #clearWindowAnimationFrameStats()
* @see android.R.styleable#WindowAnimation
+ * @deprecated animation-frames are no-longer used.
*/
+ @Deprecated
public WindowAnimationFrameStats getWindowAnimationFrameStats() {
try {
if (DEBUG) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6cea2a4..84bb9c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3116,11 +3116,19 @@
}
}
- /** @hide */
- public void resetNewUserDisclaimer() {
+ /**
+ * Acknoledges that the new managed user disclaimer was viewed by the (human) user
+ * so that {@link #ACTION_SHOW_NEW_USER_DISCLAIMER broadcast} is not sent again the next time
+ * this user is switched to.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void acknowledgeNewUserDisclaimer() {
if (mService != null) {
try {
- mService.resetNewUserDisclaimer();
+ mService.acknowledgeNewUserDisclaimer();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5651,9 +5659,10 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SHOW_NEW_USER_DISCLAIMER =
- "android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER";
+ "android.app.action.SHOW_NEW_USER_DISCLAIMER";
/**
* Widgets are enabled in keyguard
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
new file mode 100644
index 0000000..f560434
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -0,0 +1,4 @@
+rubinxu@google.com
+acjohnston@google.com
+pgrafov@google.com
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
new file mode 100644
index 0000000..fb00fe5
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -0,0 +1,2 @@
+file:WorkDeviceExperience_OWNERS
+file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ade1190..e4c3386 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -262,7 +262,7 @@
int stopUser(in ComponentName who, in UserHandle userHandle);
int logoutUser(in ComponentName who);
List<UserHandle> getSecondaryUsers(in ComponentName who);
- void resetNewUserDisclaimer();
+ void acknowledgeNewUserDisclaimer();
void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 6acbef2..10a5f14 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -1,11 +1,5 @@
# Bug component: 142675
-# Android Enterprise team
-rubinxu@google.com
-sandness@google.com
-alexkershaw@google.com
-pgrafov@google.com
+file:EnterprisePlatform_OWNERS
-# Emeritus
-yamasani@google.com
-eranm@google.com
+yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
new file mode 100644
index 0000000..dcacaa2
--- /dev/null
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -0,0 +1,5 @@
+work-device-experience+reviews@google.com
+scottjonathan@google.com
+arangelov@google.com
+kholoudm@google.com
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/communal/CommunalManager.java b/core/java/android/app/communal/CommunalManager.java
new file mode 100644
index 0000000..375a9af
--- /dev/null
+++ b/core/java/android/app/communal/CommunalManager.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.app.communal;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+/**
+ * System private class for talking with the
+ * {@link com.android.server.communal.CommunalManagerService} that handles communal mode state.
+ *
+ * @hide
+ */
+@SystemService(Context.COMMUNAL_MANAGER_SERVICE)
+public final class CommunalManager {
+ private final ICommunalManager mService;
+
+ public CommunalManager(ICommunalManager service) {
+ mService = service;
+ }
+
+ /**
+ * Updates whether or not the communal view is currently showing over the lockscreen.
+ *
+ * @param isShowing Whether communal view is showing.
+ */
+ @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE)
+ public void setCommunalViewShowing(boolean isShowing) {
+ try {
+ mService.setCommunalViewShowing(isShowing);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/app/communal/ICommunalManager.aidl b/core/java/android/app/communal/ICommunalManager.aidl
new file mode 100644
index 0000000..02e8a65
--- /dev/null
+++ b/core/java/android/app/communal/ICommunalManager.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.app.communal;
+
+/**
+ * System private API for talking with the communal manager service that handles communal mode
+ * state.
+ *
+ * @hide
+ */
+oneway interface ICommunalManager {
+ void setCommunalViewShowing(boolean isShowing);
+}
\ No newline at end of file
diff --git a/core/java/android/app/time/OWNERS b/core/java/android/app/time/OWNERS
index 8f80897..ef357e5 100644
--- a/core/java/android/app/time/OWNERS
+++ b/core/java/android/app/time/OWNERS
@@ -1,3 +1,4 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# The app-facing APIs related to both time and time zone detection.
+include /services/core/java/com/android/server/timedetector/OWNERS
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/java/android/app/timedetector/OWNERS b/core/java/android/app/timedetector/OWNERS
index 941eed8..e9dbe4a 100644
--- a/core/java/android/app/timedetector/OWNERS
+++ b/core/java/android/app/timedetector/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 847766
-mingaleev@google.com
-narayan@google.com
-nfuller@google.com
+# Internal APIs related to time detection. SDK APIs are in android.app.time.
+include /services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/app/timezone/OWNERS b/core/java/android/app/timezone/OWNERS
index 8f80897..04d78f2 100644
--- a/core/java/android/app/timezone/OWNERS
+++ b/core/java/android/app/timezone/OWNERS
@@ -1,3 +1,4 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+# Internal APIs related to APK-based time zone rule updates.
+# Deprecated, deletion tracked by b/148144561
+include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/core/java/android/app/timezonedetector/OWNERS b/core/java/android/app/timezonedetector/OWNERS
index 8f80897..fa03f1e 100644
--- a/core/java/android/app/timezonedetector/OWNERS
+++ b/core/java/android/app/timezonedetector/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Internal APIs related to time zone detection. SDK APIs are in android.app.time.
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 3b744a7..06ce053 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2099,7 +2099,7 @@
try {
return mManagerService.isBleScanAlwaysAvailable();
} catch (RemoteException e) {
- Log.e(TAG, "remote expection when calling isBleScanAlwaysAvailable", e);
+ Log.e(TAG, "remote exception when calling isBleScanAlwaysAvailable", e);
return false;
}
}
@@ -2307,7 +2307,7 @@
try {
return mManagerService.isHearingAidProfileSupported();
} catch (RemoteException e) {
- Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+ Log.e(TAG, "remote exception when calling isHearingAidProfileSupported", e);
return false;
}
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 6e918bd..e968052 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1186,11 +1186,6 @@
mAttributionSource = attributionSource;
}
- /** {@hide} */
- public void prepareToEnterProcess(@NonNull AttributionSource attributionSource) {
- setAttributionSource(attributionSource);
- }
-
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof BluetoothDevice) {
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index c30c933..d7940eb 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -156,6 +156,12 @@
"android.bluetooth.action.LE_AUDIO_CONF_CHANGED";
/**
+ * Indicates unspecified audio content.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_UNSPECIFIED = 0x0001;
+
+ /**
* Indicates conversation between humans as, for example, in telephony or video calls.
* @hide
*/
@@ -168,6 +174,66 @@
public static final int CONTEXT_TYPE_MEDIA = 0x0004;
/**
+ * Indicates instructional audio as, for example, in navigation, traffic announcements
+ * or user guidance.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_INSTRUCTIONAL = 0x0008;
+
+ /**
+ * Indicates attention seeking audio as, for example, in beeps signalling arrival of a message
+ * or keyboard clicks.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_ATTENTION_SEEKING = 0x0010;
+
+ /**
+ * Indicates immediate alerts as, for example, in a low battery alarm, timer expiry or alarm
+ * clock.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_IMMEDIATE_ALERT = 0x0020;
+
+ /**
+ * Indicates man machine communication as, for example, with voice recognition or virtual
+ * assistant.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_MAN_MACHINE = 0x0040;
+
+ /**
+ * Indicates emergency alerts as, for example, with fire alarms or other urgent alerts.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_EMERGENCY_ALERT = 0x0080;
+
+ /**
+ * Indicates ringtone as in a call alert.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_RINGTONE = 0x0100;
+
+ /**
+ * Indicates audio associated with a television program and/or with metadata conforming to the
+ * Bluetooth Broadcast TV profile.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_TV = 0x0200;
+
+ /**
+ * Indicates audio associated with a low latency live audio stream.
+ *
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_LIVE = 0x0400;
+
+ /**
+ * Indicates audio associated with a video game stream.
+ * @hide
+ */
+ public static final int CONTEXT_TYPE_GAME = 0x0800;
+
+ /**
* This represents an invalid group ID.
*
* @hide
@@ -250,6 +316,17 @@
*/
public static final int GROUP_STATUS_INACTIVE = IBluetoothLeAudio.GROUP_STATUS_INACTIVE;
+ /**
+ * Indicating that node has been added to the group.
+ * @hide
+ */
+ public static final int GROUP_NODE_ADDED = IBluetoothLeAudio.GROUP_NODE_ADDED;
+
+ /**
+ * Indicating that node has been removed from the group.
+ * @hide
+ */
+ public static final int GROUP_NODE_REMOVED = IBluetoothLeAudio.GROUP_NODE_REMOVED;
private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector =
new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio",
@@ -546,6 +623,61 @@
}
/**
+ * Add device to the given group.
+ * @param group_id group ID the device is being added to
+ * @param device the active device
+ * @return true on success, otherwise false
+ * @hide
+ */
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED
+ })
+ public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) {
+ if (VDBG) log("groupAddNode()");
+ final IBluetoothLeAudio service = getService();
+ try {
+ if (service != null && mAdapter.isEnabled()) {
+ return service.groupAddNode(group_id, device, mAttributionSource);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
+ * Remove device from a given group.
+ * @param group_id group ID the device is being removed from
+ * @param device the active device
+ * @return true on success, otherwise false
+ *
+ * @hide
+ */
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED
+ })
+ public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) {
+ if (VDBG) log("groupRemoveNode()");
+ final IBluetoothLeAudio service = getService();
+ try {
+ if (service != null && mAdapter.isEnabled()) {
+ return service.groupRemoveNode(group_id, device, mAttributionSource);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
* Set connection policy of the profile
*
* <p> The device should already be paired.
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index 20152f3..b5df4db 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -62,15 +62,15 @@
private static final String TAG = "BluetoothManager";
private static final boolean DBG = false;
- private final AttributionSource mAttributionSource;
+ private static AttributionSource sAttributionSource = null;
private final BluetoothAdapter mAdapter;
/**
* @hide
*/
public BluetoothManager(Context context) {
- mAttributionSource = resolveAttributionSource(context);
- mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
+ sAttributionSource = resolveAttributionSource(context);
+ mAdapter = BluetoothAdapter.createAdapter(sAttributionSource);
}
/** {@hide} */
@@ -79,6 +79,9 @@
if (context != null) {
res = context.getAttributionSource();
}
+ else if (sAttributionSource != null) {
+ return sAttributionSource;
+ }
if (res == null) {
res = ActivityThread.currentAttributionSource();
}
@@ -198,8 +201,8 @@
IBluetoothGatt iGatt = managerService.getBluetoothGatt();
if (iGatt == null) return devices;
devices = Attributable.setAttributionSource(
- iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource),
- mAttributionSource);
+ iGatt.getDevicesMatchingConnectionStates(states, sAttributionSource),
+ sAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 325a771..858819e 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -188,6 +188,11 @@
/** @hide */
@NonNull
@SystemApi
+ public static final ParcelUuid CAP =
+ ParcelUuid.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+ /** @hide */
+ @NonNull
+ @SystemApi
public static final ParcelUuid BASE_UUID =
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
index b060ce2..7cea33d 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -15,219 +15,223 @@
*/
package android.companion;
+import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.DataClass;
-
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* A record indicating that a device with a given address was confirmed by the user to be
* associated to a given companion app
*
* @hide
+ * TODO(b/1979395): un-hide and rename to AssociationInfo when implementing public APIs that use
+ * this class.
*/
-@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstructor = true)
public final class Association implements Parcelable {
+ /**
+ * A unique ID of this Association record.
+ * Disclosed to the clients (ie. companion applications) for referring to this record (eg. in
+ * {@code disassociate()} API call).
+ */
+ private final int mAssociationId;
private final @UserIdInt int mUserId;
- private final @NonNull String mDeviceMacAddress;
private final @NonNull String mPackageName;
+
+ private final @NonNull List<DeviceId> mDeviceIds;
private final @Nullable String mDeviceProfile;
- private final boolean mNotifyOnDeviceNearby;
+
+ private final boolean mManagedByCompanionApp;
+ private boolean mNotifyOnDeviceNearby;
private final long mTimeApprovedMs;
+ /**
+ * Creates a new Association.
+ * Only to be used by the CompanionDeviceManagerService.
+ *
+ * @hide
+ */
+ public Association(int associationId, @UserIdInt int userId, @NonNull String packageName,
+ @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile,
+ boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) {
+ if (associationId <= 0) {
+ throw new IllegalArgumentException("Association ID should be greater than 0");
+ }
+ validateDeviceIds(deviceIds);
+
+ mAssociationId = associationId;
+
+ mUserId = userId;
+ mPackageName = packageName;
+
+ mDeviceProfile = deviceProfile;
+ mDeviceIds = new ArrayList<>(deviceIds);
+
+ mManagedByCompanionApp = managedByCompanionApp;
+ mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ mTimeApprovedMs = timeApprovedMs;
+ }
+
+ /**
+ * @return the unique ID of this association record.
+ */
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
/** @hide */
public int getUserId() {
return mUserId;
}
- private String timeApprovedMsToString() {
- return new Date(mTimeApprovedMs).toString();
- }
-
-
-
- // Code below generated by codegen v1.0.22.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/Association.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- /**
- * Creates a new Association.
- *
- * @hide
- */
- @DataClass.Generated.Member
- public Association(
- @UserIdInt int userId,
- @NonNull String deviceMacAddress,
- @NonNull String packageName,
- @Nullable String deviceProfile,
- boolean notifyOnDeviceNearby,
- long timeApprovedMs) {
- this.mUserId = userId;
- com.android.internal.util.AnnotationValidations.validate(
- UserIdInt.class, null, mUserId);
- this.mDeviceMacAddress = deviceMacAddress;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mDeviceMacAddress);
- this.mPackageName = packageName;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mPackageName);
- this.mDeviceProfile = deviceProfile;
- this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
- this.mTimeApprovedMs = timeApprovedMs;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public @NonNull String getDeviceMacAddress() {
- return mDeviceMacAddress;
- }
-
- @DataClass.Generated.Member
+ /** @hide */
public @NonNull String getPackageName() {
return mPackageName;
}
- @DataClass.Generated.Member
+ /**
+ * @return list of the device's IDs. At any time a device has at least 1 ID.
+ */
+ public @NonNull List<DeviceId> getDeviceIds() {
+ return Collections.unmodifiableList(mDeviceIds);
+ }
+
+ /**
+ * @param type type of the ID.
+ * @return ID of the type if the device has such ID, {@code null} otherwise.
+ */
+ public @Nullable String getIdOfType(@NonNull String type) {
+ for (int i = mDeviceIds.size() - 1; i >= 0; i--) {
+ final DeviceId id = mDeviceIds.get(i);
+ if (Objects.equals(mDeviceIds.get(i).getType(), type)) return id.getValue();
+ }
+ return null;
+ }
+
+ /** @hide */
+ public @NonNull String getDeviceMacAddress() {
+ return Objects.requireNonNull(getIdOfType(TYPE_MAC_ADDRESS),
+ "MAC address of this device is not specified.");
+ }
+
+ /**
+ * @return the profile of the device.
+ */
public @Nullable String getDeviceProfile() {
return mDeviceProfile;
}
- @DataClass.Generated.Member
+ /** @hide */
+ public boolean isManagedByCompanionApp() {
+ return mManagedByCompanionApp;
+ }
+
+ /**
+ * Should only be used by the CdmService.
+ * @hide
+ */
+ public void setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
+ mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ }
+
+ /** @hide */
public boolean isNotifyOnDeviceNearby() {
return mNotifyOnDeviceNearby;
}
- @DataClass.Generated.Member
+ /** @hide */
public long getTimeApprovedMs() {
return mTimeApprovedMs;
}
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "Association { " +
- "userId = " + mUserId + ", " +
- "deviceMacAddress = " + mDeviceMacAddress + ", " +
- "packageName = " + mPackageName + ", " +
- "deviceProfile = " + mDeviceProfile + ", " +
- "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby + ", " +
- "timeApprovedMs = " + timeApprovedMsToString() +
- " }";
+ /** @hide */
+ public boolean belongsToPackage(@UserIdInt int userId, String packageName) {
+ return mUserId == userId && Objects.equals(mPackageName, packageName);
}
@Override
- @DataClass.Generated.Member
- public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(Association other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
+ public String toString() {
+ return "Association{"
+ + "mAssociationId=" + mAssociationId
+ + ", mUserId=" + mUserId
+ + ", mPackageName='" + mPackageName + '\''
+ + ", mDeviceIds=" + mDeviceIds
+ + ", mDeviceProfile='" + mDeviceProfile + '\''
+ + ", mManagedByCompanionApp=" + mManagedByCompanionApp
+ + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ + '}';
+ }
+ @Override
+ public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- Association that = (Association) o;
- //noinspection PointlessBooleanExpression
- return true
+ if (!(o instanceof Association)) return false;
+ final Association that = (Association) o;
+ return mAssociationId == that.mAssociationId
&& mUserId == that.mUserId
- && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
+ && mManagedByCompanionApp == that.mManagedByCompanionApp
+ && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
+ && mTimeApprovedMs == that.mTimeApprovedMs
&& Objects.equals(mPackageName, that.mPackageName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
- && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
- && mTimeApprovedMs == that.mTimeApprovedMs;
+ && Objects.equals(mDeviceIds, that.mDeviceIds);
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
- int _hash = 1;
- _hash = 31 * _hash + mUserId;
- _hash = 31 * _hash + Objects.hashCode(mDeviceMacAddress);
- _hash = 31 * _hash + Objects.hashCode(mPackageName);
- _hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
- _hash = 31 * _hash + Boolean.hashCode(mNotifyOnDeviceNearby);
- _hash = 31 * _hash + Long.hashCode(mTimeApprovedMs);
- return _hash;
+ return Objects.hash(mAssociationId, mUserId, mPackageName, mDeviceIds, mDeviceProfile,
+ mManagedByCompanionApp, mNotifyOnDeviceNearby, mTimeApprovedMs);
}
@Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
+ public int describeContents() {
+ return 0;
+ }
- byte flg = 0;
- if (mNotifyOnDeviceNearby) flg |= 0x10;
- if (mDeviceProfile != null) flg |= 0x8;
- dest.writeByte(flg);
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAssociationId);
+
dest.writeInt(mUserId);
- dest.writeString(mDeviceMacAddress);
dest.writeString(mPackageName);
- if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
+
+ dest.writeParcelableList(mDeviceIds, 0);
+ dest.writeString(mDeviceProfile);
+
+ dest.writeBoolean(mManagedByCompanionApp);
+ dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeLong(mTimeApprovedMs);
}
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
+ private Association(@NonNull Parcel in) {
+ mAssociationId = in.readInt();
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ Association(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
+ mUserId = in.readInt();
+ mPackageName = in.readString();
- byte flg = in.readByte();
- boolean notifyOnDeviceNearby = (flg & 0x10) != 0;
- int userId = in.readInt();
- String deviceMacAddress = in.readString();
- String packageName = in.readString();
- String deviceProfile = (flg & 0x8) == 0 ? null : in.readString();
- long timeApprovedMs = in.readLong();
+ mDeviceIds = in.readParcelableList(new ArrayList<>(), DeviceId.class.getClassLoader());
+ mDeviceProfile = in.readString();
- this.mUserId = userId;
- com.android.internal.util.AnnotationValidations.validate(
- UserIdInt.class, null, mUserId);
- this.mDeviceMacAddress = deviceMacAddress;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mDeviceMacAddress);
- this.mPackageName = packageName;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mPackageName);
- this.mDeviceProfile = deviceProfile;
- this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
- this.mTimeApprovedMs = timeApprovedMs;
-
- // onConstructed(); // You can define this method to get a callback
+ mManagedByCompanionApp = in.readBoolean();
+ mNotifyOnDeviceNearby = in.readBoolean();
+ mTimeApprovedMs = in.readLong();
}
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<Association> CREATOR
- = new Parcelable.Creator<Association>() {
+ public static final Parcelable.Creator<Association> CREATOR =
+ new Parcelable.Creator<Association>() {
@Override
public Association[] newArray(int size) {
return new Association[size];
@@ -239,16 +243,18 @@
}
};
- @DataClass.Generated(
- time = 1611795283642L,
- codegenVersion = "1.0.22",
- sourceFile = "frameworks/base/core/java/android/companion/Association.java",
- inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\nprivate final long mTimeApprovedMs\npublic int getUserId()\nprivate java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
- @Deprecated
- private void __metadata() {}
+ private static void validateDeviceIds(@NonNull List<DeviceId> ids) {
+ if (ids.isEmpty()) throw new IllegalArgumentException("Device must have at least 1 id.");
-
- //@formatter:on
- // End of generated code
-
+ // Make sure none of the IDs are null, and they all have different types.
+ final Set<String> types = new HashSet<>(ids.size());
+ for (int i = ids.size() - 1; i >= 0; i--) {
+ final DeviceId deviceId = ids.get(i);
+ if (deviceId == null) throw new IllegalArgumentException("DeviceId must not be null");
+ if (!types.add(deviceId.getType())) {
+ throw new IllegalArgumentException(
+ "DeviceId cannot have multiple IDs of the same type");
+ }
+ }
+ }
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bb8fa9e..36ea0b9 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -74,6 +75,21 @@
*/
public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
+ /**
+ * Device profile: a virtual display capable of rendering Android applications, and sending back
+ * input events.
+ *
+ * Only applications that have been granted
+ * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING} are allowed to
+ * request to be associated with such devices.
+ *
+ * @see AssociationRequest.Builder#setDeviceProfile
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_PROFILE_APP_STREAMING =
+ "android.app.role.COMPANION_DEVICE_APP_STREAMING";
+
/** @hide */
@StringDef(value = { DEVICE_PROFILE_WATCH })
public @interface DeviceProfile {}
@@ -229,7 +245,7 @@
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -478,10 +494,10 @@
};
@DataClass.Generated(
- time = 1615252862756L,
- codegenVersion = "1.0.22",
+ time = 1633625630103L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)")
+ inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 5e2340c..ef5f84c 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -22,7 +22,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.le.ScanFilter;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.wifi.ScanResult;
import android.os.Build;
@@ -30,9 +29,13 @@
import android.os.Parcelable;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
import java.util.regex.Pattern;
/** @hide */
@@ -73,12 +76,19 @@
static boolean matchesServiceUuid(ParcelUuid serviceUuid, ParcelUuid serviceUuidMask,
BluetoothDevice device) {
- ParcelUuid[] uuids = device.getUuids();
- final boolean result = serviceUuid == null ||
- ScanFilter.matchesServiceUuids(
- serviceUuid,
- serviceUuidMask,
- uuids == null ? Collections.emptyList() : Arrays.asList(uuids));
+ boolean result = false;
+ List<ParcelUuid> deviceUuids = device.getUuids() == null
+ ? Collections.emptyList() : Arrays.asList(device.getUuids());
+ if (serviceUuid == null) {
+ result = true;
+ } else {
+ for (ParcelUuid parcelUuid : deviceUuids) {
+ UUID uuidMask = serviceUuidMask == null ? null : serviceUuidMask.getUuid();
+ if (uuidsMaskedEquals(parcelUuid.getUuid(), serviceUuid.getUuid(), uuidMask)) {
+ result = true;
+ }
+ }
+ }
if (DEBUG) debugLogMatchResult(result, device, serviceUuid);
return result;
}
@@ -143,4 +153,23 @@
throw new IllegalArgumentException("Unknown device type: " + device);
}
}
+
+ /**
+ * Compares two {@link #UUID} with a {@link #UUID} mask.
+ *
+ * @param data first {@link #UUID}.
+ * @param uuid second {@link #UUID}.
+ * @param mask mask {@link #UUID}.
+ * @return true if both UUIDs are equals when masked, false otherwise.
+ */
+ @VisibleForTesting
+ public static boolean uuidsMaskedEquals(UUID data, UUID uuid, UUID mask) {
+ if (mask == null) {
+ return Objects.equals(data, uuid);
+ }
+ return (data.getLeastSignificantBits() & mask.getLeastSignificantBits())
+ == (uuid.getLeastSignificantBits() & mask.getLeastSignificantBits())
+ && (data.getMostSignificantBits() & mask.getMostSignificantBits())
+ == (uuid.getMostSignificantBits() & mask.getMostSignificantBits());
+ }
}
diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java
index 828d482..58898cc 100644
--- a/core/java/android/companion/BluetoothLeDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLeDeviceFilter.java
@@ -75,7 +75,7 @@
String renameSuffix, int renameBytesFrom, int renameBytesLength,
int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder) {
mNamePattern = namePattern;
- mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY);
+ mScanFilter = ObjectUtils.firstNotNull(scanFilter, new ScanFilter.Builder().build());
mRawDataFilter = rawDataFilter;
mRawDataFilterMask = rawDataFilterMask;
mRenamePrefix = renamePrefix;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 15de079..d42d6b4 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -281,7 +281,7 @@
* Wi-Fi MAC address for a given user.
*
* <p>This is a system API protected by the
- * {@link andrioid.Manifest.permission#MANAGE_COMPANION_DEVICES} permission, that’s currently
+ * {@link android.Manifest.permission#MANAGE_COMPANION_DEVICES} permission, that’s currently
* called by the Android Wi-Fi stack to determine whether user consent is required to connect
* to a Wi-Fi network. Devices that have been pre-registered as companion devices will not
* require user consent to connect.</p>
diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java
new file mode 100644
index 0000000..5deed1a
--- /dev/null
+++ b/core/java/android/companion/DeviceId.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.companion;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The class represents free-form ID of a companion device.
+ *
+ * Since companion devices may have multiple IDs of different type at the same time
+ * (eg. a MAC address and a Serial Number), this class not only stores the ID itself, it also stores
+ * the type of the ID.
+ * Both the type of the ID and its actual value are represented as {@link String}-s.
+ *
+ * Examples of device IDs:
+ * - "mac_address: f0:18:98:b3:fd:2e"
+ * - "ip_address: 128.121.35.200"
+ * - "imei: 352932100034923 / 44"
+ * - "serial_number: 96141FFAZ000B7"
+ * - "meid_hex: 35293210003492"
+ * - "meid_dic: 08918 92240 0001 3548"
+ *
+ * @hide
+ * TODO(b/1979395): un-hide when implementing public APIs that use this class.
+ */
+public final class DeviceId implements Parcelable {
+ public static final String TYPE_MAC_ADDRESS = "mac_address";
+
+ private final @NonNull String mType;
+ private final @NonNull String mValue;
+
+ /**
+ * @param type type of the ID. Non-empty. Max length - 16 characters.
+ * @param value the ID. Non-empty. Max length - 48 characters.
+ * @throws IllegalArgumentException if either {@param type} or {@param value} is empty or
+ * exceeds its max allowed length.
+ */
+ public DeviceId(@NonNull String type, @NonNull String value) {
+ if (type.isEmpty() || value.isEmpty()) {
+ throw new IllegalArgumentException("'type' and 'value' should not be empty");
+ }
+ this.mType = type;
+ this.mValue = value;
+ }
+
+ /**
+ * @return the type of the ID.
+ */
+ public @NonNull String getType() {
+ return mType;
+ }
+
+ /**
+ * @return the ID.
+ */
+ public @NonNull String getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceId{"
+ + "type='" + mType + '\''
+ + ", value='" + mValue + '\''
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DeviceId)) return false;
+ DeviceId deviceId = (DeviceId) o;
+ return Objects.equals(mType, deviceId.mType) && Objects.equals(mValue,
+ deviceId.mValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeString(mValue);
+ }
+
+ private DeviceId(@NonNull Parcel in) {
+ mType = in.readString();
+ mValue = in.readString();
+ }
+
+ public static final @NonNull Creator<DeviceId> CREATOR = new Creator<DeviceId>() {
+ @Override
+ public DeviceId createFromParcel(@NonNull Parcel in) {
+ return new DeviceId(in);
+ }
+
+ @Override
+ public DeviceId[] newArray(int size) {
+ return new DeviceId[size];
+ }
+ };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6ea6fe0..cfd3417 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3336,7 +3336,11 @@
* Service will call {@link android.app.Service#startForeground(int, android.app.Notification)
* startForeground(int, android.app.Notification)} once it begins running. The service is given
* an amount of time comparable to the ANR interval to do this, otherwise the system
- * will automatically stop the service and declare the app ANR.
+ * will automatically crash the process, in which case an internal exception
+ * {@code ForegroundServiceDidNotStartInTimeException} is logged on logcat on devices
+ * running SDK Version {@link android.os.Build.VERSION_CODES#S} or later. On older Android
+ * versions, an internal exception {@code RemoteServiceException} is logged instead, with
+ * a corresponding message.
*
* <p>Unlike the ordinary {@link #startService(Intent)}, this method can be used
* at any time, regardless of whether the app hosting the service is in a foreground
@@ -5786,6 +5790,16 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.CommunalManager} for interacting with the global system state.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.CommunalManager
+ * @hide
+ */
+ public static final String COMMUNAL_MANAGER_SERVICE = "communal_manager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.app.LocaleManager}.
*
* @see #getSystemService(String)
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ed65af3..6446b44 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -32,7 +32,6 @@
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.AppGlobals;
-import android.bluetooth.BluetoothDevice;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -2347,6 +2346,26 @@
public static final String ACTION_MANAGE_PERMISSION_USAGE =
"android.intent.action.MANAGE_PERMISSION_USAGE";
+ /**
+ * Activity action: Launch UI to view the app's feature's information.
+ *
+ * <p>
+ * Output: Nothing.
+ * </p>
+ * <p class="note">
+ * You must protect the activity that handles this action with the
+ * {@link android.Manifest.permission#START_VIEW_APP_FEATURES} permission to ensure that
+ * only the system can launch this activity. The system will not launch activities
+ * that are not properly protected.
+ * </p>
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VIEW_APP_FEATURES =
+ "android.intent.action.VIEW_APP_FEATURES";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent broadcast actions (see action variable).
@@ -11669,16 +11688,6 @@
if (fromProtectedComponent) {
mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT;
}
-
- // Special attribution fix-up logic for any BluetoothDevice extras
- // passed via Bluetooth intents
- if (mAction != null && mAction.startsWith("android.bluetooth.")
- && hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
- final BluetoothDevice device = getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (device != null) {
- device.prepareToEnterProcess(source);
- }
- }
}
/** @hide */
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7ac385b..46f1797 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -599,12 +599,6 @@
void forceDexOpt(String packageName);
/**
- * Execute the background dexopt job immediately on packages in packageNames.
- * If null, then execute on all packages.
- */
- boolean runBackgroundDexoptJob(in List<String> packageNames);
-
- /**
* Reconcile the information we have about the secondary dex files belonging to
* {@code packagName} and the actual dex files. For all dex files that were
* deleted, update the internal records and delete the generated oat files.
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 5a5f6db..edddf40 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -34,17 +34,25 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedActivityImpl;
import android.content.pm.parsing.component.ParsedAttribution;
+import android.content.pm.parsing.component.ParsedAttributionImpl;
import android.content.pm.parsing.component.ParsedComponent;
import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedInstrumentationImpl;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.content.pm.parsing.component.ParsedPermission;
import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedPermissionGroupImpl;
+import android.content.pm.parsing.component.ParsedPermissionImpl;
import android.content.pm.parsing.component.ParsedProcess;
import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedProviderImpl;
import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedServiceImpl;
import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.parsing.component.ParsedUsesPermissionImpl;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
@@ -103,8 +111,8 @@
public static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
public static ForInternedStringSet sForInternedStringSet =
Parcelling.Cache.getOrCreate(ForInternedStringSet.class);
- protected static ParsedIntentInfo.StringPairListParceler sForIntentInfoPairs =
- Parcelling.Cache.getOrCreate(ParsedIntentInfo.StringPairListParceler.class);
+ protected static ParsingUtils.StringPairListParceler sForIntentInfoPairs =
+ new ParsingUtils.StringPairListParceler();
private static final Comparator<ParsedMainComponent> ORDER_COMPARATOR =
(first, second) -> Integer.compare(second.getOrder(), first.getOrder());
@@ -270,7 +278,7 @@
protected List<ParsedInstrumentation> instrumentations = emptyList();
@NonNull
- @DataClass.ParcelWith(ParsedIntentInfo.ListParceler.class)
+// @DataClass.ParcelWith(ParsingUtils.StringPairListParceler.class)
private List<Pair<String, ParsedIntentInfo>> preferredActivityFilters = emptyList();
@NonNull
@@ -718,7 +726,7 @@
@Override
public ParsingPackageImpl addImplicitPermission(String permission) {
- addUsesPermission(new ParsedUsesPermission(permission, 0 /*usesPermissionFlags*/));
+ addUsesPermission(new ParsedUsesPermissionImpl(permission, 0 /*usesPermissionFlags*/));
this.implicitPermissions = CollectionUtils.add(this.implicitPermissions,
TextUtils.safeIntern(permission));
return this;
@@ -1277,20 +1285,24 @@
this.originalPackages = in.createStringArrayList();
this.adoptPermissions = sForInternedStringList.unparcel(in);
this.requestedPermissions = sForInternedStringList.unparcel(in);
- this.usesPermissions = in.createTypedArrayList(ParsedUsesPermission.CREATOR);
+ this.usesPermissions = ParsingUtils.createTypedInterfaceList(in,
+ ParsedUsesPermissionImpl.CREATOR);
this.implicitPermissions = sForInternedStringList.unparcel(in);
this.upgradeKeySets = sForStringSet.unparcel(in);
this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in);
this.protectedBroadcasts = sForInternedStringList.unparcel(in);
- this.activities = in.createTypedArrayList(ParsedActivity.CREATOR);
- this.receivers = in.createTypedArrayList(ParsedActivity.CREATOR);
- this.services = in.createTypedArrayList(ParsedService.CREATOR);
- this.providers = in.createTypedArrayList(ParsedProvider.CREATOR);
- this.attributions = in.createTypedArrayList(ParsedAttribution.CREATOR);
- this.permissions = in.createTypedArrayList(ParsedPermission.CREATOR);
- this.permissionGroups = in.createTypedArrayList(ParsedPermissionGroup.CREATOR);
- this.instrumentations = in.createTypedArrayList(ParsedInstrumentation.CREATOR);
+ this.activities = ParsingUtils.createTypedInterfaceList(in, ParsedActivityImpl.CREATOR);
+ this.receivers = ParsingUtils.createTypedInterfaceList(in, ParsedActivityImpl.CREATOR);
+ this.services = ParsingUtils.createTypedInterfaceList(in, ParsedServiceImpl.CREATOR);
+ this.providers = ParsingUtils.createTypedInterfaceList(in, ParsedProviderImpl.CREATOR);
+ this.attributions = ParsingUtils.createTypedInterfaceList(in,
+ ParsedAttributionImpl.CREATOR);
+ this.permissions = ParsingUtils.createTypedInterfaceList(in, ParsedPermissionImpl.CREATOR);
+ this.permissionGroups = ParsingUtils.createTypedInterfaceList(in,
+ ParsedPermissionGroupImpl.CREATOR);
+ this.instrumentations = ParsingUtils.createTypedInterfaceList(in,
+ ParsedInstrumentationImpl.CREATOR);
this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in);
this.processes = in.readHashMap(boot);
this.metaData = in.readBundle(boot);
@@ -1685,7 +1697,7 @@
private void addMimeGroupsFromComponent(ParsedComponent component) {
for (int i = component.getIntents().size() - 1; i >= 0; i--) {
- IntentFilter filter = component.getIntents().get(i);
+ IntentFilter filter = component.getIntents().get(i).getIntentFilter();
for (int groupIndex = filter.countMimeGroups() - 1; groupIndex >= 0; groupIndex--) {
mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 97af1d24..d19186e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -49,8 +49,10 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ComponentParseUtils;
import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedActivityImpl;
import android.content.pm.parsing.component.ParsedActivityUtils;
import android.content.pm.parsing.component.ParsedAttribution;
import android.content.pm.parsing.component.ParsedAttributionUtils;
@@ -70,6 +72,7 @@
import android.content.pm.parsing.component.ParsedService;
import android.content.pm.parsing.component.ParsedServiceUtils;
import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.parsing.component.ParsedUsesPermissionImpl;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
@@ -267,27 +270,27 @@
boolean collectCertificates) {
ParseResult<ParsingPackage> result;
- ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, splitPermissions,
- new Callback() {
- @Override
- public boolean hasFeature(String feature) {
- // Assume the device doesn't support anything. This will affect permission
- // parsing and will force <uses-permission/> declarations to include all
- // requiredNotFeature permissions and exclude all requiredFeature
- // permissions. This mirrors the old behavior.
- return false;
- }
+ ParsingPackageUtils parser = new ParsingPackageUtils(false, null /*separateProcesses*/,
+ null /*displayMetrics*/, splitPermissions, new Callback() {
+ @Override
+ public boolean hasFeature(String feature) {
+ // Assume the device doesn't support anything. This will affect permission
+ // parsing and will force <uses-permission/> declarations to include all
+ // requiredNotFeature permissions and exclude all requiredFeature
+ // permissions. This mirrors the old behavior.
+ return false;
+ }
- @Override
- public ParsingPackage startParsingPackage(
- @NonNull String packageName,
- @NonNull String baseApkPath,
- @NonNull String path,
- @NonNull TypedArray manifestArray, boolean isCoreApp) {
- return new ParsingPackageImpl(packageName, baseApkPath, path,
- manifestArray);
- }
- });
+ @Override
+ public ParsingPackage startParsingPackage(
+ @NonNull String packageName,
+ @NonNull String baseApkPath,
+ @NonNull String path,
+ @NonNull TypedArray manifestArray, boolean isCoreApp) {
+ return new ParsingPackageImpl(packageName, baseApkPath, path,
+ manifestArray);
+ }
+ });
result = parser.parsePackage(input, file, parseFlags);
if (result.isError()) {
return input.error(result);
@@ -625,8 +628,8 @@
final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
try {
- final boolean isCoreApp =
- parser.getAttributeBooleanValue(null, "coreApp", false);
+ final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/,
+ "coreApp",false);
final ParsingPackage pkg = mCallback.startParsingPackage(
pkgName, apkPath, codePath, manifestArray, isCoreApp);
final ParseResult<ParsingPackage> result =
@@ -732,6 +735,13 @@
} finally {
sa.recycle();
}
+
+ // If the loaded component did not specify a split, inherit the split name
+ // based on the split it is defined in.
+ // This is used to later load the correct split when starting this
+ // component.
+ String defaultSplitName = pkg.getSplitNames()[splitIndex];
+
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -753,8 +763,7 @@
case "receiver":
ParseResult<ParsedActivity> activityResult =
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
- res,
- parser, flags, sUseRoundIcon, input);
+ res, parser, flags, sUseRoundIcon, defaultSplitName, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
if (isActivity) {
@@ -768,8 +777,8 @@
break;
case "service":
ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(
- mSeparateProcesses, pkg, res, parser, flags,
- sUseRoundIcon, input);
+ mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon,
+ defaultSplitName, input);
if (serviceResult.isSuccess()) {
ParsedService service = serviceResult.getResult();
pkg.addService(service);
@@ -780,7 +789,7 @@
case "provider":
ParseResult<ParsedProvider> providerResult =
ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
- flags, sUseRoundIcon, input);
+ flags, sUseRoundIcon, defaultSplitName, input);
if (providerResult.isSuccess()) {
ParsedProvider provider = providerResult.getResult();
pkg.addProvider(provider);
@@ -790,7 +799,7 @@
break;
case "activity-alias":
activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser,
- sUseRoundIcon, input);
+ sUseRoundIcon, defaultSplitName, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
pkg.addActivity(activity);
@@ -807,14 +816,6 @@
if (result.isError()) {
return input.error(result);
}
-
- if (mainComponent != null && mainComponent.getSplitName() == null) {
- // If the loaded component did not specify a split, inherit the split name
- // based on the split it is defined in.
- // This is used to later load the correct split when starting this
- // component.
- mainComponent.setSplitName(pkg.getSplitNames()[splitIndex]);
- }
}
return input.success(pkg);
@@ -832,15 +833,15 @@
// note: application meta-data is stored off to the side, so it can
// remain null in the primary copy (we like to avoid extra copies because
// it can be large)
- ParseResult<Property> metaDataResult = parseMetaData(pkg, null, res,
- parser, "<meta-data>", input);
+ ParseResult<Property> metaDataResult = parseMetaData(pkg, null /*component*/,
+ res, parser, "<meta-data>", input);
if (metaDataResult.isSuccess() && metaDataResult.getResult() != null) {
pkg.setMetaData(metaDataResult.getResult().toBundle(pkg.getMetaData()));
}
return metaDataResult;
case "property":
- ParseResult<Property> propertyResult = parseMetaData(pkg, null, res,
- parser, "<property>", input);
+ ParseResult<Property> propertyResult = parseMetaData(pkg, null /*component*/,
+ res, parser, "<property>", input);
if (propertyResult.isSuccess()) {
pkg.addProperty(propertyResult.getResult());
}
@@ -919,7 +920,7 @@
}
}
- if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
+ if (!ParsedAttributionUtils.isCombinationValid(pkg.getAttributions())) {
return input.error(
INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Combination <attribution> tags are not valid"
@@ -1339,7 +1340,7 @@
}
if (!found) {
- pkg.addUsesPermission(new ParsedUsesPermission(name, usesPermissionFlags));
+ pkg.addUsesPermission(new ParsedUsesPermissionImpl(name, usesPermissionFlags));
}
return success;
} finally {
@@ -1807,13 +1808,14 @@
continue;
}
if (parser.getName().equals("intent")) {
- ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(null,
- pkg, res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, input);
+ ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(
+ null /*className*/, pkg, res, parser, true /*allowGlobs*/,
+ true /*allowAutoVerify*/, input);
if (result.isError()) {
return input.error(result);
}
- ParsedIntentInfo intentInfo = result.getResult();
+ IntentFilter intentInfo = result.getResult().getIntentFilter();
Uri data = null;
String dataType = null;
@@ -2077,7 +2079,7 @@
R.styleable.AndroidManifestApplication_process);
}
ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
- pkgName, null, pname, flags, mSeparateProcesses, input);
+ pkgName, null /*defProc*/, pname, flags, mSeparateProcesses, input);
if (processNameResult.isError()) {
return input.error(processNameResult);
}
@@ -2146,7 +2148,8 @@
case "receiver":
ParseResult<ParsedActivity> activityResult =
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
- res, parser, flags, sUseRoundIcon, input);
+ res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/,
+ input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
@@ -2164,7 +2167,8 @@
case "service":
ParseResult<ParsedService> serviceResult =
ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
- flags, sUseRoundIcon, input);
+ flags, sUseRoundIcon, null /*defaultSplitName*/,
+ input);
if (serviceResult.isSuccess()) {
ParsedService service = serviceResult.getResult();
hasServiceOrder |= (service.getOrder() != 0);
@@ -2176,7 +2180,8 @@
case "provider":
ParseResult<ParsedProvider> providerResult =
ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
- flags, sUseRoundIcon, input);
+ flags, sUseRoundIcon, null /*defaultSplitName*/,
+ input);
if (providerResult.isSuccess()) {
pkg.addProvider(providerResult.getResult());
}
@@ -2185,7 +2190,8 @@
break;
case "activity-alias":
activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
- parser, sUseRoundIcon, input);
+ parser, sUseRoundIcon, null /*defaultSplitName*/,
+ input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
hasActivityOrder |= (activity.getOrder() != 0);
@@ -2327,15 +2333,15 @@
// note: application meta-data is stored off to the side, so it can
// remain null in the primary copy (we like to avoid extra copies because
// it can be large)
- final ParseResult<Property> metaDataResult = parseMetaData(pkg, null, res,
- parser, "<meta-data>", input);
+ final ParseResult<Property> metaDataResult = parseMetaData(pkg, null /*component*/,
+ res, parser, "<meta-data>", input);
if (metaDataResult.isSuccess() && metaDataResult.getResult() != null) {
pkg.setMetaData(metaDataResult.getResult().toBundle(pkg.getMetaData()));
}
return metaDataResult;
case "property":
- final ParseResult<Property> propertyResult = parseMetaData(pkg, null, res,
- parser, "<property>", input);
+ final ParseResult<Property> propertyResult = parseMetaData(pkg, null /*component*/,
+ res, parser, "<property>", input);
if (propertyResult.isSuccess()) {
pkg.addProperty(propertyResult.getResult());
}
@@ -2647,7 +2653,7 @@
List<ParsedIntentInfo> filters = activity.getIntents();
final int filtersSize = filters.size();
for (int filtersIndex = 0; filtersIndex < filtersSize; filtersIndex++) {
- ParsedIntentInfo aii = filters.get(filtersIndex);
+ IntentFilter aii = filters.get(filtersIndex).getIntentFilter();
if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
@@ -2697,7 +2703,8 @@
? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
: maxAspectRatio;
- activity.setMaxAspectRatio(activity.getResizeMode(), activityAspectRatio);
+ ComponentMutateUtils.setMaxAspectRatio(activity, activity.getResizeMode(),
+ activityAspectRatio);
}
}
@@ -2714,7 +2721,8 @@
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
if (activity.getMinAspectRatio() == ASPECT_RATIO_NOT_SET) {
- activity.setMinAspectRatio(activity.getResizeMode(), minAspectRatio);
+ ComponentMutateUtils.setMinAspectRatio(activity, activity.getResizeMode(),
+ minAspectRatio);
}
}
}
@@ -2731,7 +2739,7 @@
if (supportsSizeChanges || (activity.getMetaData() != null
&& activity.getMetaData().getBoolean(
METADATA_SUPPORTS_SIZE_CHANGES, false))) {
- activity.setSupportsSizeChanges(true);
+ ComponentMutateUtils.setSupportsSizeChanges(activity, true);
}
}
}
@@ -2871,7 +2879,7 @@
private static void convertCompatPermissions(ParsingPackage pkg) {
for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) {
final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i];
- if (pkg.getTargetSdkVersion() >= info.sdkVersion) {
+ if (pkg.getTargetSdkVersion() >= info.getSdkVersion()) {
break;
}
if (!pkg.getRequestedPermissions().contains(info.getName())) {
@@ -2910,8 +2918,9 @@
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
- activity.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
- .setFlags(activity.getFlags() & ~FLAG_SUPPORTS_PICTURE_IN_PICTURE);
+ ComponentMutateUtils.setResizeMode(activity, RESIZE_MODE_UNRESIZEABLE);
+ ComponentMutateUtils.setExactFlags(activity,
+ activity.getFlags() & ~FLAG_SUPPORTS_PICTURE_IN_PICTURE);
}
}
diff --git a/core/java/android/content/pm/parsing/ParsingUtils.java b/core/java/android/content/pm/parsing/ParsingUtils.java
index f3a1740..cce984e 100644
--- a/core/java/android/content/pm/parsing/ParsingUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingUtils.java
@@ -20,16 +20,24 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedIntentInfoImpl;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.XmlResourceParser;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
import android.util.Slog;
+import com.android.internal.util.Parcelling;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/** @hide **/
public class ParsingUtils {
@@ -74,4 +82,66 @@
XmlUtils.skipCurrentTag(parser);
return input.success(null); // Type doesn't matter
}
+
+ /**
+ * Use with {@link Parcel#writeTypedList(List)}
+ *
+ * @see Parcel#createTypedArrayList(Parcelable.Creator)
+ */
+ @NonNull
+ public static <Interface, Impl extends Interface> List<Interface> createTypedInterfaceList(
+ @NonNull Parcel parcel, @NonNull Parcelable.Creator<Impl> creator) {
+ int size = parcel.readInt();
+ if (size < 0) {
+ return new ArrayList<>();
+ }
+ ArrayList<Interface> list = new ArrayList<Interface>(size);
+ while (size > 0) {
+ list.add(parcel.readTypedObject(creator));
+ size--;
+ }
+ return list;
+ }
+
+ public static class StringPairListParceler implements
+ Parcelling<List<Pair<String, ParsedIntentInfo>>> {
+
+ @Override
+ public void parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest,
+ int parcelFlags) {
+ if (item == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ final int size = item.size();
+ dest.writeInt(size);
+
+ for (int index = 0; index < size; index++) {
+ Pair<String, ParsedIntentInfo> pair = item.get(index);
+ dest.writeString(pair.first);
+ dest.writeParcelable(pair.second, parcelFlags);
+ }
+ }
+
+ @Override
+ public List<Pair<String, ParsedIntentInfo>> unparcel(Parcel source) {
+ int size = source.readInt();
+ if (size == -1) {
+ return null;
+ }
+
+ if (size == 0) {
+ return new ArrayList<>(0);
+ }
+
+ final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ list.add(Pair.create(source.readString(), source.readParcelable(
+ ParsedIntentInfoImpl.class.getClassLoader())));
+ }
+
+ return list;
+ }
+ }
}
diff --git a/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java b/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java
new file mode 100644
index 0000000..73d0b9f
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually
+ * be removed once all post-parsing mutation is moved to parsing.
+ *
+ * @hide
+ */
+public class ComponentMutateUtils {
+
+ public static void setMaxAspectRatio(@NonNull ParsedActivity activity, int resizeMode,
+ float maxAspectRatio) {
+ ((ParsedActivityImpl) activity).setMaxAspectRatio(resizeMode, maxAspectRatio);
+ }
+
+ public static void setMinAspectRatio(@NonNull ParsedActivity activity, int resizeMode,
+ float minAspectRatio) {
+ ((ParsedActivityImpl) activity).setMinAspectRatio(resizeMode, minAspectRatio);
+ }
+
+ public static void setSupportsSizeChanges(@NonNull ParsedActivity activity,
+ boolean supportsSizeChanges) {
+ ((ParsedActivityImpl) activity).setSupportsSizeChanges(supportsSizeChanges);
+ }
+
+ public static void setResizeMode(@NonNull ParsedActivity activity, int resizeMode) {
+ ((ParsedActivityImpl) activity).setResizeMode(resizeMode);
+ }
+
+ public static void setExactFlags(ParsedComponent component, int exactFlags) {
+ ((ParsedComponentImpl) component).setFlags(exactFlags);
+ }
+
+ public static void setEnabled(@NonNull ParsedMainComponent component, boolean enabled) {
+ ((ParsedMainComponentImpl) component).setEnabled(enabled);
+ }
+
+ public static void setPackageName(@NonNull ParsedComponent component,
+ @NonNull String packageName) {
+ ((ParsedComponentImpl) component).setPackageName(packageName);
+ }
+
+ public static void setDirectBootAware(@NonNull ParsedMainComponent component,
+ boolean directBootAware) {
+ ((ParsedMainComponentImpl) component).setDirectBootAware(directBootAware);
+ }
+
+ public static void setExported(@NonNull ParsedMainComponent component, boolean exported) {
+ ((ParsedMainComponentImpl) component).setExported(exported);
+ }
+
+ public static void setAuthority(@NonNull ParsedProvider provider, @Nullable String authority) {
+ ((ParsedProviderImpl) provider).setAuthority(authority);
+ }
+
+ public static void setSyncable(@NonNull ParsedProvider provider, boolean syncable) {
+ ((ParsedProviderImpl) provider).setSyncable(syncable);
+ }
+
+ public static void setProtectionLevel(@NonNull ParsedPermission permission,
+ int protectionLevel) {
+ ((ParsedPermissionImpl) permission).setProtectionLevel(protectionLevel);
+ }
+
+ public static void setParsedPermissionGroup(@NonNull ParsedPermission permission,
+ @NonNull ParsedPermissionGroup permissionGroup) {
+ ((ParsedPermissionImpl) permission).setParsedPermissionGroup(permissionGroup);
+ }
+
+ public static void setPriority(@NonNull ParsedPermissionGroup parsedPermissionGroup,
+ int priority) {
+ ((ParsedPermissionGroupImpl) parsedPermissionGroup).setPriority(priority);
+ }
+
+ public static void addStateFrom(@NonNull ParsedProcess oldProcess,
+ @NonNull ParsedProcess newProcess) {
+ ((ParsedProcessImpl) oldProcess).addStateFrom(newProcess);
+ }
+}
diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
index ae6181d..012a542 100644
--- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageUtils;
import android.content.pm.parsing.ParsingUtils;
@@ -45,13 +46,14 @@
public class ComponentParseUtils {
public static boolean isImplicitlyExposedIntent(ParsedIntentInfo intentInfo) {
- return intentInfo.hasCategory(Intent.CATEGORY_BROWSABLE)
- || intentInfo.hasAction(Intent.ACTION_SEND)
- || intentInfo.hasAction(Intent.ACTION_SENDTO)
- || intentInfo.hasAction(Intent.ACTION_SEND_MULTIPLE);
+ IntentFilter intentFilter = intentInfo.getIntentFilter();
+ return intentFilter.hasCategory(Intent.CATEGORY_BROWSABLE)
+ || intentFilter.hasAction(Intent.ACTION_SEND)
+ || intentFilter.hasAction(Intent.ACTION_SENDTO)
+ || intentFilter.hasAction(Intent.ACTION_SEND_MULTIPLE);
}
- static <Component extends ParsedComponent> ParseResult<Component> parseAllMetaData(
+ static <Component extends ParsedComponentImpl> ParseResult<Component> parseAllMetaData(
ParsingPackage pkg, Resources res, XmlResourceParser parser, String tag,
Component component, ParseInput input) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java
index 8ca86f1..a661b51 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivity.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java
@@ -16,516 +16,74 @@
package android.content.pm.parsing.component;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-import static android.content.pm.parsing.ParsingPackageUtils.ASPECT_RATIO_NOT_SET;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
-import android.content.ComponentName;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
/** @hide **/
-public class ParsedActivity extends ParsedMainComponent {
-
- private int theme;
- private int uiOptions;
-
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String targetActivity;
-
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String parentActivityName;
- @Nullable
- private String taskAffinity;
- private int privateFlags;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String permission;
-
- private int launchMode;
- private int documentLaunchMode;
- private int maxRecents;
- private int configChanges;
- private int softInputMode;
- private int persistableMode;
- private int lockTaskLaunchMode;
-
- private int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
- private int resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
-
- private float maxAspectRatio = ASPECT_RATIO_NOT_SET;
- private float minAspectRatio = ASPECT_RATIO_NOT_SET;
-
- private boolean supportsSizeChanges;
-
- @Nullable
- private String requestedVrComponent;
- private int rotationAnimation = -1;
- private int colorMode;
-
- @Nullable
- private ActivityInfo.WindowLayout windowLayout;
-
- public ParsedActivity(ParsedActivity other) {
- super(other);
- this.theme = other.theme;
- this.uiOptions = other.uiOptions;
- this.targetActivity = other.targetActivity;
- this.parentActivityName = other.parentActivityName;
- this.taskAffinity = other.taskAffinity;
- this.privateFlags = other.privateFlags;
- this.permission = other.permission;
- this.launchMode = other.launchMode;
- this.documentLaunchMode = other.documentLaunchMode;
- this.maxRecents = other.maxRecents;
- this.configChanges = other.configChanges;
- this.softInputMode = other.softInputMode;
- this.persistableMode = other.persistableMode;
- this.lockTaskLaunchMode = other.lockTaskLaunchMode;
- this.screenOrientation = other.screenOrientation;
- this.resizeMode = other.resizeMode;
- this.maxAspectRatio = other.maxAspectRatio;
- this.minAspectRatio = other.minAspectRatio;
- this.supportsSizeChanges = other.supportsSizeChanges;
- this.requestedVrComponent = other.requestedVrComponent;
- this.rotationAnimation = other.rotationAnimation;
- this.colorMode = other.colorMode;
- this.windowLayout = other.windowLayout;
- }
+public interface ParsedActivity extends ParsedMainComponent {
/**
* Generate activity object that forwards user to App Details page automatically.
* This activity should be invisible to user and user should not know or see it.
*/
- public static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
- int uiOptions, String taskAffinity, boolean hardwareAccelerated) {
- ParsedActivity activity = new ParsedActivity();
- activity.setPackageName(packageName);
- activity.theme = android.R.style.Theme_NoDisplay;
- activity.exported = true;
- activity.setName(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
- activity.setProcessName(processName);
- activity.uiOptions = uiOptions;
- activity.taskAffinity = taskAffinity;
- activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
- activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
- activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
- activity.configChanges = ParsedActivityUtils.getActivityConfigChanges(0, 0);
- activity.softInputMode = 0;
- activity.persistableMode = ActivityInfo.PERSIST_NEVER;
- activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
- activity.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
- activity.lockTaskLaunchMode = 0;
- activity.setDirectBootAware(false);
- activity.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
- activity.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
- if (hardwareAccelerated) {
- activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_HARDWARE_ACCELERATED);
- }
- return activity;
- }
-
- static ParsedActivity makeAlias(String targetActivityName, ParsedActivity target) {
- ParsedActivity alias = new ParsedActivity();
- alias.setPackageName(target.getPackageName());
- alias.setTargetActivity(targetActivityName);
- alias.configChanges = target.configChanges;
- alias.flags = target.flags;
- alias.privateFlags = target.privateFlags;
- alias.icon = target.icon;
- alias.logo = target.logo;
- alias.banner = target.banner;
- alias.labelRes = target.labelRes;
- alias.nonLocalizedLabel = target.nonLocalizedLabel;
- alias.launchMode = target.launchMode;
- alias.lockTaskLaunchMode = target.lockTaskLaunchMode;
- alias.documentLaunchMode = target.documentLaunchMode;
- alias.descriptionRes = target.descriptionRes;
- alias.screenOrientation = target.screenOrientation;
- alias.taskAffinity = target.taskAffinity;
- alias.theme = target.theme;
- alias.softInputMode = target.softInputMode;
- alias.uiOptions = target.uiOptions;
- alias.parentActivityName = target.parentActivityName;
- alias.maxRecents = target.maxRecents;
- alias.windowLayout = target.windowLayout;
- alias.resizeMode = target.resizeMode;
- alias.maxAspectRatio = target.maxAspectRatio;
- alias.minAspectRatio = target.minAspectRatio;
- alias.supportsSizeChanges = target.supportsSizeChanges;
- alias.requestedVrComponent = target.requestedVrComponent;
- alias.directBootAware = target.directBootAware;
- alias.setProcessName(target.getProcessName());
- return alias;
-
- // Not all attributes from the target ParsedActivity are copied to the alias.
- // Careful when adding an attribute and determine whether or not it should be copied.
-// alias.enabled = target.enabled;
-// alias.exported = target.exported;
-// alias.permission = target.permission;
-// alias.splitName = target.splitName;
-// alias.persistableMode = target.persistableMode;
-// alias.rotationAnimation = target.rotationAnimation;
-// alias.colorMode = target.colorMode;
-// alias.intents.addAll(target.intents);
-// alias.order = target.order;
-// alias.metaData = target.metaData;
- }
-
- public boolean isSupportsSizeChanges() {
- return supportsSizeChanges;
- }
-
- public ParsedActivity setColorMode(int colorMode) {
- this.colorMode = colorMode;
- return this;
- }
-
- public ParsedActivity setConfigChanges(int configChanges) {
- this.configChanges = configChanges;
- return this;
- }
-
- public ParsedActivity setDocumentLaunchMode(int documentLaunchMode) {
- this.documentLaunchMode = documentLaunchMode;
- return this;
- }
-
- public ParsedActivity setLaunchMode(int launchMode) {
- this.launchMode = launchMode;
- return this;
- }
-
- public ParsedActivity setLockTaskLaunchMode(int lockTaskLaunchMode) {
- this.lockTaskLaunchMode = lockTaskLaunchMode;
- return this;
- }
-
- public ParsedActivity setMaxAspectRatio(int resizeMode, float maxAspectRatio) {
- if (resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE
- || resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
- // Resizeable activities can be put in any aspect ratio.
- return this;
- }
-
- if (maxAspectRatio < 1.0f && maxAspectRatio != 0) {
- // Ignore any value lesser than 1.0.
- return this;
- }
-
- this.maxAspectRatio = maxAspectRatio;
- return this;
- }
-
- public ParsedActivity setMaxAspectRatio(float maxAspectRatio) {
- this.maxAspectRatio = maxAspectRatio;
- return this;
- }
-
- public ParsedActivity setMaxRecents(int maxRecents) {
- this.maxRecents = maxRecents;
- return this;
- }
-
- public ParsedActivity setMinAspectRatio(int resizeMode, float minAspectRatio) {
- if (resizeMode == RESIZE_MODE_RESIZEABLE
- || resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
- // Resizeable activities can be put in any aspect ratio.
- return this;
- }
-
- if (minAspectRatio < 1.0f && minAspectRatio != 0) {
- // Ignore any value lesser than 1.0.
- return this;
- }
-
- this.minAspectRatio = minAspectRatio;
- return this;
- }
-
- public ParsedActivity setMinAspectRatio(float minAspectRatio) {
- this.minAspectRatio = minAspectRatio;
- return this;
- }
-
- public ParsedActivity setParentActivityName(String parentActivityName) {
- this.parentActivityName = parentActivityName;
- return this;
- }
-
- public ParsedActivity setPersistableMode(int persistableMode) {
- this.persistableMode = persistableMode;
- return this;
- }
-
- public ParsedActivity setPrivateFlags(int privateFlags) {
- this.privateFlags = privateFlags;
- return this;
- }
-
- public ParsedActivity setRequestedVrComponent(String requestedVrComponent) {
- this.requestedVrComponent = requestedVrComponent;
- return this;
- }
-
- public ParsedActivity setRotationAnimation(int rotationAnimation) {
- this.rotationAnimation = rotationAnimation;
- return this;
- }
-
- public ParsedActivity setScreenOrientation(int screenOrientation) {
- this.screenOrientation = screenOrientation;
- return this;
- }
-
- public ParsedActivity setSoftInputMode(int softInputMode) {
- this.softInputMode = softInputMode;
- return this;
- }
-
- public ParsedActivity setSupportsSizeChanges(boolean supportsSizeChanges) {
- this.supportsSizeChanges = supportsSizeChanges;
- return this;
- }
-
- public ParsedActivity setResizeMode(int resizeMode) {
- this.resizeMode = resizeMode;
- return this;
- }
-
- public ParsedActivity setTargetActivity(String targetActivity) {
- this.targetActivity = TextUtils.safeIntern(targetActivity);
- return this;
- }
-
- public ParsedActivity setPermission(String permission) {
- // Empty string must be converted to null
- this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
- return this;
- }
-
- public ParsedActivity setTaskAffinity(String taskAffinity) {
- this.taskAffinity = taskAffinity;
- return this;
- }
-
- public ParsedActivity setTheme(int theme) {
- this.theme = theme;
- return this;
- }
-
- public ParsedActivity setUiOptions(int uiOptions) {
- this.uiOptions = uiOptions;
- return this;
- }
-
- public ParsedActivity setWindowLayout(ActivityInfo.WindowLayout windowLayout) {
- this.windowLayout = windowLayout;
- return this;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Activity{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(' ');
- ComponentName.appendShortString(sb, getPackageName(), getName());
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(this.theme);
- dest.writeInt(this.uiOptions);
- dest.writeString(this.targetActivity);
- dest.writeString(this.parentActivityName);
- dest.writeString(this.taskAffinity);
- dest.writeInt(this.privateFlags);
- sForInternedString.parcel(this.permission, dest, flags);
- dest.writeInt(this.launchMode);
- dest.writeInt(this.documentLaunchMode);
- dest.writeInt(this.maxRecents);
- dest.writeInt(this.configChanges);
- dest.writeInt(this.softInputMode);
- dest.writeInt(this.persistableMode);
- dest.writeInt(this.lockTaskLaunchMode);
- dest.writeInt(this.screenOrientation);
- dest.writeInt(this.resizeMode);
- dest.writeFloat(this.maxAspectRatio);
- dest.writeFloat(this.minAspectRatio);
- dest.writeBoolean(this.supportsSizeChanges);
- dest.writeString(this.requestedVrComponent);
- dest.writeInt(this.rotationAnimation);
- dest.writeInt(this.colorMode);
- dest.writeBundle(this.metaData);
-
- if (windowLayout != null) {
- dest.writeInt(1);
- windowLayout.writeToParcel(dest);
- } else {
- dest.writeBoolean(false);
- }
- }
-
- public ParsedActivity() {
- }
-
- protected ParsedActivity(Parcel in) {
- super(in);
- this.theme = in.readInt();
- this.uiOptions = in.readInt();
- this.targetActivity = in.readString();
- this.parentActivityName = in.readString();
- this.taskAffinity = in.readString();
- this.privateFlags = in.readInt();
- this.permission = sForInternedString.unparcel(in);
- this.launchMode = in.readInt();
- this.documentLaunchMode = in.readInt();
- this.maxRecents = in.readInt();
- this.configChanges = in.readInt();
- this.softInputMode = in.readInt();
- this.persistableMode = in.readInt();
- this.lockTaskLaunchMode = in.readInt();
- this.screenOrientation = in.readInt();
- this.resizeMode = in.readInt();
- this.maxAspectRatio = in.readFloat();
- this.minAspectRatio = in.readFloat();
- this.supportsSizeChanges = in.readBoolean();
- this.requestedVrComponent = in.readString();
- this.rotationAnimation = in.readInt();
- this.colorMode = in.readInt();
- this.metaData = in.readBundle();
- if (in.readBoolean()) {
- windowLayout = new ActivityInfo.WindowLayout(in);
- }
- }
-
@NonNull
- public static final Parcelable.Creator<ParsedActivity> CREATOR = new Creator<ParsedActivity>() {
- @Override
- public ParsedActivity createFromParcel(Parcel source) {
- return new ParsedActivity(source);
- }
-
- @Override
- public ParsedActivity[] newArray(int size) {
- return new ParsedActivity[size];
- }
- };
-
- public int getTheme() {
- return theme;
+ static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
+ int uiOptions, String taskAffinity, boolean hardwareAccelerated) {
+ // Proxy method since ParsedActivityImpl is supposed to be package visibility
+ return ParsedActivityImpl.makeAppDetailsActivity(packageName, processName, uiOptions,
+ taskAffinity, hardwareAccelerated);
}
- public int getUiOptions() {
- return uiOptions;
- }
+ int getColorMode();
+
+ int getConfigChanges();
+
+ int getDocumentLaunchMode();
+
+ int getLaunchMode();
+
+ int getLockTaskLaunchMode();
+
+ int getMaxRecents();
+
+ float getMaxAspectRatio();
+
+ float getMinAspectRatio();
@Nullable
- public String getTargetActivity() {
- return targetActivity;
- }
+ String getParentActivityName();
@Nullable
- public String getParentActivityName() {
- return parentActivityName;
- }
+ String getPermission();
+
+ int getPersistableMode();
+
+ int getPrivateFlags();
@Nullable
- public String getTaskAffinity() {
- return taskAffinity;
- }
+ String getRequestedVrComponent();
- public int getPrivateFlags() {
- return privateFlags;
- }
+ int getRotationAnimation();
+
+ int getResizeMode();
+
+ int getScreenOrientation();
+
+ int getSoftInputMode();
@Nullable
- public String getPermission() {
- return permission;
- }
-
- public int getLaunchMode() {
- return launchMode;
- }
-
- public int getDocumentLaunchMode() {
- return documentLaunchMode;
- }
-
- public int getMaxRecents() {
- return maxRecents;
- }
-
- public int getConfigChanges() {
- return configChanges;
- }
-
- public int getSoftInputMode() {
- return softInputMode;
- }
-
- public int getPersistableMode() {
- return persistableMode;
- }
-
- public int getLockTaskLaunchMode() {
- return lockTaskLaunchMode;
- }
-
- public int getScreenOrientation() {
- return screenOrientation;
- }
-
- public int getResizeMode() {
- return resizeMode;
- }
-
- public float getMaxAspectRatio() {
- return maxAspectRatio;
- }
-
- public float getMinAspectRatio() {
- return minAspectRatio;
- }
+ String getTargetActivity();
@Nullable
- public String getRequestedVrComponent() {
- return requestedVrComponent;
- }
+ String getTaskAffinity();
- public int getRotationAnimation() {
- return rotationAnimation;
- }
+ int getTheme();
- public int getColorMode() {
- return colorMode;
- }
+ int getUiOptions();
@Nullable
- public ActivityInfo.WindowLayout getWindowLayout() {
- return windowLayout;
- }
+ ActivityInfo.WindowLayout getWindowLayout();
+
+ boolean isSupportsSizeChanges();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java b/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java
new file mode 100644
index 0000000..93dc5a4
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.ParsingUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide **/
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity {
+
+ private int theme;
+ private int uiOptions;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String targetActivity;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String parentActivityName;
+ @Nullable
+ private String taskAffinity;
+ private int privateFlags;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String permission;
+
+ private int launchMode;
+ private int documentLaunchMode;
+ private int maxRecents;
+ private int configChanges;
+ private int softInputMode;
+ private int persistableMode;
+ private int lockTaskLaunchMode;
+
+ private int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ private int resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+ private float maxAspectRatio = ParsingUtils.NOT_SET;
+ private float minAspectRatio = ParsingUtils.NOT_SET;
+
+ private boolean supportsSizeChanges;
+
+ @Nullable
+ private String requestedVrComponent;
+ private int rotationAnimation = -1;
+ private int colorMode;
+
+ @Nullable
+ private ActivityInfo.WindowLayout windowLayout;
+
+ public ParsedActivityImpl(ParsedActivityImpl other) {
+ super(other);
+ this.theme = other.theme;
+ this.uiOptions = other.uiOptions;
+ this.targetActivity = other.targetActivity;
+ this.parentActivityName = other.parentActivityName;
+ this.taskAffinity = other.taskAffinity;
+ this.privateFlags = other.privateFlags;
+ this.permission = other.permission;
+ this.launchMode = other.launchMode;
+ this.documentLaunchMode = other.documentLaunchMode;
+ this.maxRecents = other.maxRecents;
+ this.configChanges = other.configChanges;
+ this.softInputMode = other.softInputMode;
+ this.persistableMode = other.persistableMode;
+ this.lockTaskLaunchMode = other.lockTaskLaunchMode;
+ this.screenOrientation = other.screenOrientation;
+ this.resizeMode = other.resizeMode;
+ this.maxAspectRatio = other.maxAspectRatio;
+ this.minAspectRatio = other.minAspectRatio;
+ this.supportsSizeChanges = other.supportsSizeChanges;
+ this.requestedVrComponent = other.requestedVrComponent;
+ this.rotationAnimation = other.rotationAnimation;
+ this.colorMode = other.colorMode;
+ this.windowLayout = other.windowLayout;
+ }
+
+ /**
+ * Generate activity object that forwards user to App Details page automatically.
+ * This activity should be invisible to user and user should not know or see it.
+ */
+ @NonNull
+ static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName,
+ int uiOptions, String taskAffinity, boolean hardwareAccelerated) {
+ ParsedActivityImpl activity = new ParsedActivityImpl();
+ activity.setPackageName(packageName);
+ activity.theme = android.R.style.Theme_NoDisplay;
+ activity.setExported(true);
+ activity.setName(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
+ activity.setProcessName(processName);
+ activity.uiOptions = uiOptions;
+ activity.taskAffinity = taskAffinity;
+ activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
+ activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
+ activity.configChanges = ParsedActivityUtils.getActivityConfigChanges(0, 0);
+ activity.softInputMode = 0;
+ activity.persistableMode = ActivityInfo.PERSIST_NEVER;
+ activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ activity.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+ activity.lockTaskLaunchMode = 0;
+ activity.setDirectBootAware(false);
+ activity.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
+ activity.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+ if (hardwareAccelerated) {
+ activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_HARDWARE_ACCELERATED);
+ }
+ return activity;
+ }
+
+ @NonNull
+ static ParsedActivityImpl makeAlias(String targetActivityName, ParsedActivity target) {
+ ParsedActivityImpl alias = new ParsedActivityImpl();
+ alias.setPackageName(target.getPackageName());
+ alias.setTargetActivity(targetActivityName);
+ alias.configChanges = target.getConfigChanges();
+ alias.setFlags(target.getFlags());
+ alias.privateFlags = target.getPrivateFlags();
+ alias.setIcon(target.getIcon());
+ alias.setLogo(target.getLogo());
+ alias.setBanner(target.getBanner());
+ alias.setLabelRes(target.getLabelRes());
+ alias.setNonLocalizedLabel(target.getNonLocalizedLabel());
+ alias.launchMode = target.getLaunchMode();
+ alias.lockTaskLaunchMode = target.getLockTaskLaunchMode();
+ alias.documentLaunchMode = target.getDocumentLaunchMode();
+ alias.setDescriptionRes(target.getDescriptionRes());
+ alias.screenOrientation = target.getScreenOrientation();
+ alias.taskAffinity = target.getTaskAffinity();
+ alias.theme = target.getTheme();
+ alias.softInputMode = target.getSoftInputMode();
+ alias.uiOptions = target.getUiOptions();
+ alias.parentActivityName = target.getParentActivityName();
+ alias.maxRecents = target.getMaxRecents();
+ alias.windowLayout = target.getWindowLayout();
+ alias.resizeMode = target.getResizeMode();
+ alias.maxAspectRatio = target.getMaxAspectRatio();
+ alias.minAspectRatio = target.getMinAspectRatio();
+ alias.supportsSizeChanges = target.isSupportsSizeChanges();
+ alias.requestedVrComponent = target.getRequestedVrComponent();
+ alias.setDirectBootAware(target.isDirectBootAware());
+ alias.setProcessName(target.getProcessName());
+ return alias;
+
+ // Not all attributes from the target ParsedActivity are copied to the alias.
+ // Careful when adding an attribute and determine whether or not it should be copied.
+// alias.enabled = target.enabled;
+// alias.exported = target.exported;
+// alias.permission = target.permission;
+// alias.splitName = target.splitName;
+// alias.persistableMode = target.persistableMode;
+// alias.rotationAnimation = target.rotationAnimation;
+// alias.colorMode = target.colorMode;
+// alias.intents.addAll(target.intents);
+// alias.order = target.order;
+// alias.metaData = target.metaData;
+ }
+
+ public ParsedActivityImpl setMaxAspectRatio(int resizeMode, float maxAspectRatio) {
+ if (resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE
+ || resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ return this;
+ }
+
+ if (maxAspectRatio < 1.0f && maxAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ return this;
+ }
+
+ this.maxAspectRatio = maxAspectRatio;
+ return this;
+ }
+
+ public ParsedActivityImpl setMinAspectRatio(int resizeMode, float minAspectRatio) {
+ if (resizeMode == RESIZE_MODE_RESIZEABLE
+ || resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ return this;
+ }
+
+ if (minAspectRatio < 1.0f && minAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ return this;
+ }
+
+ this.minAspectRatio = minAspectRatio;
+ return this;
+ }
+
+ public ParsedActivityImpl setTargetActivity(String targetActivity) {
+ this.targetActivity = TextUtils.safeIntern(targetActivity);
+ return this;
+ }
+
+ public ParsedActivityImpl setPermission(String permission) {
+ // Empty string must be converted to null
+ this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
+ return this;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Activity{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.theme);
+ dest.writeInt(this.uiOptions);
+ dest.writeString(this.targetActivity);
+ dest.writeString(this.parentActivityName);
+ dest.writeString(this.taskAffinity);
+ dest.writeInt(this.privateFlags);
+ sForInternedString.parcel(this.permission, dest, flags);
+ dest.writeInt(this.launchMode);
+ dest.writeInt(this.documentLaunchMode);
+ dest.writeInt(this.maxRecents);
+ dest.writeInt(this.configChanges);
+ dest.writeInt(this.softInputMode);
+ dest.writeInt(this.persistableMode);
+ dest.writeInt(this.lockTaskLaunchMode);
+ dest.writeInt(this.screenOrientation);
+ dest.writeInt(this.resizeMode);
+ dest.writeValue(this.maxAspectRatio);
+ dest.writeValue(this.minAspectRatio);
+ dest.writeBoolean(this.supportsSizeChanges);
+ dest.writeString(this.requestedVrComponent);
+ dest.writeInt(this.rotationAnimation);
+ dest.writeInt(this.colorMode);
+ dest.writeBundle(this.getMetaData());
+
+ if (windowLayout != null) {
+ dest.writeInt(1);
+ windowLayout.writeToParcel(dest);
+ } else {
+ dest.writeBoolean(false);
+ }
+ }
+
+ public ParsedActivityImpl() {
+ }
+
+ protected ParsedActivityImpl(Parcel in) {
+ super(in);
+ this.theme = in.readInt();
+ this.uiOptions = in.readInt();
+ this.targetActivity = in.readString();
+ this.parentActivityName = in.readString();
+ this.taskAffinity = in.readString();
+ this.privateFlags = in.readInt();
+ this.permission = sForInternedString.unparcel(in);
+ this.launchMode = in.readInt();
+ this.documentLaunchMode = in.readInt();
+ this.maxRecents = in.readInt();
+ this.configChanges = in.readInt();
+ this.softInputMode = in.readInt();
+ this.persistableMode = in.readInt();
+ this.lockTaskLaunchMode = in.readInt();
+ this.screenOrientation = in.readInt();
+ this.resizeMode = in.readInt();
+ this.maxAspectRatio = (Float) in.readValue(Float.class.getClassLoader());
+ this.minAspectRatio = (Float) in.readValue(Float.class.getClassLoader());
+ this.supportsSizeChanges = in.readBoolean();
+ this.requestedVrComponent = in.readString();
+ this.rotationAnimation = in.readInt();
+ this.colorMode = in.readInt();
+ this.setMetaData(in.readBundle());
+ if (in.readBoolean()) {
+ windowLayout = new ActivityInfo.WindowLayout(in);
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParsedActivityImpl> CREATOR =
+ new Parcelable.Creator<ParsedActivityImpl>() {
+ @Override
+ public ParsedActivityImpl createFromParcel(Parcel source) {
+ return new ParsedActivityImpl(source);
+ }
+
+ @Override
+ public ParsedActivityImpl[] newArray(int size) {
+ return new ParsedActivityImpl[size];
+ }
+ };
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedActivityImpl(
+ int theme,
+ int uiOptions,
+ @Nullable String targetActivity,
+ @Nullable String parentActivityName,
+ @Nullable String taskAffinity,
+ int privateFlags,
+ @Nullable String permission,
+ int launchMode,
+ int documentLaunchMode,
+ int maxRecents,
+ int configChanges,
+ int softInputMode,
+ int persistableMode,
+ int lockTaskLaunchMode,
+ int screenOrientation,
+ int resizeMode,
+ float maxAspectRatio,
+ float minAspectRatio,
+ boolean supportsSizeChanges,
+ @Nullable String requestedVrComponent,
+ int rotationAnimation,
+ int colorMode,
+ @Nullable ActivityInfo.WindowLayout windowLayout) {
+ this.theme = theme;
+ this.uiOptions = uiOptions;
+ this.targetActivity = targetActivity;
+ this.parentActivityName = parentActivityName;
+ this.taskAffinity = taskAffinity;
+ this.privateFlags = privateFlags;
+ this.permission = permission;
+ this.launchMode = launchMode;
+ this.documentLaunchMode = documentLaunchMode;
+ this.maxRecents = maxRecents;
+ this.configChanges = configChanges;
+ this.softInputMode = softInputMode;
+ this.persistableMode = persistableMode;
+ this.lockTaskLaunchMode = lockTaskLaunchMode;
+ this.screenOrientation = screenOrientation;
+ this.resizeMode = resizeMode;
+ this.maxAspectRatio = maxAspectRatio;
+ this.minAspectRatio = minAspectRatio;
+ this.supportsSizeChanges = supportsSizeChanges;
+ this.requestedVrComponent = requestedVrComponent;
+ this.rotationAnimation = rotationAnimation;
+ this.colorMode = colorMode;
+ this.windowLayout = windowLayout;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public int getTheme() {
+ return theme;
+ }
+
+ @DataClass.Generated.Member
+ public int getUiOptions() {
+ return uiOptions;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getTargetActivity() {
+ return targetActivity;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getParentActivityName() {
+ return parentActivityName;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getTaskAffinity() {
+ return taskAffinity;
+ }
+
+ @DataClass.Generated.Member
+ public int getPrivateFlags() {
+ return privateFlags;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getPermission() {
+ return permission;
+ }
+
+ @DataClass.Generated.Member
+ public int getLaunchMode() {
+ return launchMode;
+ }
+
+ @DataClass.Generated.Member
+ public int getDocumentLaunchMode() {
+ return documentLaunchMode;
+ }
+
+ @DataClass.Generated.Member
+ public int getMaxRecents() {
+ return maxRecents;
+ }
+
+ @DataClass.Generated.Member
+ public int getConfigChanges() {
+ return configChanges;
+ }
+
+ @DataClass.Generated.Member
+ public int getSoftInputMode() {
+ return softInputMode;
+ }
+
+ @DataClass.Generated.Member
+ public int getPersistableMode() {
+ return persistableMode;
+ }
+
+ @DataClass.Generated.Member
+ public int getLockTaskLaunchMode() {
+ return lockTaskLaunchMode;
+ }
+
+ @DataClass.Generated.Member
+ public int getScreenOrientation() {
+ return screenOrientation;
+ }
+
+ @DataClass.Generated.Member
+ public int getResizeMode() {
+ return resizeMode;
+ }
+
+ @DataClass.Generated.Member
+ public float getMaxAspectRatio() {
+ return maxAspectRatio;
+ }
+
+ @DataClass.Generated.Member
+ public float getMinAspectRatio() {
+ return minAspectRatio;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isSupportsSizeChanges() {
+ return supportsSizeChanges;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getRequestedVrComponent() {
+ return requestedVrComponent;
+ }
+
+ @DataClass.Generated.Member
+ public int getRotationAnimation() {
+ return rotationAnimation;
+ }
+
+ @DataClass.Generated.Member
+ public int getColorMode() {
+ return colorMode;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable ActivityInfo.WindowLayout getWindowLayout() {
+ return windowLayout;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setTheme( int value) {
+ theme = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setUiOptions( int value) {
+ uiOptions = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setParentActivityName(@NonNull String value) {
+ parentActivityName = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setTaskAffinity(@NonNull String value) {
+ taskAffinity = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setPrivateFlags( int value) {
+ privateFlags = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setLaunchMode( int value) {
+ launchMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setDocumentLaunchMode( int value) {
+ documentLaunchMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setMaxRecents( int value) {
+ maxRecents = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setConfigChanges( int value) {
+ configChanges = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setSoftInputMode( int value) {
+ softInputMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setPersistableMode( int value) {
+ persistableMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setLockTaskLaunchMode( int value) {
+ lockTaskLaunchMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setScreenOrientation( int value) {
+ screenOrientation = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setResizeMode( int value) {
+ resizeMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setMaxAspectRatio( float value) {
+ maxAspectRatio = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setMinAspectRatio( float value) {
+ minAspectRatio = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setSupportsSizeChanges( boolean value) {
+ supportsSizeChanges = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setRequestedVrComponent(@NonNull String value) {
+ requestedVrComponent = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setRotationAnimation( int value) {
+ rotationAnimation = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setColorMode( int value) {
+ colorMode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setWindowLayout(@NonNull ActivityInfo.WindowLayout value) {
+ windowLayout = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1630600615936L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java",
+ inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index dc7eb80..45241b0 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -22,8 +22,10 @@
import static android.content.pm.parsing.component.ComponentParseUtils.flag;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageUtils;
@@ -79,34 +81,32 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static ParseResult<ParsedActivity> parseActivityOrReceiver(String[] separateProcesses,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
- boolean useRoundIcon, ParseInput input)
+ boolean useRoundIcon, @Nullable String defaultSplitName, ParseInput input)
throws XmlPullParserException, IOException {
final String packageName = pkg.getPackageName();
- final ParsedActivity
- activity = new ParsedActivity();
+ final ParsedActivityImpl activity = new ParsedActivityImpl();
boolean receiver = "receiver".equals(parser.getName());
String tag = "<" + parser.getName() + ">";
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);
try {
- ParseResult<ParsedActivity> result =
- ParsedMainComponentUtils.parseMainComponent(
- activity, tag, separateProcesses,
- pkg, sa, flags, useRoundIcon, input,
- R.styleable.AndroidManifestActivity_banner,
- R.styleable.AndroidManifestActivity_description,
- R.styleable.AndroidManifestActivity_directBootAware,
- R.styleable.AndroidManifestActivity_enabled,
- R.styleable.AndroidManifestActivity_icon,
- R.styleable.AndroidManifestActivity_label,
- R.styleable.AndroidManifestActivity_logo,
- R.styleable.AndroidManifestActivity_name,
- R.styleable.AndroidManifestActivity_process,
- R.styleable.AndroidManifestActivity_roundIcon,
- R.styleable.AndroidManifestActivity_splitName,
- R.styleable.AndroidManifestActivity_attributionTags);
+ ParseResult<ParsedActivityImpl> result =
+ ParsedMainComponentUtils.parseMainComponent(activity, tag, separateProcesses,
+ pkg, sa, flags, useRoundIcon, defaultSplitName, input,
+ R.styleable.AndroidManifestActivity_banner,
+ R.styleable.AndroidManifestActivity_description,
+ R.styleable.AndroidManifestActivity_directBootAware,
+ R.styleable.AndroidManifestActivity_enabled,
+ R.styleable.AndroidManifestActivity_icon,
+ R.styleable.AndroidManifestActivity_label,
+ R.styleable.AndroidManifestActivity_logo,
+ R.styleable.AndroidManifestActivity_name,
+ R.styleable.AndroidManifestActivity_process,
+ R.styleable.AndroidManifestActivity_roundIcon,
+ R.styleable.AndroidManifestActivity_splitName,
+ R.styleable.AndroidManifestActivity_attributionTags);
if (result.isError()) {
- return result;
+ return input.error(result);
}
if (receiver && pkg.isCantSaveState()) {
@@ -227,8 +227,8 @@
@NonNull
public static ParseResult<ParsedActivity> parseActivityAlias(ParsingPackage pkg, Resources res,
- XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
- throws XmlPullParserException, IOException {
+ XmlResourceParser parser, boolean useRoundIcon, @Nullable String defaultSplitName,
+ @NonNull ParseInput input) throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivityAlias);
try {
String targetActivity = sa.getNonConfigurationString(
@@ -263,11 +263,11 @@
+ ", parsedActivities = " + activities);
}
- ParsedActivity activity = ParsedActivity.makeAlias(targetActivity, target);
+ ParsedActivityImpl activity = ParsedActivityImpl.makeAlias(targetActivity, target);
String tag = "<" + parser.getName() + ">";
- ParseResult<ParsedActivity> result = ParsedMainComponentUtils.parseMainComponent(
- activity, tag, null, pkg, sa, 0, useRoundIcon, input,
+ ParseResult<ParsedActivityImpl> result = ParsedMainComponentUtils.parseMainComponent(
+ activity, tag, null, pkg, sa, 0, useRoundIcon, defaultSplitName, input,
R.styleable.AndroidManifestActivityAlias_banner,
R.styleable.AndroidManifestActivityAlias_description,
NOT_SET /*directBootAwareAttr*/,
@@ -281,7 +281,7 @@
NOT_SET /*splitNameAttr*/,
R.styleable.AndroidManifestActivityAlias_attributionTags);
if (result.isError()) {
- return result;
+ return input.error(result);
}
// TODO add visibleToInstantApps attribute to activity alias
@@ -308,7 +308,7 @@
* type of logic.
*/
@NonNull
- private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivity activity,
+ private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivityImpl activity,
ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,
TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,
ParseInput input, int parentActivityNameAttr, int permissionAttr,
@@ -354,15 +354,16 @@
ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
!isReceiver, visibleToEphemeral, resources, parser, input);
if (intentResult.isSuccess()) {
- ParsedIntentInfo intent = intentResult.getResult();
- if (intent != null) {
- activity.setOrder(Math.max(intent.getOrder(), activity.getOrder()));
- activity.addIntent(intent);
+ ParsedIntentInfo intentInfo = intentResult.getResult();
+ if (intentInfo != null) {
+ IntentFilter intentFilter = intentInfo.getIntentFilter();
+ activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
+ activity.addIntent(intentInfo);
if (LOG_UNSAFE_BROADCASTS && isReceiver
&& pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
- int actionCount = intent.countActions();
+ int actionCount = intentFilter.countActions();
for (int i = 0; i < actionCount; i++) {
- final String action = intent.getAction(i);
+ final String action = intentFilter.getAction(i);
if (action == null || !action.startsWith("android.")) {
continue;
}
@@ -446,7 +447,7 @@
@NonNull
private static ParseResult<ParsedIntentInfo> parseIntentFilter(ParsingPackage pkg,
- ParsedActivity activity, boolean allowImplicitEphemeralVisibility,
+ ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
ParseInput input) throws IOException, XmlPullParserException {
ParseResult<ParsedIntentInfo> result = ParsedMainComponentUtils.parseIntentFilter(activity,
@@ -459,10 +460,11 @@
ParsedIntentInfo intent = result.getResult();
if (intent != null) {
- if (intent.isVisibleToInstantApp()) {
+ final IntentFilter intentFilter = intent.getIntentFilter();
+ if (intentFilter.isVisibleToInstantApp()) {
activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
}
- if (intent.isImplicitlyVisibleToInstantApp()) {
+ if (intentFilter.isImplicitlyVisibleToInstantApp()) {
activity.setFlags(
activity.getFlags() | ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP);
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/core/java/android/content/pm/parsing/component/ParsedAttribution.java
index db3a1c4..ac7a928 100644
--- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java
+++ b/core/java/android/content/pm/parsing/component/ParsedAttribution.java
@@ -34,255 +34,26 @@
*
* @hide
*/
-@DataClass(genAidl = false, genSetters = true, genBuilder = false)
-public class ParsedAttribution implements Parcelable {
- /** Maximum length of attribution tag */
- public static final int MAX_ATTRIBUTION_TAG_LEN = 50;
-
- /** Maximum amount of attributions per package */
- private static final int MAX_NUM_ATTRIBUTIONS = 10000;
-
- /** Tag of the attribution */
- private @NonNull String tag;
-
- /** User visible label fo the attribution */
- private @StringRes int label;
-
- /** Ids of previously declared attributions this attribution inherits from */
- private @NonNull List<String> inheritFrom;
-
- public ParsedAttribution() {}
+public interface ParsedAttribution extends Parcelable {
/**
- * @return Is this set of attributions a valid combination for a single package?
+ * Maximum length of attribution tag
+ * @hide
*/
- public static boolean isCombinationValid(@Nullable List<ParsedAttribution> attributions) {
- if (attributions == null) {
- return true;
- }
-
- ArraySet<String> attributionTags = new ArraySet<>(attributions.size());
- ArraySet<String> inheritFromAttributionTags = new ArraySet<>();
-
- int numAttributions = attributions.size();
- if (numAttributions > MAX_NUM_ATTRIBUTIONS) {
- return false;
- }
-
- for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
- boolean wasAdded = attributionTags.add(attributions.get(attributionNum).tag);
- if (!wasAdded) {
- // feature id is not unique
- return false;
- }
- }
-
- for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
- ParsedAttribution feature = attributions.get(attributionNum);
-
- int numInheritFrom = feature.inheritFrom.size();
- for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; inheritFromNum++) {
- String inheritFrom = feature.inheritFrom.get(inheritFromNum);
-
- if (attributionTags.contains(inheritFrom)) {
- // Cannot inherit from a attribution that is still defined
- return false;
- }
-
- boolean wasAdded = inheritFromAttributionTags.add(inheritFrom);
- if (!wasAdded) {
- // inheritFrom is not unique
- return false;
- }
- }
- }
-
- return true;
- }
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @android.annotation.IntDef(prefix = "MAX_", value = {
- MAX_ATTRIBUTION_TAG_LEN,
- MAX_NUM_ATTRIBUTIONS
- })
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
- @DataClass.Generated.Member
- public @interface Max {}
-
- @DataClass.Generated.Member
- public static String maxToString(@Max int value) {
- switch (value) {
- case MAX_ATTRIBUTION_TAG_LEN:
- return "MAX_ATTRIBUTION_TAG_LEN";
- case MAX_NUM_ATTRIBUTIONS:
- return "MAX_NUM_ATTRIBUTIONS";
- default: return Integer.toHexString(value);
- }
- }
-
- /**
- * Creates a new ParsedAttribution.
- *
- * @param tag
- * Tag of the attribution
- * @param label
- * User visible label fo the attribution
- * @param inheritFrom
- * Ids of previously declared attributions this attribution inherits from
- */
- @DataClass.Generated.Member
- public ParsedAttribution(
- @NonNull String tag,
- @StringRes int label,
- @NonNull List<String> inheritFrom) {
- this.tag = tag;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, tag);
- this.label = label;
- com.android.internal.util.AnnotationValidations.validate(
- StringRes.class, null, label);
- this.inheritFrom = inheritFrom;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, inheritFrom);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * Tag of the attribution
- */
- @DataClass.Generated.Member
- public @NonNull String getTag() {
- return tag;
- }
-
- /**
- * User visible label fo the attribution
- */
- @DataClass.Generated.Member
- public @StringRes int getLabel() {
- return label;
- }
+ int MAX_ATTRIBUTION_TAG_LEN = 50;
/**
* Ids of previously declared attributions this attribution inherits from
*/
- @DataClass.Generated.Member
- public @NonNull List<String> getInheritFrom() {
- return inheritFrom;
- }
+ @NonNull List<String> getInheritFrom();
+
+ /**
+ * User visible label for the attribution
+ */
+ @StringRes int getLabel();
/**
* Tag of the attribution
*/
- @DataClass.Generated.Member
- public @NonNull ParsedAttribution setTag(@NonNull String value) {
- tag = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, tag);
- return this;
- }
-
- /**
- * User visible label fo the attribution
- */
- @DataClass.Generated.Member
- public @NonNull ParsedAttribution setLabel(@StringRes int value) {
- label = value;
- com.android.internal.util.AnnotationValidations.validate(
- StringRes.class, null, label);
- return this;
- }
-
- /**
- * Ids of previously declared attributions this attribution inherits from
- */
- @DataClass.Generated.Member
- public @NonNull ParsedAttribution setInheritFrom(@NonNull List<String> value) {
- inheritFrom = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, inheritFrom);
- return this;
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- dest.writeString(tag);
- dest.writeInt(label);
- dest.writeStringList(inheritFrom);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected ParsedAttribution(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- String _tag = in.readString();
- int _label = in.readInt();
- List<String> _inheritFrom = new ArrayList<>();
- in.readStringList(_inheritFrom);
-
- this.tag = _tag;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, tag);
- this.label = _label;
- com.android.internal.util.AnnotationValidations.validate(
- StringRes.class, null, label);
- this.inheritFrom = _inheritFrom;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, inheritFrom);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<ParsedAttribution> CREATOR
- = new Parcelable.Creator<ParsedAttribution>() {
- @Override
- public ParsedAttribution[] newArray(int size) {
- return new ParsedAttribution[size];
- }
-
- @Override
- public ParsedAttribution createFromParcel(@NonNull Parcel in) {
- return new ParsedAttribution(in);
- }
- };
-
- @DataClass.Generated(
- time = 1624050667337L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java",
- inputSignatures = "public static final int MAX_ATTRIBUTION_TAG_LEN\nprivate static final int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static boolean isCombinationValid(java.util.List<android.content.pm.parsing.component.ParsedAttribution>)\nclass ParsedAttribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
+ @NonNull String getTag();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java b/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java
new file mode 100644
index 0000000..510425f
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link android.R.styleable#AndroidManifestAttribution <attribution>} tag parsed from the
+ * manifest.
+ *
+ * @hide
+ */
+@DataClass(genAidl = false, genSetters = true, genBuilder = false, genParcelable = true)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedAttributionImpl implements ParsedAttribution {
+
+ /** Maximum amount of attributions per package */
+ static final int MAX_NUM_ATTRIBUTIONS = 10000;
+
+ /** Tag of the attribution */
+ private @NonNull String tag;
+
+ /** User visible label fo the attribution */
+ private @StringRes int label;
+
+ /** Ids of previously declared attributions this attribution inherits from */
+ private @NonNull List<String> inheritFrom;
+
+ public ParsedAttributionImpl() {}
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ParsedAttributionImpl.
+ *
+ * @param tag
+ * Tag of the attribution
+ * @param label
+ * User visible label fo the attribution
+ * @param inheritFrom
+ * Ids of previously declared attributions this attribution inherits from
+ */
+ @DataClass.Generated.Member
+ public ParsedAttributionImpl(
+ @NonNull String tag,
+ @StringRes int label,
+ @NonNull List<String> inheritFrom) {
+ this.tag = tag;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, tag);
+ this.label = label;
+ com.android.internal.util.AnnotationValidations.validate(
+ StringRes.class, null, label);
+ this.inheritFrom = inheritFrom;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, inheritFrom);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Tag of the attribution
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getTag() {
+ return tag;
+ }
+
+ /**
+ * User visible label fo the attribution
+ */
+ @DataClass.Generated.Member
+ public @StringRes int getLabel() {
+ return label;
+ }
+
+ /**
+ * Ids of previously declared attributions this attribution inherits from
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getInheritFrom() {
+ return inheritFrom;
+ }
+
+ /**
+ * Tag of the attribution
+ */
+ @DataClass.Generated.Member
+ public @NonNull ParsedAttributionImpl setTag(@NonNull String value) {
+ tag = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, tag);
+ return this;
+ }
+
+ /**
+ * User visible label fo the attribution
+ */
+ @DataClass.Generated.Member
+ public @NonNull ParsedAttributionImpl setLabel(@StringRes int value) {
+ label = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ StringRes.class, null, label);
+ return this;
+ }
+
+ /**
+ * Ids of previously declared attributions this attribution inherits from
+ */
+ @DataClass.Generated.Member
+ public @NonNull ParsedAttributionImpl setInheritFrom(@NonNull List<String> value) {
+ inheritFrom = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, inheritFrom);
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(tag);
+ dest.writeInt(label);
+ dest.writeStringList(inheritFrom);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedAttributionImpl(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String _tag = in.readString();
+ int _label = in.readInt();
+ List<String> _inheritFrom = new ArrayList<>();
+ in.readStringList(_inheritFrom);
+
+ this.tag = _tag;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, tag);
+ this.label = _label;
+ com.android.internal.util.AnnotationValidations.validate(
+ StringRes.class, null, label);
+ this.inheritFrom = _inheritFrom;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, inheritFrom);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ParsedAttributionImpl> CREATOR
+ = new Parcelable.Creator<ParsedAttributionImpl>() {
+ @Override
+ public ParsedAttributionImpl[] newArray(int size) {
+ return new ParsedAttributionImpl[size];
+ }
+
+ @Override
+ public ParsedAttributionImpl createFromParcel(@NonNull Parcel in) {
+ return new ParsedAttributionImpl(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1627594502974L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java",
+ inputSignatures = "static final int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java b/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java
index dccc49a..84f1d44 100644
--- a/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java
@@ -17,11 +17,13 @@
package android.content.pm.parsing.component;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.util.ArraySet;
import com.android.internal.R;
@@ -106,6 +108,54 @@
((ArrayList) inheritFrom).trimToSize();
}
- return input.success(new ParsedAttribution(attributionTag, label, inheritFrom));
+ return input.success(new ParsedAttributionImpl(attributionTag, label, inheritFrom));
+ }
+
+ /**
+ * @return Is this set of attributions a valid combination for a single package?
+ */
+ public static boolean isCombinationValid(@Nullable List<ParsedAttribution> attributions) {
+ if (attributions == null) {
+ return true;
+ }
+
+ ArraySet<String> attributionTags = new ArraySet<>(attributions.size());
+ ArraySet<String> inheritFromAttributionTags = new ArraySet<>();
+
+ int numAttributions = attributions.size();
+ if (numAttributions > ParsedAttributionImpl.MAX_NUM_ATTRIBUTIONS) {
+ return false;
+ }
+
+ for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
+ boolean wasAdded = attributionTags.add(attributions.get(attributionNum).getTag());
+ if (!wasAdded) {
+ // feature id is not unique
+ return false;
+ }
+ }
+
+ for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
+ ParsedAttribution feature = attributions.get(attributionNum);
+
+ final List<String> inheritFromList = feature.getInheritFrom();
+ int numInheritFrom = inheritFromList.size();
+ for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; inheritFromNum++) {
+ String inheritFrom = inheritFromList.get(inheritFromNum);
+
+ if (attributionTags.contains(inheritFrom)) {
+ // Cannot inherit from a attribution that is still defined
+ return false;
+ }
+
+ boolean wasAdded = inheritFromAttributionTags.add(inheritFrom);
+ if (!wasAdded) {
+ // inheritFrom is not unique
+ return false;
+ }
+ }
+ }
+
+ return true;
}
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedComponent.java b/core/java/android/content/pm/parsing/component/ParsedComponent.java
index 838adfd..c1372f6 100644
--- a/core/java/android/content/pm/parsing/component/ParsedComponent.java
+++ b/core/java/android/content/pm/parsing/component/ParsedComponent.java
@@ -16,251 +16,49 @@
package android.content.pm.parsing.component;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-
-import static java.util.Collections.emptyMap;
-
-import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager.Property;
import android.os.Bundle;
-import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
/** @hide */
-public abstract class ParsedComponent implements Parcelable {
+public interface ParsedComponent extends Parcelable {
- private static final ParsedIntentInfo.ListParceler sForIntentInfos =
- Parcelling.Cache.getOrCreate(ParsedIntentInfo.ListParceler.class);
+ int getBanner();
@NonNull
- @DataClass.ParcelWith(ForInternedString.class)
- protected String name;
- protected int icon;
- protected int labelRes;
- @Nullable
- protected CharSequence nonLocalizedLabel;
- protected int logo;
- protected int banner;
- protected int descriptionRes;
+ ComponentName getComponentName();
- // TODO(b/135203078): Replace flags with individual booleans, scoped by subclass
- protected int flags;
+ int getDescriptionRes();
+
+ int getFlags();
+
+ int getIcon();
@NonNull
- @DataClass.ParcelWith(ForInternedString.class)
- protected String packageName;
+ List<ParsedIntentInfo> getIntents();
+
+ int getLabelRes();
+
+ int getLogo();
@Nullable
- @DataClass.PluralOf("intent")
- @DataClass.ParcelWith(ParsedIntentInfo.ListParceler.class)
- protected List<ParsedIntentInfo> intents;
+ Bundle getMetaData();
- protected ComponentName componentName;
+ @NonNull
+ String getName();
@Nullable
- protected Bundle metaData;
-
- protected Map<String, Property> mProperties = emptyMap();
-
- ParsedComponent() {
-
- }
-
- @SuppressWarnings("IncompleteCopyConstructor")
- public ParsedComponent(ParsedComponent other) {
- this.metaData = other.metaData;
- this.name = other.name;
- this.icon = other.getIcon();
- this.labelRes = other.getLabelRes();
- this.nonLocalizedLabel = other.getNonLocalizedLabel();
- this.logo = other.getLogo();
- this.banner = other.getBanner();
-
- this.descriptionRes = other.getDescriptionRes();
-
- this.flags = other.getFlags();
-
- this.setPackageName(other.packageName);
- this.intents = new ArrayList<>(other.getIntents());
- }
-
- public void addIntent(ParsedIntentInfo intent) {
- this.intents = CollectionUtils.add(this.intents, intent);
- }
-
- /** Add a property to the component */
- public void addProperty(@NonNull Property property) {
- this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
- }
+ CharSequence getNonLocalizedLabel();
@NonNull
- public List<ParsedIntentInfo> getIntents() {
- return intents != null ? intents : Collections.emptyList();
- }
-
- public ParsedComponent setBanner(int banner) {
- this.banner = banner;
- return this;
- }
-
- public ParsedComponent setDescriptionRes(int descriptionRes) {
- this.descriptionRes = descriptionRes;
- return this;
- }
-
- public ParsedComponent setFlags(int flags) {
- this.flags = flags;
- return this;
- }
-
- public ParsedComponent setIcon(int icon) {
- this.icon = icon;
- return this;
- }
-
- public ParsedComponent setLabelRes(int labelRes) {
- this.labelRes = labelRes;
- return this;
- }
-
- public ParsedComponent setLogo(int logo) {
- this.logo = logo;
- return this;
- }
-
- public ParsedComponent setMetaData(Bundle metaData) {
- this.metaData = metaData;
- return this;
- }
-
- public ParsedComponent setName(String name) {
- this.name = TextUtils.safeIntern(name);
- return this;
- }
-
- public ParsedComponent setNonLocalizedLabel(CharSequence nonLocalizedLabel) {
- this.nonLocalizedLabel = nonLocalizedLabel;
- return this;
- }
-
- @CallSuper
- public void setPackageName(@NonNull String packageName) {
- this.packageName = TextUtils.safeIntern(packageName);
- //noinspection ConstantConditions
- this.componentName = null;
-
- // Note: this method does not edit name (which can point to a class), because this package
- // name change is not changing the package in code, but the identifier used by the system.
- }
+ String getPackageName();
@NonNull
- public ComponentName getComponentName() {
- if (componentName == null) {
- componentName = new ComponentName(getPackageName(), getName());
- }
- return componentName;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(this.name);
- dest.writeInt(this.getIcon());
- dest.writeInt(this.getLabelRes());
- dest.writeCharSequence(this.getNonLocalizedLabel());
- dest.writeInt(this.getLogo());
- dest.writeInt(this.getBanner());
- dest.writeInt(this.getDescriptionRes());
- dest.writeInt(this.getFlags());
- sForInternedString.parcel(this.packageName, dest, flags);
- sForIntentInfos.parcel(this.getIntents(), dest, flags);
- dest.writeBundle(this.metaData);
- dest.writeMap(this.mProperties);
- }
-
- protected ParsedComponent(Parcel in) {
- // We use the boot classloader for all classes that we load.
- final ClassLoader boot = Object.class.getClassLoader();
- //noinspection ConstantConditions
- this.name = in.readString();
- this.icon = in.readInt();
- this.labelRes = in.readInt();
- this.nonLocalizedLabel = in.readCharSequence();
- this.logo = in.readInt();
- this.banner = in.readInt();
- this.descriptionRes = in.readInt();
- this.flags = in.readInt();
- //noinspection ConstantConditions
- this.packageName = sForInternedString.unparcel(in);
- this.intents = sForIntentInfos.unparcel(in);
- this.metaData = in.readBundle(boot);
- this.mProperties = in.readHashMap(boot);
- }
-
- @NonNull
- public String getName() {
- return name;
- }
-
- public int getIcon() {
- return icon;
- }
-
- public int getLabelRes() {
- return labelRes;
- }
-
- @Nullable
- public CharSequence getNonLocalizedLabel() {
- return nonLocalizedLabel;
- }
-
- public int getLogo() {
- return logo;
- }
-
- public int getBanner() {
- return banner;
- }
-
- public int getDescriptionRes() {
- return descriptionRes;
- }
-
- public int getFlags() {
- return flags;
- }
-
- @NonNull
- public String getPackageName() {
- return packageName;
- }
-
- @Nullable
- public Bundle getMetaData() {
- return metaData;
- }
-
- @NonNull
- public Map<String, Property> getProperties() {
- return mProperties;
- }
+ Map<String, Property> getProperties();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java b/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java
new file mode 100644
index 0000000..1c46a10
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import static java.util.Collections.emptyMap;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.parsing.ParsingUtils;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false)
+@DataClass.Suppress({"setComponentName", "setProperties", "setIntents"})
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public abstract class ParsedComponentImpl implements ParsedComponent {
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String name;
+ private int icon;
+ private int labelRes;
+ @Nullable
+ private CharSequence nonLocalizedLabel;
+ private int logo;
+ private int banner;
+ private int descriptionRes;
+
+ // TODO(b/135203078): Replace flags with individual booleans, scoped by subclass
+ private int flags;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String packageName;
+
+ @NonNull
+ @DataClass.PluralOf("intent")
+ private List<ParsedIntentInfo> intents = Collections.emptyList();
+
+ @Nullable
+ private ComponentName componentName;
+
+ @Nullable
+ private Bundle metaData;
+
+ @NonNull
+ private Map<String, Property> mProperties = emptyMap();
+
+ public ParsedComponentImpl() {
+
+ }
+
+ protected ParsedComponentImpl(ParsedComponent other) {
+ this.metaData = other.getMetaData();
+ this.name = other.getName();
+ this.icon = other.getIcon();
+ this.labelRes = other.getLabelRes();
+ this.nonLocalizedLabel = other.getNonLocalizedLabel();
+ this.logo = other.getLogo();
+ this.banner = other.getBanner();
+ this.descriptionRes = other.getDescriptionRes();
+ this.flags = other.getFlags();
+ this.packageName = other.getPackageName();
+ this.componentName = other.getComponentName();
+ this.intents = new ArrayList<>(other.getIntents());
+ this.mProperties = new ArrayMap<>();
+ this.mProperties.putAll(other.getProperties());
+ }
+
+ public void addIntent(ParsedIntentInfo intent) {
+ this.intents = CollectionUtils.add(this.intents, intent);
+ }
+
+ /** Add a property to the component */
+ public void addProperty(@NonNull Property property) {
+ this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
+ }
+
+ public ParsedComponentImpl setName(String name) {
+ this.name = TextUtils.safeIntern(name);
+ return this;
+ }
+
+ @CallSuper
+ public void setPackageName(@NonNull String packageName) {
+ this.packageName = TextUtils.safeIntern(packageName);
+ //noinspection ConstantConditions
+ this.componentName = null;
+
+ // Note: this method does not edit name (which can point to a class), because this package
+ // name change is not changing the package in code, but the identifier used by the system.
+ }
+
+ @Override
+ @NonNull
+ public ComponentName getComponentName() {
+ if (componentName == null) {
+ componentName = new ComponentName(getPackageName(), getName());
+ }
+ return componentName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(this.name);
+ dest.writeInt(this.getIcon());
+ dest.writeInt(this.getLabelRes());
+ dest.writeCharSequence(this.getNonLocalizedLabel());
+ dest.writeInt(this.getLogo());
+ dest.writeInt(this.getBanner());
+ dest.writeInt(this.getDescriptionRes());
+ dest.writeInt(this.getFlags());
+ sForInternedString.parcel(this.packageName, dest, flags);
+ dest.writeTypedList(this.getIntents());
+ dest.writeBundle(this.metaData);
+ dest.writeMap(this.mProperties);
+ }
+
+ protected ParsedComponentImpl(Parcel in) {
+ // We use the boot classloader for all classes that we load.
+ final ClassLoader boot = Object.class.getClassLoader();
+ //noinspection ConstantConditions
+ this.name = in.readString();
+ this.icon = in.readInt();
+ this.labelRes = in.readInt();
+ this.nonLocalizedLabel = in.readCharSequence();
+ this.logo = in.readInt();
+ this.banner = in.readInt();
+ this.descriptionRes = in.readInt();
+ this.flags = in.readInt();
+ //noinspection ConstantConditions
+ this.packageName = sForInternedString.unparcel(in);
+ this.intents = ParsingUtils.createTypedInterfaceList(in, ParsedIntentInfoImpl.CREATOR);
+ this.metaData = in.readBundle(boot);
+ this.mProperties = in.readHashMap(boot);
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public @NonNull String getName() {
+ return name;
+ }
+
+ @DataClass.Generated.Member
+ public int getIcon() {
+ return icon;
+ }
+
+ @DataClass.Generated.Member
+ public int getLabelRes() {
+ return labelRes;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getNonLocalizedLabel() {
+ return nonLocalizedLabel;
+ }
+
+ @DataClass.Generated.Member
+ public int getLogo() {
+ return logo;
+ }
+
+ @DataClass.Generated.Member
+ public int getBanner() {
+ return banner;
+ }
+
+ @DataClass.Generated.Member
+ public int getDescriptionRes() {
+ return descriptionRes;
+ }
+
+ @DataClass.Generated.Member
+ public int getFlags() {
+ return flags;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return packageName;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull List<ParsedIntentInfo> getIntents() {
+ return intents;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable Bundle getMetaData() {
+ return metaData;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Property> getProperties() {
+ return mProperties;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setIcon( int value) {
+ icon = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setLabelRes( int value) {
+ labelRes = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setNonLocalizedLabel(@NonNull CharSequence value) {
+ nonLocalizedLabel = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setLogo( int value) {
+ logo = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setBanner( int value) {
+ banner = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setDescriptionRes( int value) {
+ descriptionRes = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setFlags( int value) {
+ flags = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedComponentImpl setMetaData(@NonNull Bundle value) {
+ metaData = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1627680195484L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java",
+ inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate int icon\nprivate int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate int logo\nprivate int banner\nprivate int descriptionRes\nprivate int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\npublic void addIntent(android.content.pm.parsing.component.ParsedIntentInfo)\npublic void addProperty(android.content.pm.PackageManager.Property)\npublic android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java
index 6d798fd..5c33cfd 100644
--- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java
@@ -40,7 +40,7 @@
@NonNull
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- static <Component extends ParsedComponent> ParseResult<Component> parseComponent(
+ static <Component extends ParsedComponentImpl> ParseResult<Component> parseComponent(
Component component, String tag, ParsingPackage pkg, TypedArray array,
boolean useRoundIcon, ParseInput input, int bannerAttr, int descriptionAttr,
int iconAttr, int labelAttr, int logoAttr, int nameAttr, int roundIconAttr) {
@@ -96,7 +96,7 @@
return input.success(component);
}
- static ParseResult<Bundle> addMetaData(ParsedComponent component, ParsingPackage pkg,
+ static ParseResult<Bundle> addMetaData(ParsedComponentImpl component, ParsingPackage pkg,
Resources resources, XmlResourceParser parser, ParseInput input) {
ParseResult<Property> result = ParsingPackageUtils.parseMetaData(pkg, component,
resources, parser, "<meta-data>", input);
@@ -110,7 +110,7 @@
return input.success(component.getMetaData());
}
- static ParseResult<Property> addProperty(ParsedComponent component, ParsingPackage pkg,
+ static ParseResult<Property> addProperty(ParsedComponentImpl component, ParsingPackage pkg,
Resources resources, XmlResourceParser parser, ParseInput input) {
ParseResult<Property> result = ParsingPackageUtils.parseMetaData(pkg, component,
resources, parser, "<property>", input);
diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
index 4178920..e8fcc00 100644
--- a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
+++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
@@ -16,114 +16,18 @@
package android.content.pm.parsing.component;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
/** @hide */
-public class ParsedInstrumentation extends ParsedComponent {
+public interface ParsedInstrumentation extends ParsedComponent {
@Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String targetPackage;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String targetProcesses;
- private boolean handleProfiling;
- private boolean functionalTest;
-
- public ParsedInstrumentation() {
- }
-
- public ParsedInstrumentation setFunctionalTest(boolean functionalTest) {
- this.functionalTest = functionalTest;
- return this;
- }
-
- public ParsedInstrumentation setHandleProfiling(boolean handleProfiling) {
- this.handleProfiling = handleProfiling;
- return this;
- }
-
- public ParsedInstrumentation setTargetPackage(@Nullable String targetPackage) {
- this.targetPackage = TextUtils.safeIntern(targetPackage);
- return this;
- }
-
- public ParsedInstrumentation setTargetProcesses(@Nullable String targetProcesses) {
- this.targetProcesses = TextUtils.safeIntern(targetProcesses);
- return this;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Instrumentation{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(' ');
- ComponentName.appendShortString(sb, getPackageName(), getName());
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- sForInternedString.parcel(this.targetPackage, dest, flags);
- sForInternedString.parcel(this.targetProcesses, dest, flags);
- dest.writeBoolean(this.handleProfiling);
- dest.writeBoolean(this.functionalTest);
- }
-
- protected ParsedInstrumentation(Parcel in) {
- super(in);
- this.targetPackage = sForInternedString.unparcel(in);
- this.targetProcesses = sForInternedString.unparcel(in);
- this.handleProfiling = in.readByte() != 0;
- this.functionalTest = in.readByte() != 0;
- }
-
- @NonNull
- public static final Parcelable.Creator<ParsedInstrumentation> CREATOR =
- new Parcelable.Creator<ParsedInstrumentation>() {
- @Override
- public ParsedInstrumentation createFromParcel(Parcel source) {
- return new ParsedInstrumentation(source);
- }
-
- @Override
- public ParsedInstrumentation[] newArray(int size) {
- return new ParsedInstrumentation[size];
- }
- };
+ String getTargetPackage();
@Nullable
- public String getTargetPackage() {
- return targetPackage;
- }
+ String getTargetProcesses();
- @Nullable
- public String getTargetProcesses() {
- return targetProcesses;
- }
+ boolean isFunctionalTest();
- public boolean isHandleProfiling() {
- return handleProfiling;
- }
-
- public boolean isFunctionalTest() {
- return functionalTest;
- }
+ boolean isHandleProfiling();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java
new file mode 100644
index 0000000..d2b5368
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedInstrumentationImpl extends ParsedComponentImpl implements
+ ParsedInstrumentation {
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String targetPackage;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String targetProcesses;
+ private boolean handleProfiling;
+ private boolean functionalTest;
+
+ public ParsedInstrumentationImpl() {
+ }
+
+ public ParsedInstrumentationImpl setTargetPackage(@Nullable String targetPackage) {
+ this.targetPackage = TextUtils.safeIntern(targetPackage);
+ return this;
+ }
+
+ public ParsedInstrumentationImpl setTargetProcesses(@Nullable String targetProcesses) {
+ this.targetProcesses = TextUtils.safeIntern(targetProcesses);
+ return this;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Instrumentation{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ sForInternedString.parcel(this.targetPackage, dest, flags);
+ sForInternedString.parcel(this.targetProcesses, dest, flags);
+ dest.writeBoolean(this.handleProfiling);
+ dest.writeBoolean(this.functionalTest);
+ }
+
+ protected ParsedInstrumentationImpl(Parcel in) {
+ super(in);
+ this.targetPackage = sForInternedString.unparcel(in);
+ this.targetProcesses = sForInternedString.unparcel(in);
+ this.handleProfiling = in.readByte() != 0;
+ this.functionalTest = in.readByte() != 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParsedInstrumentationImpl> CREATOR =
+ new Parcelable.Creator<ParsedInstrumentationImpl>() {
+ @Override
+ public ParsedInstrumentationImpl createFromParcel(Parcel source) {
+ return new ParsedInstrumentationImpl(source);
+ }
+
+ @Override
+ public ParsedInstrumentationImpl[] newArray(int size) {
+ return new ParsedInstrumentationImpl[size];
+ }
+ };
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedInstrumentationImpl(
+ @Nullable String targetPackage,
+ @Nullable String targetProcesses,
+ boolean handleProfiling,
+ boolean functionalTest) {
+ this.targetPackage = targetPackage;
+ this.targetProcesses = targetProcesses;
+ this.handleProfiling = handleProfiling;
+ this.functionalTest = functionalTest;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getTargetPackage() {
+ return targetPackage;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getTargetProcesses() {
+ return targetProcesses;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isHandleProfiling() {
+ return handleProfiling;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isFunctionalTest() {
+ return functionalTest;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedInstrumentationImpl setHandleProfiling( boolean value) {
+ handleProfiling = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedInstrumentationImpl setFunctionalTest( boolean value) {
+ functionalTest = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1627595809880L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java",
+ inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate boolean handleProfiling\nprivate boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java
index f122fd6..df5e73e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java
@@ -39,13 +39,13 @@
public static ParseResult<ParsedInstrumentation> parseInstrumentation(ParsingPackage pkg,
Resources res, XmlResourceParser parser, boolean useRoundIcon,
ParseInput input) throws IOException, XmlPullParserException {
- ParsedInstrumentation
- instrumentation = new ParsedInstrumentation();
+ ParsedInstrumentationImpl
+ instrumentation = new ParsedInstrumentationImpl();
String tag = "<" + parser.getName() + ">";
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestInstrumentation);
try {
- ParseResult<ParsedInstrumentation> result = ParsedComponentUtils.parseComponent(
+ ParseResult<ParsedInstrumentationImpl> result = ParsedComponentUtils.parseComponent(
instrumentation, tag, pkg, sa, useRoundIcon, input,
R.styleable.AndroidManifestInstrumentation_banner,
NOT_SET /*descriptionAttr*/,
@@ -55,7 +55,7 @@
R.styleable.AndroidManifestInstrumentation_name,
R.styleable.AndroidManifestInstrumentation_roundIcon);
if (result.isError()) {
- return result;
+ return input.error(result);
}
// @formatter:off
@@ -70,7 +70,13 @@
sa.recycle();
}
- return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, instrumentation,
- input);
+ ParseResult<ParsedInstrumentationImpl> result =
+ ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, instrumentation, input);
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult());
}
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
index 59d4a95..1e36ccca 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
@@ -20,188 +20,25 @@
import android.annotation.Nullable;
import android.content.IntentFilter;
import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Pair;
+import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling;
import java.util.ArrayList;
import java.util.List;
/** @hide **/
-public final class ParsedIntentInfo extends IntentFilter {
+public interface ParsedIntentInfo extends Parcelable {
- public static final Parceler PARCELER = new Parceler();
+ boolean isHasDefault();
- public ParsedIntentInfo setHasDefault(boolean hasDefault) {
- this.hasDefault = hasDefault;
- return this;
- }
+ int getLabelRes();
- public ParsedIntentInfo setIcon(int icon) {
- this.icon = icon;
- return this;
- }
+ @Nullable CharSequence getNonLocalizedLabel();
- public ParsedIntentInfo setLabelRes(int labelRes) {
- this.labelRes = labelRes;
- return this;
- }
+ int getIcon();
- public ParsedIntentInfo setNonLocalizedLabel(CharSequence nonLocalizedLabel) {
- this.nonLocalizedLabel = nonLocalizedLabel;
- return this;
- }
-
- public static class Parceler implements Parcelling<ParsedIntentInfo> {
-
- @Override
- public void parcel(ParsedIntentInfo item, Parcel dest, int parcelFlags) {
- item.writeIntentInfoToParcel(dest, parcelFlags);
- }
-
- @NonNull
- @Override
- public ParsedIntentInfo unparcel(Parcel source) {
- return new ParsedIntentInfo(source);
- }
- }
-
- public static class ListParceler implements Parcelling<List<ParsedIntentInfo>> {
-
- /**
- * <p>
- * Implementation note: The serialized form for the intent list also contains the name
- * of the concrete class that's stored in the list, and assumes that every element of the
- * list is of the same type. This is very similar to the original parcelable mechanism.
- * We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable
- * and is public API. It also declares Parcelable related methods as final which means
- * we can't extend them. The approach of using composition instead of inheritance leads to
- * a large set of cascading changes in the PackageManagerService, which seem undesirable.
- *
- * <p>
- * <b>WARNING: </b> The list of objects returned by this function might need to be fixed up
- * to make sure their owner fields are consistent. See {@code fixupOwner}.
- */
- @Override
- public void parcel(List<ParsedIntentInfo> item, Parcel dest, int parcelFlags) {
- if (item == null) {
- dest.writeInt(-1);
- return;
- }
-
- final int size = item.size();
- dest.writeInt(size);
-
- for (int index = 0; index < size; index++) {
- PARCELER.parcel(item.get(index), dest, parcelFlags);
- }
- }
-
- @Override
- public List<ParsedIntentInfo> unparcel(Parcel source) {
- int size = source.readInt();
- if (size == -1) {
- return null;
- }
-
- if (size == 0) {
- return new ArrayList<>(0);
- }
-
- final ArrayList<ParsedIntentInfo> intentsList = new ArrayList<>(size);
- for (int i = 0; i < size; ++i) {
- intentsList.add(PARCELER.unparcel(source));
- }
-
- return intentsList;
- }
- }
-
- public static class StringPairListParceler implements Parcelling<List<Pair<String, ParsedIntentInfo>>> {
-
- @Override
- public void parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest,
- int parcelFlags) {
- if (item == null) {
- dest.writeInt(-1);
- return;
- }
-
- final int size = item.size();
- dest.writeInt(size);
-
- for (int index = 0; index < size; index++) {
- Pair<String, ParsedIntentInfo> pair = item.get(index);
- dest.writeString(pair.first);
- PARCELER.parcel(pair.second, dest, parcelFlags);
- }
- }
-
- @Override
- public List<Pair<String, ParsedIntentInfo>> unparcel(Parcel source) {
- int size = source.readInt();
- if (size == -1) {
- return null;
- }
-
- if (size == 0) {
- return new ArrayList<>(0);
- }
-
- final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size);
- for (int i = 0; i < size; ++i) {
- list.add(Pair.create(source.readString(), PARCELER.unparcel(source)));
- }
-
- return list;
- }
- }
-
- private boolean hasDefault;
- private int labelRes;
- @Nullable
- private CharSequence nonLocalizedLabel;
- private int icon;
-
- public ParsedIntentInfo() {
- }
-
- public ParsedIntentInfo(Parcel in) {
- super(in);
- hasDefault = in.readBoolean();
- labelRes = in.readInt();
- nonLocalizedLabel = in.readCharSequence();
- icon = in.readInt();
- }
-
- public void writeIntentInfoToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeBoolean(hasDefault);
- dest.writeInt(labelRes);
- dest.writeCharSequence(nonLocalizedLabel);
- dest.writeInt(icon);
- }
-
- public String toString() {
- return "ParsedIntentInfo{"
- + Integer.toHexString(System.identityHashCode(this))
- + '}';
- }
-
- public boolean isHasDefault() {
- return hasDefault;
- }
-
- public int getLabelRes() {
- return labelRes;
- }
-
- @Nullable
- public CharSequence getNonLocalizedLabel() {
- return nonLocalizedLabel;
- }
-
- public int getIcon() {
- return icon;
- }
+ @NonNull IntentFilter getIntentFilter();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java
new file mode 100644
index 0000000..9ff7a16
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentFilter;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** @hide **/
+@DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
+ genBuilder = false, genConstructor = false)
+@DataClass.Suppress({"setIntentFilter"})
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedIntentInfoImpl implements ParsedIntentInfo {
+
+ private boolean mHasDefault;
+ private int mLabelRes;
+ @Nullable
+ private CharSequence mNonLocalizedLabel;
+ private int mIcon;
+
+ @NonNull
+ private IntentFilter mIntentFilter = new IntentFilter();
+
+ public ParsedIntentInfoImpl() {}
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public boolean isHasDefault() {
+ return mHasDefault;
+ }
+
+ @DataClass.Generated.Member
+ public int getLabelRes() {
+ return mLabelRes;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getNonLocalizedLabel() {
+ return mNonLocalizedLabel;
+ }
+
+ @DataClass.Generated.Member
+ public int getIcon() {
+ return mIcon;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull IntentFilter getIntentFilter() {
+ return mIntentFilter;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedIntentInfoImpl setHasDefault( boolean value) {
+ mHasDefault = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedIntentInfoImpl setLabelRes( int value) {
+ mLabelRes = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedIntentInfoImpl setNonLocalizedLabel(@NonNull CharSequence value) {
+ mNonLocalizedLabel = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedIntentInfoImpl setIcon( int value) {
+ mIcon = value;
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mHasDefault) flg |= 0x1;
+ if (mNonLocalizedLabel != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mLabelRes);
+ if (mNonLocalizedLabel != null) dest.writeCharSequence(mNonLocalizedLabel);
+ dest.writeInt(mIcon);
+ dest.writeTypedObject(mIntentFilter, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedIntentInfoImpl(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean hasDefault = (flg & 0x1) != 0;
+ int labelRes = in.readInt();
+ CharSequence nonLocalizedLabel = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence();
+ int icon = in.readInt();
+ IntentFilter intentFilter = (IntentFilter) in.readTypedObject(IntentFilter.CREATOR);
+
+ this.mHasDefault = hasDefault;
+ this.mLabelRes = labelRes;
+ this.mNonLocalizedLabel = nonLocalizedLabel;
+ this.mIcon = icon;
+ this.mIntentFilter = intentFilter;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIntentFilter);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ParsedIntentInfoImpl> CREATOR
+ = new Parcelable.Creator<ParsedIntentInfoImpl>() {
+ @Override
+ public ParsedIntentInfoImpl[] newArray(int size) {
+ return new ParsedIntentInfoImpl[size];
+ }
+
+ @Override
+ public ParsedIntentInfoImpl createFromParcel(@NonNull Parcel in) {
+ return new ParsedIntentInfoImpl(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1627691925408L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java",
+ inputSignatures = "private boolean mHasDefault\nprivate int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
index 668b9cc..cb72c2b 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
@@ -53,11 +53,13 @@
ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
boolean allowAutoVerify, ParseInput input)
throws XmlPullParserException, IOException {
- ParsedIntentInfo intentInfo = new ParsedIntentInfo();
+ ParsedIntentInfoImpl intentInfo = new ParsedIntentInfoImpl();
+ IntentFilter intentFilter = intentInfo.getIntentFilter();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestIntentFilter);
try {
- intentInfo.setPriority(sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0));
- intentInfo.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0));
+ intentFilter.setPriority(
+ sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0));
+ intentFilter.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0));
TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label);
if (v != null) {
@@ -78,7 +80,7 @@
}
if (allowAutoVerify) {
- intentInfo.setAutoVerify(sa.getBoolean(
+ intentFilter.setAutoVerify(sa.getBoolean(
R.styleable.AndroidManifestIntentFilter_autoVerify,
false));
}
@@ -102,12 +104,12 @@
if (value == null) {
result = input.error("No value supplied for <android:name>");
} else if (value.isEmpty()) {
- intentInfo.addAction(value);
+ intentFilter.addAction(value);
// Prior to R, this was not a failure
result = input.deferError("No value supplied for <android:name>",
ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
} else {
- intentInfo.addAction(value);
+ intentFilter.addAction(value);
result = input.success(null);
}
break;
@@ -117,12 +119,12 @@
if (value == null) {
result = input.error("No value supplied for <android:name>");
} else if (value.isEmpty()) {
- intentInfo.addCategory(value);
+ intentFilter.addCategory(value);
// Prior to R, this was not a failure
result = input.deferError("No value supplied for <android:name>",
ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
} else {
- intentInfo.addCategory(value);
+ intentFilter.addCategory(value);
result = input.success(null);
}
break;
@@ -140,14 +142,14 @@
}
}
- intentInfo.setHasDefault(intentInfo.hasCategory(Intent.CATEGORY_DEFAULT));
+ intentInfo.setHasDefault(intentFilter.hasCategory(Intent.CATEGORY_DEFAULT));
if (DEBUG) {
final StringBuilder cats = new StringBuilder("Intent d=");
cats.append(intentInfo.isHasDefault());
cats.append(", cat=");
- final Iterator<String> it = intentInfo.categoriesIterator();
+ final Iterator<String> it = intentFilter.categoriesIterator();
if (it != null) {
while (it.hasNext()) {
cats.append(' ');
@@ -163,13 +165,14 @@
@NonNull
private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo,
Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
+ IntentFilter intentFilter = intentInfo.getIntentFilter();
TypedArray sa = resources.obtainAttributes(parser, R.styleable.AndroidManifestData);
try {
String str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_mimeType, 0);
if (str != null) {
try {
- intentInfo.addDataType(str);
+ intentFilter.addDataType(str);
} catch (IntentFilter.MalformedMimeTypeException e) {
return input.error(e.toString());
}
@@ -178,26 +181,26 @@
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_mimeGroup, 0);
if (str != null) {
- intentInfo.addMimeGroup(str);
+ intentFilter.addMimeGroup(str);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_scheme, 0);
if (str != null) {
- intentInfo.addDataScheme(str);
+ intentFilter.addDataScheme(str);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_ssp, 0);
if (str != null) {
- intentInfo.addDataSchemeSpecificPart(str,
+ intentFilter.addDataSchemeSpecificPart(str,
PatternMatcher.PATTERN_LITERAL);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_sspPrefix, 0);
if (str != null) {
- intentInfo.addDataSchemeSpecificPart(str,
+ intentFilter.addDataSchemeSpecificPart(str,
PatternMatcher.PATTERN_PREFIX);
}
@@ -208,7 +211,7 @@
return input.error(
"sspPattern not allowed here; ssp must be literal");
}
- intentInfo.addDataSchemeSpecificPart(str,
+ intentFilter.addDataSchemeSpecificPart(str,
PatternMatcher.PATTERN_SIMPLE_GLOB);
}
@@ -219,14 +222,14 @@
return input.error(
"sspAdvancedPattern not allowed here; ssp must be literal");
}
- intentInfo.addDataSchemeSpecificPart(str,
+ intentFilter.addDataSchemeSpecificPart(str,
PatternMatcher.PATTERN_ADVANCED_GLOB);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_sspSuffix, 0);
if (str != null) {
- intentInfo.addDataSchemeSpecificPart(str,
+ intentFilter.addDataSchemeSpecificPart(str,
PatternMatcher.PATTERN_SUFFIX);
}
@@ -236,19 +239,19 @@
String port = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_port, 0);
if (host != null) {
- intentInfo.addDataAuthority(host, port);
+ intentFilter.addDataAuthority(host, port);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_path, 0);
if (str != null) {
- intentInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
+ intentFilter.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_pathPrefix, 0);
if (str != null) {
- intentInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
+ intentFilter.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
}
str = sa.getNonConfigurationString(
@@ -258,7 +261,7 @@
return input.error(
"pathPattern not allowed here; path must be literal");
}
- intentInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ intentFilter.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
}
str = sa.getNonConfigurationString(
@@ -268,13 +271,13 @@
return input.error(
"pathAdvancedPattern not allowed here; path must be literal");
}
- intentInfo.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ intentFilter.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
}
str = sa.getNonConfigurationString(
R.styleable.AndroidManifestData_pathSuffix, 0);
if (str != null) {
- intentInfo.addDataPath(str, PatternMatcher.PATTERN_SUFFIX);
+ intentFilter.addDataPath(str, PatternMatcher.PATTERN_SUFFIX);
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java b/core/java/android/content/pm/parsing/component/ParsedMainComponent.java
index 433bfd3..2507205 100644
--- a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java
+++ b/core/java/android/content/pm/parsing/component/ParsedMainComponent.java
@@ -16,157 +16,30 @@
package android.content.pm.parsing.component;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-
import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
/** @hide */
-public class ParsedMainComponent extends ParsedComponent {
+public interface ParsedMainComponent extends ParsedComponent {
@Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- protected String processName;
- protected boolean directBootAware;
- protected boolean enabled = true;
- protected boolean exported;
- protected int order;
-
- @Nullable
- protected String splitName;
- @Nullable
- protected String[] attributionTags;
-
- public ParsedMainComponent() {
- }
-
- public ParsedMainComponent(ParsedMainComponent other) {
- super(other);
- this.processName = other.processName;
- this.directBootAware = other.directBootAware;
- this.enabled = other.enabled;
- this.exported = other.exported;
- this.order = other.order;
- this.splitName = other.splitName;
- this.attributionTags = other.attributionTags;
- }
-
- public ParsedMainComponent setOrder(int order) {
- this.order = order;
- return this;
- }
-
- public ParsedMainComponent setProcessName(String processName) {
- this.processName = TextUtils.safeIntern(processName);
- return this;
- }
-
- public ParsedMainComponent setEnabled(boolean enabled) {
- this.enabled = enabled;
- return this;
- }
+ String[] getAttributionTags();
/**
* A main component's name is a class name. This makes code slightly more readable.
*/
- public String getClassName() {
- return getName();
- }
+ String getClassName();
- @Override
- public int describeContents() {
- return 0;
- }
+ boolean isDirectBootAware();
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- sForInternedString.parcel(this.processName, dest, flags);
- dest.writeBoolean(this.directBootAware);
- dest.writeBoolean(this.enabled);
- dest.writeBoolean(this.exported);
- dest.writeInt(this.order);
- dest.writeString(this.splitName);
- dest.writeString8Array(this.attributionTags);
- }
+ boolean isEnabled();
- protected ParsedMainComponent(Parcel in) {
- super(in);
- this.processName = sForInternedString.unparcel(in);
- this.directBootAware = in.readBoolean();
- this.enabled = in.readBoolean();
- this.exported = in.readBoolean();
- this.order = in.readInt();
- this.splitName = in.readString();
- this.attributionTags = in.createString8Array();
- }
+ boolean isExported();
- public static final Parcelable.Creator<ParsedMainComponent> CREATOR =
- new Parcelable.Creator<ParsedMainComponent>() {
- @Override
- public ParsedMainComponent createFromParcel(Parcel source) {
- return new ParsedMainComponent(source);
- }
-
- @Override
- public ParsedMainComponent[] newArray(int size) {
- return new ParsedMainComponent[size];
- }
- };
+ int getOrder();
@Nullable
- public String getProcessName() {
- return processName;
- }
-
- public boolean isDirectBootAware() {
- return directBootAware;
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public boolean isExported() {
- return exported;
- }
-
- public int getOrder() {
- return order;
- }
+ String getProcessName();
@Nullable
- public String getSplitName() {
- return splitName;
- }
-
- @Nullable
- public String[] getAttributionTags() {
- return attributionTags;
- }
-
- public ParsedMainComponent setDirectBootAware(boolean value) {
- directBootAware = value;
- return this;
- }
-
- public ParsedMainComponent setExported(boolean value) {
- exported = value;
- return this;
- }
-
- public ParsedMainComponent setSplitName(@Nullable String value) {
- splitName = value;
- return this;
- }
-
- public ParsedMainComponent setAttributionTags(@Nullable String[] value) {
- attributionTags = value;
- return this;
- }
+ String getSplitName();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java b/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java
new file mode 100644
index 0000000..6051435
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent {
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String processName;
+ private boolean directBootAware;
+ private boolean enabled = true;
+ private boolean exported;
+ private int order;
+
+ @Nullable
+ private String splitName;
+ @Nullable
+ private String[] attributionTags;
+
+ public ParsedMainComponentImpl() {
+ }
+
+ public ParsedMainComponentImpl(ParsedMainComponent other) {
+ super(other);
+ this.processName = other.getProcessName();
+ this.directBootAware = other.isDirectBootAware();
+ this.enabled = other.isEnabled();
+ this.exported = other.isExported();
+ this.order = other.getOrder();
+ this.splitName = other.getSplitName();
+ this.attributionTags = other.getAttributionTags();
+ }
+
+ public ParsedMainComponentImpl setProcessName(String processName) {
+ this.processName = TextUtils.safeIntern(processName);
+ return this;
+ }
+
+ /**
+ * A main component's name is a class name. This makes code slightly more readable.
+ */
+ public String getClassName() {
+ return getName();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ sForInternedString.parcel(this.processName, dest, flags);
+ dest.writeBoolean(this.directBootAware);
+ dest.writeBoolean(this.enabled);
+ dest.writeBoolean(this.exported);
+ dest.writeInt(this.order);
+ dest.writeString(this.splitName);
+ dest.writeString8Array(this.attributionTags);
+ }
+
+ protected ParsedMainComponentImpl(Parcel in) {
+ super(in);
+ this.processName = sForInternedString.unparcel(in);
+ this.directBootAware = in.readBoolean();
+ this.enabled = in.readBoolean();
+ this.exported = in.readBoolean();
+ this.order = in.readInt();
+ this.splitName = in.readString();
+ this.attributionTags = in.createString8Array();
+ }
+
+ public static final Parcelable.Creator<ParsedMainComponentImpl> CREATOR =
+ new Parcelable.Creator<ParsedMainComponentImpl>() {
+ @Override
+ public ParsedMainComponentImpl createFromParcel(Parcel source) {
+ return new ParsedMainComponentImpl(source);
+ }
+
+ @Override
+ public ParsedMainComponentImpl[] newArray(int size) {
+ return new ParsedMainComponentImpl[size];
+ }
+ };
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedMainComponentImpl(
+ @Nullable String processName,
+ boolean directBootAware,
+ boolean enabled,
+ boolean exported,
+ int order,
+ @Nullable String splitName,
+ @Nullable String[] attributionTags) {
+ this.processName = processName;
+ this.directBootAware = directBootAware;
+ this.enabled = enabled;
+ this.exported = exported;
+ this.order = order;
+ this.splitName = splitName;
+ this.attributionTags = attributionTags;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getProcessName() {
+ return processName;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isDirectBootAware() {
+ return directBootAware;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isExported() {
+ return exported;
+ }
+
+ @DataClass.Generated.Member
+ public int getOrder() {
+ return order;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getSplitName() {
+ return splitName;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String[] getAttributionTags() {
+ return attributionTags;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
+ directBootAware = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedMainComponentImpl setEnabled( boolean value) {
+ enabled = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedMainComponentImpl setExported( boolean value) {
+ exported = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedMainComponentImpl setOrder( int value) {
+ order = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedMainComponentImpl setSplitName(@android.annotation.NonNull String value) {
+ splitName = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedMainComponentImpl setAttributionTags(@android.annotation.NonNull String... value) {
+ attributionTags = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1627324857874L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java",
+ inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate boolean directBootAware\nprivate boolean enabled\nprivate boolean exported\nprivate int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic java.lang.String getClassName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java
index 869e81c..87f75b0 100644
--- a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java
@@ -19,6 +19,7 @@
import static android.content.pm.parsing.ParsingUtils.NOT_SET;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.IntentFilter;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingUtils;
@@ -44,12 +45,12 @@
@NonNull
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- static <Component extends ParsedMainComponent> ParseResult<Component> parseMainComponent(
+ static <Component extends ParsedMainComponentImpl> ParseResult<Component> parseMainComponent(
Component component, String tag, String[] separateProcesses, ParsingPackage pkg,
- TypedArray array, int flags, boolean useRoundIcon, ParseInput input, int bannerAttr,
- int descriptionAttr, int directBootAwareAttr, int enabledAttr, int iconAttr,
- int labelAttr, int logoAttr, int nameAttr, int processAttr, int roundIconAttr,
- int splitNameAttr, int attributionTagsAttr) {
+ TypedArray array, int flags, boolean useRoundIcon, @Nullable String defaultSplitName,
+ @NonNull ParseInput input, int bannerAttr, int descriptionAttr, int directBootAwareAttr,
+ int enabledAttr, int iconAttr, int labelAttr, int logoAttr, int nameAttr,
+ int processAttr, int roundIconAttr, int splitNameAttr, int attributionTagsAttr) {
ParseResult<Component> result = ParsedComponentUtils.parseComponent(component, tag, pkg,
array, useRoundIcon, input, bannerAttr, descriptionAttr, iconAttr, labelAttr,
logoAttr, nameAttr, roundIconAttr);
@@ -95,6 +96,10 @@
component.setSplitName(array.getNonConfigurationString(splitNameAttr, 0));
}
+ if (defaultSplitName != null && component.getSplitName() == null) {
+ component.setSplitName(defaultSplitName);
+ }
+
if (attributionTagsAttr != NOT_SET) {
final String attributionTags = array.getNonConfigurationString(attributionTagsAttr, 0);
if (attributionTags != null) {
@@ -119,7 +124,8 @@
}
ParsedIntentInfo intent = intentResult.getResult();
- int actionCount = intent.countActions();
+ IntentFilter intentFilter = intent.getIntentFilter();
+ int actionCount = intentFilter.countActions();
if (actionCount == 0 && failOnNoActions) {
Slog.w(TAG, "No actions in " + parser.getName() + " at " + pkg.getBaseApkPath() + " "
+ parser.getPositionDescription());
@@ -136,7 +142,7 @@
} else {
intentVisibility = IntentFilter.VISIBILITY_NONE;
}
- intent.setVisibilityToInstantApp(intentVisibility);
+ intentFilter.setVisibilityToInstantApp(intentVisibility);
return input.success(intentResult.getResult());
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index 0f82941..6acdb6e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -16,206 +16,28 @@
package android.content.pm.parsing.component;
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.PermissionInfo;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
-
-import java.util.Locale;
import java.util.Set;
/** @hide */
-public class ParsedPermission extends ParsedComponent {
-
- private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+public interface ParsedPermission extends ParsedComponent {
@Nullable
- private String backgroundPermission;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String group;
- private int requestRes;
- private int protectionLevel;
- private boolean tree;
- @Nullable
- private ParsedPermissionGroup parsedPermissionGroup;
- @Nullable
- private Set<String> knownCerts;
-
- @VisibleForTesting
- public ParsedPermission() {
- }
-
- public ParsedPermission(ParsedPermission other) {
- super(other);
- this.backgroundPermission = other.backgroundPermission;
- this.group = other.group;
- this.requestRes = other.requestRes;
- this.protectionLevel = other.protectionLevel;
- this.tree = other.tree;
- this.parsedPermissionGroup = other.parsedPermissionGroup;
- }
-
- public ParsedPermission setBackgroundPermission(String backgroundPermission) {
- this.backgroundPermission = backgroundPermission;
- return this;
- }
-
- public ParsedPermission setGroup(String group) {
- this.group = TextUtils.safeIntern(group);
- return this;
- }
-
- public boolean isRuntime() {
- return getProtection() == PermissionInfo.PROTECTION_DANGEROUS;
- }
-
- public boolean isAppOp() {
- return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
- }
-
- @PermissionInfo.Protection
- public int getProtection() {
- return protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- }
-
- public int getProtectionFlags() {
- return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
- }
-
- public @Nullable Set<String> getKnownCerts() {
- return knownCerts;
- }
-
- protected void setKnownCert(String knownCert) {
- // Convert the provided digest to upper case for consistent Set membership
- // checks when verifying the signing certificate digests of requesting apps.
- this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
- }
-
- protected void setKnownCerts(String[] knownCerts) {
- this.knownCerts = new ArraySet<>();
- for (String knownCert : knownCerts) {
- this.knownCerts.add(knownCert.toUpperCase(Locale.US));
- }
- }
-
- public int calculateFootprint() {
- int size = getName().length();
- if (getNonLocalizedLabel() != null) {
- size += getNonLocalizedLabel().length();
- }
- return size;
- }
-
- public ParsedPermission setKnownCerts(Set<String> knownCerts) {
- this.knownCerts = knownCerts;
- return this;
- }
-
- public ParsedPermission setRequestRes(int requestRes) {
- this.requestRes = requestRes;
- return this;
- }
-
- public ParsedPermission setTree(boolean tree) {
- this.tree = tree;
- return this;
- }
-
- public String toString() {
- return "Permission{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + getName() + "}";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeString(this.backgroundPermission);
- dest.writeString(this.group);
- dest.writeInt(this.requestRes);
- dest.writeInt(this.protectionLevel);
- dest.writeBoolean(this.tree);
- dest.writeParcelable(this.parsedPermissionGroup, flags);
- sForStringSet.parcel(knownCerts, dest, flags);
- }
-
- protected ParsedPermission(Parcel in) {
- super(in);
- // We use the boot classloader for all classes that we load.
- final ClassLoader boot = Object.class.getClassLoader();
- this.backgroundPermission = in.readString();
- this.group = in.readString();
- this.requestRes = in.readInt();
- this.protectionLevel = in.readInt();
- this.tree = in.readBoolean();
- this.parsedPermissionGroup = in.readParcelable(boot);
- this.knownCerts = sForStringSet.unparcel(in);
- }
-
- @NonNull
- public static final Parcelable.Creator<ParsedPermission> CREATOR =
- new Parcelable.Creator<ParsedPermission>() {
- @Override
- public ParsedPermission createFromParcel(Parcel source) {
- return new ParsedPermission(source);
- }
-
- @Override
- public ParsedPermission[] newArray(int size) {
- return new ParsedPermission[size];
- }
- };
+ String getBackgroundPermission();
@Nullable
- public String getBackgroundPermission() {
- return backgroundPermission;
- }
+ String getGroup();
@Nullable
- public String getGroup() {
- return group;
- }
-
- public int getRequestRes() {
- return requestRes;
- }
-
- public int getProtectionLevel() {
- return protectionLevel;
- }
-
- public boolean isTree() {
- return tree;
- }
+ Set<String> getKnownCerts();
@Nullable
- public ParsedPermissionGroup getParsedPermissionGroup() {
- return parsedPermissionGroup;
- }
+ ParsedPermissionGroup getParsedPermissionGroup();
- public ParsedPermission setProtectionLevel(int value) {
- protectionLevel = value;
- return this;
- }
+ int getProtectionLevel();
- public ParsedPermission setParsedPermissionGroup(@Nullable ParsedPermissionGroup value) {
- parsedPermissionGroup = value;
- return this;
- }
+ int getRequestRes();
+
+ boolean isTree();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java b/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java
index 9fb95c4..22aa085 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java
@@ -16,184 +16,16 @@
package android.content.pm.parsing.component;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
/** @hide */
-@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
- genAidl = false)
-public class ParsedPermissionGroup extends ParsedComponent {
+public interface ParsedPermissionGroup extends ParsedComponent {
- private int requestDetailResourceId;
- private int backgroundRequestResourceId;
- private int backgroundRequestDetailResourceId;
- private int requestRes;
- private int priority;
+ int getBackgroundRequestDetailResourceId();
- public String toString() {
- return "PermissionGroup{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + getName() + "}";
- }
+ int getBackgroundRequestResourceId();
- public ParsedPermissionGroup() {
- }
+ int getPriority();
+ int getRequestDetailResourceId();
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- public ParsedPermissionGroup(
- int requestDetailResourceId,
- int backgroundRequestResourceId,
- int backgroundRequestDetailResourceId,
- int requestRes,
- int priority) {
- this.requestDetailResourceId = requestDetailResourceId;
- this.backgroundRequestResourceId = backgroundRequestResourceId;
- this.backgroundRequestDetailResourceId = backgroundRequestDetailResourceId;
- this.requestRes = requestRes;
- this.priority = priority;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public int getRequestDetailResourceId() {
- return requestDetailResourceId;
- }
-
- @DataClass.Generated.Member
- public int getBackgroundRequestResourceId() {
- return backgroundRequestResourceId;
- }
-
- @DataClass.Generated.Member
- public int getBackgroundRequestDetailResourceId() {
- return backgroundRequestDetailResourceId;
- }
-
- @DataClass.Generated.Member
- public int getRequestRes() {
- return requestRes;
- }
-
- @DataClass.Generated.Member
- public int getPriority() {
- return priority;
- }
-
- @DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroup setRequestDetailResourceId( int value) {
- requestDetailResourceId = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroup setBackgroundRequestResourceId( int value) {
- backgroundRequestResourceId = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroup setBackgroundRequestDetailResourceId( int value) {
- backgroundRequestDetailResourceId = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroup setRequestRes( int value) {
- requestRes = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @android.annotation.NonNull ParsedPermissionGroup setPriority( int value) {
- priority = value;
- return this;
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- super.writeToParcel(dest, flags);
-
- dest.writeInt(requestDetailResourceId);
- dest.writeInt(backgroundRequestResourceId);
- dest.writeInt(backgroundRequestDetailResourceId);
- dest.writeInt(requestRes);
- dest.writeInt(priority);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected ParsedPermissionGroup(@android.annotation.NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- super(in);
-
- int _requestDetailResourceId = in.readInt();
- int _backgroundRequestResourceId = in.readInt();
- int _backgroundRequestDetailResourceId = in.readInt();
- int _requestRes = in.readInt();
- int _priority = in.readInt();
-
- this.requestDetailResourceId = _requestDetailResourceId;
- this.backgroundRequestResourceId = _backgroundRequestResourceId;
- this.backgroundRequestDetailResourceId = _backgroundRequestDetailResourceId;
- this.requestRes = _requestRes;
- this.priority = _priority;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @android.annotation.NonNull Parcelable.Creator<ParsedPermissionGroup> CREATOR
- = new Parcelable.Creator<ParsedPermissionGroup>() {
- @Override
- public ParsedPermissionGroup[] newArray(int size) {
- return new ParsedPermissionGroup[size];
- }
-
- @Override
- public ParsedPermissionGroup createFromParcel(@android.annotation.NonNull Parcel in) {
- return new ParsedPermissionGroup(in);
- }
- };
-
- @DataClass.Generated(
- time = 1624052057830L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java",
- inputSignatures = "private int requestDetailResourceId\nprivate int backgroundRequestResourceId\nprivate int backgroundRequestDetailResourceId\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\nclass ParsedPermissionGroup extends android.content.pm.parsing.component.ParsedComponent implements []\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
+ int getRequestRes();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java b/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java
new file mode 100644
index 0000000..1fa04cf
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import android.os.Parcel;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
+ genAidl = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements
+ ParsedPermissionGroup {
+
+ private int requestDetailResourceId;
+ private int backgroundRequestResourceId;
+ private int backgroundRequestDetailResourceId;
+ private int requestRes;
+ private int priority;
+
+ public String toString() {
+ return "PermissionGroup{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + getName() + "}";
+ }
+
+ public ParsedPermissionGroupImpl() {
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedPermissionGroupImpl(
+ int requestDetailResourceId,
+ int backgroundRequestResourceId,
+ int backgroundRequestDetailResourceId,
+ int requestRes,
+ int priority) {
+ this.requestDetailResourceId = requestDetailResourceId;
+ this.backgroundRequestResourceId = backgroundRequestResourceId;
+ this.backgroundRequestDetailResourceId = backgroundRequestDetailResourceId;
+ this.requestRes = requestRes;
+ this.priority = priority;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public int getRequestDetailResourceId() {
+ return requestDetailResourceId;
+ }
+
+ @DataClass.Generated.Member
+ public int getBackgroundRequestResourceId() {
+ return backgroundRequestResourceId;
+ }
+
+ @DataClass.Generated.Member
+ public int getBackgroundRequestDetailResourceId() {
+ return backgroundRequestDetailResourceId;
+ }
+
+ @DataClass.Generated.Member
+ public int getRequestRes() {
+ return requestRes;
+ }
+
+ @DataClass.Generated.Member
+ public int getPriority() {
+ return priority;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestDetailResourceId( int value) {
+ requestDetailResourceId = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestResourceId( int value) {
+ backgroundRequestResourceId = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailResourceId( int value) {
+ backgroundRequestDetailResourceId = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
+ requestRes = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ParsedPermissionGroupImpl setPriority( int value) {
+ priority = value;
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ super.writeToParcel(dest, flags);
+
+ dest.writeInt(requestDetailResourceId);
+ dest.writeInt(backgroundRequestResourceId);
+ dest.writeInt(backgroundRequestDetailResourceId);
+ dest.writeInt(requestRes);
+ dest.writeInt(priority);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedPermissionGroupImpl(@android.annotation.NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ super(in);
+
+ int _requestDetailResourceId = in.readInt();
+ int _backgroundRequestResourceId = in.readInt();
+ int _backgroundRequestDetailResourceId = in.readInt();
+ int _requestRes = in.readInt();
+ int _priority = in.readInt();
+
+ this.requestDetailResourceId = _requestDetailResourceId;
+ this.backgroundRequestResourceId = _backgroundRequestResourceId;
+ this.backgroundRequestDetailResourceId = _backgroundRequestDetailResourceId;
+ this.requestRes = _requestRes;
+ this.priority = _priority;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull android.os.Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
+ = new android.os.Parcelable.Creator<ParsedPermissionGroupImpl>() {
+ @Override
+ public ParsedPermissionGroupImpl[] newArray(int size) {
+ return new ParsedPermissionGroupImpl[size];
+ }
+
+ @Override
+ public ParsedPermissionGroupImpl createFromParcel(@android.annotation.NonNull Parcel in) {
+ return new ParsedPermissionGroupImpl(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1627602253988L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java",
+ inputSignatures = "private int requestDetailResourceId\nprivate int backgroundRequestResourceId\nprivate int backgroundRequestDetailResourceId\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\nclass ParsedPermissionGroupImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermissionGroup]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java b/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
new file mode 100644
index 0000000..2145e44
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.util.Locale;
+import java.util.Set;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission {
+
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
+ @Nullable
+ private String backgroundPermission;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String group;
+ private int requestRes;
+ private int protectionLevel;
+ private boolean tree;
+ @Nullable
+ private ParsedPermissionGroup parsedPermissionGroup;
+ @Nullable
+ private Set<String> knownCerts;
+
+ @VisibleForTesting
+ public ParsedPermissionImpl() {
+ }
+
+ public ParsedPermissionImpl(ParsedPermission other) {
+ super(other);
+ this.backgroundPermission = other.getBackgroundPermission();
+ this.group = other.getGroup();
+ this.requestRes = other.getRequestRes();
+ this.protectionLevel = other.getProtectionLevel();
+ this.tree = other.isTree();
+ this.parsedPermissionGroup = other.getParsedPermissionGroup();
+ }
+
+ public ParsedPermissionImpl setGroup(String group) {
+ this.group = TextUtils.safeIntern(group);
+ return this;
+ }
+
+ protected void setKnownCert(String knownCert) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+ }
+
+ protected void setKnownCerts(String[] knownCerts) {
+ this.knownCerts = new ArraySet<>();
+ for (String knownCert : knownCerts) {
+ this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
+ public String toString() {
+ return "Permission{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + getName() + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(this.backgroundPermission);
+ dest.writeString(this.group);
+ dest.writeInt(this.requestRes);
+ dest.writeInt(this.protectionLevel);
+ dest.writeBoolean(this.tree);
+ dest.writeParcelable(this.parsedPermissionGroup, flags);
+ sForStringSet.parcel(knownCerts, dest, flags);
+ }
+
+ protected ParsedPermissionImpl(Parcel in) {
+ super(in);
+ // We use the boot classloader for all classes that we load.
+ final ClassLoader boot = Object.class.getClassLoader();
+ this.backgroundPermission = in.readString();
+ this.group = TextUtils.safeIntern(in.readString());
+ this.requestRes = in.readInt();
+ this.protectionLevel = in.readInt();
+ this.tree = in.readBoolean();
+ this.parsedPermissionGroup = in.readParcelable(boot);
+ this.knownCerts = sForStringSet.unparcel(in);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParsedPermissionImpl> CREATOR =
+ new Parcelable.Creator<ParsedPermissionImpl>() {
+ @Override
+ public ParsedPermissionImpl createFromParcel(Parcel source) {
+ return new ParsedPermissionImpl(source);
+ }
+
+ @Override
+ public ParsedPermissionImpl[] newArray(int size) {
+ return new ParsedPermissionImpl[size];
+ }
+ };
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedPermissionImpl(
+ @Nullable String backgroundPermission,
+ @Nullable String group,
+ int requestRes,
+ int protectionLevel,
+ boolean tree,
+ @Nullable ParsedPermissionGroup parsedPermissionGroup,
+ @Nullable Set<String> knownCerts) {
+ this.backgroundPermission = backgroundPermission;
+ this.group = group;
+ this.requestRes = requestRes;
+ this.protectionLevel = protectionLevel;
+ this.tree = tree;
+ this.parsedPermissionGroup = parsedPermissionGroup;
+ this.knownCerts = knownCerts;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getBackgroundPermission() {
+ return backgroundPermission;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getGroup() {
+ return group;
+ }
+
+ @DataClass.Generated.Member
+ public int getRequestRes() {
+ return requestRes;
+ }
+
+ @DataClass.Generated.Member
+ public int getProtectionLevel() {
+ return protectionLevel;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isTree() {
+ return tree;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable ParsedPermissionGroup getParsedPermissionGroup() {
+ return parsedPermissionGroup;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable Set<String> getKnownCerts() {
+ return knownCerts;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedPermissionImpl setBackgroundPermission(@NonNull String value) {
+ backgroundPermission = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedPermissionImpl setRequestRes( int value) {
+ requestRes = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedPermissionImpl setProtectionLevel( int value) {
+ protectionLevel = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedPermissionImpl setTree( boolean value) {
+ tree = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedPermissionImpl setParsedPermissionGroup(@NonNull ParsedPermissionGroup value) {
+ parsedPermissionGroup = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedPermissionImpl setKnownCerts(@NonNull Set<String> value) {
+ knownCerts = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1627598236506L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java",
+ inputSignatures = "private static com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate int requestRes\nprivate int protectionLevel\nprivate boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroup parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected void setKnownCert(java.lang.String)\nprotected void setKnownCerts(java.lang.String[])\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 5a7a5ef..66e9d3d 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -45,10 +45,9 @@
XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
throws IOException, XmlPullParserException {
String packageName = pkg.getPackageName();
- ParsedPermission
- permission = new ParsedPermission();
+ ParsedPermissionImpl permission = new ParsedPermissionImpl();
String tag = "<" + parser.getName() + ">";
- final ParseResult<ParsedPermission> result;
+ ParseResult<ParsedPermissionImpl> result;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission);
try {
@@ -62,7 +61,7 @@
R.styleable.AndroidManifestPermission_name,
R.styleable.AndroidManifestPermission_roundIcon);
if (result.isError()) {
- return result;
+ return input.error(result);
}
if (sa.hasValue(
@@ -121,7 +120,7 @@
}
// For now only platform runtime permissions can be restricted
- if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
+ if (!isRuntime(permission) || !"android".equals(permission.getPackageName())) {
permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_HARD_RESTRICTED);
permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_SOFT_RESTRICTED);
} else {
@@ -139,26 +138,31 @@
permission.setProtectionLevel(
PermissionInfo.fixProtectionLevel(permission.getProtectionLevel()));
- final int otherProtectionFlags = permission.getProtectionFlags()
+ final int otherProtectionFlags = getProtectionFlags(permission)
& ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT
| PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY);
if (otherProtectionFlags != 0
- && permission.getProtection() != PermissionInfo.PROTECTION_SIGNATURE
- && permission.getProtection() != PermissionInfo.PROTECTION_INTERNAL) {
+ && getProtection(permission) != PermissionInfo.PROTECTION_SIGNATURE
+ && getProtection(permission) != PermissionInfo.PROTECTION_INTERNAL) {
return input.error("<permission> protectionLevel specifies a non-instant, non-appop,"
+ " non-runtimeOnly flag but is not based on signature or internal type");
}
- return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
+ result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult());
}
@NonNull
public static ParseResult<ParsedPermission> parsePermissionTree(ParsingPackage pkg, Resources res,
XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
throws IOException, XmlPullParserException {
- ParsedPermission permission = new ParsedPermission();
+ ParsedPermissionImpl permission = new ParsedPermissionImpl();
String tag = "<" + parser.getName() + ">";
- final ParseResult<ParsedPermission> result;
+ ParseResult<ParsedPermissionImpl> result;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionTree);
try {
@@ -172,7 +176,7 @@
R.styleable.AndroidManifestPermissionTree_name,
R.styleable.AndroidManifestPermissionTree_roundIcon);
if (result.isError()) {
- return result;
+ return input.error(result);
}
} finally {
sa.recycle();
@@ -190,21 +194,25 @@
permission.setProtectionLevel(PermissionInfo.PROTECTION_NORMAL)
.setTree(true);
- return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission,
- input);
+ result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult());
}
@NonNull
public static ParseResult<ParsedPermissionGroup> parsePermissionGroup(ParsingPackage pkg,
Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
throws IOException, XmlPullParserException {
- ParsedPermissionGroup
- permissionGroup = new ParsedPermissionGroup();
+ ParsedPermissionGroupImpl
+ permissionGroup = new ParsedPermissionGroupImpl();
String tag = "<" + parser.getName() + ">";
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionGroup);
try {
- ParseResult<ParsedPermissionGroup> result = ParsedComponentUtils.parseComponent(
+ ParseResult<ParsedPermissionGroupImpl> result = ParsedComponentUtils.parseComponent(
permissionGroup, tag, pkg, sa, useRoundIcon, input,
R.styleable.AndroidManifestPermissionGroup_banner,
R.styleable.AndroidManifestPermissionGroup_description,
@@ -214,7 +222,7 @@
R.styleable.AndroidManifestPermissionGroup_name,
R.styleable.AndroidManifestPermissionGroup_roundIcon);
if (result.isError()) {
- return result;
+ return input.error(result);
}
// @formatter:off
@@ -229,7 +237,38 @@
sa.recycle();
}
- return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permissionGroup,
- input);
+ ParseResult<ParsedPermissionGroupImpl> result = ComponentParseUtils.parseAllMetaData(pkg,
+ res, parser, tag, permissionGroup, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult());
+ }
+
+ public static boolean isRuntime(@NonNull ParsedPermission permission) {
+ return getProtection(permission) == PermissionInfo.PROTECTION_DANGEROUS;
+ }
+
+ public static boolean isAppOp(@NonNull ParsedPermission permission) {
+ return (permission.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+ }
+
+ @PermissionInfo.Protection
+ public static int getProtection(@NonNull ParsedPermission permission) {
+ return permission.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE;
+ }
+
+ public static int getProtectionFlags(@NonNull ParsedPermission permission) {
+ return permission.getProtectionLevel() & ~PermissionInfo.PROTECTION_MASK_BASE;
+ }
+
+ public static int calculateFootprint(@NonNull ParsedPermission permission) {
+ int size = permission.getName().length();
+ CharSequence nonLocalizedLabel = permission.getNonLocalizedLabel();
+ if (nonLocalizedLabel != null) {
+ size += nonLocalizedLabel.length();
+ }
+ return size;
}
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index fe10225..c2d5163 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -16,240 +16,27 @@
package android.content.pm.parsing.component;
-import static java.util.Collections.emptySet;
-
import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
-import android.os.Parcel;
import android.os.Parcelable;
-import android.util.ArraySet;
-
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
import java.util.Set;
/** @hide */
-@DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
- genBuilder = false)
-public class ParsedProcess implements Parcelable {
+public interface ParsedProcess extends Parcelable {
@NonNull
- private String name;
- @NonNull
- @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
- private Set<String> deniedPermissions = emptySet();
+ Set<String> getDeniedPermissions();
@ApplicationInfo.GwpAsanMode
- private int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT;
+ int getGwpAsanMode();
+
@ApplicationInfo.MemtagMode
- private int memtagMode = ApplicationInfo.MEMTAG_DEFAULT;
+ int getMemtagMode();
+
+ @NonNull
+ String getName();
+
@ApplicationInfo.NativeHeapZeroInitialized
- private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
-
- public ParsedProcess() {
- }
-
- public ParsedProcess(@NonNull ParsedProcess other) {
- name = other.name;
- deniedPermissions = new ArraySet<>(other.deniedPermissions);
- }
-
- public void addStateFrom(@NonNull ParsedProcess other) {
- deniedPermissions = CollectionUtils.addAll(deniedPermissions, other.deniedPermissions);
- }
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- public ParsedProcess(
- @NonNull String name,
- @NonNull Set<String> deniedPermissions,
- @ApplicationInfo.GwpAsanMode int gwpAsanMode,
- @ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
- this.name = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, name);
- this.deniedPermissions = deniedPermissions;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, deniedPermissions);
- this.gwpAsanMode = gwpAsanMode;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
- this.memtagMode = memtagMode;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.MemtagMode.class, null, memtagMode);
- this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public @NonNull String getName() {
- return name;
- }
-
- @DataClass.Generated.Member
- public @NonNull Set<String> getDeniedPermissions() {
- return deniedPermissions;
- }
-
- @DataClass.Generated.Member
- public @ApplicationInfo.GwpAsanMode int getGwpAsanMode() {
- return gwpAsanMode;
- }
-
- @DataClass.Generated.Member
- public @ApplicationInfo.MemtagMode int getMemtagMode() {
- return memtagMode;
- }
-
- @DataClass.Generated.Member
- public @ApplicationInfo.NativeHeapZeroInitialized int getNativeHeapZeroInitialized() {
- return nativeHeapZeroInitialized;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedProcess setName(@NonNull String value) {
- name = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, name);
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedProcess setDeniedPermissions(@NonNull Set<String> value) {
- deniedPermissions = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, deniedPermissions);
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedProcess setGwpAsanMode(@ApplicationInfo.GwpAsanMode int value) {
- gwpAsanMode = value;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedProcess setMemtagMode(@ApplicationInfo.MemtagMode int value) {
- memtagMode = value;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.MemtagMode.class, null, memtagMode);
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedProcess setNativeHeapZeroInitialized(@ApplicationInfo.NativeHeapZeroInitialized int value) {
- nativeHeapZeroInitialized = value;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
- return this;
- }
-
- @DataClass.Generated.Member
- static Parcelling<Set<String>> sParcellingForDeniedPermissions =
- Parcelling.Cache.get(
- Parcelling.BuiltIn.ForInternedStringSet.class);
- static {
- if (sParcellingForDeniedPermissions == null) {
- sParcellingForDeniedPermissions = Parcelling.Cache.put(
- new Parcelling.BuiltIn.ForInternedStringSet());
- }
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- dest.writeString(name);
- sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
- dest.writeInt(gwpAsanMode);
- dest.writeInt(memtagMode);
- dest.writeInt(nativeHeapZeroInitialized);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected ParsedProcess(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- String _name = in.readString();
- Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
- int _gwpAsanMode = in.readInt();
- int _memtagMode = in.readInt();
- int _nativeHeapZeroInitialized = in.readInt();
-
- this.name = _name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, name);
- this.deniedPermissions = _deniedPermissions;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, deniedPermissions);
- this.gwpAsanMode = _gwpAsanMode;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
- this.memtagMode = _memtagMode;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.MemtagMode.class, null, memtagMode);
- this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
- com.android.internal.util.AnnotationValidations.validate(
- ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<ParsedProcess> CREATOR
- = new Parcelable.Creator<ParsedProcess>() {
- @Override
- public ParsedProcess[] newArray(int size) {
- return new ParsedProcess[size];
- }
-
- @Override
- public ParsedProcess createFromParcel(@NonNull Parcel in) {
- return new ParsedProcess(in);
- }
- };
-
- @DataClass.Generated(
- time = 1623692988845L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
+ int getNativeHeapZeroInitialized();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
new file mode 100644
index 0000000..3fd60eb
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static java.util.Collections.emptySet;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Set;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
+ genBuilder = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedProcessImpl implements ParsedProcess {
+
+ @NonNull
+ private String name;
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+ private Set<String> deniedPermissions = emptySet();
+
+ @ApplicationInfo.GwpAsanMode
+ private int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT;
+ @ApplicationInfo.MemtagMode
+ private int memtagMode = ApplicationInfo.MEMTAG_DEFAULT;
+ @ApplicationInfo.NativeHeapZeroInitialized
+ private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
+
+ public ParsedProcessImpl() {
+ }
+
+ public ParsedProcessImpl(@NonNull ParsedProcess other) {
+ name = other.getName();
+ deniedPermissions = new ArraySet<>(other.getDeniedPermissions());
+ gwpAsanMode = other.getGwpAsanMode();
+ memtagMode = other.getMemtagMode();
+ nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ }
+
+ public void addStateFrom(@NonNull ParsedProcess other) {
+ deniedPermissions = CollectionUtils.addAll(deniedPermissions, other.getDeniedPermissions());
+ gwpAsanMode = other.getGwpAsanMode();
+ memtagMode = other.getMemtagMode();
+ nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedProcessImpl(
+ @NonNull String name,
+ @NonNull Set<String> deniedPermissions,
+ @ApplicationInfo.GwpAsanMode int gwpAsanMode,
+ @ApplicationInfo.MemtagMode int memtagMode,
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ this.name = name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.deniedPermissions = deniedPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deniedPermissions);
+ this.gwpAsanMode = gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getName() {
+ return name;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getDeniedPermissions() {
+ return deniedPermissions;
+ }
+
+ @DataClass.Generated.Member
+ public @ApplicationInfo.GwpAsanMode int getGwpAsanMode() {
+ return gwpAsanMode;
+ }
+
+ @DataClass.Generated.Member
+ public @ApplicationInfo.MemtagMode int getMemtagMode() {
+ return memtagMode;
+ }
+
+ @DataClass.Generated.Member
+ public @ApplicationInfo.NativeHeapZeroInitialized int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setName(@NonNull String value) {
+ name = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setDeniedPermissions(@NonNull Set<String> value) {
+ deniedPermissions = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deniedPermissions);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setGwpAsanMode(@ApplicationInfo.GwpAsanMode int value) {
+ gwpAsanMode = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setMemtagMode(@ApplicationInfo.MemtagMode int value) {
+ memtagMode = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setNativeHeapZeroInitialized(@ApplicationInfo.NativeHeapZeroInitialized int value) {
+ nativeHeapZeroInitialized = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Set<String>> sParcellingForDeniedPermissions =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForInternedStringSet.class);
+ static {
+ if (sParcellingForDeniedPermissions == null) {
+ sParcellingForDeniedPermissions = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForInternedStringSet());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(name);
+ sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
+ dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ dest.writeInt(nativeHeapZeroInitialized);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedProcessImpl(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String _name = in.readString();
+ Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
+ int _gwpAsanMode = in.readInt();
+ int _memtagMode = in.readInt();
+ int _nativeHeapZeroInitialized = in.readInt();
+
+ this.name = _name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.deniedPermissions = _deniedPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deniedPermissions);
+ this.gwpAsanMode = _gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = _memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ParsedProcessImpl> CREATOR
+ = new Parcelable.Creator<ParsedProcessImpl>() {
+ @Override
+ public ParsedProcessImpl[] newArray(int size) {
+ return new ParsedProcessImpl[size];
+ }
+
+ @Override
+ public ParsedProcessImpl createFromParcel(@NonNull Parcel in) {
+ return new ParsedProcessImpl(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1627605368434L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
+ inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
index 54dd295..cf83586 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -81,7 +81,7 @@
private static ParseResult<ParsedProcess> parseProcess(Set<String> perms, String[] separateProcesses,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
ParseInput input) throws IOException, XmlPullParserException {
- ParsedProcess proc = new ParsedProcess();
+ ParsedProcessImpl proc = new ParsedProcessImpl();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProcess);
try {
if (perms != null) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedProvider.java b/core/java/android/content/pm/parsing/component/ParsedProvider.java
index 9a12b48..1211ce2 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProvider.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProvider.java
@@ -16,214 +16,31 @@
package android.content.pm.parsing.component;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.pm.PathPermission;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.PatternMatcher;
-import android.text.TextUtils;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
/** @hide **/
-public class ParsedProvider extends ParsedMainComponent {
-
- @NonNull
- @DataClass.ParcelWith(ForInternedString.class)
- private String authority;
- private boolean syncable;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String readPermission;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String writePermission;
- private boolean grantUriPermissions;
- private boolean forceUriPermissions;
- private boolean multiProcess;
- private int initOrder;
- @Nullable
- private PatternMatcher[] uriPermissionPatterns;
- @Nullable
- private PathPermission[] pathPermissions;
-
- public ParsedProvider(ParsedProvider other) {
- super(other);
-
- this.authority = other.authority;
- this.syncable = other.syncable;
- this.readPermission = other.readPermission;
- this.writePermission = other.writePermission;
- this.grantUriPermissions = other.grantUriPermissions;
- this.forceUriPermissions = other.forceUriPermissions;
- this.multiProcess = other.multiProcess;
- this.initOrder = other.initOrder;
- this.uriPermissionPatterns = other.uriPermissionPatterns;
- this.pathPermissions = other.pathPermissions;
- }
-
- public ParsedProvider setAuthority(String authority) {
- this.authority = TextUtils.safeIntern(authority);
- return this;
- }
-
- public ParsedProvider setForceUriPermissions(boolean forceUriPermissions) {
- this.forceUriPermissions = forceUriPermissions;
- return this;
- }
-
- public ParsedProvider setGrantUriPermissions(boolean grantUriPermissions) {
- this.grantUriPermissions = grantUriPermissions;
- return this;
- }
-
- public ParsedProvider setInitOrder(int initOrder) {
- this.initOrder = initOrder;
- return this;
- }
-
- public ParsedProvider setMultiProcess(boolean multiProcess) {
- this.multiProcess = multiProcess;
- return this;
- }
-
- public ParsedProvider setPathPermissions(PathPermission[] pathPermissions) {
- this.pathPermissions = pathPermissions;
- return this;
- }
-
- public ParsedProvider setSyncable(boolean syncable) {
- this.syncable = syncable;
- return this;
- }
-
- public ParsedProvider setReadPermission(String readPermission) {
- // Empty string must be converted to null
- this.readPermission = TextUtils.isEmpty(readPermission)
- ? null : readPermission.intern();
- return this;
- }
-
- public ParsedProvider setUriPermissionPatterns(PatternMatcher[] uriPermissionPatterns) {
- this.uriPermissionPatterns = uriPermissionPatterns;
- return this;
- }
-
- public ParsedProvider setWritePermission(String writePermission) {
- // Empty string must be converted to null
- this.writePermission = TextUtils.isEmpty(writePermission)
- ? null : writePermission.intern();
- return this;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Provider{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(' ');
- ComponentName.appendShortString(sb, getPackageName(), getName());
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeString(this.authority);
- dest.writeBoolean(this.syncable);
- sForInternedString.parcel(this.readPermission, dest, flags);
- sForInternedString.parcel(this.writePermission, dest, flags);
- dest.writeBoolean(this.grantUriPermissions);
- dest.writeBoolean(this.forceUriPermissions);
- dest.writeBoolean(this.multiProcess);
- dest.writeInt(this.initOrder);
- dest.writeTypedArray(this.uriPermissionPatterns, flags);
- dest.writeTypedArray(this.pathPermissions, flags);
- }
-
- public ParsedProvider() {
- }
-
- protected ParsedProvider(Parcel in) {
- super(in);
- //noinspection ConstantConditions
- this.authority = in.readString();
- this.syncable = in.readBoolean();
- this.readPermission = sForInternedString.unparcel(in);
- this.writePermission = sForInternedString.unparcel(in);
- this.grantUriPermissions = in.readBoolean();
- this.forceUriPermissions = in.readBoolean();
- this.multiProcess = in.readBoolean();
- this.initOrder = in.readInt();
- this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
- this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
- }
-
- @NonNull
- public static final Parcelable.Creator<ParsedProvider> CREATOR = new Creator<ParsedProvider>() {
- @Override
- public ParsedProvider createFromParcel(Parcel source) {
- return new ParsedProvider(source);
- }
-
- @Override
- public ParsedProvider[] newArray(int size) {
- return new ParsedProvider[size];
- }
- };
-
- @NonNull
- public String getAuthority() {
- return authority;
- }
-
- public boolean isSyncable() {
- return syncable;
- }
+public interface ParsedProvider extends ParsedMainComponent {
@Nullable
- public String getReadPermission() {
- return readPermission;
- }
+ String getAuthority();
- @Nullable
- public String getWritePermission() {
- return writePermission;
- }
+ int getInitOrder();
- public boolean isGrantUriPermissions() {
- return grantUriPermissions;
- }
+ boolean isMultiProcess();
- public boolean isForceUriPermissions() {
- return forceUriPermissions;
- }
+ @Nullable PathPermission[] getPathPermissions();
- public boolean isMultiProcess() {
- return multiProcess;
- }
+ @Nullable String getReadPermission();
- public int getInitOrder() {
- return initOrder;
- }
+ @Nullable PatternMatcher[] getUriPermissionPatterns();
- @Nullable
- public PatternMatcher[] getUriPermissionPatterns() {
- return uriPermissionPatterns;
- }
+ @Nullable String getWritePermission();
- @Nullable
- public PathPermission[] getPathPermissions() {
- return pathPermissions;
- }
+ boolean isForceUriPermissions();
+
+ boolean isGrantUriPermissions();
+
+ boolean isSyncable();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java b/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java
new file mode 100644
index 0000000..774c3fc
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.PathPermission;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide **/
+@DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider {
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String authority;
+ private boolean syncable;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String readPermission;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String writePermission;
+ private boolean grantUriPermissions;
+ private boolean forceUriPermissions;
+ private boolean multiProcess;
+ private int initOrder;
+ @Nullable
+ private PatternMatcher[] uriPermissionPatterns;
+ @Nullable
+ private PathPermission[] pathPermissions;
+
+ public ParsedProviderImpl(ParsedProvider other) {
+ super(other);
+
+ this.authority = other.getAuthority();
+ this.syncable = other.isSyncable();
+ this.readPermission = other.getReadPermission();
+ this.writePermission = other.getWritePermission();
+ this.grantUriPermissions = other.isGrantUriPermissions();
+ this.forceUriPermissions = other.isForceUriPermissions();
+ this.multiProcess = other.isMultiProcess();
+ this.initOrder = other.getInitOrder();
+ this.uriPermissionPatterns = other.getUriPermissionPatterns();
+ this.pathPermissions = other.getPathPermissions();
+ }
+
+ public ParsedProviderImpl setReadPermission(String readPermission) {
+ // Empty string must be converted to null
+ this.readPermission = TextUtils.isEmpty(readPermission)
+ ? null : readPermission.intern();
+ return this;
+ }
+
+ public ParsedProviderImpl setWritePermission(String writePermission) {
+ // Empty string must be converted to null
+ this.writePermission = TextUtils.isEmpty(writePermission)
+ ? null : writePermission.intern();
+ return this;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Provider{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(this.authority);
+ dest.writeBoolean(this.syncable);
+ sForInternedString.parcel(this.readPermission, dest, flags);
+ sForInternedString.parcel(this.writePermission, dest, flags);
+ dest.writeBoolean(this.grantUriPermissions);
+ dest.writeBoolean(this.forceUriPermissions);
+ dest.writeBoolean(this.multiProcess);
+ dest.writeInt(this.initOrder);
+ dest.writeTypedArray(this.uriPermissionPatterns, flags);
+ dest.writeTypedArray(this.pathPermissions, flags);
+ }
+
+ public ParsedProviderImpl() {
+ }
+
+ protected ParsedProviderImpl(Parcel in) {
+ super(in);
+ this.authority = in.readString();
+ this.syncable = in.readBoolean();
+ this.readPermission = sForInternedString.unparcel(in);
+ this.writePermission = sForInternedString.unparcel(in);
+ this.grantUriPermissions = in.readBoolean();
+ this.forceUriPermissions = in.readBoolean();
+ this.multiProcess = in.readBoolean();
+ this.initOrder = in.readInt();
+ this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+ this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParsedProviderImpl> CREATOR =
+ new Parcelable.Creator<ParsedProviderImpl>() {
+ @Override
+ public ParsedProviderImpl createFromParcel(Parcel source) {
+ return new ParsedProviderImpl(source);
+ }
+
+ @Override
+ public ParsedProviderImpl[] newArray(int size) {
+ return new ParsedProviderImpl[size];
+ }
+ };
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedProviderImpl(
+ @Nullable String authority,
+ boolean syncable,
+ @Nullable String readPermission,
+ @Nullable String writePermission,
+ boolean grantUriPermissions,
+ boolean forceUriPermissions,
+ boolean multiProcess,
+ int initOrder,
+ @Nullable PatternMatcher[] uriPermissionPatterns,
+ @Nullable PathPermission[] pathPermissions) {
+ this.authority = authority;
+ this.syncable = syncable;
+ this.readPermission = readPermission;
+ this.writePermission = writePermission;
+ this.grantUriPermissions = grantUriPermissions;
+ this.forceUriPermissions = forceUriPermissions;
+ this.multiProcess = multiProcess;
+ this.initOrder = initOrder;
+ this.uriPermissionPatterns = uriPermissionPatterns;
+ this.pathPermissions = pathPermissions;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getAuthority() {
+ return authority;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isSyncable() {
+ return syncable;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getReadPermission() {
+ return readPermission;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getWritePermission() {
+ return writePermission;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isGrantUriPermissions() {
+ return grantUriPermissions;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isForceUriPermissions() {
+ return forceUriPermissions;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isMultiProcess() {
+ return multiProcess;
+ }
+
+ @DataClass.Generated.Member
+ public int getInitOrder() {
+ return initOrder;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable PatternMatcher[] getUriPermissionPatterns() {
+ return uriPermissionPatterns;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable PathPermission[] getPathPermissions() {
+ return pathPermissions;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setAuthority(@NonNull String value) {
+ authority = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setSyncable( boolean value) {
+ syncable = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setGrantUriPermissions( boolean value) {
+ grantUriPermissions = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setForceUriPermissions( boolean value) {
+ forceUriPermissions = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setMultiProcess( boolean value) {
+ multiProcess = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setInitOrder( int value) {
+ initOrder = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setUriPermissionPatterns(@NonNull PatternMatcher... value) {
+ uriPermissionPatterns = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedProviderImpl setPathPermissions(@NonNull PathPermission... value) {
+ pathPermissions = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1627590522169L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java",
+ inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.Nullable android.os.PatternMatcher[] uriPermissionPatterns\nprivate @android.annotation.Nullable android.content.pm.PathPermission[] pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedProviderImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic android.content.pm.parsing.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedProvider]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java b/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java
index c4b8b98..de9dd44 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java
@@ -20,6 +20,8 @@
import static android.content.pm.parsing.component.ComponentParseUtils.flag;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentFilter;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.pm.parsing.ParsingPackage;
@@ -49,35 +51,35 @@
@NonNull
public static ParseResult<ParsedProvider> parseProvider(String[] separateProcesses,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
- boolean useRoundIcon, ParseInput input)
+ boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)
throws IOException, XmlPullParserException {
String authority;
boolean visibleToEphemeral;
final int targetSdkVersion = pkg.getTargetSdkVersion();
final String packageName = pkg.getPackageName();
- final ParsedProvider provider = new ParsedProvider();
+ final ParsedProviderImpl provider = new ParsedProviderImpl();
final String tag = parser.getName();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProvider);
try {
- ParseResult<ParsedProvider> result =
+ ParseResult<ParsedProviderImpl> result =
ParsedMainComponentUtils.parseMainComponent(provider, tag, separateProcesses,
- pkg, sa, flags, useRoundIcon, input,
- R.styleable.AndroidManifestProvider_banner,
- R.styleable.AndroidManifestProvider_description,
- R.styleable.AndroidManifestProvider_directBootAware,
- R.styleable.AndroidManifestProvider_enabled,
- R.styleable.AndroidManifestProvider_icon,
- R.styleable.AndroidManifestProvider_label,
- R.styleable.AndroidManifestProvider_logo,
- R.styleable.AndroidManifestProvider_name,
- R.styleable.AndroidManifestProvider_process,
- R.styleable.AndroidManifestProvider_roundIcon,
- R.styleable.AndroidManifestProvider_splitName,
- R.styleable.AndroidManifestProvider_attributionTags);
+ pkg, sa, flags, useRoundIcon, defaultSplitName, input,
+ R.styleable.AndroidManifestProvider_banner,
+ R.styleable.AndroidManifestProvider_description,
+ R.styleable.AndroidManifestProvider_directBootAware,
+ R.styleable.AndroidManifestProvider_enabled,
+ R.styleable.AndroidManifestProvider_icon,
+ R.styleable.AndroidManifestProvider_label,
+ R.styleable.AndroidManifestProvider_logo,
+ R.styleable.AndroidManifestProvider_name,
+ R.styleable.AndroidManifestProvider_process,
+ R.styleable.AndroidManifestProvider_roundIcon,
+ R.styleable.AndroidManifestProvider_splitName,
+ R.styleable.AndroidManifestProvider_attributionTags);
if (result.isError()) {
- return result;
+ return input.error(result);
}
authority = sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_authorities, 0);
@@ -156,7 +158,7 @@
@NonNull
private static ParseResult<ParsedProvider> parseProviderTags(ParsingPackage pkg, String tag,
Resources res, XmlResourceParser parser, boolean visibleToEphemeral,
- ParsedProvider provider, ParseInput input)
+ ParsedProviderImpl provider, ParseInput input)
throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
@@ -179,7 +181,8 @@
result = intentResult;
if (intentResult.isSuccess()) {
ParsedIntentInfo intent = intentResult.getResult();
- provider.setOrder(Math.max(intent.getOrder(), provider.getOrder()));
+ IntentFilter intentFilter = intent.getIntentFilter();
+ provider.setOrder(Math.max(intentFilter.getOrder(), provider.getOrder()));
provider.addIntent(intent);
}
break;
@@ -211,7 +214,7 @@
}
@NonNull
- private static ParseResult<ParsedProvider> parseGrantUriPermission(ParsedProvider provider,
+ private static ParseResult<ParsedProvider> parseGrantUriPermission(ParsedProviderImpl provider,
ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
TypedArray sa = resources.obtainAttributes(parser,
R.styleable.AndroidManifestGrantUriPermission);
@@ -277,7 +280,7 @@
}
@NonNull
- private static ParseResult<ParsedProvider> parsePathPermission(ParsedProvider provider,
+ private static ParseResult<ParsedProvider> parsePathPermission(ParsedProviderImpl provider,
ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
TypedArray sa = resources.obtainAttributes(parser,
R.styleable.AndroidManifestPathPermission);
diff --git a/core/java/android/content/pm/parsing/component/ParsedService.java b/core/java/android/content/pm/parsing/component/ParsedService.java
index 5499a13..6736afa 100644
--- a/core/java/android/content/pm/parsing/component/ParsedService.java
+++ b/core/java/android/content/pm/parsing/component/ParsedService.java
@@ -16,93 +16,13 @@
package android.content.pm.parsing.component;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
/** @hide **/
-public class ParsedService extends ParsedMainComponent {
+public interface ParsedService extends ParsedMainComponent {
- private int foregroundServiceType;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- private String permission;
-
- public ParsedService(ParsedService other) {
- super(other);
- this.foregroundServiceType = other.foregroundServiceType;
- this.permission = other.permission;
- }
-
- public ParsedService setForegroundServiceType(int foregroundServiceType) {
- this.foregroundServiceType = foregroundServiceType;
- return this;
- }
-
- public ParsedMainComponent setPermission(String permission) {
- // Empty string must be converted to null
- this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
- return this;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Service{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(' ');
- ComponentName.appendShortString(sb, getPackageName(), getName());
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(this.foregroundServiceType);
- sForInternedString.parcel(this.permission, dest, flags);
- }
-
- public ParsedService() {
- }
-
- protected ParsedService(Parcel in) {
- super(in);
- this.foregroundServiceType = in.readInt();
- this.permission = sForInternedString.unparcel(in);
- }
-
- @NonNull
- public static final Parcelable.Creator<ParsedService> CREATOR = new Creator<ParsedService>() {
- @Override
- public ParsedService createFromParcel(Parcel source) {
- return new ParsedService(source);
- }
-
- @Override
- public ParsedService[] newArray(int size) {
- return new ParsedService[size];
- }
- };
-
- public int getForegroundServiceType() {
- return foregroundServiceType;
- }
+ int getForegroundServiceType();
@Nullable
- public String getPermission() {
- return permission;
- }
+ String getPermission();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java b/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java
new file mode 100644
index 0000000..a85fb5c
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide **/
+@DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService {
+
+ private int foregroundServiceType;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String permission;
+
+ public ParsedServiceImpl(ParsedServiceImpl other) {
+ super(other);
+ this.foregroundServiceType = other.foregroundServiceType;
+ this.permission = other.permission;
+ }
+
+ public ParsedMainComponent setPermission(String permission) {
+ // Empty string must be converted to null
+ this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
+ return this;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Service{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.foregroundServiceType);
+ sForInternedString.parcel(this.permission, dest, flags);
+ }
+
+ public ParsedServiceImpl() {
+ }
+
+ protected ParsedServiceImpl(Parcel in) {
+ super(in);
+ this.foregroundServiceType = in.readInt();
+ this.permission = sForInternedString.unparcel(in);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParsedServiceImpl> CREATOR =
+ new Parcelable.Creator<ParsedServiceImpl>() {
+ @Override
+ public ParsedServiceImpl createFromParcel(Parcel source) {
+ return new ParsedServiceImpl(source);
+ }
+
+ @Override
+ public ParsedServiceImpl[] newArray(int size) {
+ return new ParsedServiceImpl[size];
+ }
+ };
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedServiceImpl(
+ int foregroundServiceType,
+ @Nullable String permission) {
+ this.foregroundServiceType = foregroundServiceType;
+ this.permission = permission;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public int getForegroundServiceType() {
+ return foregroundServiceType;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getPermission() {
+ return permission;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedServiceImpl setForegroundServiceType( int value) {
+ foregroundServiceType = value;
+ return this;
+ }
+
+ @DataClass.Generated(
+ time = 1627592563052L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java",
+ inputSignatures = "private int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java b/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java
index 59267f9..d27a0ed 100644
--- a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java
@@ -19,6 +19,8 @@
import static android.content.pm.parsing.component.ComponentParseUtils.flag;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.parsing.ParsingPackage;
@@ -45,19 +47,20 @@
@NonNull
public static ParseResult<ParsedService> parseService(String[] separateProcesses,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
- boolean useRoundIcon, ParseInput input)
+ boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)
throws XmlPullParserException, IOException {
boolean visibleToEphemeral;
boolean setExported;
final String packageName = pkg.getPackageName();
- final ParsedService service = new ParsedService();
+ final ParsedServiceImpl service = new ParsedServiceImpl();
String tag = parser.getName();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestService);
try {
- ParseResult<ParsedService> result = ParsedMainComponentUtils.parseMainComponent(
- service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, input,
+ ParseResult<ParsedServiceImpl> result = ParsedMainComponentUtils.parseMainComponent(
+ service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, defaultSplitName,
+ input,
R.styleable.AndroidManifestService_banner,
R.styleable.AndroidManifestService_description,
R.styleable.AndroidManifestService_directBootAware,
@@ -73,7 +76,7 @@
);
if (result.isError()) {
- return result;
+ return input.error(result);
}
setExported = sa.hasValue(R.styleable.AndroidManifestService_exported);
@@ -138,7 +141,8 @@
parseResult = intentResult;
if (intentResult.isSuccess()) {
ParsedIntentInfo intent = intentResult.getResult();
- service.setOrder(Math.max(intent.getOrder(), service.getOrder()));
+ IntentFilter intentFilter = intent.getIntentFilter();
+ service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
service.addIntent(intent);
}
break;
diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java b/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java
index 020784d..e2f5f14 100644
--- a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java
@@ -16,17 +16,11 @@
package android.content.pm.parsing.component;
-import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
-import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -36,24 +30,14 @@
*
* @hide
*/
-@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
- genAidl = false)
-public class ParsedUsesPermission implements Parcelable {
-
- @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
- @NonNull
- private String name;
-
- @UsesPermissionFlags
- private int usesPermissionFlags;
+public interface ParsedUsesPermission extends Parcelable {
/**
* Strong assertion by a developer that they will never use this permission to derive the
* physical location of the device, regardless of ACCESS_FINE_LOCATION and/or
* ACCESS_COARSE_LOCATION being granted.
*/
- public static final int FLAG_NEVER_FOR_LOCATION =
- PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+ int FLAG_NEVER_FOR_LOCATION = PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
/**
* @hide
@@ -62,132 +46,11 @@
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_NEVER_FOR_LOCATION
})
- public @interface UsesPermissionFlags {}
+ @interface UsesPermissionFlags {}
+ @NonNull
+ String getName();
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- public ParsedUsesPermission(
- @NonNull String name,
- @UsesPermissionFlags int usesPermissionFlags) {
- this.name = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, name);
- this.usesPermissionFlags = usesPermissionFlags;
- com.android.internal.util.AnnotationValidations.validate(
- UsesPermissionFlags.class, null, usesPermissionFlags);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public @NonNull String getName() {
- return name;
- }
-
- @DataClass.Generated.Member
- public @UsesPermissionFlags int getUsesPermissionFlags() {
- return usesPermissionFlags;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedUsesPermission setName(@NonNull String value) {
- name = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, name);
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ParsedUsesPermission setUsesPermissionFlags(@UsesPermissionFlags int value) {
- usesPermissionFlags = value;
- com.android.internal.util.AnnotationValidations.validate(
- UsesPermissionFlags.class, null, usesPermissionFlags);
- return this;
- }
-
- @DataClass.Generated.Member
- static Parcelling<String> sParcellingForName =
- Parcelling.Cache.get(
- Parcelling.BuiltIn.ForInternedString.class);
- static {
- if (sParcellingForName == null) {
- sParcellingForName = Parcelling.Cache.put(
- new Parcelling.BuiltIn.ForInternedString());
- }
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- sParcellingForName.parcel(name, dest, flags);
- dest.writeInt(usesPermissionFlags);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected ParsedUsesPermission(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- String _name = sParcellingForName.unparcel(in);
- int _usesPermissionFlags = in.readInt();
-
- this.name = _name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, name);
- this.usesPermissionFlags = _usesPermissionFlags;
- com.android.internal.util.AnnotationValidations.validate(
- UsesPermissionFlags.class, null, usesPermissionFlags);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<ParsedUsesPermission> CREATOR
- = new Parcelable.Creator<ParsedUsesPermission>() {
- @Override
- public ParsedUsesPermission[] newArray(int size) {
- return new ParsedUsesPermission[size];
- }
-
- @Override
- public ParsedUsesPermission createFromParcel(@NonNull Parcel in) {
- return new ParsedUsesPermission(in);
- }
- };
-
- @DataClass.Generated(
- time = 1626207990753L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java",
- inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\npublic static final int FLAG_NEVER_FOR_LOCATION\nclass ParsedUsesPermission extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
+ @UsesPermissionFlags
+ int getUsesPermissionFlags();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java b/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java
new file mode 100644
index 0000000..d3c7afb
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+/**
+ * A {@link android.R.styleable#AndroidManifestUsesPermission
+ * <uses-permission>} tag parsed from the manifest.
+ *
+ * @hide
+ */
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
+ genAidl = false)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedUsesPermissionImpl implements ParsedUsesPermission {
+
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
+ @NonNull
+ private String name;
+
+ @ParsedUsesPermission.UsesPermissionFlags
+ private int usesPermissionFlags;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedUsesPermissionImpl(
+ @NonNull String name,
+ @ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags) {
+ this.name = name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.usesPermissionFlags = usesPermissionFlags;
+ com.android.internal.util.AnnotationValidations.validate(
+ ParsedUsesPermission.UsesPermissionFlags.class, null, usesPermissionFlags);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getName() {
+ return name;
+ }
+
+ @DataClass.Generated.Member
+ public @ParsedUsesPermission.UsesPermissionFlags int getUsesPermissionFlags() {
+ return usesPermissionFlags;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedUsesPermissionImpl setName(@NonNull String value) {
+ name = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ParsedUsesPermissionImpl setUsesPermissionFlags(@ParsedUsesPermission.UsesPermissionFlags int value) {
+ usesPermissionFlags = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ ParsedUsesPermission.UsesPermissionFlags.class, null, usesPermissionFlags);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<String> sParcellingForName =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForInternedString.class);
+ static {
+ if (sParcellingForName == null) {
+ sParcellingForName = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForInternedString());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ sParcellingForName.parcel(name, dest, flags);
+ dest.writeInt(usesPermissionFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedUsesPermissionImpl(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String _name = sParcellingForName.unparcel(in);
+ int _usesPermissionFlags = in.readInt();
+
+ this.name = _name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.usesPermissionFlags = _usesPermissionFlags;
+ com.android.internal.util.AnnotationValidations.validate(
+ ParsedUsesPermission.UsesPermissionFlags.class, null, usesPermissionFlags);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ParsedUsesPermissionImpl> CREATOR
+ = new Parcelable.Creator<ParsedUsesPermissionImpl>() {
+ @Override
+ public ParsedUsesPermissionImpl[] newArray(int size) {
+ return new ParsedUsesPermissionImpl[size];
+ }
+
+ @Override
+ public ParsedUsesPermissionImpl createFromParcel(@NonNull Parcel in) {
+ return new ParsedUsesPermissionImpl(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1627674645598L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java",
+ inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java b/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java
index 7370823..b70353a4 100644
--- a/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java
+++ b/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java
@@ -17,8 +17,11 @@
package android.content.pm.permission;
import android.Manifest;
+import android.annotation.NonNull;
import android.content.pm.parsing.component.ParsedUsesPermission;
+import com.android.internal.util.DataClass;
+
/**
* Implements compatibility support for permissions, and old applications
* will be automatically granted it.
@@ -32,9 +35,12 @@
*
* @hide
*/
-public class CompatibilityPermissionInfo extends ParsedUsesPermission {
+@DataClass(genGetters = true, genBuilder = false)
+public class CompatibilityPermissionInfo {
- public final int sdkVersion;
+ @NonNull
+ private final String mName;
+ private final int mSdkVersion;
/**
* List of new permissions that have been added since 1.0.
@@ -47,16 +53,60 @@
public static final CompatibilityPermissionInfo[] COMPAT_PERMS =
new CompatibilityPermissionInfo[]{
new CompatibilityPermissionInfo(Manifest.permission.POST_NOTIFICATIONS,
- android.os.Build.VERSION_CODES.TIRAMISU, 0 /*usesPermissionFlags*/),
+ android.os.Build.VERSION_CODES.TIRAMISU),
new CompatibilityPermissionInfo(Manifest.permission.WRITE_EXTERNAL_STORAGE,
- android.os.Build.VERSION_CODES.DONUT, 0 /*usesPermissionFlags*/),
+ android.os.Build.VERSION_CODES.DONUT),
new CompatibilityPermissionInfo(Manifest.permission.READ_PHONE_STATE,
- android.os.Build.VERSION_CODES.DONUT, 0 /*usesPermissionFlags*/)
+ android.os.Build.VERSION_CODES.DONUT)
};
- private CompatibilityPermissionInfo(String name, int sdkVersion,
- @UsesPermissionFlags int usesPermissionFlags) {
- super(name, usesPermissionFlags);
- this.sdkVersion = sdkVersion;
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public CompatibilityPermissionInfo(
+ @NonNull String name,
+ int sdkVersion) {
+ this.mName = name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mName);
+ this.mSdkVersion = sdkVersion;
+
+ // onConstructed(); // You can define this method to get a callback
}
+
+ @DataClass.Generated.Member
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ @DataClass.Generated.Member
+ public int getSdkVersion() {
+ return mSdkVersion;
+ }
+
+ @DataClass.Generated(
+ time = 1627674427184L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mName\nprivate final int mSdkVersion\npublic static final android.content.pm.permission.CompatibilityPermissionInfo[] COMPAT_PERMS\nclass CompatibilityPermissionInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index b66f048..8ebb8ec 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1736,35 +1736,9 @@
* object and the given one. Does not change the values of either. Any
* undefined fields in <var>delta</var> are ignored.
* @return Returns a bit mask indicating which configuration
- * values has changed, containing any combination of
- * {@link android.content.pm.ActivityInfo#CONFIG_FONT_SCALE
- * PackageManager.ActivityInfo.CONFIG_FONT_SCALE},
- * {@link android.content.pm.ActivityInfo#CONFIG_MCC
- * PackageManager.ActivityInfo.CONFIG_MCC},
- * {@link android.content.pm.ActivityInfo#CONFIG_MNC
- * PackageManager.ActivityInfo.CONFIG_MNC},
- * {@link android.content.pm.ActivityInfo#CONFIG_LOCALE
- * PackageManager.ActivityInfo.CONFIG_LOCALE},
- * {@link android.content.pm.ActivityInfo#CONFIG_TOUCHSCREEN
- * PackageManager.ActivityInfo.CONFIG_TOUCHSCREEN},
- * {@link android.content.pm.ActivityInfo#CONFIG_KEYBOARD
- * PackageManager.ActivityInfo.CONFIG_KEYBOARD},
- * {@link android.content.pm.ActivityInfo#CONFIG_NAVIGATION
- * PackageManager.ActivityInfo.CONFIG_NAVIGATION},
- * {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION
- * PackageManager.ActivityInfo.CONFIG_ORIENTATION},
- * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_LAYOUT
- * PackageManager.ActivityInfo.CONFIG_SCREEN_LAYOUT}, or
- * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE
- * PackageManager.ActivityInfo.CONFIG_SCREEN_SIZE}, or
- * {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE
- * PackageManager.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE}.
- * {@link android.content.pm.ActivityInfo#CONFIG_LAYOUT_DIRECTION
- * PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}.
- * {@link android.content.pm.ActivityInfo#CONFIG_FONT_WEIGHT_ADJUSTMENT
- * PackageManager.ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT.
+ * values have changed.
*/
- public int diff(Configuration delta) {
+ public @Config int diff(Configuration delta) {
return diff(delta, false /* compareUndefined */, false /* publicOnly */);
}
@@ -2620,10 +2594,10 @@
* {@link #updateFrom(Configuration)} will treat it as a no-op and not update that member.
*
* This is fine for device configurations as no member is ever undefined.
- * {@hide}
*/
- @UnsupportedAppUsage
- public static Configuration generateDelta(Configuration base, Configuration change) {
+ @NonNull
+ public static Configuration generateDelta(
+ @NonNull Configuration base, @NonNull Configuration change) {
final Configuration delta = new Configuration();
if (base.fontScale != change.fontScale) {
delta.fontScale = change.fontScale;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 12e41e2..a6f2e40 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -27,6 +27,7 @@
import android.annotation.ColorInt;
import android.annotation.ColorRes;
import android.annotation.DimenRes;
+import android.annotation.Discouraged;
import android.annotation.DrawableRes;
import android.annotation.FontRes;
import android.annotation.FractionRes;
@@ -1466,6 +1467,12 @@
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
*/
+ @Discouraged(message = "Use of this function is discouraged because it makes internal calls to "
+ + "`getIdentifier()`, which uses resource reflection. Reflection makes it "
+ + "harder to perform build optimizations and compile-time verification of "
+ + "code. It is much more efficient to retrieve resource values by "
+ + "identifier (e.g. `getValue(R.foo.bar, outValue, true)`) than by name "
+ + "(e.g. `getValue(\"foo\", outvalue, true)`).")
public void getValue(String name, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
mResourcesImpl.getValue(name, outValue, resolveRefs);
@@ -2198,6 +2205,11 @@
* @return int The associated resource identifier. Returns 0 if no such
* resource was found. (0 is not a valid resource ID.)
*/
+ @Discouraged(message = "Use of this function is discouraged because resource reflection makes "
+ + "it harder to perform build optimizations and compile-time "
+ + "verification of code. It is much more efficient to retrieve "
+ + "resources by identifier (e.g. `R.foo.bar`) than by name (e.g. "
+ + "`getIdentifier(\"bar\", \"foo\", null)`).")
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ddac22c..afea4e5 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -22,7 +22,7 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
@@ -258,11 +258,12 @@
private <T> T overrideProperty(Key<T> key) {
if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
(mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
- DeviceStateOrientationMap deviceStateOrientationMap =
- mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP);
+ DeviceStateSensorOrientationMap deviceStateSensorOrientationMap =
+ mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP);
synchronized (mLock) {
- Integer ret = deviceStateOrientationMap.getSensorOrientation(mFoldedDeviceState ?
- DeviceStateOrientationMap.FOLDED : DeviceStateOrientationMap.NORMAL);
+ Integer ret = deviceStateSensorOrientationMap.getSensorOrientation(
+ mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED :
+ DeviceStateSensorOrientationMap.NORMAL);
if (ret >= 0) {
return (T) ret;
} else {
@@ -1319,6 +1320,42 @@
new Key<Boolean>("android.flash.info.available", boolean.class);
/**
+ * <p>Maximum flashlight brightness level.</p>
+ * <p>If this value is greater than 1, then the device supports controlling the
+ * flashlight brightness level via
+ * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.
+ * If this value is equal to 1, flashlight brightness control is not supported.
+ * This value will be -1 if the flash unit is not available.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> FLASH_INFO_STRENGTH_MAXIMUM_LEVEL =
+ new Key<Integer>("android.flash.info.strengthMaximumLevel", int.class);
+
+ /**
+ * <p>Default flashlight brightness level to be set via
+ * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.</p>
+ * <p>If flash unit is available this will be greater than or equal to 1 and less
+ * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.
+ * If flash unit is not available this will be set to -1.</p>
+ * <p>Setting flashlight brightness above the default level
+ * (i.e.<code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL android.flash.info.strengthDefaultLevel}</code>) may make the device more
+ * likely to reach thermal throttling conditions and slow down, or drain the
+ * battery quicker than normal. To minimize such issues, it is recommended to
+ * start the flashlight at this default brightness until a user explicitly requests
+ * a brighter level.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL
+ * @see CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> FLASH_INFO_STRENGTH_DEFAULT_LEVEL =
+ new Key<Integer>("android.flash.info.strengthDefaultLevel", int.class);
+
+ /**
* <p>List of hot pixel correction modes for {@link CaptureRequest#HOT_PIXEL_MODE android.hotPixel.mode} that are supported by this
* camera device.</p>
* <p>FULL mode camera devices will always support FAST.</p>
@@ -4056,7 +4093,7 @@
* Clients are advised to not cache or store the orientation value of such logical sensors.
* In case repeated queries to CameraCharacteristics are not preferred, then clients can
* also access the entire mapping from device state to sensor orientation in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Do note that a dynamically changing sensor orientation value in camera characteristics
* will not be the best way to establish the orientation per frame. Clients that want to
* know the sensor orientation of a particular captured frame should query the
@@ -4384,7 +4421,7 @@
* values. The orientation value may need to change depending on the specific folding
* state. Information about the mapping between the device folding state and the
* sensor orientation can be obtained in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Device state orientation maps are optional and maybe present on devices that support
* {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -4398,8 +4435,8 @@
@PublicKey
@NonNull
@SyntheticKey
- public static final Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP =
- new Key<android.hardware.camera2.params.DeviceStateOrientationMap>("android.info.deviceStateOrientationMap", android.hardware.camera2.params.DeviceStateOrientationMap.class);
+ public static final Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP =
+ new Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap>("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class);
/**
* <p>HAL must populate the array with
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 2e86a8b..93f1d61 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,7 +31,6 @@
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraInjectionSessionImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
@@ -118,8 +117,16 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mFoldStateListener = new FoldStateListener(context);
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ try {
+ context.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ } catch (IllegalStateException e) {
+ Log.v(TAG, "Failed to register device state listener!");
+ Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mFoldStateListener = null;
+ }
}
private HandlerThread mHandlerThread;
@@ -185,7 +192,9 @@
synchronized (mLock) {
DeviceStateListener listener = chars.getDeviceStateListener();
listener.onDeviceStateChanged(mFoldedDeviceState);
- mDeviceStateListeners.add(new WeakReference<>(listener));
+ if (mFoldStateListener != null) {
+ mDeviceStateListeners.add(new WeakReference<>(listener));
+ }
}
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 0276815..08276ac 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1022,8 +1022,7 @@
* <p>which define a transform from input sensor colors, <code>P_in = [ r g b ]</code>,
* to output linear sRGB, <code>P_out = [ r' g' b' ]</code>,</p>
* <p>with colors as follows:</p>
- * <pre><code>
- * r' = I0r + I1g + I2b
+ * <pre><code>r' = I0r + I1g + I2b
* g' = I3r + I4g + I5b
* b' = I6r + I7g + I8b
* </code></pre>
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 3745022..e393a66 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -50,11 +50,10 @@
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Capability;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
-import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.OisSample;
@@ -763,7 +762,7 @@
}
});
sGetCommandMap.put(
- CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP.getNativeKey(),
+ CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(),
new GetCommand() {
@Override
@SuppressWarnings("unchecked")
@@ -1004,7 +1003,7 @@
return map;
}
- private DeviceStateOrientationMap getDeviceStateOrientationMap() {
+ private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() {
long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);
// Do not warn if map is null while s is not. This is valid.
@@ -1012,7 +1011,7 @@
return null;
}
- DeviceStateOrientationMap map = new DeviceStateOrientationMap(mapArray);
+ DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray);
return map;
}
diff --git a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
similarity index 90%
rename from core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
rename to core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index 3907f04..200409e 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -40,7 +40,7 @@
*
* @see CameraCharacteristics#SENSOR_ORIENTATION
*/
-public final class DeviceStateOrientationMap {
+public final class DeviceStateSensorOrientationMap {
/**
* Needs to be kept in sync with the HIDL/AIDL DeviceState
*/
@@ -85,10 +85,10 @@
*
* @hide
*/
- public DeviceStateOrientationMap(final long[] elements) {
+ public DeviceStateSensorOrientationMap(final long[] elements) {
mElements = Objects.requireNonNull(elements, "elements must not be null");
if ((elements.length % 2) != 0) {
- throw new IllegalArgumentException("Device state orientation map length " +
+ throw new IllegalArgumentException("Device state sensor orientation map length " +
elements.length + " is not even!");
}
@@ -121,7 +121,8 @@
}
/**
- * Check if this DeviceStateOrientationMap is equal to another DeviceStateOrientationMap.
+ * Check if this DeviceStateSensorOrientationMap is equal to another
+ * DeviceStateSensorOrientationMap.
*
* <p>Two device state orientation maps are equal if and only if all of their elements are
* {@link Object#equals equal}.</p>
@@ -136,8 +137,8 @@
if (this == obj) {
return true;
}
- if (obj instanceof DeviceStateOrientationMap) {
- final DeviceStateOrientationMap other = (DeviceStateOrientationMap) obj;
+ if (obj instanceof DeviceStateSensorOrientationMap) {
+ final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj;
return Arrays.equals(mElements, other.mElements);
}
return false;
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index f5b2ac5..2b52e96 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -22,11 +22,9 @@
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.Arrays;
import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
/**
* AmbientDisplayConfiguration encapsulates reading access to the configuration of ambient display.
@@ -90,7 +88,12 @@
/** {@hide} */
public boolean tapSensorAvailable() {
- return !TextUtils.isEmpty(tapSensorType());
+ for (String tapType : tapSensorTypeMapping()) {
+ if (!TextUtils.isEmpty(tapType)) {
+ return true;
+ }
+ }
+ return false;
}
/** {@hide} */
@@ -143,18 +146,18 @@
return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType);
}
- /** {@hide} */
- private String tapSensorType() {
- return mContext.getResources().getString(R.string.config_dozeTapSensorType);
- }
-
- /** {@hide} */
- public String tapSensorType(int posture) {
- return getSensorFromPostureMapping(
- mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping),
- tapSensorType(),
- posture
- );
+ /** {@hide}
+ * May support multiple postures.
+ */
+ public String[] tapSensorTypeMapping() {
+ String[] postureMapping =
+ mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping);
+ if (ArrayUtils.isEmpty(postureMapping)) {
+ return new String[] {
+ mContext.getResources().getString(R.string.config_dozeTapSensorType)
+ };
+ }
+ return postureMapping;
}
/** {@hide} */
@@ -253,20 +256,4 @@
private boolean boolSetting(String name, int user, int def) {
return Settings.Secure.getIntForUser(mContext.getContentResolver(), name, def, user) != 0;
}
-
- /** {@hide} */
- public static String getSensorFromPostureMapping(
- String[] postureMapping,
- String defaultValue,
- int posture) {
- String sensorType = defaultValue;
- if (postureMapping != null && posture < postureMapping.length) {
- sensorType = postureMapping[posture];
- } else {
- Log.e(TAG, "Unsupported doze posture " + posture
- + " postureMapping=" + Arrays.toString(postureMapping));
- }
-
- return TextUtils.isEmpty(sensorType) ? defaultValue : sensorType;
- }
}
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index 1a52f11..cee6a5b 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -63,6 +63,9 @@
if (listener == null) {
throw new IllegalArgumentException("listener must not be null.");
}
+ if (executor == null) {
+ throw new IllegalArgumentException("executor must not be null.");
+ }
try {
mService.deviceSelect(logicalAddress,
getCallbackWrapper(logicalAddress, executor, listener));
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index dac1b49..5874385 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -115,8 +115,6 @@
public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
- /** @hide */
- @SystemApi
@IntDef ({
RESULT_SUCCESS,
RESULT_TIMEOUT,
@@ -297,19 +295,19 @@
/**
* HDMI CEC enabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_ENABLED
*/
- @SystemApi
public static final int HDMI_CEC_CONTROL_ENABLED = 1;
/**
* HDMI CEC disabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_ENABLED
*/
- @SystemApi
public static final int HDMI_CEC_CONTROL_DISABLED = 0;
/**
* @hide
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_ENABLED
*/
@IntDef(prefix = { "HDMI_CEC_CONTROL_" }, value = {
HDMI_CEC_CONTROL_ENABLED,
@@ -322,18 +320,17 @@
/**
* Version constant for HDMI-CEC v1.4b.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_VERSION
*/
- @SystemApi
public static final int HDMI_CEC_VERSION_1_4_B = 0x05;
/**
* Version constant for HDMI-CEC v2.0.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_VERSION
*/
- @SystemApi
public static final int HDMI_CEC_VERSION_2_0 = 0x06;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_VERSION
* @hide
*/
@IntDef(prefix = { "HDMI_CEC_VERSION_" }, value = {
@@ -347,18 +344,17 @@
/**
* Routing Control feature enabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_ROUTING_CONTROL
*/
- @SystemApi
public static final int ROUTING_CONTROL_ENABLED = 1;
/**
* Routing Control feature disabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_ROUTING_CONTROL
*/
- @SystemApi
public static final int ROUTING_CONTROL_DISABLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_ROUTING_CONTROL
* @hide
*/
@IntDef(prefix = { "ROUTING_CONTROL_" }, value = {
@@ -375,9 +371,8 @@
* Upon waking up, attempt to turn on the TV via {@code <One Touch Play>} but do not turn on the
* Audio system via {@code <System Audio Mode Request>}.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
*/
- @SystemApi
public static final String POWER_CONTROL_MODE_TV = "to_tv";
/**
* Send CEC power control messages to TV and Audio System:
@@ -385,9 +380,8 @@
* Upon waking up, attempt to turn on the TV via {@code <One Touch Play>} and attempt to turn on
* the Audio system via {@code <System Audio Mode Request>}.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
*/
- @SystemApi
public static final String POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM = "to_tv_and_audio_system";
/**
* Broadcast CEC power control messages to all devices in the network:
@@ -395,9 +389,8 @@
* Upon waking up, attempt to turn on the TV via {@code <One Touch Play>} and attempt to turn on
* the Audio system via {@code <System Audio Mode Request>}.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
*/
- @SystemApi
public static final String POWER_CONTROL_MODE_BROADCAST = "broadcast";
/**
* Don't send any CEC power control messages:
@@ -405,11 +398,11 @@
* Upon waking up, do not turn on the TV via {@code <One Touch Play>} and do not turn on the
* Audio system via {@code <System Audio Mode Request>}.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
*/
- @SystemApi
public static final String POWER_CONTROL_MODE_NONE = "none";
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
* @hide
*/
@StringDef(prefix = { "POWER_CONTROL_MODE_" }, value = {
@@ -425,18 +418,17 @@
/**
* No action to be taken.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST
*/
- @SystemApi
public static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE = "none";
/**
* Go to standby immediately.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST
*/
- @SystemApi
public static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW = "standby_now";
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST
* @hide
*/
@StringDef(prefix = { "POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_" }, value = {
@@ -450,18 +442,17 @@
/**
* System Audio Control enabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL
*/
- @SystemApi
public static final int SYSTEM_AUDIO_CONTROL_ENABLED = 1;
/**
* System Audio Control disabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL
*/
- @SystemApi
public static final int SYSTEM_AUDIO_CONTROL_DISABLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL
* @hide
*/
@IntDef(prefix = { "SYSTEM_AUDIO_CONTROL_" }, value = {
@@ -475,18 +466,17 @@
/**
* System Audio Mode muting enabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING
*/
- @SystemApi
public static final int SYSTEM_AUDIO_MODE_MUTING_ENABLED = 1;
/**
* System Audio Mode muting disabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING
*/
- @SystemApi
public static final int SYSTEM_AUDIO_MODE_MUTING_DISABLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING
* @hide
*/
@IntDef(prefix = { "SYSTEM_AUDIO_MODE_MUTING_" }, value = {
@@ -501,17 +491,13 @@
* HDMI CEC enabled.
*
* @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
- * @hide
*/
- @SystemApi
public static final int VOLUME_CONTROL_ENABLED = 1;
/**
* HDMI CEC disabled.
*
* @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
- * @hide
*/
- @SystemApi
public static final int VOLUME_CONTROL_DISABLED = 0;
/**
* @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
@@ -528,18 +514,17 @@
/**
* TV Wake on One Touch Play enabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY
*/
- @SystemApi
public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED = 1;
/**
* TV Wake on One Touch Play disabled.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY
*/
- @SystemApi
public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY
* @hide
*/
@IntDef(prefix = { "TV_WAKE_ON_ONE_TOUCH_PLAY_" }, value = {
@@ -553,18 +538,17 @@
/**
* Sending <Standby> on sleep.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP
*/
- @SystemApi
public static final int TV_SEND_STANDBY_ON_SLEEP_ENABLED = 1;
/**
* Not sending <Standby> on sleep.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP
*/
- @SystemApi
public static final int TV_SEND_STANDBY_ON_SLEEP_DISABLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP
* @hide
*/
@IntDef(prefix = { "TV_SEND_STANDBY_ON_SLEEP_" }, value = {
@@ -578,34 +562,40 @@
/**
* RC profile none.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_TV
* @hide
*/
public static final int RC_PROFILE_TV_NONE = 0x0;
/**
* RC profile 1.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_TV
* @hide
*/
public static final int RC_PROFILE_TV_ONE = 0x2;
/**
* RC profile 2.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_TV
* @hide
*/
public static final int RC_PROFILE_TV_TWO = 0x6;
/**
* RC profile 3.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_TV
* @hide
*/
public static final int RC_PROFILE_TV_THREE = 0xA;
/**
* RC profile 4.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_TV
* @hide
*/
public static final int RC_PROFILE_TV_FOUR = 0xE;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_TV
* @hide
*/
@IntDef(prefix = { "RC_PROFILE_TV_" }, value = {
@@ -618,175 +608,157 @@
@Retention(RetentionPolicy.SOURCE)
public @interface RcProfileTv {}
- // -- RC profile parameter defining if a source handles the root menu.
+ // -- RC profile parameter defining if a source handles a specific menu.
/**
- * Handles the root menu.
+ * Handles the menu.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU
+ * @see HdmiControlManager#
+ * CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU
* @hide
*/
- public static final int RC_PROFILE_SOURCE_ROOT_MENU_HANDLED = 1;
+ public static final int RC_PROFILE_SOURCE_MENU_HANDLED = 1;
/**
- * Doesn't handle the root menu.
+ * Doesn't handle the menu.
*
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU
+ * @see HdmiControlManager#
+ * CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU
* @hide
*/
- public static final int RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED = 0;
+ public static final int RC_PROFILE_SOURCE_MENU_NOT_HANDLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU
+ * @see HdmiControlManager#CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU
+ * @see HdmiControlManager#
+ * CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU
* @hide
*/
- @IntDef(prefix = { "RC_PROFILE_SOURCE_ROOT_MENU_" }, value = {
- RC_PROFILE_SOURCE_ROOT_MENU_HANDLED,
- RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED
+ @IntDef(prefix = { "RC_PROFILE_SOURCE_MENU_" }, value = {
+ RC_PROFILE_SOURCE_MENU_HANDLED,
+ RC_PROFILE_SOURCE_MENU_NOT_HANDLED
})
@Retention(RetentionPolicy.SOURCE)
- public @interface RcProfileSourceHandlesRootMenu {}
+ public @interface RcProfileSourceHandlesMenu {}
- // -- RC profile parameter defining if a source handles the setup menu.
+ // -- Whether the Short Audio Descriptor (SAD) for a specific codec should be queried or not.
/**
- * Handles the setup menu.
+ * Query the SAD.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_LPCM
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MPEG1
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MP3
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MPEG2
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_AAC
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DTS
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_ATRAC
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DDP
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DTSHD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_TRUEHD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DST
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_WMAPRO
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MAX
*/
- public static final int RC_PROFILE_SOURCE_SETUP_MENU_HANDLED = 1;
+ public static final int QUERY_SAD_ENABLED = 1;
/**
- * Doesn't handle the setup menu.
+ * Don't query the SAD.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_LPCM
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MPEG1
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MP3
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MPEG2
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_AAC
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DTS
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_ATRAC
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DDP
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DTSHD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_TRUEHD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DST
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_WMAPRO
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MAX
*/
- public static final int RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED = 0;
+ public static final int QUERY_SAD_DISABLED = 0;
/**
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_LPCM
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MPEG1
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MP3
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MPEG2
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_AAC
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DTS
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_ATRAC
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DDP
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DTSHD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_TRUEHD
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_DST
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_WMAPRO
+ * @see HdmiControlManager#CEC_SETTING_NAME_QUERY_SAD_MAX
* @hide
*/
- @IntDef(prefix = { "RC_PROFILE_SOURCE_SETUP_MENU_" }, value = {
- RC_PROFILE_SOURCE_SETUP_MENU_HANDLED,
- RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED
+ @IntDef(prefix = { "QUERY_SAD_" }, value = {
+ QUERY_SAD_ENABLED,
+ QUERY_SAD_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
- public @interface RcProfileSourceHandlesSetupMenu {}
-
-
- // -- RC profile parameter defining if a source handles the contents menu.
- /**
- * Handles the contents menu.
- *
- * @hide
- */
- public static final int RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED = 1;
- /**
- * Doesn't handle the contents menu.
- *
- * @hide
- */
- public static final int RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED = 0;
- /**
- * @hide
- */
- @IntDef(prefix = { "RC_PROFILE_SOURCE_CONTENTS_MENU_" }, value = {
- RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED,
- RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RcProfileSourceHandlesContentsMenu {}
-
-
- // -- RC profile parameter defining if a source handles the top menu.
- /**
- * Handles the top menu.
- *
- * @hide
- */
- public static final int RC_PROFILE_SOURCE_TOP_MENU_HANDLED = 1;
- /**
- * Doesn't handle the top menu.
- *
- * @hide
- */
- public static final int RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED = 0;
- /**
- * @hide
- */
- @IntDef(prefix = { "RC_PROFILE_SOURCE_TOP_MENU_" }, value = {
- RC_PROFILE_SOURCE_TOP_MENU_HANDLED,
- RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RcProfileSourceHandlesTopMenu {}
-
-
- // -- RC profile parameter defining if a source handles the media context sensitive menu.
- /**
- * Handles the media context sensitive menu.
- *
- * @hide
- */
- public static final int RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED = 1;
- /**
- * Doesn't handle the media context sensitive menu.
- *
- * @hide
- */
- public static final int RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED = 0;
- /**
- * @hide
- */
- @IntDef(prefix = { "RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_" }, value = {
- RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED,
- RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RcProfileSourceHandlesMediaContextSensitiveMenu {}
+ public @interface SadPresenceInQuery {}
// -- Settings available in the CEC Configuration.
/**
* Name of a setting deciding whether the CEC is enabled.
*
- * @hide
+ * @see HdmiControlManager#setHdmiCecEnabled(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_HDMI_CEC_ENABLED = "hdmi_cec_enabled";
/**
* Name of a setting controlling the version of HDMI-CEC used.
*
- * @hide
+ * @see HdmiControlManager#setHdmiCecVersion(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_HDMI_CEC_VERSION = "hdmi_cec_version";
/**
* Name of a setting deciding whether the Routing Control feature is enabled.
*
- * @hide
+ * @see HdmiControlManager#setRoutingControl(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
/**
* Name of a setting deciding on the power control mode.
*
- * @hide
+ * @see HdmiControlManager#setPowerControlMode(String)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_POWER_CONTROL_MODE = "power_control_mode";
/**
* Name of a setting deciding on power state action when losing Active Source.
*
- * @hide
+ * @see HdmiControlManager#setPowerStateChangeOnActiveSourceLost(String)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST =
"power_state_change_on_active_source_lost";
/**
* Name of a setting deciding whether System Audio Control is enabled.
*
- * @hide
+ * @see HdmiControlManager#setSystemAudioControl(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL =
"system_audio_control";
/**
* Name of a setting deciding whether System Audio Muting is allowed.
*
- * @hide
+ * @see HdmiControlManager#setSystemAudioModeMuting(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING =
"system_audio_mode_muting";
/**
@@ -819,28 +791,24 @@
*
* <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
*
- * @hide
- * @see android.hardware.hdmi.HdmiControlManager#setHdmiCecVolumeControlEnabled(int)
+ * @see HdmiControlManager#setHdmiCecVolumeControlEnabled(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_VOLUME_CONTROL_MODE =
"volume_control_enabled";
/**
* Name of a setting deciding whether the TV will automatically turn on upon reception
* of the CEC command <Text View On> or <Image View On>.
*
- * @hide
+ * @see HdmiControlManager#setTvWakeOnOneTouchPlay(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY =
"tv_wake_on_one_touch_play";
/**
* Name of a setting deciding whether the TV will also turn off other CEC devices
* when it goes to standby mode.
*
- * @hide
+ * @see HdmiControlManager#setTvSendStandbyOnSleep(int)
*/
- @SystemApi
public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
"tv_send_standby_on_sleep";
/**
@@ -892,6 +860,111 @@
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU =
"rc_profile_source_handles_media_context_sensitive_menu";
/**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the LPCM codec
+ * (0x1) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_LPCM = "query_sad_lpcm";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the DD codec
+ * (0x2) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_DD = "query_sad_dd";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the MPEG1 codec
+ * (0x3) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_MPEG1 = "query_sad_mpeg1";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the MP3 codec
+ * (0x4) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_MP3 = "query_sad_mp3";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the MPEG2 codec
+ * (0x5) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_MPEG2 = "query_sad_mpeg2";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the AAC codec
+ * (0x6) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_AAC = "query_sad_aac";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the DTS codec
+ * (0x7) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_DTS = "query_sad_dts";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the ATRAC codec
+ * (0x8) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_ATRAC = "query_sad_atrac";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the ONEBITAUDIO
+ * codec (0x9) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO = "query_sad_onebitaudio";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the DDP codec
+ * (0xA) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_DDP = "query_sad_ddp";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the DTSHD codec
+ * (0xB) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_DTSHD = "query_sad_dtshd";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the TRUEHD codec
+ * (0xC) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_TRUEHD = "query_sad_truehd";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the DST codec
+ * (0xD) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_DST = "query_sad_dst";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the WMAPRO codec
+ * (0xE) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_WMAPRO = "query_sad_wmapro";
+ /**
+ * Name of a setting representing whether the Short Audio Descriptor (SAD) for the MAX codec
+ * (0xF) should be queried or not.
+ *
+ * @see HdmiControlManager#setSadPresenceInQuery(String, int)
+ */
+ public static final String CEC_SETTING_NAME_QUERY_SAD_MAX = "query_sad_max";
+ /**
* @hide
*/
@StringDef(prefix = { "CEC_SETTING_NAME_" }, value = {
@@ -910,9 +983,46 @@
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+ CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ CEC_SETTING_NAME_QUERY_SAD_DD,
+ CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ CEC_SETTING_NAME_QUERY_SAD_MP3,
+ CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ CEC_SETTING_NAME_QUERY_SAD_AAC,
+ CEC_SETTING_NAME_QUERY_SAD_DTS,
+ CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ CEC_SETTING_NAME_QUERY_SAD_DDP,
+ CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ CEC_SETTING_NAME_QUERY_SAD_DST,
+ CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ CEC_SETTING_NAME_QUERY_SAD_MAX,
})
public @interface CecSettingName {}
+ /**
+ * @hide
+ */
+ @StringDef(prefix = { "CEC_SETTING_NAME_QUERY_SAD_" }, value = {
+ CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ CEC_SETTING_NAME_QUERY_SAD_DD,
+ CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ CEC_SETTING_NAME_QUERY_SAD_MP3,
+ CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ CEC_SETTING_NAME_QUERY_SAD_AAC,
+ CEC_SETTING_NAME_QUERY_SAD_DTS,
+ CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ CEC_SETTING_NAME_QUERY_SAD_DDP,
+ CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ CEC_SETTING_NAME_QUERY_SAD_DST,
+ CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ CEC_SETTING_NAME_QUERY_SAD_MAX,
+ })
+ public @interface CecSettingSad {}
+
// True if we have a logical device of type playback hosted in the system.
private final boolean mHasPlaybackDevice;
// True if we have a logical device of type TV hosted in the system.
@@ -966,11 +1076,8 @@
* See {@link HdmiDeviceInfo#DEVICE_PLAYBACK}
* See {@link HdmiDeviceInfo#DEVICE_TV}
* See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}
- *
- * @hide
*/
@Nullable
- @SystemApi
@SuppressLint("RequiresPermission")
public HdmiClient getClient(int type) {
if (mService == null) {
@@ -999,11 +1106,8 @@
* system if the system is configured to host more than one type of HDMI-CEC logical devices.
*
* @return {@link HdmiPlaybackClient} instance. {@code null} on failure.
- *
- * @hide
*/
@Nullable
- @SystemApi
@SuppressLint("RequiresPermission")
public HdmiPlaybackClient getPlaybackClient() {
return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK);
@@ -1017,11 +1121,8 @@
* system if the system is configured to host more than one type of HDMI-CEC logical devices.
*
* @return {@link HdmiTvClient} instance. {@code null} on failure.
- *
- * @hide
*/
@Nullable
- @SystemApi
@SuppressLint("RequiresPermission")
public HdmiTvClient getTvClient() {
return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV);
@@ -1067,11 +1168,8 @@
*
* @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An
* empty list will be returned if there is none.
- *
- * @hide
*/
@NonNull
- @SystemApi
public List<HdmiDeviceInfo> getConnectedDevices() {
try {
return mService.getDeviceList();
@@ -1082,11 +1180,9 @@
/**
* @removed
- * @hide
* @deprecated Please use {@link #getConnectedDevices()} instead.
*/
@Deprecated
- @SystemApi
public List<HdmiDeviceInfo> getConnectedDevicesList() {
try {
return mService.getDeviceList();
@@ -1102,10 +1198,7 @@
* <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
*
* @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
- *
- * @hide
*/
- @SystemApi
public void powerOffDevice(@NonNull HdmiDeviceInfo deviceInfo) {
Objects.requireNonNull(deviceInfo);
try {
@@ -1118,11 +1211,9 @@
/**
* @removed
- * @hide
* @deprecated Please use {@link #powerOffDevice(deviceInfo)} instead.
*/
@Deprecated
- @SystemApi
public void powerOffRemoteDevice(@NonNull HdmiDeviceInfo deviceInfo) {
Objects.requireNonNull(deviceInfo);
try {
@@ -1155,11 +1246,9 @@
/**
* @removed
- * @hide
* @deprecated Please use {@link #powerOnDevice(deviceInfo)} instead.
*/
@Deprecated
- @SystemApi
public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
Objects.requireNonNull(deviceInfo);
try {
@@ -1180,10 +1269,7 @@
* streaming on their TVs.
*
* @param deviceInfo HdmiDeviceInfo of the target device
- *
- * @hide
*/
- @SystemApi
public void setActiveSource(@NonNull HdmiDeviceInfo deviceInfo) {
Objects.requireNonNull(deviceInfo);
try {
@@ -1195,11 +1281,9 @@
/**
* @removed
- * @hide
* @deprecated Please use {@link #setActiveSource(deviceInfo)} instead.
*/
@Deprecated
- @SystemApi
public void requestRemoteDeviceToBecomeActiveSource(@NonNull HdmiDeviceInfo deviceInfo) {
Objects.requireNonNull(deviceInfo);
try {
@@ -1292,9 +1376,7 @@
*
* @param hdmiCecVolumeControlEnabled target state of HDMI CEC volume control.
* @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setHdmiCecVolumeControlEnabled(
@VolumeControl int hdmiCecVolumeControlEnabled) {
@@ -1308,9 +1390,9 @@
/**
* Returns whether volume changes via HDMI CEC are enabled.
- * @hide
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@VolumeControl
public int getHdmiCecVolumeControlEnabled() {
@@ -1340,10 +1422,7 @@
* <p>Physical address needs to be automatically adjusted when devices are phyiscally or
* electrically added or removed from the device tree. Please see HDMI Specification Version
* 1.4b 8.7 Physical Address for more details on the address discovery proccess.
- *
- * @hide
*/
- @SystemApi
public int getPhysicalAddress() {
try {
return mService.getPhysicalAddress();
@@ -1360,10 +1439,7 @@
* @param targetDevice {@link HdmiDeviceInfo} of the target device.
* @return true if {@code targetDevice} is directly or indirectly
* connected to the current device.
- *
- * @hide
*/
- @SystemApi
public boolean isDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
Objects.requireNonNull(targetDevice);
int physicalAddress = getPhysicalAddress();
@@ -1380,11 +1456,9 @@
/**
* @removed
- * @hide
* @deprecated Please use {@link #isDeviceConnected(targetDevice)} instead.
*/
@Deprecated
- @SystemApi
public boolean isRemoteDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
Objects.requireNonNull(targetDevice);
int physicalAddress = getPhysicalAddress();
@@ -1401,10 +1475,7 @@
/**
* Listener used to get hotplug event from HDMI port.
- *
- * @hide
*/
- @SystemApi
public interface HotplugEventListener {
void onReceived(HdmiHotplugEvent event);
}
@@ -1422,7 +1493,8 @@
* Called when HDMI Control (CEC) is enabled/disabled.
*
* @param isCecEnabled status of HDMI Control
- * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+ * {@link android.hardware.hdmi.HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_ENABLED}:
+ * {@code HDMI_CEC_CONTROL_ENABLED} if enabled.
* @param isCecAvailable status of CEC support of the connected display (the TV).
* {@code true} if supported.
*
@@ -1455,10 +1527,7 @@
/**
* Listener used to get vendor-specific commands.
- *
- * @hide
*/
- @SystemApi
public interface VendorCommandListener {
/**
* Called when a vendor command is received.
@@ -1502,10 +1571,7 @@
*
* @param listener {@link HotplugEventListener} instance
* @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void addHotplugEventListener(HotplugEventListener listener) {
addHotplugEventListener(ConcurrentUtils.DIRECT_EXECUTOR, listener);
@@ -1519,10 +1585,7 @@
*
* @param listener {@link HotplugEventListener} instance
* @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void addHotplugEventListener(@NonNull @CallbackExecutor Executor executor,
@NonNull HotplugEventListener listener) {
@@ -1548,10 +1611,7 @@
* Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
*
* @param listener {@link HotplugEventListener} instance to be removed
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void removeHotplugEventListener(HotplugEventListener listener) {
if (mService == null) {
@@ -1760,10 +1820,7 @@
/**
* Listener used to get setting change notification.
- *
- * @hide
*/
- @SystemApi
public interface CecSettingChangeListener {
/**
* Called when value of a setting changes.
@@ -1844,10 +1901,7 @@
*
* @return a set of user-modifiable settings.
* @throws RuntimeException when the HdmiControlService is not available.
- *
- * @hide
*/
- @SystemApi
@NonNull
@CecSettingName
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -1871,10 +1925,7 @@
* @throws IllegalArgumentException when setting {@code name} does not exist.
* @throws IllegalArgumentException when setting {@code name} value type is invalid.
* @throws RuntimeException when the HdmiControlService is not available.
- *
- * @hide
*/
- @SystemApi
@NonNull
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public List<String> getAllowedCecSettingStringValues(@NonNull @CecSettingName String name) {
@@ -1897,10 +1948,7 @@
* @throws IllegalArgumentException when setting {@code name} does not exist.
* @throws IllegalArgumentException when setting {@code name} value type is invalid.
* @throws RuntimeException when the HdmiControlService is not available.
- *
- * @hide
*/
- @SystemApi
@NonNull
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public List<Integer> getAllowedCecSettingIntValues(@NonNull @CecSettingName String name) {
@@ -1920,10 +1968,7 @@
* Set the global status of HDMI CEC.
*
* <p>This allows to enable/disable HDMI CEC on the device.
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setHdmiCecEnabled(@NonNull @HdmiCecControl int value) {
if (mService == null) {
@@ -1941,10 +1986,7 @@
* Get the current global status of HDMI CEC.
*
* <p>Reflects whether HDMI CEC is currently enabled on the device.
- *
- * @hide
*/
- @SystemApi
@NonNull
@HdmiCecControl
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -1970,10 +2012,7 @@
* Binder thread. This means that all callback implementations must be
* thread safe. To specify the execution thread, use
* {@link addHdmiCecEnabledChangeListener(Executor, CecSettingChangeListener)}.
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void addHdmiCecEnabledChangeListener(@NonNull CecSettingChangeListener listener) {
addHdmiCecEnabledChangeListener(ConcurrentUtils.DIRECT_EXECUTOR, listener);
@@ -1984,10 +2023,7 @@
*
* <p>To stop getting the notification,
* use {@link #removeHdmiCecEnabledChangeListener(CecSettingChangeListener)}.
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void addHdmiCecEnabledChangeListener(
@NonNull @CallbackExecutor Executor executor,
@@ -1997,10 +2033,7 @@
/**
* Remove change listener for global status of HDMI CEC.
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void removeHdmiCecEnabledChangeListener(
@NonNull CecSettingChangeListener listener) {
@@ -2012,9 +2045,8 @@
*
* <p>Allows to select either CEC 1.4b or 2.0 to be used by the device.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_VERSION
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setHdmiCecVersion(@NonNull @HdmiCecVersion int value) {
if (mService == null) {
@@ -2033,9 +2065,8 @@
*
* <p>Reflects which CEC version 1.4b or 2.0 is currently used by the device.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_VERSION
*/
- @SystemApi
@NonNull
@HdmiCecVersion
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2059,9 +2090,8 @@
* receiving Routing Control related messages. If disabled, you can only
* switch the input via controls on this device.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_ROUTING_CONTROL
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setRoutingControl(@NonNull @RoutingControl int value) {
if (mService == null) {
@@ -2083,9 +2113,8 @@
* receiving Routing Control related messages. If disabled, you can only
* switch the input via controls on this device.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_ROUTING_CONTROL
*/
- @SystemApi
@NonNull
@RoutingControl
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2107,9 +2136,8 @@
* <p>Specifies to which devices Power Control messages should be sent:
* only to the TV, broadcast to all devices, no power control messages.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setPowerControlMode(@NonNull @PowerControlMode String value) {
if (mService == null) {
@@ -2129,9 +2157,8 @@
* <p>Reflects to which devices Power Control messages should be sent:
* only to the TV, broadcast to all devices, no power control messages.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_CONTROL_MODE
*/
- @SystemApi
@NonNull
@PowerControlMode
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2152,9 +2179,8 @@
*
* <p>Sets the action taken: do nothing or go to sleep immediately.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setPowerStateChangeOnActiveSourceLost(
@NonNull @ActiveSourceLostBehavior String value) {
@@ -2175,9 +2201,8 @@
*
* <p>Reflects the action taken: do nothing or go to sleep immediately.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST
*/
- @SystemApi
@NonNull
@ActiveSourceLostBehavior
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2203,9 +2228,8 @@
* the AVR instead of TV speaker or Audio System speakers. If disabled, the
* System Audio Mode will never be activated.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setSystemAudioControl(@NonNull @SystemAudioControl int value) {
if (mService == null) {
@@ -2228,9 +2252,8 @@
* the AVR instead of TV speaker or Audio System speakers. If disabled, the
* System Audio Mode will never be activated.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL
*/
- @SystemApi
@NonNull
@SystemAudioControl
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2251,9 +2274,8 @@
*
* <p>Sets whether the device should be muted when System Audio Mode is turned off.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setSystemAudioModeMuting(@NonNull @SystemAudioModeMuting int value) {
if (mService == null) {
@@ -2272,9 +2294,8 @@
*
* <p>Reflects whether the device should be muted when System Audio Mode is turned off.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING
*/
- @SystemApi
@NonNull
@SystemAudioModeMuting
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2296,9 +2317,8 @@
* <p>Sets whether the TV should wake up upon reception of <Text View On>
* or <Image View On>.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setTvWakeOnOneTouchPlay(@NonNull @TvWakeOnOneTouchPlay int value) {
if (mService == null) {
@@ -2318,9 +2338,8 @@
* <p>Reflects whether the TV should wake up upon reception of <Text View On>
* or <Image View On>.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY
*/
- @SystemApi
@NonNull
@TvWakeOnOneTouchPlay
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2342,9 +2361,8 @@
* <p>Sets whether the device will also turn off other CEC devices
* when it goes to standby mode.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void setTvSendStandbyOnSleep(@NonNull @TvSendStandbyOnSleep int value) {
if (mService == null) {
@@ -2364,9 +2382,8 @@
* <p>Reflects whether the device will also turn off other CEC devices
* when it goes to standby mode.
*
- * @hide
+ * @see HdmiControlManager#CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP
*/
- @SystemApi
@NonNull
@TvSendStandbyOnSleep
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
@@ -2381,4 +2398,102 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Set presence of one Short Audio Descriptor (SAD) in the query.
+ *
+ * <p>Allows the caller to specify whether the SAD for a specific audio codec should be
+ * present in the <Request Short Audio Descriptor> query. Each <Request Short Audio
+ * Descriptor> message can carry at most 4 SADs at a time. This method allows the caller to
+ * limit the amount of SADs queried and therefore limit the amount of CEC messages on the bus.
+ *
+ * <p>When an ARC connection is established, the TV sends a
+ * <Request Short Audio Descriptor> query to the Audio System that it's connected to. If
+ * an SAD is queried and the Audio System reports that it supports that SAD, the TV can send
+ * audio in that format to be output on the Audio System via ARC.
+ * If a codec is not queried, the TV doesn't know if the connected Audio System supports this
+ * SAD and doesn't send audio in that format to the Audio System.
+ *
+ * @param setting SAD to set.
+ * @param value Presence to set the SAD to.
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void setSadPresenceInQuery(@NonNull @CecSettingSad String setting,
+ @SadPresenceInQuery int value) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ throw new RuntimeException("HdmiControlService is not available");
+ }
+ try {
+ mService.setCecSettingIntValue(setting, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set presence of multiple Short Audio Descriptors (SADs) in the query.
+ *
+ * <p>Allows the caller to specify whether the SADs for specific audio codecs should be present
+ * in the <Request Short Audio Descriptor> query. For audio codecs that are not specified,
+ * the SAD's presence remains at its previous value. Each <Request Short Audio Descriptor>
+ * message can carry at most 4 SADs at a time. This method allows the caller to limit the amount
+ * of SADs queried and therefore limit the amount of CEC messages on the bus.
+ *
+ * <p>When an ARC connection is established, the TV sends a
+ * <Request Short Audio Descriptor> query to the Audio System that it's connected to. If
+ * an SAD is queried and the Audio System reports that it supports that SAD, the TV can send
+ * audio in that format to be output on the Audio System via ARC.
+ * If a codec is not queried, the TV doesn't know if the connected Audio System supports this
+ * SAD and doesn't send audio in that format to the Audio System.
+ *
+ *
+ * @param settings SADs to set.
+ * @param value Presence to set all specified SADs to.
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void setSadsPresenceInQuery(@NonNull @CecSettingSad List<String> settings,
+ @SadPresenceInQuery int value) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ throw new RuntimeException("HdmiControlService is not available");
+ }
+ try {
+ for (String sad : settings) {
+ mService.setCecSettingIntValue(sad, value);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get presence of one Short Audio Descriptor (SAD) in the query.
+ *
+ * <p>Reflects whether the SAD for a specific audio codec should be present in the
+ * <Request Short Audio Descriptor> query.
+ *
+ * <p>When an ARC connection is established, the TV sends a
+ * <Request Short Audio Descriptor> query to the Audio System that it's connected to. If
+ * an SAD is queried and the Audio System reports that it supports that SAD, the TV can send
+ * audio in that format to be output on the Audio System via ARC.
+ * If a codec is not queried, the TV doesn't know if the connected Audio System supports this
+ * SAD and doesn't send audio in that format to the Audio System.
+ *
+ * @param setting SAD to get.
+ * @return Current presence of the specified SAD.
+ */
+ @SadPresenceInQuery
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public int getSadPresenceInQuery(@NonNull @CecSettingSad String setting) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ throw new RuntimeException("HdmiControlService is not available");
+ }
+ try {
+ return mService.getCecSettingIntValue(setting);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 35a974b..9a2cd06 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -204,7 +204,7 @@
aidlEvent.status,
modelHandle, aidlEvent.captureAvailable, captureSession,
aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
- audioFormat, aidlEvent.data);
+ audioFormat, aidlEvent.data, aidlEvent.recognitionStillActive);
}
public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 163e6f0..b0439d0 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1175,6 +1175,11 @@
@UnsupportedAppUsage
@NonNull
public final byte[] data;
+ /**
+ * Is recognition still active after this event.
+ * @hide
+ */
+ public final boolean recognitionStillActive;
/** @hide */
@TestApi
@@ -1182,6 +1187,16 @@
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
+ this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+ capturePreambleMs, triggerInData, captureFormat, data,
+ status == RECOGNITION_STATUS_GET_STATE_RESPONSE);
+ }
+
+ /** @hide */
+ public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+ int captureSession, int captureDelayMs, int capturePreambleMs,
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ boolean recognitionStillActive) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
@@ -1191,6 +1206,7 @@
this.triggerInData = triggerInData;
this.captureFormat = requireNonNull(captureFormat);
this.data = data != null ? data : new byte[0];
+ this.recognitionStillActive = recognitionStillActive;
}
/**
@@ -1266,8 +1282,10 @@
.build();
}
byte[] data = in.readBlob();
+ boolean recognitionStillActive = in.readBoolean();
return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
- captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data);
+ captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data,
+ recognitionStillActive);
}
/** @hide */
@@ -1293,8 +1311,8 @@
dest.writeByte((byte)0);
}
dest.writeBlob(data);
+ dest.writeBoolean(recognitionStillActive);
}
-
@Override
public int hashCode() {
final int prime = 31;
@@ -1312,6 +1330,7 @@
result = prime * result + Arrays.hashCode(data);
result = prime * result + soundModelHandle;
result = prime * result + status;
+ result = result + (recognitionStillActive ? 1289 : 1291);
return result;
}
@@ -1334,6 +1353,8 @@
return false;
if (!Arrays.equals(data, other.data))
return false;
+ if (recognitionStillActive != other.recognitionStillActive)
+ return false;
if (soundModelHandle != other.soundModelHandle)
return false;
if (status != other.status)
@@ -1370,7 +1391,9 @@
(", encoding=" + captureFormat.getEncoding()))
+ ((captureFormat == null) ? "" :
(", channelMask=" + captureFormat.getChannelMask()))
- + ", data=" + (data == null ? 0 : data.length) + "]";
+ + ", data=" + (data == null ? 0 : data.length)
+ + ", recognitionStillActive=" + recognitionStillActive
+ + "]";
}
}
@@ -1673,11 +1696,21 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
- int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
- @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
+ int captureSession, int captureDelayMs, int capturePreambleMs,
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
+ this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+ capturePreambleMs, triggerInData, captureFormat, data, keyphraseExtras,
+ status == RECOGNITION_STATUS_GET_STATE_RESPONSE);
+ }
+
+ public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+ int captureSession, int captureDelayMs, int capturePreambleMs,
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ @Nullable KeyphraseRecognitionExtra[] keyphraseExtras,
+ boolean recognitionStillActive) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
- capturePreambleMs, triggerInData, captureFormat, data);
+ capturePreambleMs, triggerInData, captureFormat, data, recognitionStillActive);
this.keyphraseExtras =
keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
}
@@ -1713,11 +1746,12 @@
.build();
}
byte[] data = in.readBlob();
+ boolean recognitionStillActive = in.readBoolean();
KeyphraseRecognitionExtra[] keyphraseExtras =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
return new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
captureSession, captureDelayMs, capturePreambleMs, triggerInData,
- captureFormat, data, keyphraseExtras);
+ captureFormat, data, keyphraseExtras, recognitionStillActive);
}
@Override
@@ -1738,6 +1772,7 @@
dest.writeByte((byte)0);
}
dest.writeBlob(data);
+ dest.writeBoolean(recognitionStillActive);
dest.writeTypedArray(keyphraseExtras, flags);
}
@@ -1782,7 +1817,9 @@
(", encoding=" + captureFormat.getEncoding()))
+ ((captureFormat == null) ? "" :
(", channelMask=" + captureFormat.getChannelMask()))
- + ", data=" + (data == null ? 0 : data.length) + "]";
+ + ", data=" + (data == null ? 0 : data.length)
+ + ", recognitionStillActive=" + recognitionStillActive
+ + "]";
}
}
@@ -1798,9 +1835,17 @@
boolean captureAvailable, int captureSession, int captureDelayMs,
int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
@Nullable byte[] data) {
- super(status, soundModelHandle, captureAvailable, captureSession,
- captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
- data);
+ this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+ capturePreambleMs, triggerInData, captureFormat, data,
+ status == RECOGNITION_STATUS_GET_STATE_RESPONSE);
+ }
+
+ public GenericRecognitionEvent(int status, int soundModelHandle,
+ boolean captureAvailable, int captureSession, int captureDelayMs,
+ int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+ @Nullable byte[] data, boolean recognitionStillActive) {
+ super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+ capturePreambleMs, triggerInData, captureFormat, data, recognitionStillActive);
}
public static final @android.annotation.NonNull Parcelable.Creator<GenericRecognitionEvent> CREATOR
@@ -1818,7 +1863,8 @@
RecognitionEvent event = RecognitionEvent.fromParcel(in);
return new GenericRecognitionEvent(event.status, event.soundModelHandle,
event.captureAvailable, event.captureSession, event.captureDelayMs,
- event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data);
+ event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data,
+ event.recognitionStillActive);
}
@Override
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 6999e3d..aa3c361 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -590,10 +590,6 @@
@Override
public final void initializeInternal(@NonNull IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
- if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) {
- Log.w(TAG, "The token has already registered, ignore this initialization.");
- return;
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
mConfigTracker.onInitialize(configChanges);
mPrivOps.set(privilegedOperations);
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index 4ea8a54..f55bcd3 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -2,4 +2,5 @@
include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
-per-file SSL*, Uri*, Url* = prb@google.com, dauletz@google.com, narayan@google.com, ngeoffray@google.com
+per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
+per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index f6852e6..0eb4cf3 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -17,16 +17,26 @@
package android.net;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.sntp.Duration64;
+import android.net.sntp.Timestamp64;
import android.os.SystemClock;
import android.util.Log;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.TrafficStatsConstants;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.Arrays;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.Supplier;
/**
* {@hide}
@@ -60,17 +70,21 @@
private static final int NTP_STRATUM_DEATH = 0;
private static final int NTP_STRATUM_MAX = 15;
- // Number of seconds between Jan 1, 1900 and Jan 1, 1970
- // 70 years plus 17 leap days
- private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
+ // The source of the current system clock time, replaceable for testing.
+ private final Supplier<Instant> mSystemTimeSupplier;
- // system time computed from NTP server response
+ private final Random mRandom;
+
+ // The last offset calculated from an NTP server response
+ private long mClockOffset;
+
+ // The last system time computed from an NTP server response
private long mNtpTime;
- // value of SystemClock.elapsedRealtime() corresponding to mNtpTime
+ // The value of SystemClock.elapsedRealtime() corresponding to mNtpTime / mClockOffset
private long mNtpTimeReference;
- // round trip time in milliseconds
+ // The round trip (network) time in milliseconds
private long mRoundTripTime;
private static class InvalidServerReplyException extends Exception {
@@ -81,6 +95,13 @@
@UnsupportedAppUsage
public SntpClient() {
+ this(Instant::now, defaultRandom());
+ }
+
+ @VisibleForTesting
+ public SntpClient(Supplier<Instant> systemTimeSupplier, Random random) {
+ mSystemTimeSupplier = Objects.requireNonNull(systemTimeSupplier);
+ mRandom = Objects.requireNonNull(random);
}
/**
@@ -126,9 +147,13 @@
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
// get current time and write it to the request packet
- final long requestTime = System.currentTimeMillis();
+ final Instant requestTime = mSystemTimeSupplier.get();
+ final Timestamp64 requestTimestamp = Timestamp64.fromInstant(requestTime);
+
+ final Timestamp64 randomizedRequestTimestamp =
+ requestTimestamp.randomizeSubMillis(mRandom);
final long requestTicks = SystemClock.elapsedRealtime();
- writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
+ writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, randomizedRequestTimestamp);
socket.send(request);
@@ -136,42 +161,44 @@
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
final long responseTicks = SystemClock.elapsedRealtime();
- final long responseTime = requestTime + (responseTicks - requestTicks);
+ final Instant responseTime = requestTime.plusMillis(responseTicks - requestTicks);
+ final Timestamp64 responseTimestamp = Timestamp64.fromInstant(responseTime);
// extract the results
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
- final long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
- final long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
- final long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
- final long referenceTime = readTimeStamp(buffer, REFERENCE_TIME_OFFSET);
+ final Timestamp64 referenceTimestamp = readTimeStamp(buffer, REFERENCE_TIME_OFFSET);
+ final Timestamp64 originateTimestamp = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
+ final Timestamp64 receiveTimestamp = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
+ final Timestamp64 transmitTimestamp = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
/* Do validation according to RFC */
- // TODO: validate originateTime == requestTime.
- checkValidServerReply(leap, mode, stratum, transmitTime, referenceTime);
+ checkValidServerReply(leap, mode, stratum, transmitTimestamp, referenceTimestamp,
+ randomizedRequestTimestamp, originateTimestamp);
- long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
- // receiveTime = originateTime + transit + skew
- // responseTime = transmitTime + transit - skew
- // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
- // = ((originateTime + transit + skew - originateTime) +
- // (transmitTime - (transmitTime + transit - skew)))/2
- // = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
- // = (transit + skew - transit + skew)/2
- // = (2 * skew)/2 = skew
- long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
- EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
+ long totalTransactionDurationMillis = responseTicks - requestTicks;
+ long serverDurationMillis =
+ Duration64.between(receiveTimestamp, transmitTimestamp).toDuration().toMillis();
+ long roundTripTimeMillis = totalTransactionDurationMillis - serverDurationMillis;
+
+ Duration clockOffsetDuration = calculateClockOffset(requestTimestamp,
+ receiveTimestamp, transmitTimestamp, responseTimestamp);
+ long clockOffsetMillis = clockOffsetDuration.toMillis();
+
+ EventLogTags.writeNtpSuccess(
+ address.toString(), roundTripTimeMillis, clockOffsetMillis);
if (DBG) {
- Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
- "clock offset: " + clockOffset + "ms");
+ Log.d(TAG, "round trip: " + roundTripTimeMillis + "ms, "
+ + "clock offset: " + clockOffsetMillis + "ms");
}
// save our results - use the times on this side of the network latency
// (response rather than request time)
- mNtpTime = responseTime + clockOffset;
+ mClockOffset = clockOffsetMillis;
+ mNtpTime = responseTime.plus(clockOffsetDuration).toEpochMilli();
mNtpTimeReference = responseTicks;
- mRoundTripTime = roundTripTime;
+ mRoundTripTime = roundTripTimeMillis;
} catch (Exception e) {
EventLogTags.writeNtpFailure(address.toString(), e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
@@ -186,6 +213,28 @@
return true;
}
+ /** Performs the NTP clock offset calculation. */
+ @VisibleForTesting
+ public static Duration calculateClockOffset(Timestamp64 clientRequestTimestamp,
+ Timestamp64 serverReceiveTimestamp, Timestamp64 serverTransmitTimestamp,
+ Timestamp64 clientResponseTimestamp) {
+ // According to RFC4330:
+ // t is the system clock offset (the adjustment we are trying to find)
+ // t = ((T2 - T1) + (T3 - T4)) / 2
+ //
+ // Which is:
+ // t = (([server]receiveTimestamp - [client]requestTimestamp)
+ // + ([server]transmitTimestamp - [client]responseTimestamp)) / 2
+ //
+ // See the NTP spec and tests: the numeric types used are deliberate:
+ // + Duration64.between() uses 64-bit arithmetic (32-bit for the seconds).
+ // + plus() / dividedBy() use Duration, which isn't the double precision floating point
+ // used in NTPv4, but is good enough.
+ return Duration64.between(clientRequestTimestamp, serverReceiveTimestamp)
+ .plus(Duration64.between(clientResponseTimestamp, serverTransmitTimestamp))
+ .dividedBy(2);
+ }
+
@Deprecated
@UnsupportedAppUsage
public boolean requestTime(String host, int timeout) {
@@ -194,6 +243,14 @@
}
/**
+ * Returns the offset calculated to apply to the client clock to arrive at {@link #getNtpTime()}
+ */
+ @VisibleForTesting
+ public long getClockOffset() {
+ return mClockOffset;
+ }
+
+ /**
* Returns the time computed from the NTP transaction.
*
* @return time value computed from NTP server response.
@@ -225,8 +282,9 @@
}
private static void checkValidServerReply(
- byte leap, byte mode, int stratum, long transmitTime, long referenceTime)
- throws InvalidServerReplyException {
+ byte leap, byte mode, int stratum, Timestamp64 transmitTimestamp,
+ Timestamp64 referenceTimestamp, Timestamp64 randomizedRequestTimestamp,
+ Timestamp64 originateTimestamp) throws InvalidServerReplyException {
if (leap == NTP_LEAP_NOSYNC) {
throw new InvalidServerReplyException("unsynchronized server");
}
@@ -236,73 +294,68 @@
if ((stratum == NTP_STRATUM_DEATH) || (stratum > NTP_STRATUM_MAX)) {
throw new InvalidServerReplyException("untrusted stratum: " + stratum);
}
- if (transmitTime == 0) {
- throw new InvalidServerReplyException("zero transmitTime");
+ if (!randomizedRequestTimestamp.equals(originateTimestamp)) {
+ throw new InvalidServerReplyException(
+ "originateTimestamp != randomizedRequestTimestamp");
}
- if (referenceTime == 0) {
- throw new InvalidServerReplyException("zero reference timestamp");
+ if (transmitTimestamp.equals(Timestamp64.ZERO)) {
+ throw new InvalidServerReplyException("zero transmitTimestamp");
+ }
+ if (referenceTimestamp.equals(Timestamp64.ZERO)) {
+ throw new InvalidServerReplyException("zero referenceTimestamp");
}
}
/**
* Reads an unsigned 32 bit big endian number from the given offset in the buffer.
*/
- private long read32(byte[] buffer, int offset) {
- byte b0 = buffer[offset];
- byte b1 = buffer[offset+1];
- byte b2 = buffer[offset+2];
- byte b3 = buffer[offset+3];
+ private long readUnsigned32(byte[] buffer, int offset) {
+ int i0 = buffer[offset++] & 0xFF;
+ int i1 = buffer[offset++] & 0xFF;
+ int i2 = buffer[offset++] & 0xFF;
+ int i3 = buffer[offset] & 0xFF;
- // convert signed bytes to unsigned values
- int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
- int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
- int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
- int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);
-
- return ((long)i0 << 24) + ((long)i1 << 16) + ((long)i2 << 8) + (long)i3;
+ int bits = (i0 << 24) | (i1 << 16) | (i2 << 8) | i3;
+ return bits & 0xFFFF_FFFFL;
}
/**
- * Reads the NTP time stamp at the given offset in the buffer and returns
- * it as a system time (milliseconds since January 1, 1970).
+ * Reads the NTP time stamp from the given offset in the buffer.
*/
- private long readTimeStamp(byte[] buffer, int offset) {
- long seconds = read32(buffer, offset);
- long fraction = read32(buffer, offset + 4);
- // Special case: zero means zero.
- if (seconds == 0 && fraction == 0) {
- return 0;
- }
- return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
+ private Timestamp64 readTimeStamp(byte[] buffer, int offset) {
+ long seconds = readUnsigned32(buffer, offset);
+ int fractionBits = (int) readUnsigned32(buffer, offset + 4);
+ return Timestamp64.fromComponents(seconds, fractionBits);
}
/**
- * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp
- * at the given offset in the buffer.
+ * Writes the NTP time stamp at the given offset in the buffer.
*/
- private void writeTimeStamp(byte[] buffer, int offset, long time) {
- // Special case: zero means zero.
- if (time == 0) {
- Arrays.fill(buffer, offset, offset + 8, (byte) 0x00);
- return;
- }
-
- long seconds = time / 1000L;
- long milliseconds = time - seconds * 1000L;
- seconds += OFFSET_1900_TO_1970;
-
+ private void writeTimeStamp(byte[] buffer, int offset, Timestamp64 timestamp) {
+ long seconds = timestamp.getEraSeconds();
// write seconds in big endian format
- buffer[offset++] = (byte)(seconds >> 24);
- buffer[offset++] = (byte)(seconds >> 16);
- buffer[offset++] = (byte)(seconds >> 8);
- buffer[offset++] = (byte)(seconds >> 0);
+ buffer[offset++] = (byte) (seconds >>> 24);
+ buffer[offset++] = (byte) (seconds >>> 16);
+ buffer[offset++] = (byte) (seconds >>> 8);
+ buffer[offset++] = (byte) (seconds);
- long fraction = milliseconds * 0x100000000L / 1000L;
+ int fractionBits = timestamp.getFractionBits();
// write fraction in big endian format
- buffer[offset++] = (byte)(fraction >> 24);
- buffer[offset++] = (byte)(fraction >> 16);
- buffer[offset++] = (byte)(fraction >> 8);
- // low order bits should be random data
- buffer[offset++] = (byte)(Math.random() * 255.0);
+ buffer[offset++] = (byte) (fractionBits >>> 24);
+ buffer[offset++] = (byte) (fractionBits >>> 16);
+ buffer[offset++] = (byte) (fractionBits >>> 8);
+ buffer[offset] = (byte) (fractionBits);
+ }
+
+ private static Random defaultRandom() {
+ Random random;
+ try {
+ random = SecureRandom.getInstanceStrong();
+ } catch (NoSuchAlgorithmException e) {
+ // This should never happen.
+ Slog.wtf(TAG, "Unable to access SecureRandom", e);
+ random = new Random(System.currentTimeMillis());
+ }
+ return random;
}
}
diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING
index 8c13ef9..a379c33 100644
--- a/core/java/android/net/TEST_MAPPING
+++ b/core/java/android/net/TEST_MAPPING
@@ -16,5 +16,24 @@
{
"path": "frameworks/opt/net/wifi"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.net"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
]
}
diff --git a/core/java/android/net/sntp/Duration64.java b/core/java/android/net/sntp/Duration64.java
new file mode 100644
index 0000000..7f29cdb
--- /dev/null
+++ b/core/java/android/net/sntp/Duration64.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 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.net.sntp;
+
+import java.time.Duration;
+
+/**
+ * A type similar to {@link Timestamp64} but used when calculating the difference between two
+ * timestamps. As such, it is a signed type, but still uses 64-bits in total and so can only
+ * represent half the magnitude of {@link Timestamp64}.
+ *
+ * <p>See <a href="https://www.eecis.udel.edu/~mills/time.html">4. Time Difference Calculations</a>.
+ *
+ * @hide
+ */
+public final class Duration64 {
+
+ public static final Duration64 ZERO = new Duration64(0);
+ private final long mBits;
+
+ private Duration64(long bits) {
+ this.mBits = bits;
+ }
+
+ /**
+ * Returns the difference between two 64-bit NTP timestamps as a {@link Duration64}, as
+ * described in the NTP spec. The times represented by the timestamps have to be within {@link
+ * Timestamp64#MAX_SECONDS_IN_ERA} (~68 years) of each other for the calculation to produce a
+ * correct answer.
+ */
+ public static Duration64 between(Timestamp64 startInclusive, Timestamp64 endExclusive) {
+ long oneBits = (startInclusive.getEraSeconds() << 32)
+ | (startInclusive.getFractionBits() & 0xFFFF_FFFFL);
+ long twoBits = (endExclusive.getEraSeconds() << 32)
+ | (endExclusive.getFractionBits() & 0xFFFF_FFFFL);
+ long resultBits = twoBits - oneBits;
+ return new Duration64(resultBits);
+ }
+
+ /**
+ * Add two {@link Duration64} instances together. This performs the calculation in {@link
+ * Duration} and returns a {@link Duration} to increase the magnitude of accepted arguments,
+ * since {@link Duration64} only supports signed 32-bit seconds. The use of {@link Duration}
+ * limits precision to nanoseconds.
+ */
+ public Duration plus(Duration64 other) {
+ // From https://www.eecis.udel.edu/~mills/time.html:
+ // "The offset and delay calculations require sums and differences of these raw timestamp
+ // differences that can span no more than from 34 years in the future to 34 years in the
+ // past without overflow. This is a fundamental limitation in 64-bit integer calculations.
+ //
+ // In the NTPv4 reference implementation, all calculations involving offset and delay values
+ // use 64-bit floating double arithmetic, with the exception of raw timestamp subtraction,
+ // as mentioned above. The raw timestamp differences are then converted to 64-bit floating
+ // double format without loss of precision or chance of overflow in subsequent
+ // calculations."
+ //
+ // Here, we use Duration instead, which provides sufficient range, but loses precision below
+ // nanos.
+ return this.toDuration().plus(other.toDuration());
+ }
+
+ /**
+ * Returns a {@link Duration64} equivalent of the supplied duration, if the magnitude can be
+ * represented. Because {@link Duration64} uses a fixed point type for sub-second values it
+ * cannot represent all nanosecond values precisely and so the conversion can be lossy.
+ *
+ * @throws IllegalArgumentException if the supplied duration is too big to be represented
+ */
+ public static Duration64 fromDuration(Duration duration) {
+ long seconds = duration.getSeconds();
+ if (seconds < Integer.MIN_VALUE || seconds > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException();
+ }
+ long bits = (seconds << 32)
+ | (Timestamp64.nanosToFractionBits(duration.getNano()) & 0xFFFF_FFFFL);
+ return new Duration64(bits);
+ }
+
+ /**
+ * Returns a {@link Duration} equivalent of this duration. Because {@link Duration64} uses a
+ * fixed point type for sub-second values it can values smaller than nanosecond precision and so
+ * the conversion can be lossy.
+ */
+ public Duration toDuration() {
+ int seconds = getSeconds();
+ int nanos = getNanos();
+ return Duration.ofSeconds(seconds, nanos);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Duration64 that = (Duration64) o;
+ return mBits == that.mBits;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(mBits);
+ }
+
+ @Override
+ public String toString() {
+ Duration duration = toDuration();
+ return Long.toHexString(mBits)
+ + "(" + duration.getSeconds() + "s " + duration.getNano() + "ns)";
+ }
+
+ /**
+ * Returns the <em>signed</em> seconds in this duration.
+ */
+ public int getSeconds() {
+ return (int) (mBits >> 32);
+ }
+
+ /**
+ * Returns the <em>unsigned</em> nanoseconds in this duration (truncated).
+ */
+ public int getNanos() {
+ return Timestamp64.fractionBitsToNanos((int) (mBits & 0xFFFF_FFFFL));
+ }
+}
diff --git a/core/java/android/net/sntp/OWNERS b/core/java/android/net/sntp/OWNERS
new file mode 100644
index 0000000..9a3e264
--- /dev/null
+++ b/core/java/android/net/sntp/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/net/sntp/Timestamp64.java b/core/java/android/net/sntp/Timestamp64.java
new file mode 100644
index 0000000..8ddfd77
--- /dev/null
+++ b/core/java/android/net/sntp/Timestamp64.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 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.net.sntp;
+
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Random;
+
+/**
+ * The 64-bit type ("timestamp") that NTP uses to represent a point in time. It only holds the
+ * lowest 32-bits of the number of seconds since 1900-01-01 00:00:00. Consequently, to turn an
+ * instance into an unambiguous point in time the era number must be known. Era zero runs from
+ * 1900-01-01 00:00:00 to a date in 2036.
+ *
+ * It stores sub-second values using a 32-bit fixed point type, so it can resolve values smaller
+ * than a nanosecond, but is imprecise (i.e. it truncates).
+ *
+ * See also <a href=https://www.eecis.udel.edu/~mills/y2k.html>NTP docs</a>.
+ *
+ * @hide
+ */
+public final class Timestamp64 {
+
+ public static final Timestamp64 ZERO = fromComponents(0, 0);
+ static final int SUB_MILLIS_BITS_TO_RANDOMIZE = 32 - 10;
+
+ // Number of seconds between Jan 1, 1900 and Jan 1, 1970
+ // 70 years plus 17 leap days
+ static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
+ static final long MAX_SECONDS_IN_ERA = 0xFFFF_FFFFL;
+ static final long SECONDS_IN_ERA = MAX_SECONDS_IN_ERA + 1;
+
+ static final int NANOS_PER_SECOND = 1_000_000_000;
+
+ /** Creates a {@link Timestamp64} from the seconds and fraction components. */
+ public static Timestamp64 fromComponents(long eraSeconds, int fractionBits) {
+ return new Timestamp64(eraSeconds, fractionBits);
+ }
+
+ /** Creates a {@link Timestamp64} by decoding a string in the form "e4dc720c.4d4fc9eb". */
+ public static Timestamp64 fromString(String string) {
+ final int requiredLength = 17;
+ if (string.length() != requiredLength || string.charAt(8) != '.') {
+ throw new IllegalArgumentException(string);
+ }
+ String eraSecondsString = string.substring(0, 8);
+ String fractionString = string.substring(9);
+ long eraSeconds = Long.parseLong(eraSecondsString, 16);
+
+ // Use parseLong() because the type is unsigned. Integer.parseInt() will reject 0x70000000
+ // or above as being out of range.
+ long fractionBitsAsLong = Long.parseLong(fractionString, 16);
+ if (fractionBitsAsLong < 0 || fractionBitsAsLong > 0xFFFFFFFFL) {
+ throw new IllegalArgumentException("Invalid fractionBits:" + fractionString);
+ }
+ return new Timestamp64(eraSeconds, (int) fractionBitsAsLong);
+ }
+
+ /**
+ * Converts an {@link Instant} into a {@link Timestamp64}. This is lossy: Timestamp64 only
+ * contains the number of seconds in a given era, but the era is not stored. Also, sub-second
+ * values are not stored precisely.
+ */
+ public static Timestamp64 fromInstant(Instant instant) {
+ long ntpEraSeconds = instant.getEpochSecond() + OFFSET_1900_TO_1970;
+ if (ntpEraSeconds < 0) {
+ ntpEraSeconds = SECONDS_IN_ERA - (-ntpEraSeconds % SECONDS_IN_ERA);
+ }
+ ntpEraSeconds %= SECONDS_IN_ERA;
+
+ long nanos = instant.getNano();
+ int fractionBits = nanosToFractionBits(nanos);
+
+ return new Timestamp64(ntpEraSeconds, fractionBits);
+ }
+
+ private final long mEraSeconds;
+ private final int mFractionBits;
+
+ private Timestamp64(long eraSeconds, int fractionBits) {
+ if (eraSeconds < 0 || eraSeconds > MAX_SECONDS_IN_ERA) {
+ throw new IllegalArgumentException(
+ "Invalid parameters. seconds=" + eraSeconds + ", fraction=" + fractionBits);
+ }
+ this.mEraSeconds = eraSeconds;
+ this.mFractionBits = fractionBits;
+ }
+
+ /** Returns the number of seconds in the NTP era. */
+ public long getEraSeconds() {
+ return mEraSeconds;
+ }
+
+ /** Returns the fraction of a second as 32-bit, unsigned fixed-point bits. */
+ public int getFractionBits() {
+ return mFractionBits;
+ }
+
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("%08x.%08x", mEraSeconds, mFractionBits);
+ }
+
+ /** Returns the instant represented by this value in the specified NTP era. */
+ public Instant toInstant(int ntpEra) {
+ long secondsSinceEpoch = mEraSeconds - OFFSET_1900_TO_1970;
+ secondsSinceEpoch += ntpEra * SECONDS_IN_ERA;
+
+ int nanos = fractionBitsToNanos(mFractionBits);
+ return Instant.ofEpochSecond(secondsSinceEpoch, nanos);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Timestamp64 that = (Timestamp64) o;
+ return mEraSeconds == that.mEraSeconds && mFractionBits == that.mFractionBits;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEraSeconds, mFractionBits);
+ }
+
+ static int fractionBitsToNanos(int fractionBits) {
+ long fractionBitsLong = fractionBits & 0xFFFF_FFFFL;
+ return (int) ((fractionBitsLong * NANOS_PER_SECOND) >>> 32);
+ }
+
+ static int nanosToFractionBits(long nanos) {
+ if (nanos > NANOS_PER_SECOND) {
+ throw new IllegalArgumentException();
+ }
+ return (int) ((nanos << 32) / NANOS_PER_SECOND);
+ }
+
+ /**
+ * Randomizes the fraction bits that represent sub-millisecond values. i.e. the randomization
+ * won't change the number of milliseconds represented after truncation. This is used to
+ * implement the part of the NTP spec that calls for clients with millisecond accuracy clocks
+ * to send randomized LSB values rather than zeros.
+ */
+ public Timestamp64 randomizeSubMillis(Random random) {
+ int randomizedFractionBits =
+ randomizeLowestBits(random, this.mFractionBits, SUB_MILLIS_BITS_TO_RANDOMIZE);
+ return new Timestamp64(mEraSeconds, randomizedFractionBits);
+ }
+
+ /**
+ * Randomizes the specified number of LSBs in {@code value} by using replacement bits from
+ * {@code Random.getNextInt()}.
+ */
+ @VisibleForTesting
+ public static int randomizeLowestBits(Random random, int value, int bitsToRandomize) {
+ if (bitsToRandomize < 1 || bitsToRandomize >= Integer.SIZE) {
+ // There's no point in randomizing all bits or none of the bits.
+ throw new IllegalArgumentException(Integer.toString(bitsToRandomize));
+ }
+
+ int upperBitMask = 0xFFFF_FFFF << bitsToRandomize;
+ int lowerBitMask = ~upperBitMask;
+
+ int randomValue = random.nextInt();
+ return (value & upperBitMask) | (randomValue & lowerBitMask);
+ }
+}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 34f079a..1c1fc2c 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -26,6 +26,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
/**
* Interface for objects containing battery attribution data.
@@ -43,6 +44,7 @@
* @hide
*/
@IntDef(prefix = {"POWER_COMPONENT_"}, value = {
+ POWER_COMPONENT_ANY,
POWER_COMPONENT_SCREEN,
POWER_COMPONENT_CPU,
POWER_COMPONENT_BLUETOOTH,
@@ -65,6 +67,7 @@
public static @interface PowerComponent {
}
+ public static final int POWER_COMPONENT_ANY = -1;
public static final int POWER_COMPONENT_SCREEN = OsProtoEnums.POWER_COMPONENT_SCREEN; // 0
public static final int POWER_COMPONENT_CPU = OsProtoEnums.POWER_COMPONENT_CPU; // 1
public static final int POWER_COMPONENT_BLUETOOTH = OsProtoEnums.POWER_COMPONENT_BLUETOOTH; // 2
@@ -151,9 +154,145 @@
*/
public static final int POWER_MODEL_MEASURED_ENERGY = 2;
+ /**
+ * Identifiers of consumed power aggregations.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"PROCESS_STATE_"}, value = {
+ PROCESS_STATE_ANY,
+ PROCESS_STATE_FOREGROUND,
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProcessState {
+ }
+
+ public static final int PROCESS_STATE_ANY = 0;
+ public static final int PROCESS_STATE_FOREGROUND = 1;
+ public static final int PROCESS_STATE_BACKGROUND = 2;
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+
+ static final int PROCESS_STATE_COUNT = 4;
+
+ private static final String[] sProcessStateNames = new String[PROCESS_STATE_COUNT];
+
+ static {
+ // Assign individually to avoid future mismatch
+ sProcessStateNames[PROCESS_STATE_ANY] = "any";
+ sProcessStateNames[PROCESS_STATE_FOREGROUND] = "fg";
+ sProcessStateNames[PROCESS_STATE_BACKGROUND] = "bg";
+ sProcessStateNames[PROCESS_STATE_FOREGROUND_SERVICE] = "fgs";
+ }
+
+ private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = {
+ POWER_COMPONENT_CPU,
+ };
+
static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
static final int COLUMN_COUNT = 1;
+ /**
+ * Identifies power attribution dimensions that a caller is interested in.
+ */
+ public static final class Dimensions {
+ public final @PowerComponent int powerComponent;
+ public final @ProcessState int processState;
+
+ public Dimensions(int powerComponent, int processState) {
+ this.powerComponent = powerComponent;
+ this.processState = processState;
+ }
+
+ @Override
+ public String toString() {
+ boolean dimensionSpecified = false;
+ StringBuilder sb = new StringBuilder();
+ if (powerComponent != POWER_COMPONENT_ANY) {
+ sb.append("powerComponent=").append(sPowerComponentNames[powerComponent]);
+ dimensionSpecified = true;
+ }
+ if (processState != PROCESS_STATE_ANY) {
+ if (dimensionSpecified) {
+ sb.append(", ");
+ }
+ sb.append("processState=").append(sProcessStateNames[processState]);
+ dimensionSpecified = true;
+ }
+ if (!dimensionSpecified) {
+ sb.append("any components and process states");
+ }
+ return sb.toString();
+ }
+ }
+
+ public static final Dimensions UNSPECIFIED_DIMENSIONS =
+ new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY);
+
+ /**
+ * Identifies power attribution dimensions that are captured by an data element of
+ * a BatteryConsumer. These Keys are used to access those values and to set them using
+ * Builders. See for example {@link #getConsumedPower(Key)}.
+ *
+ * Keys cannot be allocated by the client - they can only be obtained by calling
+ * {@link #getKeys} or {@link #getKey}. All BatteryConsumers that are part of the
+ * same BatteryUsageStats share the same set of keys, therefore it is safe to obtain
+ * the keys from one BatteryConsumer and apply them to other BatteryConsumers
+ * in the same BatteryUsageStats.
+ */
+ public static final class Key {
+ public final @PowerComponent int powerComponent;
+ public final @ProcessState int processState;
+
+ final int mPowerModelColumnIndex;
+ final int mPowerColumnIndex;
+ final int mDurationColumnIndex;
+ private String mShortString;
+
+ private Key(int powerComponent, int processState, int powerModelColumnIndex,
+ int powerColumnIndex, int durationColumnIndex) {
+ this.powerComponent = powerComponent;
+ this.processState = processState;
+
+ mPowerModelColumnIndex = powerModelColumnIndex;
+ mPowerColumnIndex = powerColumnIndex;
+ mDurationColumnIndex = durationColumnIndex;
+ }
+
+ @SuppressWarnings("EqualsUnsafeCast")
+ @Override
+ public boolean equals(Object o) {
+ // Skipping null and class check for performance
+ final Key key = (Key) o;
+ return powerComponent == key.powerComponent
+ && processState == key.processState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = powerComponent;
+ result = 31 * result + processState;
+ return result;
+ }
+
+ /**
+ * Returns a string suitable for use in dumpsys.
+ */
+ public String toShortString() {
+ if (mShortString == null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(powerComponentIdToString(powerComponent));
+ if (processState != PROCESS_STATE_ANY) {
+ sb.append(':');
+ sb.append(processStateToString(processState));
+ }
+ mShortString = sb.toString();
+ }
+ return mShortString;
+ }
+ }
+
protected final BatteryConsumerData mData;
protected final PowerComponents mPowerComponents;
@@ -171,7 +310,37 @@
* Total power consumed by this consumer, in mAh.
*/
public double getConsumedPower() {
- return mPowerComponents.getConsumedPower();
+ return mPowerComponents.getConsumedPower(UNSPECIFIED_DIMENSIONS);
+ }
+
+ /**
+ * Returns power consumed aggregated over the specified dimensions, in mAh.
+ */
+ public double getConsumedPower(Dimensions dimensions) {
+ return mPowerComponents.getConsumedPower(dimensions);
+ }
+
+ /**
+ * Returns keys for various power values attributed to the specified component
+ * held by this BatteryUsageStats object.
+ */
+ public Key[] getKeys(@PowerComponent int componentId) {
+ return mData.getKeys(componentId);
+ }
+
+ /**
+ * Returns the key for the power attributed to the specified component,
+ * for all values of other dimensions such as process state.
+ */
+ public Key getKey(@PowerComponent int componentId) {
+ return mData.getKey(componentId, PROCESS_STATE_ANY);
+ }
+
+ /**
+ * Returns the key for the power attributed to the specified component and process state.
+ */
+ public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
+ return mData.getKey(componentId, processState);
}
/**
@@ -182,7 +351,19 @@
* @return Amount of consumed power in mAh.
*/
public double getConsumedPower(@PowerComponent int componentId) {
- return mPowerComponents.getConsumedPower(componentId);
+ return mPowerComponents.getConsumedPower(
+ mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY));
+ }
+
+ /**
+ * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
+ *
+ * @param key The key of the power component, obtained by calling {@link #getKey} or
+ * {@link #getKeys} method.
+ * @return Amount of consumed power in mAh.
+ */
+ public double getConsumedPower(@NonNull Key key) {
+ return mPowerComponents.getConsumedPower(key);
}
/**
@@ -192,7 +373,18 @@
* {@link BatteryConsumer#POWER_COMPONENT_CPU}.
*/
public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
- return mPowerComponents.getPowerModel(componentId);
+ return mPowerComponents.getPowerModel(
+ mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY));
+ }
+
+ /**
+ * Returns the ID of the model that was used for power estimation.
+ *
+ * @param key The key of the power component, obtained by calling {@link #getKey} or
+ * {@link #getKeys} method.
+ */
+ public @PowerModel int getPowerModel(@NonNull BatteryConsumer.Key key) {
+ return mPowerComponents.getPowerModel(key);
}
/**
@@ -227,7 +419,20 @@
* @return Amount of time in milliseconds.
*/
public long getUsageDurationMillis(@PowerComponent int componentId) {
- return mPowerComponents.getUsageDurationMillis(componentId);
+ return mPowerComponents.getUsageDurationMillis(getKey(componentId));
+ }
+
+ /**
+ * Returns the amount of time since BatteryStats reset used by the specified component, e.g.
+ * CPU, WiFi etc.
+ *
+ *
+ * @param key The key of the power component, obtained by calling {@link #getKey} or
+ * {@link #getKeys} method.
+ * @return Amount of time in milliseconds.
+ */
+ public long getUsageDurationMillis(@NonNull Key key) {
+ return mPowerComponents.getUsageDurationMillis(key);
}
/**
@@ -245,6 +450,9 @@
* Returns the name of the specified component. Intended for logging and debugging.
*/
public static String powerComponentIdToString(@BatteryConsumer.PowerComponent int componentId) {
+ if (componentId == POWER_COMPONENT_ANY) {
+ return "all";
+ }
return sPowerComponentNames[componentId];
}
@@ -263,6 +471,13 @@
}
/**
+ * Returns the name of the specified process state. Intended for logging and debugging.
+ */
+ public static String processStateToString(@BatteryConsumer.ProcessState int processState) {
+ return sProcessStateNames[processState];
+ }
+
+ /**
* Prints the stats in a human-readable format.
*/
public void dump(PrintWriter pw) {
@@ -347,6 +562,44 @@
return new BatteryConsumerData(cursorWindow, cursorRow, layout);
}
+ public Key[] getKeys(int componentId) {
+ return layout.keys[componentId];
+ }
+
+ Key getKeyOrThrow(int componentId, int processState) {
+ Key key = getKey(componentId, processState);
+ if (key == null) {
+ if (processState == PROCESS_STATE_ANY) {
+ throw new IllegalArgumentException(
+ "Unsupported power component ID: " + componentId);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported power component ID: " + componentId
+ + " process state: " + processState);
+ }
+ }
+ return key;
+ }
+
+ Key getKey(int componentId, int processState) {
+ if (componentId >= POWER_COMPONENT_COUNT) {
+ return null;
+ }
+
+ if (processState == PROCESS_STATE_ANY) {
+ // The 0-th key for each component corresponds to the roll-up,
+ // across all dimensions. We might as well skip the iteration over the array.
+ return layout.keys[componentId][0];
+ } else {
+ for (Key key : layout.keys[componentId]) {
+ if (key.processState == processState) {
+ return key;
+ }
+ }
+ }
+ return null;
+ }
+
void putInt(int columnIndex, int value) {
if (mCursorRow == -1) {
return;
@@ -405,59 +658,91 @@
}
static class BatteryConsumerDataLayout {
+ private static final Key[] KEY_ARRAY = new Key[0];
public final String[] customPowerComponentNames;
public final int customPowerComponentCount;
public final boolean powerModelsIncluded;
-
- public final int consumedPowerColumn;
- public final int firstConsumedPowerColumn;
+ public final boolean processStateDataIncluded;
+ public final Key[][] keys;
+ public final int totalConsumedPowerColumnIndex;
public final int firstCustomConsumedPowerColumn;
- public final int firstUsageDurationColumn;
public final int firstCustomUsageDurationColumn;
- public final int firstPowerModelColumn;
public final int columnCount;
private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
- boolean powerModelsIncluded) {
+ boolean powerModelsIncluded, boolean includeProcessStateData) {
this.customPowerComponentNames = customPowerComponentNames;
this.customPowerComponentCount = customPowerComponentNames.length;
this.powerModelsIncluded = powerModelsIncluded;
+ this.processStateDataIncluded = includeProcessStateData;
int columnIndex = firstColumn;
- consumedPowerColumn = columnIndex++;
- firstConsumedPowerColumn = columnIndex;
- columnIndex += BatteryConsumer.POWER_COMPONENT_COUNT;
+ totalConsumedPowerColumnIndex = columnIndex++;
+
+ keys = new Key[POWER_COMPONENT_COUNT][];
+
+ ArrayList<Key> perComponentKeys = new ArrayList<>();
+ for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+ perComponentKeys.clear();
+
+ // Declare the Key for the power component, ignoring other dimensions.
+ perComponentKeys.add(
+ new Key(componentId, PROCESS_STATE_ANY,
+ powerModelsIncluded ? columnIndex++ : -1, // power model
+ columnIndex++, // power
+ columnIndex++ // usage duration
+ ));
+
+ // Declare Keys for all process states, if needed
+ if (includeProcessStateData) {
+ boolean isSupported = false;
+ for (int id : SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE) {
+ if (id == componentId) {
+ isSupported = true;
+ break;
+ }
+ }
+ if (isSupported) {
+ for (int processState = 0; processState < PROCESS_STATE_COUNT;
+ processState++) {
+ if (processState == PROCESS_STATE_ANY) {
+ continue;
+ }
+
+ perComponentKeys.add(
+ new Key(componentId, processState,
+ powerModelsIncluded ? columnIndex++ : -1, // power model
+ columnIndex++, // power
+ columnIndex++ // usage duration
+ ));
+ }
+ }
+ }
+
+ keys[componentId] = perComponentKeys.toArray(KEY_ARRAY);
+ }
firstCustomConsumedPowerColumn = columnIndex;
columnIndex += customPowerComponentCount;
- firstUsageDurationColumn = columnIndex;
- columnIndex += BatteryConsumer.POWER_COMPONENT_COUNT;
-
firstCustomUsageDurationColumn = columnIndex;
columnIndex += customPowerComponentCount;
- if (powerModelsIncluded) {
- firstPowerModelColumn = columnIndex;
- columnIndex += BatteryConsumer.POWER_COMPONENT_COUNT;
- } else {
- firstPowerModelColumn = -1;
- }
-
columnCount = columnIndex;
}
}
static BatteryConsumerDataLayout createBatteryConsumerDataLayout(
- String[] customPowerComponentNames, boolean includePowerModels) {
+ String[] customPowerComponentNames, boolean includePowerModels,
+ boolean includeProcessStateData) {
int columnCount = BatteryConsumer.COLUMN_COUNT;
columnCount = Math.max(columnCount, AggregateBatteryConsumer.COLUMN_COUNT);
columnCount = Math.max(columnCount, UidBatteryConsumer.COLUMN_COUNT);
columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT);
return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames,
- includePowerModels);
+ includePowerModels, includeProcessStateData);
}
protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
@@ -471,6 +756,11 @@
mPowerComponentsBuilder = new PowerComponents.Builder(data);
}
+ @Nullable
+ public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
+ return mData.getKey(componentId, processState);
+ }
+
/**
* Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
*
@@ -494,7 +784,15 @@
@NonNull
public T setConsumedPower(@PowerComponent int componentId, double componentPower,
@PowerModel int powerModel) {
- mPowerComponentsBuilder.setConsumedPower(componentId, componentPower, powerModel);
+ mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_ANY),
+ componentPower, powerModel);
+ return (T) this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
+ mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel);
return (T) this;
}
@@ -522,7 +820,16 @@
@NonNull
public T setUsageDurationMillis(@UidBatteryConsumer.PowerComponent int componentId,
long componentUsageTimeMillis) {
- mPowerComponentsBuilder.setUsageDurationMillis(componentId, componentUsageTimeMillis);
+ mPowerComponentsBuilder.setUsageDurationMillis(getKey(componentId, PROCESS_STATE_ANY),
+ componentUsageTimeMillis);
+ return (T) this;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setUsageDurationMillis(Key key, long componentUsageTimeMillis) {
+ mPowerComponentsBuilder.setUsageDurationMillis(key, componentUsageTimeMillis);
return (T) this;
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index dd387da..30613b9 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -654,6 +654,11 @@
}
/**
+ * Returns true if battery consumption is tracked on a per-process-state basis.
+ */
+ public abstract boolean isProcessStateDataAvailable();
+
+ /**
* The statistics associated with a particular uid.
*/
public static abstract class Uid {
@@ -2309,6 +2314,38 @@
public abstract Timer getScreenBrightnessTimer(int brightnessBin);
/**
+ * Returns the number of physical displays on the device.
+ *
+ * {@hide}
+ */
+ public abstract int getDisplayCount();
+
+ /**
+ * Returns the time in microseconds that the screen has been on for a display while the
+ * device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenOnTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been dozing while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been on with the given brightness
+ * level while the device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs);
+
+ /**
* Returns the time in microseconds that power save mode has been enabled while the device was
* running on battery.
*
@@ -5044,6 +5081,71 @@
pw.println(sb.toString());
}
+ final int numDisplays = getDisplayCount();
+ if (numDisplays > 1) {
+ pw.println("");
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY START");
+ pw.println(sb.toString());
+
+ for (int display = 0; display < numDisplays; display++) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Display ");
+ sb.append(display);
+ sb.append(" Statistics:");
+ pw.println(sb.toString());
+
+ final long displayScreenOnTime = getDisplayScreenOnTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen on: ");
+ formatTimeMs(sb, displayScreenOnTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenOnTime, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(" Screen brightness levels:");
+ didOne = false;
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ final long timeUs = getDisplayScreenBrightnessTime(display, bin, rawRealtime);
+ if (timeUs == 0) {
+ continue;
+ }
+ didOne = true;
+ sb.append("\n ");
+ sb.append(prefix);
+ sb.append(SCREEN_BRIGHTNESS_NAMES[bin]);
+ sb.append(" ");
+ formatTimeMs(sb, timeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeUs, displayScreenOnTime));
+ sb.append(")");
+ }
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
+
+ final long displayScreenDozeTimeUs = getDisplayScreenDozeTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen Doze: ");
+ formatTimeMs(sb, displayScreenDozeTimeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenDozeTimeUs, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+ }
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY END");
+ pw.println(sb.toString());
+ }
+
pw.println("");
pw.print(prefix);
sb.setLength(0);
@@ -5309,6 +5411,7 @@
new BatteryUsageStatsQuery.Builder()
.setMaxStatsAgeMs(0)
.includePowerModels()
+ .includeProcessStateData()
.build());
stats.dump(pw, prefix);
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index d37c0eb..ed44fb6 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -98,8 +98,10 @@
static final String XML_ATTR_USER_ID = "user_id";
static final String XML_ATTR_SCOPE = "scope";
static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_";
+ static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data";
static final String XML_ATTR_START_TIMESTAMP = "start_timestamp";
static final String XML_ATTR_END_TIMESTAMP = "end_timestamp";
+ static final String XML_ATTR_PROCESS_STATE = "process_state";
static final String XML_ATTR_POWER = "power";
static final String XML_ATTR_DURATION = "duration";
static final String XML_ATTR_MODEL = "model";
@@ -129,6 +131,7 @@
private final long mChargeTimeRemainingMs;
private final String[] mCustomPowerComponentNames;
private final boolean mIncludesPowerModels;
+ private final boolean mIncludesProcessStateData;
private final List<UidBatteryConsumer> mUidBatteryConsumers;
private final List<UserBatteryConsumer> mUserBatteryConsumers;
private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
@@ -148,6 +151,7 @@
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
mIncludesPowerModels = builder.mIncludePowerModels;
+ mIncludesProcessStateData = builder.mIncludesProcessStateData;
mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow;
double totalPowerMah = 0;
@@ -288,6 +292,10 @@
return mCustomPowerComponentNames;
}
+ public boolean isProcessStateDataIncluded() {
+ return mIncludesProcessStateData;
+ }
+
/**
* Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
*/
@@ -317,11 +325,12 @@
mChargeTimeRemainingMs = source.readLong();
mCustomPowerComponentNames = source.readStringArray();
mIncludesPowerModels = source.readBoolean();
+ mIncludesProcessStateData = source.readBoolean();
mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
BatteryConsumer.BatteryConsumerDataLayout dataLayout =
BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames,
- mIncludesPowerModels);
+ mIncludesPowerModels, mIncludesProcessStateData);
final int numRows = mBatteryConsumersCursorWindow.getNumRows();
@@ -376,6 +385,8 @@
dest.writeLong(mChargeTimeRemainingMs);
dest.writeStringArray(mCustomPowerComponentNames);
dest.writeBoolean(mIncludesPowerModels);
+ dest.writeBoolean(mIncludesProcessStateData);
+
mBatteryConsumersCursorWindow.writeToParcel(dest, flags);
if (mHistoryBuffer != null) {
@@ -537,16 +548,22 @@
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- final double devicePowerMah = deviceConsumer.getConsumedPower(componentId);
- final double appsPowerMah = appsConsumer.getConsumedPower(componentId);
- if (devicePowerMah == 0 && appsPowerMah == 0) {
- continue;
- }
+ for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) {
+ final double devicePowerMah = deviceConsumer.getConsumedPower(key);
+ final double appsPowerMah = appsConsumer.getConsumedPower(key);
+ if (devicePowerMah == 0 && appsPowerMah == 0) {
+ continue;
+ }
- final String componentName = BatteryConsumer.powerComponentIdToString(componentId);
- printPowerComponent(pw, prefix, componentName, devicePowerMah, appsPowerMah,
- deviceConsumer.getPowerModel(componentId),
- deviceConsumer.getUsageDurationMillis(componentId));
+ String label = BatteryConsumer.powerComponentIdToString(componentId);
+ if (key.processState != BatteryConsumer.PROCESS_STATE_ANY) {
+ label = label
+ + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
+ }
+ printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah,
+ deviceConsumer.getPowerModel(key),
+ deviceConsumer.getUsageDurationMillis(key));
+ }
}
for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
@@ -571,10 +588,10 @@
dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
}
- private void printPowerComponent(PrintWriter pw, String prefix, String componentName,
+ private void printPowerComponent(PrintWriter pw, String prefix, String label,
double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
StringBuilder sb = new StringBuilder();
- sb.append(prefix).append(" ").append(componentName).append(": ")
+ sb.append(prefix).append(" ").append(label).append(": ")
.append(PowerCalculator.formatCharge(devicePowerMah));
if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
&& powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
@@ -615,7 +632,8 @@
serializer.attribute(null, XML_ATTR_PREFIX_CUSTOM_COMPONENT + i,
mCustomPowerComponentNames[i]);
}
-
+ serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA,
+ mIncludesProcessStateData);
serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs);
serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs);
serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs);
@@ -659,7 +677,11 @@
i++;
}
- builder = new Builder(customComponentNames.toArray(new String[0]), true);
+ final boolean includesProcStateData = parser.getAttributeBoolean(null,
+ XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
+
+ builder = new Builder(customComponentNames.toArray(new String[0]), true,
+ includesProcStateData);
builder.setStatsStartTimestamp(
parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
@@ -731,6 +753,7 @@
@NonNull
private final String[] mCustomPowerComponentNames;
private final boolean mIncludePowerModels;
+ private final boolean mIncludesProcessStateData;
private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private long mStatsStartTimestampMs;
private long mStatsEndTimestampMs;
@@ -750,19 +773,21 @@
private Parcel mHistoryBuffer;
public Builder(@NonNull String[] customPowerComponentNames) {
- this(customPowerComponentNames, false);
+ this(customPowerComponentNames, false, false);
}
- public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) {
+ public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
+ boolean includeProcessStateData) {
mBatteryConsumersCursorWindow =
new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
mBatteryConsumerDataLayout =
BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames,
- includePowerModels);
+ includePowerModels, includeProcessStateData);
mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
mCustomPowerComponentNames = customPowerComponentNames;
mIncludePowerModels = includePowerModels;
+ mIncludesProcessStateData = includeProcessStateData;
for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
@@ -772,6 +797,10 @@
}
}
+ public boolean isProcessStateDataNeeded() {
+ return mIncludesProcessStateData;
+ }
+
/**
* Constructs a read-only object using the Builder values.
*/
@@ -954,6 +983,11 @@
"BatteryUsageStats have different custom power components");
}
+ if (mIncludesProcessStateData && !stats.mIncludesProcessStateData) {
+ throw new IllegalArgumentException(
+ "Added BatteryUsageStats does not include process state data");
+ }
+
if (mUserBatteryConsumerBuilders.size() != 0
|| !stats.getUserBatteryConsumers().isEmpty()) {
throw new UnsupportedOperationException(
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 97f24cc..187b64f 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -41,6 +41,7 @@
@IntDef(flag = true, prefix = { "FLAG_BATTERY_USAGE_STATS_" }, value = {
FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL,
FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY,
+ FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BatteryUsageStatsFlags {}
@@ -52,19 +53,21 @@
*
* @hide
*/
- public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 1;
+ public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 0x0001;
/**
* Indicates that battery history should be included in the BatteryUsageStats.
* @hide
*/
- public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 2;
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 0x0002;
/**
* Indicates that identifiers of power models used for computations of power
* consumption should be included in the BatteryUsageStats.
*/
- public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS = 4;
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS = 0x0004;
+
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA = 0x0008;
private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
@@ -209,6 +212,16 @@
}
/**
+ * Requests that per-process state data be included in the BatteryUsageStats, if
+ * available. Check {@link BatteryUsageStats#isProcessStateDataIncluded()} on the result
+ * to see if the data is available.
+ */
+ public Builder includeProcessStateData() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA;
+ return this;
+ }
+
+ /**
* Requests to return modeled battery usage stats only, even if on-device
* power monitoring data is available.
*
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e7c3a83..92861fb 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,18 +1,18 @@
# Haptics
-per-file CombinedVibrationEffect.aidl = michaelwr@google.com
-per-file CombinedVibrationEffect.java = michaelwr@google.com
-per-file ExternalVibration.aidl = michaelwr@google.com
-per-file ExternalVibration.java = michaelwr@google.com
-per-file IExternalVibrationController.aidl = michaelwr@google.com
-per-file IExternalVibratorService.aidl = michaelwr@google.com
-per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file NullVibrator.java = michaelwr@google.com
-per-file SystemVibrator.java = michaelwr@google.com
-per-file SystemVibratorManager.java = michaelwr@google.com
-per-file VibrationEffect.aidl = michaelwr@google.com
-per-file VibrationEffect.java = michaelwr@google.com
-per-file Vibrator.java = michaelwr@google.com
-per-file VibratorManager.java = michaelwr@google.com
+per-file CombinedVibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file CombinedVibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file ExternalVibration.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file ExternalVibration.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file IExternalVibrationController.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file IExternalVibratorService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file IVibratorManagerService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file NullVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file SystemVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file SystemVibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file Vibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
# PowerManager
per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 44d51db..ab2c8c0 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -384,6 +384,8 @@
long thisNativePtr, long otherNativePtr, int offset, int length);
@CriticalNative
private static native boolean nativeHasFileDescriptors(long nativePtr);
+ private static native boolean nativeHasFileDescriptorsInRange(
+ long nativePtr, int offset, int length);
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
@@ -399,7 +401,7 @@
private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
@CriticalNative
- private static native long nativeGetBlobAshmemSize(long nativePtr);
+ private static native long nativeGetOpenAshmemSize(long nativePtr);
public final static Parcelable.Creator<String> STRING_CREATOR
= new Parcelable.Creator<String>() {
@@ -717,11 +719,26 @@
/**
* Report whether the parcel contains any marshalled file descriptors.
*/
- public final boolean hasFileDescriptors() {
+ public boolean hasFileDescriptors() {
return nativeHasFileDescriptors(mNativePtr);
}
/**
+ * Report whether the parcel contains any marshalled file descriptors in the range defined by
+ * {@code offset} and {@code length}.
+ *
+ * @param offset The offset from which the range starts. Should be between 0 and
+ * {@link #dataSize()}.
+ * @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code
+ * offset}.
+ * @return whether there are file descriptors or not.
+ * @throws IllegalArgumentException if the parameters are out of the permitted ranges.
+ */
+ public boolean hasFileDescriptors(int offset, int length) {
+ return nativeHasFileDescriptorsInRange(mNativePtr, offset, length);
+ }
+
+ /**
* Check if the object used in {@link #readValue(ClassLoader)} / {@link #writeValue(Object)}
* has file descriptors.
*
@@ -2874,9 +2891,11 @@
/**
* Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
- * the type required for each item. If the item to be deserialized is not an instance
- * of that class or any of its children class
- * a {@link BadParcelableException} will be thrown.
+ * the type required for each item.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
*/
public <T> void readList(@NonNull List<? super T> outVal,
@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
@@ -3536,15 +3555,26 @@
int start = dataPosition();
int type = readInt();
if (isLengthPrefixed(type)) {
- int length = readInt();
- setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
- return new LazyValue(this, start, length, type, loader);
+ int objectLength = readInt();
+ int end = MathUtils.addOrThrow(dataPosition(), objectLength);
+ int valueLength = end - start;
+ setDataPosition(end);
+ return new LazyValue(this, start, valueLength, type, loader);
} else {
return readValue(type, loader, /* clazz */ null);
}
}
+
private static final class LazyValue implements Supplier<Object> {
+ /**
+ * | 4B | 4B |
+ * mSource = Parcel{... | type | length | object | ...}
+ * a b c d
+ * length = d - c
+ * mPosition = a
+ * mLength = d - a
+ */
private final int mPosition;
private final int mLength;
private final int mType;
@@ -3592,7 +3622,7 @@
public void writeToParcel(Parcel out) {
Parcel source = mSource;
if (source != null) {
- out.appendFrom(source, mPosition, mLength + 8);
+ out.appendFrom(source, mPosition, mLength);
} else {
out.writeValue(mObject);
}
@@ -3601,7 +3631,7 @@
public boolean hasFileDescriptors() {
Parcel source = mSource;
return (source != null)
- ? getValueParcel(source).hasFileDescriptors()
+ ? source.hasFileDescriptors(mPosition, mLength)
: Parcel.hasFileDescriptors(mObject);
}
@@ -3662,10 +3692,7 @@
Parcel parcel = mValueParcel;
if (parcel == null) {
parcel = Parcel.obtain();
- // mLength is the length of object representation, excluding the type and length.
- // mPosition is the position of the entire value container, right before the type.
- // So, we add 4 bytes for the type + 4 bytes for the length written.
- parcel.appendFrom(source, mPosition, mLength + 8);
+ parcel.appendFrom(source, mPosition, mLength);
mValueParcel = parcel;
}
return parcel;
@@ -3869,8 +3896,11 @@
/**
* Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
- * required for each item. If the item to be deserialized is not an instance of that class or
- * any of its children classes a {@link BadParcelableException} will be thrown.
+ * required for each item.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
*/
@Nullable
public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
@@ -3936,8 +3966,11 @@
/**
* Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
- * as the required type. If the item to be deserialized is not an instance of that class
- * or any of its children classes a {@link BadParcelableException} will be thrown.
+ * as the required type.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children class or there there was an error
+ * trying to read the {@link Parcelable.Creator}.
*/
@Nullable
public <T> Parcelable.Creator<T> readParcelableCreator(
@@ -4348,8 +4381,8 @@
/**
* @hide For testing
*/
- public long getBlobAshmemSize() {
- return nativeGetBlobAshmemSize(mNativePtr);
+ public long getOpenAshmemSize() {
+ return nativeGetOpenAshmemSize(mNativePtr);
}
private static String valueTypeToString(int type) {
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 259e673..4b12aa6 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -15,6 +15,9 @@
*/
package android.os;
+import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
+import static android.os.BatteryConsumer.POWER_COMPONENT_COUNT;
+import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
import android.annotation.NonNull;
@@ -49,25 +52,41 @@
}
/**
- * Total power consumed by this consumer, in mAh.
+ * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
*/
- public double getConsumedPower() {
- return mData.getDouble(mData.layout.consumedPowerColumn);
+ public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
+ if (dimensions.powerComponent != POWER_COMPONENT_ANY) {
+ return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
+ dimensions.processState).mPowerColumnIndex);
+ } else if (dimensions.processState != PROCESS_STATE_ANY) {
+ boolean foundSome = false;
+ double totalPowerMah = 0;
+ for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+ BatteryConsumer.Key key = mData.getKey(componentId, dimensions.processState);
+ if (key != null) {
+ foundSome = true;
+ totalPowerMah += mData.getDouble(key.mPowerColumnIndex);
+ }
+ }
+ if (!foundSome) {
+ throw new IllegalArgumentException(
+ "No data included in BatteryUsageStats for " + dimensions);
+ }
+ return totalPowerMah;
+ } else {
+ return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
+ }
}
/**
* Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
*
- * @param componentId The ID of the power component, e.g.
- * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
+ * or {@link BatteryConsumer#getKeys} method.
* @return Amount of consumed power in mAh.
*/
- public double getConsumedPower(@BatteryConsumer.PowerComponent int componentId) {
- if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
- throw new IllegalArgumentException(
- "Unsupported power component ID: " + componentId);
- }
- return mData.getDouble(mData.layout.firstConsumedPowerColumn + componentId);
+ public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
+ return mData.getDouble(key.mPowerColumnIndex);
}
/**
@@ -102,27 +121,23 @@
}
@BatteryConsumer.PowerModel
- int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
- if (!mData.layout.powerModelsIncluded) {
+ int getPowerModel(BatteryConsumer.Key key) {
+ if (key.mPowerModelColumnIndex == -1) {
throw new IllegalStateException(
"Power model IDs were not requested in the BatteryUsageStatsQuery");
}
- return mData.getInt(mData.layout.firstPowerModelColumn + componentId);
+ return mData.getInt(key.mPowerModelColumnIndex);
}
/**
* Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
*
- * @param componentId The ID of the power component, e.g.
- * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
+ * or {@link BatteryConsumer#getKeys} method.
* @return Amount of time in milliseconds.
*/
- public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int componentId) {
- if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
- throw new IllegalArgumentException(
- "Unsupported power component ID: " + componentId);
- }
- return mData.getLong(mData.layout.firstUsageDurationColumn + componentId);
+ public long getUsageDurationMillis(BatteryConsumer.Key key) {
+ return mData.getLong(key.mDurationColumnIndex);
}
/**
@@ -143,17 +158,29 @@
public void dump(PrintWriter pw, boolean skipEmptyComponents) {
String separator = "";
+ StringBuilder sb = new StringBuilder();
+
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- final double componentPower = getConsumedPower(componentId);
- if (skipEmptyComponents && componentPower == 0) {
- continue;
+ for (BatteryConsumer.Key key: mData.getKeys(componentId)) {
+ final double componentPower = getConsumedPower(key);
+ final long durationMs = getUsageDurationMillis(key);
+ if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
+ continue;
+ }
+
+ sb.append(separator);
+ separator = " ";
+ sb.append(key.toShortString());
+ sb.append("=");
+ sb.append(PowerCalculator.formatCharge(componentPower));
+
+ if (durationMs != 0) {
+ sb.append(" (");
+ BatteryStats.formatTimeMsNoSpace(sb, durationMs);
+ sb.append(")");
+ }
}
- pw.print(separator);
- separator = " ";
- pw.print(BatteryConsumer.powerComponentIdToString(componentId));
- pw.print("=");
- PowerCalculator.printPowerMah(pw, componentPower);
}
final int customComponentCount = mData.layout.customPowerComponentCount;
@@ -166,12 +193,14 @@
if (skipEmptyComponents && customComponentPower == 0) {
continue;
}
- pw.print(separator);
+ sb.append(separator);
separator = " ";
- pw.print(getCustomPowerComponentName(customComponentId));
- pw.print("=");
- PowerCalculator.printPowerMah(pw, customComponentPower);
+ sb.append(getCustomPowerComponentName(customComponentId));
+ sb.append("=");
+ sb.append(PowerCalculator.formatCharge(customComponentPower));
}
+
+ pw.print(sb);
}
/** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
@@ -193,8 +222,10 @@
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(componentId));
- final long durationMs = getUsageDurationMillis(componentId);
+
+ final BatteryConsumer.Key key = mData.getKey(componentId, PROCESS_STATE_ANY);
+ final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
+ final long durationMs = getUsageDurationMillis(key);
if (powerDeciCoulombs == 0 && durationMs == 0) {
// No interesting data. Make sure not to even write the COMPONENT int.
@@ -254,25 +285,32 @@
serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- final double powerMah = getConsumedPower(componentId);
- final long durationMs = getUsageDurationMillis(componentId);
- if (powerMah == 0 && durationMs == 0) {
- continue;
- }
+ final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
+ for (BatteryConsumer.Key key : keys) {
+ final double powerMah = getConsumedPower(key);
+ final long durationMs = getUsageDurationMillis(key);
+ if (powerMah == 0 && durationMs == 0) {
+ continue;
+ }
- serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
- if (powerMah != 0) {
- serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
+ if (key.processState != PROCESS_STATE_ANY) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
+ key.processState);
+ }
+ if (powerMah != 0) {
+ serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
+ }
+ if (durationMs != 0) {
+ serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
+ }
+ if (mData.layout.powerModelsIncluded) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
+ getPowerModel(key));
+ }
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
}
- if (durationMs != 0) {
- serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
- }
- if (mData.layout.powerModelsIncluded) {
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
- getPowerModel(componentId));
- }
- serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
}
final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
@@ -316,6 +354,7 @@
switch (parser.getName()) {
case BatteryUsageStats.XML_TAG_COMPONENT: {
int componentId = -1;
+ int processState = PROCESS_STATE_ANY;
double powerMah = 0;
long durationMs = 0;
int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
@@ -324,6 +363,9 @@
case BatteryUsageStats.XML_ATTR_ID:
componentId = parser.getAttributeInt(i);
break;
+ case BatteryUsageStats.XML_ATTR_PROCESS_STATE:
+ processState = parser.getAttributeInt(i);
+ break;
case BatteryUsageStats.XML_ATTR_POWER:
powerMah = parser.getAttributeDouble(i);
break;
@@ -335,8 +377,10 @@
break;
}
}
- builder.setConsumedPower(componentId, powerMah, model);
- builder.setUsageDurationMillis(componentId, durationMs);
+ final BatteryConsumer.Key key =
+ builder.mData.getKey(componentId, processState);
+ builder.setConsumedPower(key, powerMah, model);
+ builder.setUsageDurationMillis(key, durationMs);
break;
}
case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: {
@@ -376,33 +420,22 @@
Builder(BatteryConsumer.BatteryConsumerData data) {
mData = data;
- if (mData.layout.powerModelsIncluded) {
- for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) {
- mData.putLong(mData.layout.firstPowerModelColumn + i,
- POWER_MODEL_UNINITIALIZED);
+ for (BatteryConsumer.Key[] keys : mData.layout.keys) {
+ for (BatteryConsumer.Key key : keys) {
+ if (key.mPowerModelColumnIndex != -1) {
+ mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
+ }
}
}
}
- /**
- * Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
- *
- * @param componentId The ID of the power component, e.g.
- * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
- * @param componentPower Amount of consumed power in mAh.
- */
@NonNull
- public Builder setConsumedPower(@BatteryConsumer.PowerComponent int componentId,
- double componentPower, @BatteryConsumer.PowerModel int powerModel) {
- if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
- throw new IllegalArgumentException(
- "Unsupported power component ID: " + componentId);
+ public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
+ int powerModel) {
+ mData.putDouble(key.mPowerColumnIndex, componentPower);
+ if (key.mPowerModelColumnIndex != -1) {
+ mData.putInt(key.mPowerModelColumnIndex, powerModel);
}
- mData.putDouble(mData.layout.firstConsumedPowerColumn + componentId, componentPower);
- if (mData.layout.powerModelsIncluded) {
- mData.putLong(mData.layout.firstPowerModelColumn + componentId, powerModel);
- }
-
return this;
}
@@ -423,22 +456,10 @@
return this;
}
- /**
- * Sets the amount of time used by the specified component, e.g. CPU, WiFi etc.
- *
- * @param componentId The ID of the power component, e.g.
- * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
- * @param componentUsageDurationMillis Amount of time in milliseconds.
- */
@NonNull
- public Builder setUsageDurationMillis(@BatteryConsumer.PowerComponent int componentId,
+ public Builder setUsageDurationMillis(BatteryConsumer.Key key,
long componentUsageDurationMillis) {
- if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
- throw new IllegalArgumentException(
- "Unsupported power component ID: " + componentId);
- }
- mData.putLong(mData.layout.firstUsageDurationColumn + componentId,
- componentUsageDurationMillis);
+ mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis);
return this;
}
@@ -474,47 +495,75 @@
if (mData.layout.customPowerComponentCount
!= otherData.layout.customPowerComponentCount) {
throw new IllegalArgumentException(
- "Number of power components does not match: "
+ "Number of custom power components does not match: "
+ otherData.layout.customPowerComponentCount
+ ", expected: " + mData.layout.customPowerComponentCount);
}
- for (int i = BatteryConsumer.POWER_COMPONENT_COUNT - 1; i >= 0; i--) {
- final int powerColumnIndex = mData.layout.firstConsumedPowerColumn + i;
- mData.putDouble(powerColumnIndex,
- mData.getDouble(powerColumnIndex)
- + otherData.getDouble(powerColumnIndex));
+ for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0;
+ componentId--) {
+ final BatteryConsumer.Key[] keys = mData.layout.keys[componentId];
+ for (BatteryConsumer.Key key: keys) {
+ BatteryConsumer.Key otherKey = null;
+ for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) {
+ if (aKey.equals(key)) {
+ otherKey = aKey;
+ break;
+ }
+ }
- final int durationColumnIndex = mData.layout.firstUsageDurationColumn + i;
- mData.putLong(durationColumnIndex,
- mData.getLong(durationColumnIndex)
- + otherData.getLong(durationColumnIndex));
+ if (otherKey == null) {
+ continue;
+ }
+
+ mData.putDouble(key.mPowerColumnIndex,
+ mData.getDouble(key.mPowerColumnIndex)
+ + otherData.getDouble(otherKey.mPowerColumnIndex));
+ mData.putLong(key.mDurationColumnIndex,
+ mData.getLong(key.mDurationColumnIndex)
+ + otherData.getLong(otherKey.mDurationColumnIndex));
+
+ if (key.mPowerModelColumnIndex == -1) {
+ continue;
+ }
+
+ boolean undefined = false;
+ if (otherKey.mPowerModelColumnIndex == -1) {
+ undefined = true;
+ } else {
+ final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
+ int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
+ if (powerModel == POWER_MODEL_UNINITIALIZED) {
+ mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
+ } else if (powerModel != otherPowerModel
+ && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
+ undefined = true;
+ }
+ }
+
+ if (undefined) {
+ mData.putInt(key.mPowerModelColumnIndex,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
+ }
}
for (int i = mData.layout.customPowerComponentCount - 1; i >= 0; i--) {
final int powerColumnIndex = mData.layout.firstCustomConsumedPowerColumn + i;
+ final int otherPowerColumnIndex =
+ otherData.layout.firstCustomConsumedPowerColumn + i;
mData.putDouble(powerColumnIndex,
- mData.getDouble(powerColumnIndex) + otherData.getDouble(powerColumnIndex));
+ mData.getDouble(powerColumnIndex) + otherData.getDouble(
+ otherPowerColumnIndex));
final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
+ final int otherDurationColumnIndex =
+ otherData.layout.firstCustomUsageDurationColumn + i;
mData.putLong(usageColumnIndex,
- mData.getLong(usageColumnIndex) + otherData.getLong(usageColumnIndex)
+ mData.getLong(usageColumnIndex) + otherData.getLong(
+ otherDurationColumnIndex)
);
}
-
- if (mData.layout.powerModelsIncluded && otherData.layout.powerModelsIncluded) {
- for (int i = BatteryConsumer.POWER_COMPONENT_COUNT - 1; i >= 0; i--) {
- final int columnIndex = mData.layout.firstPowerModelColumn + i;
- int powerModel = mData.getInt(columnIndex);
- int otherPowerModel = otherData.getInt(columnIndex);
- if (powerModel == POWER_MODEL_UNINITIALIZED) {
- mData.putLong(columnIndex, otherPowerModel);
- } else if (powerModel != otherPowerModel
- && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
- mData.putLong(columnIndex, BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
- }
- }
}
/**
@@ -525,11 +574,12 @@
double totalPowerMah = 0;
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- totalPowerMah +=
- mData.getDouble(mData.layout.firstConsumedPowerColumn + componentId);
+ totalPowerMah += mData.getDouble(
+ mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex);
}
for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
- totalPowerMah += mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + i);
+ totalPowerMah += mData.getDouble(
+ mData.layout.firstCustomConsumedPowerColumn + i);
}
return totalPowerMah;
}
@@ -539,14 +589,15 @@
*/
@NonNull
public PowerComponents build() {
- mData.putDouble(mData.layout.consumedPowerColumn, getTotalPower());
+ mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
- if (mData.layout.powerModelsIncluded) {
- for (int i = BatteryConsumer.POWER_COMPONENT_COUNT - 1; i >= 0; i--) {
- final int powerModel = mData.getInt(mData.layout.firstPowerModelColumn + i);
- if (powerModel == POWER_MODEL_UNINITIALIZED) {
- mData.putInt(mData.layout.firstPowerModelColumn + i,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ for (BatteryConsumer.Key[] keys : mData.layout.keys) {
+ for (BatteryConsumer.Key key : keys) {
+ if (key.mPowerModelColumnIndex != -1) {
+ if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
+ mData.putInt(key.mPowerModelColumnIndex,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
}
}
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 2e41382..753f349 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -2094,14 +2095,14 @@
* Returns true if the device is currently in light idle mode. This happens when a device
* has had its screen off for a short time, switching it into a batching mode where we
* execute jobs, syncs, networking on a batching schedule. You can monitor for changes to
- * this state with {@link #ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED}.
+ * this state with {@link #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED}.
*
- * @return Returns true if currently in active light device idle mode, else false. This is
+ * @return Returns true if currently in active device light idle mode, else false. This is
* when light idle mode restrictions are being actively applied; it will return false if the
* device is in a long-term idle mode but currently running a maintenance window where
* restrictions have been lifted.
*/
- public boolean isLightDeviceIdleMode() {
+ public boolean isDeviceLightIdleMode() {
try {
return mService.isLightDeviceIdleMode();
} catch (RemoteException e) {
@@ -2110,6 +2111,18 @@
}
/**
+ * @see #isDeviceLightIdleMode()
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S,
+ publicAlternatives = "Use {@link #isDeviceLightIdleMode()} instead.")
+ public boolean isLightDeviceIdleMode() {
+ return isDeviceLightIdleMode();
+ }
+
+ /**
* Return whether the given application package name is on the device's power allowlist.
* Apps can be placed on the allowlist through the settings UI invoked by
* {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -2559,12 +2572,26 @@
= "android.os.action.DEVICE_IDLE_MODE_CHANGED";
/**
- * Intent that is broadcast when the state of {@link #isLightDeviceIdleMode()} changes.
+ * Intent that is broadcast when the state of {@link #isDeviceLightIdleMode()} changes.
* This broadcast is only sent to registered receivers.
*/
+ @SuppressLint("ActionValue") // Need to do "LIGHT_DEVICE_IDLE..." for legacy reasons
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
- = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+ public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED =
+ // Use the old string so we don't break legacy apps.
+ "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+
+ /**
+ * @see #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553,
+ publicAlternatives = "Use {@link #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED} instead")
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED =
+ ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
/**
* @hide Intent that is broadcast when the set of power save allowlist apps has changed.
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0c3debb1..a243453 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -51,6 +51,7 @@
mRegisteredListeners = new ArrayMap<>();
private final Object mLock = new Object();
+ @GuardedBy("mLock")
private AllVibratorsInfo mVibratorInfo;
@UnsupportedAppUsage
@@ -73,7 +74,15 @@
int[] vibratorIds = mVibratorManager.getVibratorIds();
VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
for (int i = 0; i < vibratorIds.length; i++) {
- vibratorInfos[i] = mVibratorManager.getVibrator(vibratorIds[i]).getInfo();
+ Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]);
+ if (vibrator instanceof NullVibrator) {
+ Log.w(TAG, "Vibrator manager service not ready; "
+ + "Info not yet available for vibrator: " + vibratorIds[i]);
+ // This should never happen after the vibrator manager service is ready.
+ // Skip caching this vibrator until then.
+ return VibratorInfo.EMPTY_VIBRATOR_INFO;
+ }
+ vibratorInfos[i] = vibrator.getInfo();
}
return mVibratorInfo = new AllVibratorsInfo(vibratorInfos);
}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index f9f4463..1a082d1 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -106,16 +106,39 @@
@Override
public void dump(PrintWriter pw, boolean skipEmptyComponents) {
- final double consumedPower = getConsumedPower();
pw.print("UID ");
UserHandle.formatUid(pw, getUid());
pw.print(": ");
- PowerCalculator.printPowerMah(pw, consumedPower);
+ PowerCalculator.printPowerMah(pw, getConsumedPower());
+
+ if (mData.layout.processStateDataIncluded) {
+ StringBuilder sb = new StringBuilder();
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ skipEmptyComponents);
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ skipEmptyComponents);
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ skipEmptyComponents);
+ pw.print(sb);
+ }
+
pw.print(" ( ");
mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */);
pw.print(" ) ");
}
+ private void appendProcessStateData(StringBuilder sb, @ProcessState int processState,
+ boolean skipEmptyComponents) {
+ Dimensions dimensions = new Dimensions(POWER_COMPONENT_ANY, processState);
+ final double power = mPowerComponents.getConsumedPower(dimensions);
+ if (power == 0 && skipEmptyComponents) {
+ return;
+ }
+
+ sb.append(" ").append(processStateToString(processState)).append(": ")
+ .append(PowerCalculator.formatCharge(power));
+ }
+
static UidBatteryConsumer create(BatteryConsumerData data) {
return new UidBatteryConsumer(data);
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index feffcbd..aa9028e 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -165,7 +165,11 @@
return ctx != null ? ctx.getResources().getFloat(resId) : defaultValue;
}
- /** @hide */
+ /**
+ * Get the info describing this vibrator.
+ *
+ * @hide
+ */
protected VibratorInfo getInfo() {
return VibratorInfo.EMPTY_VIBRATOR_INFO;
}
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 486f9f1..cf51496 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -461,7 +461,8 @@
int supportedPrimitivesCount = mSupportedPrimitives.size();
String[] names = new String[supportedPrimitivesCount];
for (int i = 0; i < supportedPrimitivesCount; i++) {
- names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i));
+ names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i))
+ + "(" + mSupportedPrimitives.valueAt(i) + "ms)";
}
return names;
}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 4d46325..13b22d3 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -505,8 +505,8 @@
final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
signature.hashingInfo);
- final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
- signature.signingInfo);
+ final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
+ signature.signingInfos);
if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
@@ -520,7 +520,7 @@
if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
}
- if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
+ if (signingInfos.signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
throw new IOException(
"additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
}
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
index 688e3e9..2044502 100644
--- a/core/java/android/os/incremental/V4Signature.java
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -28,6 +28,7 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
/**
* V4 signature fields.
@@ -74,7 +75,7 @@
}
/**
- * V4 signature data.
+ * Signature data.
*/
public static class SigningInfo {
public final byte[] apkDigest; // used to match with the corresponding APK
@@ -98,7 +99,13 @@
* Constructs SigningInfo from byte array.
*/
public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
- ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+ return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+ }
+
+ /**
+ * Constructs SigningInfo from byte buffer.
+ */
+ public static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException {
byte[] apkDigest = readBytes(buffer);
byte[] certificate = readBytes(buffer);
byte[] additionalData = readBytes(buffer);
@@ -110,6 +117,62 @@
}
}
+ /**
+ * Optional signature data block with ID.
+ */
+ public static class SigningInfoBlock {
+ public final int blockId;
+ public final byte[] signingInfo;
+
+ public SigningInfoBlock(int blockId, byte[] signingInfo) {
+ this.blockId = blockId;
+ this.signingInfo = signingInfo;
+ }
+
+ static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException {
+ int blockId = buffer.getInt();
+ byte[] signingInfo = readBytes(buffer);
+ return new SigningInfoBlock(blockId, signingInfo);
+ }
+ }
+
+ /**
+ * V4 signature data.
+ */
+ public static class SigningInfos {
+ // Default signature.
+ public final SigningInfo signingInfo;
+ // Additional signatures corresponding to extended V2/V3/V31 blocks.
+ public final SigningInfoBlock[] signingInfoBlocks;
+
+ public SigningInfos(SigningInfo signingInfo) {
+ this.signingInfo = signingInfo;
+ this.signingInfoBlocks = new SigningInfoBlock[0];
+ }
+
+ public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) {
+ this.signingInfo = signingInfo;
+ this.signingInfoBlocks = signingInfoBlocks;
+ }
+
+ /**
+ * Constructs SigningInfos from byte array.
+ */
+ public static SigningInfos fromByteArray(byte[] bytes) throws IOException {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+ SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer);
+ if (!buffer.hasRemaining()) {
+ return new SigningInfos(signingInfo);
+ }
+ ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1);
+ while (buffer.hasRemaining()) {
+ signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer));
+ }
+ return new SigningInfos(signingInfo,
+ signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()]));
+ }
+ }
+
public final int version; // Always 2 for now.
/**
* Raw byte array containing the IncFS hashing data.
@@ -118,11 +181,11 @@
@Nullable public final byte[] hashingInfo;
/**
- * Raw byte array containing the V4 signature data.
+ * Raw byte array containing V4 signatures.
* <p>Passed as-is to the kernel. Can be retrieved later.
- * @see SigningInfo#fromByteArray(byte[])
+ * @see SigningInfos#fromByteArray(byte[])
*/
- @Nullable public final byte[] signingInfo;
+ @Nullable public final byte[] signingInfos;
/**
* Construct a V4Signature from .idsig file.
@@ -185,10 +248,10 @@
return this.version == SUPPORTED_VERSION;
}
- private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfo) {
+ private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfos) {
this.version = version;
this.hashingInfo = hashingInfo;
- this.signingInfo = signingInfo;
+ this.signingInfos = signingInfos;
}
private static V4Signature readFrom(InputStream stream) throws IOException {
@@ -205,7 +268,7 @@
private void writeTo(OutputStream stream) throws IOException {
writeIntLE(stream, this.version);
writeBytes(stream, this.hashingInfo);
- writeBytes(stream, this.signingInfo);
+ writeBytes(stream, this.signingInfos);
}
// Utility methods.
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index db9d4e2..d4c9ade 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -219,6 +219,39 @@
/**
* Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission. Call this method if you are the datasource which would not blame you
+ * for access to the data since you are the data.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(String, AttributionSource)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param permission The permission to check.
+ * @param attributionSource the permission identity
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(String, AttributionSource)
+ */
+ @PermissionCheckerManager.PermissionResult
+ public int checkPermissionForDataDeliveryFromDataSource(@NonNull String permission,
+ @NonNull AttributionSource attributionSource, @Nullable String message) {
+ return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(mContext, permission,
+ PermissionChecker.PID_UNKNOWN, attributionSource, message);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
* has a given permission.
*
* <strong>NOTE:</strong> Use this method only for permission checks at the
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 8553c24..0cc5bfd 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -706,6 +706,25 @@
/**
* Contains the recent calls.
+ * <p>
+ * Note: If you want to query the call log and limit the results to a single value, you should
+ * append the {@link #LIMIT_PARAM_KEY} parameter to the content URI. For example:
+ * <pre>
+ * {@code
+ * getContentResolver().query(
+ * Calls.CONTENT_URI.buildUpon().appendQueryParameter(LIMIT_PARAM_KEY, "1")
+ * .build(),
+ * null, null, null, null);
+ * }
+ * </pre>
+ * <p>
+ * The call log provider enforces strict SQL grammar, so you CANNOT append "LIMIT" to the SQL
+ * query as below:
+ * <pre>
+ * {@code
+ * getContentResolver().query(Calls.CONTENT_URI, null, "LIMIT 1", null, null);
+ * }
+ * </pre>
*/
public static class Calls implements BaseColumns {
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 5e66bff..f8aa98e 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -37,6 +37,7 @@
import android.content.ContextWrapper;
import android.content.CursorEntityIterator;
import android.content.Entity;
+import android.content.Entity.NamedContentValues;
import android.content.EntityIterator;
import android.content.Intent;
import android.content.IntentFilter;
@@ -55,6 +56,7 @@
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.Pair;
import android.view.View;
@@ -64,7 +66,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -5129,6 +5133,8 @@
*/
public final static class RawContactsEntity
implements BaseColumns, DataColumns, RawContactsColumns {
+ private static final String TAG = "ContactsContract.RawContactsEntity";
+
/**
* This utility class cannot be instantiated
*/
@@ -5181,6 +5187,73 @@
* <P>Type: INTEGER</P>
*/
public static final String DATA_ID = "data_id";
+
+ /**
+ * Query raw contacts entity by a contact ID, which can potentially be a corp profile
+ * contact ID
+ *
+ * @param context A context to get the ContentResolver from
+ * @param contactId Contact ID, which can potentialy be a corp profile contact ID.
+ *
+ * @return A map from a mimetype to a List of the entity content values.
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public static @NonNull Map<String, List<ContentValues>> queryRawContactEntity(
+ @NonNull ContentResolver contentResolver, long contactId) {
+ Uri uri = RawContactsEntity.CONTENT_URI;
+ long realContactId = contactId;
+
+ if (Contacts.isEnterpriseContactId(contactId)) {
+ uri = RawContactsEntity.CORP_CONTENT_URI;
+ realContactId = contactId - Contacts.ENTERPRISE_CONTACT_ID_BASE;
+ }
+ final Map<String, List<ContentValues>> contentValuesListMap =
+ new HashMap<String, List<ContentValues>>();
+ // The resolver may return the entity iterator with no data. It is possible.
+ // e.g. If all the data in the contact of the given contact id are not exportable ones,
+ // they are hidden from the view of this method, though contact id itself exists.
+ EntityIterator entityIterator = null;
+ try {
+ final String selection = Data.CONTACT_ID + "=?";
+ final String[] selectionArgs = new String[] {String.valueOf(realContactId)};
+
+ entityIterator = RawContacts.newEntityIterator(contentResolver.query(
+ uri, null, selection, selectionArgs, null));
+
+ if (entityIterator == null) {
+ Log.e(TAG, "EntityIterator is null");
+ return contentValuesListMap;
+ }
+
+ if (!entityIterator.hasNext()) {
+ Log.w(TAG, "Data does not exist. contactId: " + realContactId);
+ return contentValuesListMap;
+ }
+
+ while (entityIterator.hasNext()) {
+ Entity entity = entityIterator.next();
+ for (NamedContentValues namedContentValues : entity.getSubValues()) {
+ ContentValues contentValues = namedContentValues.values;
+ String key = contentValues.getAsString(Data.MIMETYPE);
+ if (key != null) {
+ List<ContentValues> contentValuesList = contentValuesListMap.get(key);
+ if (contentValuesList == null) {
+ contentValuesList = new ArrayList<ContentValues>();
+ contentValuesListMap.put(key, contentValuesList);
+ }
+ contentValuesList.add(contentValues);
+ }
+ }
+ }
+ } finally {
+ if (entityIterator != null) {
+ entityIterator.close();
+ }
+ }
+ return contentValuesListMap;
+ }
}
/**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index ef486a9..54b87ab7 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -181,6 +181,22 @@
public static final String NAMESPACE_CONNECTIVITY = "connectivity";
/**
+ * Namespace for CaptivePortalLogin module.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
+
+ /**
+ * Namespace for Tethering module.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_TETHERING = "tethering";
+
+ /**
* Namespace for content capture feature used by on-device machine intelligence
* to provide suggestions in a privacy-safe manner.
*
@@ -298,6 +314,14 @@
public static final String NAMESPACE_NETD_NATIVE = "netd_native";
/**
+ * Namespace for all Android NNAPI related features.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
+
+ /**
* Namespace for features related to the Package Manager Service.
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dc6e647..93061ee 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11050,24 +11050,6 @@
public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
/**
- * Whether HDMI control shall be enabled. If disabled, no CEC/MHL command will be
- * sent or processed. (0 = false, 1 = true)
- * @hide
- */
- @Readable
- public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled";
-
- /**
- * Whether TV will also turn off other CEC devices when it goes to standby mode.
- * (0 = false, 1 = true)
- *
- * @hide
- */
- @Readable
- public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
- "hdmi_control_auto_device_off_enabled";
-
- /**
* Whether or not media is shown automatically when bypassing as a heads up.
* @hide
*/
@@ -17675,40 +17657,42 @@
"android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
/**
- * Activity Action: For system or preinstalled apps to show their {@link Activity} in 2-pane
- * mode in Settings app on large screen devices.
+ * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
+ * in Settings app on large screen devices.
* <p>
- * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI} must be included to
- * specify the intent for the activity which will be displayed in 2-pane mode in Settings app.
+ * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
+ * specify the intent for the activity which will be embedded in Settings app.
* It's an intent URI string from {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
*
- * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY} must be included to
+ * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY} must be included to
* specify a key that indicates the menu item which will be highlighted on settings home menu.
* <p>
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK =
- "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK";
+ public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY =
+ "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
/**
- * Activity Extra: Specify the intent for the {@link Activity} which will be displayed in 2-pane
- * mode in Settings app. It's an intent URI string from
+ * Activity Extra: Specify the intent for the {@link Activity} which will be embedded in
+ * Settings app. It's an intent URI string from
* {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
* <p>
- * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+ * This must be passed as an extra field to
+ * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}.
*/
- public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI =
- "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI";
+ public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI =
+ "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
/**
* Activity Extra: Specify a key that indicates the menu item which should be highlighted on
* settings home menu.
* <p>
- * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+ * This must be passed as an extra field to
+ * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}.
*/
- public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY =
- "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY";
+ public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY =
+ "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
/**
* Performs a strict and comprehensive check of whether a calling package is allowed to
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 71f90fd2..c945954 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -83,11 +83,11 @@
* </intent-filter>
* <meta-data
* android:name="android.service.notification.default_filter_types"
- * android:value="conversations,alerting">
+ * android:value="conversations|alerting">
* </meta-data>
* <meta-data
* android:name="android.service.notification.disabled_filter_types"
- * android:value="ongoing,silent">
+ * android:value="ongoing|silent">
* </meta-data>
* </service></pre>
*
@@ -112,8 +112,9 @@
private final String TAG = getClass().getSimpleName();
/**
- * The name of the {@code meta-data} tag containing a comma separated list of default
- * integer notification types that should be provided to this listener. See
+ * The name of the {@code meta-data} tag containing a pipe separated list of default
+ * integer notification types or "ongoing", "conversations", "alerting", or "silent"
+ * that should be provided to this listener. See
* {@link #FLAG_FILTER_TYPE_ONGOING},
* {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
* and {@link #FLAG_FILTER_TYPE_SILENT}.
@@ -1698,7 +1699,7 @@
private ArrayList<Notification.Action> mSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private boolean mCanBubble;
- private boolean mVisuallyInterruptive;
+ private boolean mIsTextChanged;
private boolean mIsConversation;
private ShortcutInfo mShortcutInfo;
private @RankingAdjustment int mRankingAdjustment;
@@ -1735,7 +1736,7 @@
out.writeTypedList(mSmartActions, flags);
out.writeCharSequenceList(mSmartReplies);
out.writeBoolean(mCanBubble);
- out.writeBoolean(mVisuallyInterruptive);
+ out.writeBoolean(mIsTextChanged);
out.writeBoolean(mIsConversation);
out.writeParcelable(mShortcutInfo, flags);
out.writeInt(mRankingAdjustment);
@@ -1773,7 +1774,7 @@
mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
mSmartReplies = in.readCharSequenceList();
mCanBubble = in.readBoolean();
- mVisuallyInterruptive = in.readBoolean();
+ mIsTextChanged = in.readBoolean();
mIsConversation = in.readBoolean();
mShortcutInfo = in.readParcelable(cl);
mRankingAdjustment = in.readInt();
@@ -1976,8 +1977,8 @@
}
/** @hide */
- public boolean visuallyInterruptive() {
- return mVisuallyInterruptive;
+ public boolean isTextChanged() {
+ return mIsTextChanged;
}
/** @hide */
@@ -2032,7 +2033,7 @@
int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies, boolean canBubble,
- boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo,
+ boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
int rankingAdjustment, boolean isBubble) {
mKey = key;
mRank = rank;
@@ -2054,7 +2055,7 @@
mSmartActions = smartActions;
mSmartReplies = smartReplies;
mCanBubble = canBubble;
- mVisuallyInterruptive = visuallyInterruptive;
+ mIsTextChanged = isTextChanged;
mIsConversation = isConversation;
mShortcutInfo = shortcutInfo;
mRankingAdjustment = rankingAdjustment;
@@ -2095,7 +2096,7 @@
other.mSmartActions,
other.mSmartReplies,
other.mCanBubble,
- other.mVisuallyInterruptive,
+ other.mIsTextChanged,
other.mIsConversation,
other.mShortcutInfo,
other.mRankingAdjustment,
@@ -2152,7 +2153,7 @@
== (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
&& Objects.equals(mSmartReplies, other.mSmartReplies)
&& Objects.equals(mCanBubble, other.mCanBubble)
- && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive)
+ && Objects.equals(mIsTextChanged, other.mIsTextChanged)
&& Objects.equals(mIsConversation, other.mIsConversation)
// Shortcutinfo doesn't have equals either; use id
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
diff --git a/core/java/android/service/timezone/ITimeZoneProvider.aidl b/core/java/android/service/timezone/ITimeZoneProvider.aidl
index 793bcc6..4a404cf 100644
--- a/core/java/android/service/timezone/ITimeZoneProvider.aidl
+++ b/core/java/android/service/timezone/ITimeZoneProvider.aidl
@@ -22,6 +22,7 @@
* @hide
*/
oneway interface ITimeZoneProvider {
- void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis);
+ void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis,
+ in long eventFilteringAgeThresholdMillis);
void stopUpdates();
}
diff --git a/core/java/android/service/timezone/ITimeZoneProviderManager.aidl b/core/java/android/service/timezone/ITimeZoneProviderManager.aidl
index bf4fe0a..bccb096 100644
--- a/core/java/android/service/timezone/ITimeZoneProviderManager.aidl
+++ b/core/java/android/service/timezone/ITimeZoneProviderManager.aidl
@@ -16,13 +16,11 @@
package android.service.timezone;
-import android.service.timezone.TimeZoneProviderSuggestion;
+import android.service.timezone.TimeZoneProviderEvent;
/**
* @hide
*/
oneway interface ITimeZoneProviderManager {
- void onTimeZoneProviderSuggestion(in TimeZoneProviderSuggestion timeZoneProviderSuggestion);
- void onTimeZoneProviderUncertain();
- void onTimeZoneProviderPermanentFailure(in String failureReason);
+ void onTimeZoneProviderEvent(in TimeZoneProviderEvent timeZoneProviderEvent);
}
diff --git a/core/java/android/service/timezone/OWNERS b/core/java/android/service/timezone/OWNERS
index 28aff18..b5144d1 100644
--- a/core/java/android/service/timezone/OWNERS
+++ b/core/java/android/service/timezone/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
-nfuller@google.com
-include /core/java/android/app/timedetector/OWNERS
+# System APIs for system server time zone detection plugins.
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.aidl b/core/java/android/service/timezone/TimeZoneProviderEvent.aidl
new file mode 100644
index 0000000..b7a3533
--- /dev/null
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.timezone;
+
+/**
+ * @hide
+ */
+parcelable TimeZoneProviderEvent;
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
new file mode 100644
index 0000000..7005281
--- /dev/null
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.timezone;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.time.Duration;
+import java.util.Objects;
+
+/**
+ * Encapsulates a reported event from a {@link TimeZoneProviderService}.
+ *
+ * @hide
+ */
+public final class TimeZoneProviderEvent implements Parcelable {
+
+ @IntDef(prefix = "EVENT_TYPE_",
+ value = { EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUGGESTION, EVENT_TYPE_UNCERTAIN })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+ public @interface EventType {}
+
+ /**
+ * The provider failed permanently. See {@link
+ * TimeZoneProviderService#reportPermanentFailure(Throwable)}
+ */
+ public static final @EventType int EVENT_TYPE_PERMANENT_FAILURE = 1;
+
+ /**
+ * The provider made a suggestion. See {@link
+ * TimeZoneProviderService#reportSuggestion(TimeZoneProviderSuggestion)}
+ */
+ public static final @EventType int EVENT_TYPE_SUGGESTION = 2;
+
+ /**
+ * The provider was uncertain about the time zone. See {@link
+ * TimeZoneProviderService#reportUncertain()}
+ */
+ public static final @EventType int EVENT_TYPE_UNCERTAIN = 3;
+
+ private final @EventType int mType;
+
+ @ElapsedRealtimeLong
+ private final long mCreationElapsedMillis;
+
+ @Nullable
+ private final TimeZoneProviderSuggestion mSuggestion;
+
+ @Nullable
+ private final String mFailureCause;
+
+ private TimeZoneProviderEvent(@EventType int type,
+ @ElapsedRealtimeLong long creationElapsedMillis,
+ @Nullable TimeZoneProviderSuggestion suggestion,
+ @Nullable String failureCause) {
+ mType = type;
+ mCreationElapsedMillis = creationElapsedMillis;
+ mSuggestion = suggestion;
+ mFailureCause = failureCause;
+ }
+
+ /** Returns a event of type {@link #EVENT_TYPE_SUGGESTION}. */
+ public static TimeZoneProviderEvent createSuggestionEvent(
+ @ElapsedRealtimeLong long creationElapsedMillis,
+ @NonNull TimeZoneProviderSuggestion suggestion) {
+ return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis,
+ Objects.requireNonNull(suggestion), null);
+ }
+
+ /** Returns a event of type {@link #EVENT_TYPE_UNCERTAIN}. */
+ public static TimeZoneProviderEvent createUncertainEvent(
+ @ElapsedRealtimeLong long creationElapsedMillis) {
+ return new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null);
+ }
+
+ /** Returns a event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
+ public static TimeZoneProviderEvent createPermanentFailureEvent(
+ @ElapsedRealtimeLong long creationElapsedMillis,
+ @NonNull String cause) {
+ return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, creationElapsedMillis, null,
+ Objects.requireNonNull(cause));
+ }
+
+ /**
+ * Returns the event type.
+ */
+ public @EventType int getType() {
+ return mType;
+ }
+
+ /** Returns the time according to the elapsed realtime clock when the event was created. */
+ @ElapsedRealtimeLong
+ public long getCreationElapsedMillis() {
+ return mCreationElapsedMillis;
+ }
+
+ /**
+ * Returns the suggestion. Populated when {@link #getType()} is {@link #EVENT_TYPE_SUGGESTION}.
+ */
+ @Nullable
+ public TimeZoneProviderSuggestion getSuggestion() {
+ return mSuggestion;
+ }
+
+ /**
+ * Returns the failure cauese. Populated when {@link #getType()} is {@link
+ * #EVENT_TYPE_PERMANENT_FAILURE}.
+ */
+ @Nullable
+ public String getFailureCause() {
+ return mFailureCause;
+ }
+
+ public static final @NonNull Creator<TimeZoneProviderEvent> CREATOR =
+ new Creator<TimeZoneProviderEvent>() {
+ @Override
+ public TimeZoneProviderEvent createFromParcel(Parcel in) {
+ int type = in.readInt();
+ long creationElapsedMillis = in.readLong();
+ TimeZoneProviderSuggestion suggestion =
+ in.readParcelable(getClass().getClassLoader());
+ String failureCause = in.readString8();
+ return new TimeZoneProviderEvent(
+ type, creationElapsedMillis, suggestion, failureCause);
+ }
+
+ @Override
+ public TimeZoneProviderEvent[] newArray(int size) {
+ return new TimeZoneProviderEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeLong(mCreationElapsedMillis);
+ parcel.writeParcelable(mSuggestion, 0);
+ parcel.writeString8(mFailureCause);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneProviderEvent{"
+ + "mType=" + mType
+ + ", mCreationElapsedMillis=" + Duration.ofMillis(mCreationElapsedMillis).toString()
+ + ", mSuggestion=" + mSuggestion
+ + ", mFailureCause=" + mFailureCause
+ + '}';
+ }
+
+ /**
+ * Similar to {@link #equals} except this methods checks for equivalence, not equality.
+ * i.e. two {@link #EVENT_TYPE_UNCERTAIN} and {@link #EVENT_TYPE_PERMANENT_FAILURE} events are
+ * always equivalent, two {@link #EVENT_TYPE_SUGGESTION} events are equivalent if they suggest
+ * the same time zones.
+ */
+ @SuppressWarnings("ReferenceEquality")
+ public boolean isEquivalentTo(@Nullable TimeZoneProviderEvent other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || mType != other.mType) {
+ return false;
+ }
+ if (mType == EVENT_TYPE_SUGGESTION) {
+ return mSuggestion.isEquivalentTo(other.getSuggestion());
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneProviderEvent that = (TimeZoneProviderEvent) o;
+ return mType == that.mType
+ && mCreationElapsedMillis == that.mCreationElapsedMillis
+ && Objects.equals(mSuggestion, that.mSuggestion)
+ && Objects.equals(mFailureCause, that.mFailureCause);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause);
+ }
+}
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index b516b02..0d215f6 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -26,10 +26,14 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
/**
@@ -122,7 +126,9 @@
* #onDestroy()} can occur on a different thread from those made to {@link
* TimeZoneProviderService}-defined service methods, so implementations must be defensive and not
* assume an ordering between them, e.g. a call to {@link #onStopUpdates()} can occur after {@link
- * #onDestroy()} and should be handled safely.
+ * #onDestroy()} and should be handled safely. {@link #mLock} is used to ensure that synchronous
+ * calls like {@link #dump(FileDescriptor, PrintWriter, String[])} are safe with respect to
+ * asynchronous behavior.
*
* @hide
*/
@@ -162,12 +168,30 @@
private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper();
+ /** The object used for operations that occur between the main / handler thread. */
+ private final Object mLock = new Object();
+
+ /** The handler used for most operations. */
private final Handler mHandler = BackgroundThread.getHandler();
/** Set by {@link #mHandler} thread. */
+ @GuardedBy("mLock")
@Nullable
private ITimeZoneProviderManager mManager;
+ /** Set by {@link #mHandler} thread. */
+ @GuardedBy("mLock")
+ private long mEventFilteringAgeThresholdMillis;
+
+ /**
+ * The type of the last suggestion sent to the system server. Used to de-dupe suggestions client
+ * side and avoid calling into the system server unnecessarily. {@code null} means no previous
+ * event has been sent this cycle; this field is cleared when the service is started.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private TimeZoneProviderEvent mLastEventSent;
+
@Override
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
@@ -182,12 +206,20 @@
Objects.requireNonNull(suggestion);
mHandler.post(() -> {
- ITimeZoneProviderManager manager = mManager;
- if (manager != null) {
- try {
- manager.onTimeZoneProviderSuggestion(suggestion);
- } catch (RemoteException | RuntimeException e) {
- Log.w(TAG, e);
+ synchronized (mLock) {
+ ITimeZoneProviderManager manager = mManager;
+ if (manager != null) {
+ try {
+ TimeZoneProviderEvent thisEvent =
+ TimeZoneProviderEvent.createSuggestionEvent(
+ SystemClock.elapsedRealtime(), suggestion);
+ if (shouldSendEvent(thisEvent)) {
+ manager.onTimeZoneProviderEvent(thisEvent);
+ mLastEventSent = thisEvent;
+ }
+ } catch (RemoteException | RuntimeException e) {
+ Log.w(TAG, e);
+ }
}
}
});
@@ -200,12 +232,20 @@
*/
public final void reportUncertain() {
mHandler.post(() -> {
- ITimeZoneProviderManager manager = mManager;
- if (manager != null) {
- try {
- manager.onTimeZoneProviderUncertain();
- } catch (RemoteException | RuntimeException e) {
- Log.w(TAG, e);
+ synchronized (mLock) {
+ ITimeZoneProviderManager manager = mManager;
+ if (manager != null) {
+ try {
+ TimeZoneProviderEvent thisEvent =
+ TimeZoneProviderEvent.createUncertainEvent(
+ SystemClock.elapsedRealtime());
+ if (shouldSendEvent(thisEvent)) {
+ manager.onTimeZoneProviderEvent(thisEvent);
+ mLastEventSent = thisEvent;
+ }
+ } catch (RemoteException | RuntimeException e) {
+ Log.w(TAG, e);
+ }
}
}
});
@@ -219,21 +259,56 @@
Objects.requireNonNull(cause);
mHandler.post(() -> {
- ITimeZoneProviderManager manager = mManager;
- if (manager != null) {
- try {
- manager.onTimeZoneProviderPermanentFailure(cause.getMessage());
- } catch (RemoteException | RuntimeException e) {
- Log.w(TAG, e);
+ synchronized (mLock) {
+ ITimeZoneProviderManager manager = mManager;
+ if (manager != null) {
+ try {
+ String causeString = cause.getMessage();
+ TimeZoneProviderEvent thisEvent =
+ TimeZoneProviderEvent.createPermanentFailureEvent(
+ SystemClock.elapsedRealtime(), causeString);
+ if (shouldSendEvent(thisEvent)) {
+ manager.onTimeZoneProviderEvent(thisEvent);
+ mLastEventSent = thisEvent;
+ }
+ } catch (RemoteException | RuntimeException e) {
+ Log.w(TAG, e);
+ }
}
}
});
}
+ @GuardedBy("mLock")
+ private boolean shouldSendEvent(TimeZoneProviderEvent newEvent) {
+ // Always send an event if it indicates a state or suggestion change.
+ if (!newEvent.isEquivalentTo(mLastEventSent)) {
+ return true;
+ }
+
+ // Guard against implementations that generate a lot of uninteresting events in a short
+ // space of time and would cause the time_zone_detector to evaluate time zone suggestions
+ // too frequently.
+ //
+ // If the new event and last event sent are equivalent, the client will still send an update
+ // if their creation times are sufficiently different. This enables the time_zone_detector
+ // to better understand how recently the location time zone provider was certain /
+ // uncertain, which can be useful when working out ordering of events, e.g. to work out
+ // whether a suggestion was generated before or after a device left airplane mode.
+ long timeSinceLastEventMillis =
+ newEvent.getCreationElapsedMillis() - mLastEventSent.getCreationElapsedMillis();
+ return timeSinceLastEventMillis > mEventFilteringAgeThresholdMillis;
+ }
+
private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager,
- @DurationMillisLong long initializationTimeoutMillis) {
- mManager = manager;
- onStartUpdates(initializationTimeoutMillis);
+ @DurationMillisLong long initializationTimeoutMillis,
+ @DurationMillisLong long eventFilteringAgeThresholdMillis) {
+ synchronized (mLock) {
+ mManager = manager;
+ mEventFilteringAgeThresholdMillis = eventFilteringAgeThresholdMillis;
+ mLastEventSent = null;
+ onStartUpdates(initializationTimeoutMillis);
+ }
}
/**
@@ -265,8 +340,10 @@
public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);
private void onStopUpdatesInternal() {
- onStopUpdates();
- mManager = null;
+ synchronized (mLock) {
+ onStopUpdates();
+ mManager = null;
+ }
}
/**
@@ -275,12 +352,22 @@
*/
public abstract void onStopUpdates();
+ /** @hide */
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ synchronized (mLock) {
+ writer.append("mLastEventSent=" + mLastEventSent);
+ }
+ }
+
private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub {
public void startUpdates(@NonNull ITimeZoneProviderManager manager,
- @DurationMillisLong long initializationTimeoutMillis) {
+ @DurationMillisLong long initializationTimeoutMillis,
+ @DurationMillisLong long eventFilteringAgeThresholdMillis) {
Objects.requireNonNull(manager);
- mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis));
+ mHandler.post(() -> onStartUpdatesInternal(
+ manager, initializationTimeoutMillis, eventFilteringAgeThresholdMillis));
}
public void stopUpdates() {
diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
index cf299a7..229fa26 100644
--- a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
+++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
@@ -18,6 +18,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -121,6 +122,24 @@
parcel.writeLong(mElapsedRealtimeMillis);
}
+ /**
+ * Similar to {@link #equals} except this methods checks for equivalence, not equality.
+ * i.e. two suggestions are equivalent if they suggest the same time zones.
+ *
+ * @hide
+ */
+ @SuppressWarnings("ReferenceEquality")
+ public boolean isEquivalentTo(@Nullable TimeZoneProviderSuggestion other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+ // Only check the time zone IDs. The times can be different, but we don't mind.
+ return mTimeZoneIds.equals(other.mTimeZoneIds);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 30e4a23..88818b6 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -2044,7 +2044,11 @@
/**
* Registers a callback that will be notified when visible activities have been changed.
*
- * @param executor The handler to receive the callback.
+ * Note: The {@link VisibleActivityCallback#onVisible(VisibleActivityInfo)} will be called
+ * immediately with current visible activities when the callback is registered for the first
+ * time. If the callback is already registered, this method does nothing.
+ *
+ * @param executor The executor which will be used to invoke the callback.
* @param callback The callback to receive the response.
*
* @throws IllegalStateException if calling this method before onCreate().
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index dd4de0a..3028a6d 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -691,6 +691,11 @@
* {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ *
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
void onMessageWaitingIndicatorChanged(boolean mwi);
@@ -710,6 +715,11 @@
* {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ *
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
void onCallForwardingIndicatorChanged(boolean cfi);
@@ -868,6 +878,10 @@
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
* @param callState {@link PreciseCallState}
*/
@RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -910,6 +924,10 @@
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
* @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
*/
@RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -932,9 +950,9 @@
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
*
* @param dataConnectionState {@link PreciseDataConnectionState}
*/
@@ -1063,6 +1081,10 @@
* given subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ *
* @param emergencyNumberList Map associating all active subscriptions on the device with
* the list of emergency numbers originating from that
* subscription.
@@ -1157,6 +1179,11 @@
* For example, it could be the current active opportunistic subscription
* in use, or the subscription user selected as default data subscription in
* DSDS mode.
+ *
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ *
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
void onActiveDataSubscriptionIdChanged(int subId);
@@ -1225,6 +1252,11 @@
* <p>Because registration failures are ephemeral, this callback is not sticky.
* Registrants will not receive the most recent past value when registering.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+ *
* @param cellIdentity the CellIdentity, which must include the globally unique
* identifier
* for the cell (for example, all components of the CGI or ECGI).
@@ -1308,6 +1340,10 @@
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
* @param callAttributes the call attributes
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1324,6 +1360,11 @@
* <p>Barring info is provided for all services applicable to the current camped/registered
* cell, for the registered PLMN and current access class/access category.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+ *
* @param barringInfo for all services on the current cell.
* @see android.telephony.BarringInfo
*/
@@ -1341,6 +1382,10 @@
/**
* Callback invoked when the current physical channel configuration has changed
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
* @param configs List of the current {@link PhysicalChannelConfig}s
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1357,6 +1402,10 @@
/**
* Callback invoked when the data enabled changes.
*
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
* @param enabled {@code true} if data is enabled, otherwise disabled.
* @param reason Reason for data enabled/disabled.
* See {@link TelephonyManager.DataEnabledReason}.
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 6e25160..946fc30 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -111,19 +111,25 @@
* {@link android.webkit.WebView#findAddress(String)} for more information.
*
* @deprecated use {@link android.view.textclassifier.TextClassifier#generateLinks(
- * TextLinks.Request)} instead and avoid it even when targeting API levels where no alternative
- * is available.
+ * TextLinks.Request)} instead, and avoid {@link #MAP_ADDRESSES} even when targeting API levels
+ * where no alternative is available.
*/
@Deprecated
public static final int MAP_ADDRESSES = 0x08;
/**
- * Bit mask indicating that all available patterns should be matched in
- * methods that take an options mask
- * <p><strong>Note:</strong></p> {@link #MAP_ADDRESSES} is deprecated.
- * Use {@link android.view.textclassifier.TextClassifier#generateLinks(TextLinks.Request)}
- * instead and avoid it even when targeting API levels where no alternative is available.
+ * Bit mask indicating that all available patterns should be matched in methods
+ * that take an options mask. Note that this should be avoided, as the {@link
+ * #MAP_ADDRESSES} field uses the {@link android.webkit.WebView#findAddress(
+ * String)} method, which has various limitations and has been deprecated: see
+ * the documentation for {@link android.webkit.WebView#findAddress(String)} for
+ * more information.
+ *
+ * @deprecated use {@link android.view.textclassifier.TextClassifier#generateLinks(
+ * TextLinks.Request)} instead, and avoid {@link #ALL} even when targeting API levels where no
+ * alternative is available.
*/
+ @Deprecated
public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS | MAP_ADDRESSES;
/**
diff --git a/core/java/android/timezone/OWNERS b/core/java/android/timezone/OWNERS
index 8f80897..8b5e156 100644
--- a/core/java/android/timezone/OWNERS
+++ b/core/java/android/timezone/OWNERS
@@ -1,3 +1,5 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+# APIs originally intended to provide a stable API surface to access time zone rules data for use by
+# unbundled components like a telephony mainline module and the ART module. Not exposed, potentially
+# deletable if callers do not unbundle.
+include platform/libcore:/OWNERS
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 3d39fbe..2c89a15 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -50,9 +50,13 @@
= "settings_use_new_backup_eligibility_rules";
/** @hide */
public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
-
/** @hide */
public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
+ /**
+ * Support per app's language selection
+ * @hide
+ */
+ public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -76,11 +80,13 @@
DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "false");
+ DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
static {
PERSISTENT_FLAGS = new HashSet<>();
+ PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL);
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index 8e4e99e..7e65d61 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -70,8 +70,8 @@
*/
public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
- private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
- private static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;
+ static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+ static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;
/**
* Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
@@ -260,7 +260,8 @@
verityDigest, mApk.getChannel().size(), signatureInfo);
}
- return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests);
+ return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests,
+ blockId);
}
private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
@@ -572,13 +573,18 @@
// All these are verified if requested.
public final Map<Integer, byte[]> contentDigests;
+ // ID of the signature block used to verify.
+ public final int blockId;
+
public VerifiedSigner(X509Certificate[] certs,
ApkSigningBlockUtils.VerifiedProofOfRotation por,
- byte[] verityRootHash, Map<Integer, byte[]> contentDigests) {
+ byte[] verityRootHash, Map<Integer, byte[]> contentDigests,
+ int blockId) {
this.certs = certs;
this.por = por;
this.verityRootHash = verityRootHash;
this.contentDigests = contentDigests;
+ this.blockId = blockId;
}
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
index 844816c..6b26155 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
@@ -16,6 +16,7 @@
package android.util.apk;
+import static android.util.apk.ApkSignatureSchemeV3Verifier.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
@@ -52,38 +53,57 @@
* @hide for internal use only.
*/
public class ApkSignatureSchemeV4Verifier {
+ static final int APK_SIGNATURE_SCHEME_DEFAULT = 0xffffffff;
+
/**
- * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the
+ * Extracts and verifies APK Signature Scheme v4 signature of the provided APK and returns the
* certificates associated with each signer.
*/
public static VerifiedSigner extractCertificates(String apkFile)
throws SignatureNotFoundException, SecurityException {
+ Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> pair = extractSignature(apkFile);
+ return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT);
+ }
+
+ /**
+ * Extracts APK Signature Scheme v4 signature of the provided APK.
+ */
+ public static Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> extractSignature(
+ String apkFile) throws SignatureNotFoundException {
final File apk = new File(apkFile);
final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
apk.getAbsolutePath());
if (signatureBytes == null || signatureBytes.length == 0) {
throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
}
-
- final V4Signature signature;
- final V4Signature.HashingInfo hashingInfo;
- final V4Signature.SigningInfo signingInfo;
try {
- signature = V4Signature.readFrom(signatureBytes);
-
+ final V4Signature signature = V4Signature.readFrom(signatureBytes);
if (!signature.isVersionSupported()) {
throw new SecurityException(
"v4 signature version " + signature.version + " is not supported");
}
-
- hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo);
- signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo);
+ final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
+ signature.hashingInfo);
+ final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
+ signature.signingInfos);
+ return Pair.create(hashingInfo, signingInfos);
} catch (IOException e) {
throw new SignatureNotFoundException("Failed to read V4 signature.", e);
}
+ }
+
+ /**
+ * Verifies APK Signature Scheme v4 signature and returns the
+ * certificates associated with each signer.
+ */
+ public static VerifiedSigner verify(String apkFile, final V4Signature.HashingInfo hashingInfo,
+ final V4Signature.SigningInfos signingInfos, final int v3BlockId)
+ throws SignatureNotFoundException, SecurityException {
+ final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos,
+ v3BlockId);
// Verify signed data and extract certificates and apk digest.
- final byte[] signedData = V4Signature.getSignedData(apk.length(), hashingInfo,
+ final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo,
signingInfo);
final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData);
@@ -95,6 +115,28 @@
return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests);
}
+ private static V4Signature.SigningInfo findSigningInfoForBlockId(
+ final V4Signature.SigningInfos signingInfos, final int v3BlockId)
+ throws SignatureNotFoundException {
+ // Use default signingInfo for v3 block.
+ if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT
+ || v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
+ return signingInfos.signingInfo;
+ }
+ for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) {
+ if (v3BlockId == signingInfoBlock.blockId) {
+ try {
+ return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo);
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read V4 signature block: " + signingInfoBlock.blockId, e);
+ }
+ }
+ }
+ throw new SecurityException(
+ "Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId);
+ }
+
private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo,
final byte[] signedData) throws SecurityException {
if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 35c602a..41b749e 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -22,6 +22,7 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
@@ -31,6 +32,8 @@
import android.content.pm.parsing.result.ParseResult;
import android.os.Build;
import android.os.Trace;
+import android.os.incremental.V4Signature;
+import android.util.Pair;
import android.util.jar.StrictJarFile;
import com.android.internal.util.ArrayUtils;
@@ -189,16 +192,19 @@
boolean verifyFull) throws SignatureNotFoundException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
try {
- ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
- ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
- Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
- Signature[] signerSigs = convertToSignatures(signerCerts);
+ final Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> v4Pair =
+ ApkSignatureSchemeV4Verifier.extractSignature(apkPath);
+ final V4Signature.HashingInfo hashingInfo = v4Pair.first;
+ final V4Signature.SigningInfos signingInfos = v4Pair.second;
+
Signature[] pastSignerSigs = null;
+ Map<Integer, byte[]> nonstreamingDigests = null;
+ Certificate[][] nonstreamingCerts = null;
- if (verifyFull) {
- Map<Integer, byte[]> nonstreamingDigests;
- Certificate[][] nonstreamingCerts;
-
+ int v3BlockId = APK_SIGNATURE_SCHEME_DEFAULT;
+ // If V4 contains additional signing blocks then we need to always run v2/v3 verifier
+ // to figure out which block they use.
+ if (verifyFull || signingInfos.signingInfoBlocks.length > 0) {
try {
// v4 is an add-on and requires v2 or v3 signature to validate against its
// certificate and digest
@@ -215,6 +221,7 @@
pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i));
}
}
+ v3BlockId = v3Signer.blockId;
} catch (SignatureNotFoundException e) {
try {
ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
@@ -227,7 +234,15 @@
+ apkPath, ee);
}
}
+ }
+ ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
+ ApkSignatureSchemeV4Verifier.verify(apkPath, hashingInfo, signingInfos,
+ v3BlockId);
+ Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+
+ if (verifyFull) {
Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
if (nonstreamingSigs.length != signerSigs.length) {
throw new SecurityException(
@@ -260,7 +275,7 @@
} catch (SignatureNotFoundException e) {
throw e;
} catch (Exception e) {
- // APK Signature Scheme v4 signature found but did not verify
+ // APK Signature Scheme v4 signature found but did not verify.
return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
+ " using APK Signature Scheme v4", e);
diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl
deleted file mode 100644
index a063a70..0000000
--- a/core/java/android/view/IApplicationToken.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/* //device/java/android/android/view/IApplicationToken.aidl
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.view;
-
-/** {@hide} */
-interface IApplicationToken
-{
- String getName();
-}
-
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f7d32d0..0b4857d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -34,7 +34,6 @@
import android.os.ParcelFileDescriptor;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
-import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.ICrossWindowBlurEnabledListener;
import android.view.IDisplayWindowInsetsController;
@@ -61,6 +60,7 @@
import android.view.InputDevice;
import android.view.IInputFilter;
import android.view.AppTransitionAnimationSpec;
+import android.view.TaskTransitionSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
import android.view.SurfaceControl;
@@ -347,6 +347,14 @@
Bitmap screenshotWallpaper();
/**
+ * Mirrors the wallpaper for the given display.
+ *
+ * @param displayId ID of the display for the wallpaper.
+ * @return A SurfaceControl for the parent of the mirrored wallpaper.
+ */
+ SurfaceControl mirrorWallpaperSurface(int displayId);
+
+ /**
* Registers a wallpaper visibility listener.
* @return Current visibility.
*/
@@ -884,4 +892,17 @@
* @hide
*/
void setTaskSnapshotEnabled(boolean enabled);
+
+ /**
+ * Customized the task transition animation with a task transition spec.
+ *
+ * @param spec the spec that will be used to customize the task animations
+ */
+ void setTaskTransitionSpec(in TaskTransitionSpec spec);
+
+ /**
+ * Clears any task transition spec that has been previously set and
+ * reverts to using the default task transition with no spec changes.
+ */
+ void clearTaskTransitionSpec();
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 3917279..6179881 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -317,6 +317,26 @@
return insets;
}
+ public Insets calculateInsets(Rect frame, @InsetsType int types,
+ InsetsVisibilities overrideVisibilities) {
+ Insets insets = Insets.NONE;
+ for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ InsetsSource source = mSources[type];
+ if (source == null) {
+ continue;
+ }
+ int publicType = InsetsState.toPublicType(type);
+ if ((publicType & types) == 0) {
+ continue;
+ }
+ if (!overrideVisibilities.getVisibility(type)) {
+ continue;
+ }
+ insets = Insets.max(source.calculateInsets(frame, true), insets);
+ }
+ return insets;
+ }
+
public Insets calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -482,6 +502,26 @@
return mDisplayCutout.get();
}
+ public void getDisplayCutoutSafe(Rect outBounds) {
+ outBounds.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ final DisplayCutout cutout = mDisplayCutout.get();
+ final Rect displayFrame = mDisplayFrame;
+ if (!cutout.isEmpty()) {
+ if (cutout.getSafeInsetLeft() > 0) {
+ outBounds.left = displayFrame.left + cutout.getSafeInsetLeft();
+ }
+ if (cutout.getSafeInsetTop() > 0) {
+ outBounds.top = displayFrame.top + cutout.getSafeInsetTop();
+ }
+ if (cutout.getSafeInsetRight() > 0) {
+ outBounds.right = displayFrame.right - cutout.getSafeInsetRight();
+ }
+ if (cutout.getSafeInsetBottom() > 0) {
+ outBounds.bottom = displayFrame.bottom - cutout.getSafeInsetBottom();
+ }
+ }
+ }
+
public void setRoundedCorners(RoundedCorners roundedCorners) {
mRoundedCorners = roundedCorners;
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 40942ea7..3d57db9 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -434,8 +434,8 @@
/**
* This flag indicates that the window that received this motion event is partly
- * or wholly obscured by another visible window above it. This flag is set to true
- * if the event directly passed through the obscured area.
+ * or wholly obscured by another visible window above it and the event directly passed through
+ * the obscured area.
*
* A security sensitive application can check this flag to identify situations in which
* a malicious application may have covered up part of its content for the purpose
@@ -447,8 +447,8 @@
/**
* This flag indicates that the window that received this motion event is partly
- * or wholly obscured by another visible window above it. This flag is set to true
- * even if the event did not directly pass through the obscured area.
+ * or wholly obscured by another visible window above it and the event did not directly pass
+ * through the obscured area.
*
* A security sensitive application can check this flag to identify situations in which
* a malicious application may have covered up part of its content for the purpose
@@ -456,7 +456,7 @@
* to drop the suspect touches or to take additional precautions to confirm the user's
* actual intent.
*
- * Unlike FLAG_WINDOW_IS_OBSCURED, this is true even if the window that received this event is
+ * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
* obstructed in areas other than the touched location.
*/
public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5be85b0..30160c3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -243,7 +243,8 @@
private static native void nativeRemoveJankDataListener(long nativeListener);
private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener);
private static native int nativeGetGPUContextPriority();
- private static native void nativeSetTransformHint(long nativeObject, int transformHint);
+ private static native void nativeSetTransformHint(long nativeObject,
+ @SurfaceControl.BufferTransform int transformHint);
private static native int nativeGetTransformHint(long nativeObject);
private static native int nativeGetLayerId(long nativeObject);
private static native void nativeAddTransactionCommittedListener(long nativeObject,
@@ -3753,7 +3754,7 @@
/**
* @hide
*/
- public int getTransformHint() {
+ public @SurfaceControl.BufferTransform int getTransformHint() {
checkNotReleased();
return nativeGetTransformHint(mNativeObject);
}
@@ -3767,7 +3768,7 @@
* with the same size.
* @hide
*/
- public void setTransformHint(@Surface.Rotation int transformHint) {
+ public void setTransformHint(@SurfaceControl.BufferTransform int transformHint) {
nativeSetTransformHint(mNativeObject, transformHint);
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 11b161a..a6c5042d 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -292,11 +292,18 @@
*/
@TestApi
public void relayout(WindowManager.LayoutParams attrs) {
+ relayout(attrs, SurfaceControl.Transaction::apply);
+ }
+
+ /**
+ * Forces relayout and draw and allows to set a custom callback when it is finished
+ * @hide
+ */
+ public void relayout(WindowManager.LayoutParams attrs,
+ WindowlessWindowManager.ResizeCompleteCallback callback) {
mViewRoot.setLayoutParams(attrs, false);
mViewRoot.setReportNextDraw();
- mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), (SurfaceControl.Transaction t) -> {
- t.apply();
- });
+ mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b5f108c..1c44f443 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -214,7 +214,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final Rect mSurfaceFrame = new Rect();
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- int mTransformHint = 0;
+ @SurfaceControl.BufferTransform int mTransformHint = 0;
private boolean mGlobalListenersAdded;
private boolean mAttachedToWindow;
diff --git a/core/java/android/view/TaskTransitionSpec.aidl b/core/java/android/view/TaskTransitionSpec.aidl
new file mode 100644
index 0000000..08af15c
--- /dev/null
+++ b/core/java/android/view/TaskTransitionSpec.aidl
@@ -0,0 +1,20 @@
+/*
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+/** @hide */
+parcelable TaskTransitionSpec;
diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java
new file mode 100644
index 0000000..e90d6e1
--- /dev/null
+++ b/core/java/android/view/TaskTransitionSpec.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Holds information about how to execute task transition animations.
+ *
+ * This class is intended to be used with IWindowManager.setTaskTransitionSpec methods when
+ * we want more customization over the way default task transitions are executed.
+ *
+ * @hide
+ */
+public class TaskTransitionSpec implements Parcelable {
+ /**
+ * The background color to use during task animations (override the default background color)
+ */
+ public final int backgroundColor;
+
+ /**
+ * TEMPORARY FIELD (b/202383002)
+ * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
+ *
+ * A set of {@InsetsState.InternalInsetsType}s we want to use as the source to set the bounds
+ * of the task during the animation. Used to make sure that task animate above the taskbar.
+ * Will also be used to crop to the frame size of the inset source to the inset size to prevent
+ * the taskbar rounded corners overlay from being invisible during task transition animation.
+ */
+ public final Set<Integer> animationBoundInsets;
+
+ public TaskTransitionSpec(
+ int backgroundColor, Set<Integer> animationBoundInsets) {
+ this.backgroundColor = backgroundColor;
+ this.animationBoundInsets = animationBoundInsets;
+ }
+
+ public TaskTransitionSpec(Parcel in) {
+ this.backgroundColor = in.readInt();
+
+ int animationBoundInsetsSize = in.readInt();
+ this.animationBoundInsets = new ArraySet<>(animationBoundInsetsSize);
+ for (int i = 0; i < animationBoundInsetsSize; i++) {
+ this.animationBoundInsets.add(in.readInt());
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(backgroundColor);
+
+ dest.writeInt(animationBoundInsets.size());
+ for (int insetType : animationBoundInsets) {
+ dest.writeInt(insetType);
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<TaskTransitionSpec>
+ CREATOR = new Parcelable.Creator<TaskTransitionSpec>() {
+ public TaskTransitionSpec createFromParcel(Parcel in) {
+ return new TaskTransitionSpec(in);
+ }
+
+ public TaskTransitionSpec[] newArray(int size) {
+ return new TaskTransitionSpec[size];
+ }
+ };
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bbdbb9b..dabe1e9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -744,8 +744,9 @@
* To enable touch filtering, call {@link #setFilterTouchesWhenObscured(boolean)} or set the
* android:filterTouchesWhenObscured layout attribute to true. When enabled, the framework
* will discard touches that are received whenever the view's window is obscured by
- * another visible window. As a result, the view will not receive touches whenever a
- * toast, dialog or other window appears above the view's window.
+ * another visible window at the touched location. As a result, the view will not receive touches
+ * whenever the touch passed through a toast, dialog or other window that appears above the view's
+ * window.
* </p><p>
* For more fine-grained control over security, consider overriding the
* {@link #onFilterTouchEventForSecurity(MotionEvent)} method to implement your own
@@ -12671,7 +12672,7 @@
/**
* Gets whether the framework should discard touches when the view's
- * window is obscured by another visible window.
+ * window is obscured by another visible window at the touched location.
* Refer to the {@link View} security documentation for more details.
*
* @return True if touch filtering is enabled.
@@ -12687,7 +12688,7 @@
/**
* Sets whether the framework should discard touches when the view's
- * window is obscured by another visible window.
+ * window is obscured by another visible window at the touched location.
* Refer to the {@link View} security documentation for more details.
*
* @param enabled True if touch filtering should be enabled.
@@ -22356,6 +22357,20 @@
}
/**
+ * If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
+ *
+ * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
+ * HW accelerated, it can't handle drawing RenderNodes.
+ *
+ * @hide
+ */
+ protected final boolean drawsWithRenderNode(Canvas canvas) {
+ return mAttachInfo != null
+ && mAttachInfo.mHardwareAccelerated
+ && canvas.isHardwareAccelerated();
+ }
+
+ /**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
@@ -22364,14 +22379,8 @@
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
- /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
- *
- * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
- * HW accelerated, it can't handle drawing RenderNodes.
- */
- boolean drawingWithRenderNode = mAttachInfo != null
- && mAttachInfo.mHardwareAccelerated
- && hardwareAcceleratedCanvas;
+
+ boolean drawingWithRenderNode = drawsWithRenderNode(canvas);
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
@@ -27077,7 +27086,7 @@
switch (event.mAction) {
case DragEvent.ACTION_DRAG_STARTED: {
- if (result && li.mOnDragListener != null) {
+ if (result && li != null && li.mOnDragListener != null) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -27091,7 +27100,8 @@
refreshDrawableState();
} break;
case DragEvent.ACTION_DROP: {
- if (result && (li.mOnDragListener != null | li.mOnReceiveContentListener != null)) {
+ if (result && li != null && (li.mOnDragListener != null
+ || li.mOnReceiveContentListener != null)) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED);
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 73294b3..07d5fc5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1066,6 +1066,7 @@
*/
@TestApi
@Nullable
+ @UnsupportedAppUsage // Visible for Studio; least-worst option available
public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
Callable<OutputStream> callback) {
final View.AttachInfo attachInfo = tree.mAttachInfo;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d229cf6..fe67232 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4266,7 +4266,7 @@
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
- final ArrayList<View> preorderedList = isHardwareAccelerated()
+ final ArrayList<View> preorderedList = drawsWithRenderNode(canvas)
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b9c216d..89e1e08 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -140,7 +140,6 @@
import android.os.UserHandle;
import android.sysprop.DisplayProperties;
import android.util.AndroidRuntimeException;
-import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
@@ -160,7 +159,6 @@
import android.view.View.FocusDirection;
import android.view.View.MeasureSpec;
import android.view.Window.OnContentApplyWindowInsetsListener;
-import android.view.WindowInsets.Side.InsetsSide;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -314,7 +312,8 @@
private ArrayList<OnBufferTransformHintChangedListener> mTransformHintListeners =
new ArrayList<>();
- private @Surface.Rotation int mPreviousTransformHint = Surface.ROTATION_0;
+ private @SurfaceControl.BufferTransform
+ int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
/**
* Callback for notifying about global configuration changes.
*/
@@ -510,9 +509,11 @@
private final Point mSurfaceSize = new Point();
private final Point mLastSurfaceSize = new Point();
- final Rect mTempRect; // used in the transaction to not thrash the heap.
- final Rect mVisRect; // used to retrieve visible rect of focused view.
- private final Rect mTempBoundsRect = new Rect(); // used to set the size of the bounds surface.
+ private final Rect mVisRect = new Rect(); // used to retrieve visible rect of focused view.
+ private final Rect mTempRect = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private final WindowLayout mWindowLayout = new WindowLayout();
// This is used to reduce the race between window focus changes being dispatched from
// the window manager and input events coming through the input system.
@@ -735,47 +736,20 @@
}
/**
- * This is only used when the UI thread is paused due to {@link #mNextDrawUseBlastSync} being
- * set. Specifically, it's only used when calling
- * {@link BLASTBufferQueue#setNextTransaction(Transaction)} and then merged with
- * {@link #mSurfaceChangedTransaction}. It doesn't need to be thread safe since it's only
- * accessed when the UI thread is paused.
+ * This is only used on the RenderThread when handling a blast sync. Specifically, it's only
+ * used when calling {@link BLASTBufferQueue#setNextTransaction(Transaction)} and then merged
+ * with a tmp transaction on the Render Thread. The tmp transaction is then merged into
+ * {@link #mSurfaceChangedTransaction} on the UI Thread, avoiding any threading issues.
*/
private final SurfaceControl.Transaction mRtBLASTSyncTransaction =
new SurfaceControl.Transaction();
/**
- * Keeps track of whether the WM requested to use BLAST Sync when calling relayout. When set,
- * we pause the UI thread to ensure we don't get overlapping requests. We then send a
- * transaction to {@link BLASTBufferQueue#setNextTransaction(Transaction)}, which is then sent
- * back to WM to synchronize.
- *
- * This flag is set to false only after the synchronized transaction that contains the buffer
- * has been sent to SurfaceFlinger.
- */
- private boolean mNextDrawUseBlastSync = false;
-
- /**
- * Wait for the blast sync transaction complete callback before drawing and queuing up more
- * frames. This will prevent out of order buffers submissions when WM has requested to
- * synchronize with the client.
- */
- private boolean mWaitForBlastSyncComplete = false;
-
- /**
* Keeps track of the last frame number that was attempted to draw. Should only be accessed on
* the RenderThread.
*/
private long mRtLastAttemptedDrawFrameNum = 0;
- /**
- * Keeps track of whether a traverse was triggered while the UI thread was paused. This can
- * occur when the client is waiting on another process to submit the transaction that
- * contains the buffer. The UI thread needs to wait on the callback before it can submit
- * another buffer.
- */
- private boolean mRequestedTraverseWhilePaused = false;
-
private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
@@ -808,8 +782,6 @@
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
- mTempRect = new Rect();
- mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mLeashToken = new Binder();
@@ -984,29 +956,6 @@
}
}
- // TODO(b/161810301): Make this private after window layout is moved to the client side.
- public static void computeWindowBounds(WindowManager.LayoutParams attrs, InsetsState state,
- Rect displayFrame, Rect outBounds) {
- final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
- final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
- final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
- final Rect df = displayFrame;
- Insets insets = Insets.of(0, 0, 0, 0);
- for (int i = types.size() - 1; i >= 0; i--) {
- final InsetsSource source = state.peekSource(types.valueAt(i));
- if (source == null) {
- continue;
- }
- insets = Insets.max(insets, source.calculateInsets(
- df, attrs.isFitInsetsIgnoringVisibility()));
- }
- final int left = (sidesToFit & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
- final int top = (sidesToFit & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
- final int right = (sidesToFit & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
- final int bottom = (sidesToFit & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
- outBounds.set(df.left + left, df.top + top, df.right - right, df.bottom - bottom);
- }
-
private Configuration getConfiguration() {
return mContext.getResources().getConfiguration();
}
@@ -1167,8 +1116,13 @@
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
- computeWindowBounds(mWindowAttributes, mInsetsController.getState(),
- getConfiguration().windowConfiguration.getBounds(), mTmpFrames.frame);
+ final InsetsState state = mInsetsController.getState();
+ final Rect displayCutoutSafe = mTempRect;
+ state.getDisplayCutoutSafe(displayCutoutSafe);
+ mWindowLayout.computeWindowFrames(mWindowAttributes, state,
+ displayCutoutSafe, getConfiguration().windowConfiguration.getBounds(),
+ mInsetsController.getRequestedVisibilities(),
+ null /* attachedWindowFrame */, mTmpFrames.frame, mTempRect2);
setFrame(mTmpFrames.frame);
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
@@ -1620,7 +1574,7 @@
mForceNextWindowRelayout = forceNextWindowRelayout;
mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
- if (msg == MSG_RESIZED_REPORT && !mNextDrawUseBlastSync) {
+ if (msg == MSG_RESIZED_REPORT) {
reportNextDraw();
}
@@ -1965,11 +1919,11 @@
private void setBoundsLayerCrop(Transaction t) {
// Adjust of insets and update the bounds layer so child surfaces do not draw into
// the surface inset region.
- mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
- mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+ mTempRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+ mTempRect.inset(mWindowAttributes.surfaceInsets.left,
mWindowAttributes.surfaceInsets.top,
mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
- t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
+ t.setWindowCrop(mBoundsLayer, mTempRect);
}
/**
@@ -2515,23 +2469,6 @@
return;
}
- // This is to ensure we don't end up queueing new frames while waiting on a previous frame
- // to get latched. This can happen when there's been a sync request for this window. The
- // frame could be in a transaction that's passed to different processes to ensure
- // synchronization. It continues to block until ViewRootImpl receives a callback that the
- // transaction containing the buffer has been sent to SurfaceFlinger. Once we receive, that
- // signal, we know it's safe to start queuing new buffers.
- //
- // When the callback is invoked, it will trigger a traversal request if
- // mRequestedTraverseWhilePaused is set so there's no need to attempt a retry here.
- if (mWaitForBlastSyncComplete) {
- if (DEBUG_BLAST) {
- Log.w(mTag, "Can't perform draw while waiting for a transaction complete");
- }
- mRequestedTraverseWhilePaused = true;
- return;
- }
-
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
@@ -2775,6 +2712,7 @@
}
}
final boolean wasReportNextDraw = mReportNextDraw;
+ boolean useBlastSync = false;
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
|| mForceNextWindowRelayout) {
@@ -2827,7 +2765,7 @@
}
reportNextDraw();
if (isHardwareEnabled()) {
- mNextDrawUseBlastSync = true;
+ useBlastSync = true;
}
}
@@ -3301,7 +3239,7 @@
}
mPendingTransitions.clear();
}
- performDraw();
+ performDraw(useBlastSync);
} else {
if (isViewVisible) {
// Try again
@@ -3987,34 +3925,19 @@
}
/**
- * Only call this on the UI Thread.
- */
- void clearBlastSync() {
- mNextDrawUseBlastSync = false;
- mWaitForBlastSyncComplete = false;
- if (DEBUG_BLAST) {
- Log.d(mTag, "Scheduling a traversal=" + mRequestedTraverseWhilePaused
- + " due to a previous skipped traversal.");
- }
- if (mRequestedTraverseWhilePaused) {
- mRequestedTraverseWhilePaused = false;
- scheduleTraversals();
- }
- }
-
- /**
* @hide
*/
public boolean isHardwareEnabled() {
return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
}
- private boolean addFrameCompleteCallbackIfNeeded(boolean reportNextDraw) {
+ private boolean addFrameCompleteCallbackIfNeeded(boolean useBlastSync,
+ boolean reportNextDraw) {
if (!isHardwareEnabled()) {
return false;
}
- if (!mNextDrawUseBlastSync && !reportNextDraw) {
+ if (!useBlastSync && !reportNextDraw) {
return false;
}
@@ -4036,30 +3959,22 @@
// for the current draw attempt.
if (frameWasNotDrawn) {
mBlastBufferQueue.setNextTransaction(null);
- mBlastBufferQueue.setTransactionCompleteCallback(mRtLastAttemptedDrawFrameNum,
- null);
// Apply the transactions that were sent to mergeWithNextTransaction since the
// frame didn't draw on this vsync. It's possible the frame will draw later, but
// it's better to not be sync than to block on a frame that may never come.
mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum);
}
+ Transaction tmpTransaction = new Transaction();
+ tmpTransaction.merge(mRtBLASTSyncTransaction);
mHandler.postAtFrontOfQueue(() -> {
- if (mNextDrawUseBlastSync) {
- // We don't need to synchronize mRtBLASTSyncTransaction here since we're
- // guaranteed that this is called after onFrameDraw and mNextDrawUseBlastSync
- // is only true when the UI thread is paused. Therefore, no one should be
- // modifying this object until the next vsync.
- mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction);
+ if (useBlastSync) {
+ mSurfaceChangedTransaction.merge(tmpTransaction);
}
if (reportNextDraw) {
pendingDrawFinished();
}
-
- if (frameWasNotDrawn) {
- clearBlastSync();
- }
});
});
return true;
@@ -4095,21 +4010,19 @@
});
}
- private void addFrameCallbackIfNeeded() {
- final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
+ private void addFrameCallbackIfNeeded(boolean useBlastSync) {
final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
- if (!nextDrawUseBlastSync && !needsCallbackForBlur) {
+ if (!useBlastSync && !needsCallbackForBlur) {
return;
}
if (DEBUG_BLAST) {
Log.d(mTag, "Creating frameDrawingCallback"
- + " nextDrawUseBlastSync=" + nextDrawUseBlastSync
+ + " nextDrawUseBlastSync=" + useBlastSync
+ " hasBlurUpdates=" + hasBlurUpdates);
}
- mWaitForBlastSyncComplete = nextDrawUseBlastSync;
final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
@@ -4117,7 +4030,7 @@
HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
if (DEBUG_BLAST) {
Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "."
- + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync);
+ + " Creating transactionCompleteCallback=" + useBlastSync);
}
mRtLastAttemptedDrawFrameNum = frame;
@@ -4131,7 +4044,7 @@
return;
}
- if (nextDrawUseBlastSync) {
+ if (useBlastSync) {
// Frame callbacks will always occur after submitting draw requests and before
// the draw actually occurs. This will ensure that we set the next transaction
// for the frame that's about to get drawn and not on a previous frame that.
@@ -4139,35 +4052,27 @@
// We don't need to synchronize mRtBLASTSyncTransaction here since it's not
// being modified and only sent to BlastBufferQueue.
mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
-
- mBlastBufferQueue.setTransactionCompleteCallback(frame, frameNumber -> {
- if (DEBUG_BLAST) {
- Log.d(mTag, "Received transactionCompleteCallback frameNum=" + frame);
- }
- mHandler.postAtFrontOfQueue(this::clearBlastSync);
- });
}
};
registerRtFrameCallback(frameDrawingCallback);
}
- private void performDraw() {
+ private void performDraw(boolean useBlastSync) {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}
- final boolean fullRedrawNeeded =
- mFullRedrawNeeded || mReportNextDraw || mNextDrawUseBlastSync;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw || useBlastSync;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
- addFrameCallbackIfNeeded();
+ addFrameCallbackIfNeeded(useBlastSync);
addFrameCommitCallbackIfNeeded();
- boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(mReportNextDraw);
+ boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(useBlastSync, mReportNextDraw);
try {
boolean canUseAsync = draw(fullRedrawNeeded);
@@ -4412,7 +4317,7 @@
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
boolean useAsyncReport = false;
- if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
+ if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (isHardwareEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
@@ -5654,7 +5559,7 @@
}
/**
- * Marks the the input event as finished then forwards it to the next stage.
+ * Marks the input event as finished then forwards it to the next stage.
*/
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
@@ -7855,8 +7760,7 @@
int transformHint = mSurfaceControl.getTransformHint();
if (mPreviousTransformHint != transformHint) {
mPreviousTransformHint = transformHint;
- dispatchTransformHintChanged(
- SurfaceControl.rotationToBufferTransform(transformHint));
+ dispatchTransformHintChanged(transformHint);
}
} else {
destroySurface();
@@ -10464,7 +10368,7 @@
@Override
public @SurfaceControl.BufferTransform int getBufferTransformHint() {
- return SurfaceControl.rotationToBufferTransform(mSurfaceControl.getTransformHint());
+ return mSurfaceControl.getTransformHint();
}
@Override
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
new file mode 100644
index 0000000..cdc1977
--- /dev/null
+++ b/core/java/android/view/WindowLayout.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+
+import android.graphics.Insets;
+import android.graphics.Rect;
+
+/**
+ * Computes window frames.
+ * @hide
+ */
+public class WindowLayout {
+ private final Rect mTempDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
+ private final Rect mTempRect = new Rect();
+
+ public boolean computeWindowFrames(WindowManager.LayoutParams attrs, InsetsState state,
+ Rect displayCutoutSafe, Rect windowBounds, InsetsVisibilities requestedVisibilities,
+ Rect attachedWindowFrame, Rect outDisplayFrame, Rect outParentFrame) {
+ final int type = attrs.type;
+ final int fl = attrs.flags;
+ final int pfl = attrs.privateFlags;
+ final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
+
+ // Compute bounds restricted by insets
+ final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
+ attrs.isFitInsetsIgnoringVisibility());
+ final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
+ final int left = (sides & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
+ final int top = (sides & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
+ final int right = (sides & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
+ final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
+ outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
+ windowBounds.right - right, windowBounds.bottom - bottom);
+
+ if (attachedWindowFrame == null) {
+ outParentFrame.set(outDisplayFrame);
+ if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
+ final InsetsSource source = state.peekSource(ITYPE_IME);
+ if (source != null) {
+ outParentFrame.inset(source.calculateInsets(
+ outParentFrame, false /* ignoreVisibility */));
+ }
+ }
+ } else {
+ outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
+ }
+
+ // Compute bounds restricted by display cutout
+ final DisplayCutout cutout = state.getDisplayCutout();
+ if (cutout.isEmpty()) {
+ return false;
+ }
+ boolean clippedByDisplayCutout = false;
+ final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
+ displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
+
+ // Ensure that windows with a non-ALWAYS display cutout mode are laid out in
+ // the cutout safe zone.
+ final int cutoutMode = attrs.layoutInDisplayCutoutMode;
+ if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+ final Rect displayFrame = state.getDisplayFrame();
+ final InsetsSource statusBarSource = state.peekSource(ITYPE_STATUS_BAR);
+ if (statusBarSource != null && displayCutoutSafe.top > displayFrame.top) {
+ // Make sure that the zone we're avoiding for the cutout is at least as tall as the
+ // status bar; otherwise fullscreen apps will end up cutting halfway into the status
+ // bar.
+ displayCutoutSafeExceptMaybeBars.top =
+ Math.max(statusBarSource.getFrame().bottom, displayCutoutSafe.top);
+ }
+ if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
+ if (displayFrame.width() < displayFrame.height()) {
+ displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
+ displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+ } else {
+ displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
+ displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+ }
+ }
+ final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
+ if (layoutInScreen && layoutInsetDecor
+ && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+ || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
+ final Insets systemBarsInsets = state.calculateInsets(
+ displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities);
+ if (systemBarsInsets.left > 0) {
+ displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
+ }
+ if (systemBarsInsets.top > 0) {
+ displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
+ }
+ if (systemBarsInsets.right > 0) {
+ displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+ }
+ if (systemBarsInsets.bottom > 0) {
+ displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+ }
+ }
+ if (type == TYPE_INPUT_METHOD) {
+ final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+ if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) {
+ // The IME can always extend under the bottom cutout if the navbar is there.
+ displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+ }
+ }
+ final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
+
+ // TYPE_BASE_APPLICATION windows are never considered floating here because they don't
+ // get cropped / shifted to the displayFrame in WindowState.
+ final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
+ && type != TYPE_BASE_APPLICATION;
+
+ // Windows that are attached to a parent and laid out in said parent already avoid
+ // the cutout according to that parent and don't need to be further constrained.
+ // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
+ // They will later be cropped or shifted using the displayFrame in WindowState,
+ // which prevents overlap with the DisplayCutout.
+ if (!attachedInParent && !floatingInScreenWindow) {
+ mTempRect.set(outParentFrame);
+ outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
+ clippedByDisplayCutout = !mTempRect.equals(outParentFrame);
+ }
+ outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
+ }
+ return clippedByDisplayCutout;
+ }
+}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 18013e8..c92a3a0 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
@@ -709,6 +710,16 @@
}
}
}
+
+ /** @hide */
+ @Nullable
+ public SurfaceControl mirrorWallpaperSurface(int displayId) {
+ try {
+ return getWindowManagerService().mirrorWallpaperSurface(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5dd7c84..dd80416 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -351,25 +351,11 @@
throw e.rethrowFromSystemServer();
}
- int size = possibleDisplayInfos.size();
- DisplayInfo currentDisplayInfo;
- WindowInsets windowInsets = null;
- if (size > 0) {
- currentDisplayInfo = possibleDisplayInfos.get(0);
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
- // TODO(181127261) not computing insets correctly - need to have underlying
- // frame reflect the faked orientation.
- windowInsets = getWindowInsetsFromServerForDisplay(
- currentDisplayInfo.displayId, params,
- new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
- currentDisplayInfo.getNaturalHeight()), isScreenRound,
- WINDOWING_MODE_FULLSCREEN);
- }
-
Set<WindowMetrics> maxMetrics = new HashSet<>();
- for (int i = 0; i < size; i++) {
+ WindowInsets windowInsets;
+ DisplayInfo currentDisplayInfo;
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
// Calculate max bounds for this rotation and state.
@@ -377,7 +363,18 @@
currentDisplayInfo.logicalHeight);
// Calculate insets for the rotated max bounds.
- // TODO(181127261) calculate insets for each display rotation and state.
+ final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
+ // Initialize insets based upon display rotation. Note any window-provided insets
+ // will not be set.
+ windowInsets = getWindowInsetsFromServerForDisplay(
+ currentDisplayInfo.displayId, params,
+ new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+ currentDisplayInfo.getNaturalHeight()), isScreenRound,
+ WINDOWING_MODE_FULLSCREEN);
+ // Set the hardware-provided insets.
+ windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
+ currentDisplayInfo.roundedCorners)
+ .setDisplayCutout(currentDisplayInfo.displayCutout).build();
maxMetrics.add(new WindowMetrics(maxBounds, windowInsets));
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index ff6f8ac..6bfb14b 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -740,7 +740,10 @@
CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
CONTENT_CHANGE_TYPE_PANE_TITLE,
CONTENT_CHANGE_TYPE_PANE_APPEARED,
- CONTENT_CHANGE_TYPE_PANE_DISAPPEARED
+ CONTENT_CHANGE_TYPE_PANE_DISAPPEARED,
+ CONTENT_CHANGE_TYPE_DRAG_STARTED,
+ CONTENT_CHANGE_TYPE_DRAG_DROPPED,
+ CONTENT_CHANGE_TYPE_DRAG_CANCELLED
})
public @interface ContentChangeTypes {}
@@ -989,6 +992,9 @@
case CONTENT_CHANGE_TYPE_PANE_APPEARED: return "CONTENT_CHANGE_TYPE_PANE_APPEARED";
case CONTENT_CHANGE_TYPE_PANE_DISAPPEARED:
return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED";
+ case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
+ case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
+ case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
default: return Integer.toHexString(type);
}
}
@@ -1047,6 +1053,7 @@
/**
* Sets the event type.
*
+ * <b>Note: An event must represent a single event type.</b>
* @param eventType The event type.
*
* @throws IllegalStateException If called from an AccessibilityService.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e9dec12..4730eaa 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -5075,6 +5075,30 @@
@NonNull public static final AccessibilityAction ACTION_DRAG_CANCEL =
new AccessibilityAction(R.id.accessibilityActionDragCancel);
+ /**
+ * Action to perform a left swipe.
+ */
+ @NonNull public static final AccessibilityAction ACTION_SWIPE_LEFT =
+ new AccessibilityAction(R.id.accessibilityActionSwipeLeft);
+
+ /**
+ * Action to perform a right swipe.
+ */
+ @NonNull public static final AccessibilityAction ACTION_SWIPE_RIGHT =
+ new AccessibilityAction(R.id.accessibilityActionSwipeRight);
+
+ /**
+ * Action to perform an up swipe.
+ */
+ @NonNull public static final AccessibilityAction ACTION_SWIPE_UP =
+ new AccessibilityAction(R.id.accessibilityActionSwipeUp);
+
+ /**
+ * Action to perform a down swipe.
+ */
+ @NonNull public static final AccessibilityAction ACTION_SWIPE_DOWN =
+ new AccessibilityAction(R.id.accessibilityActionSwipeDown);
+
private final int mActionId;
private final CharSequence mLabel;
diff --git a/core/java/android/window/ITransitionMetricsReporter.aidl b/core/java/android/window/ITransitionMetricsReporter.aidl
new file mode 100644
index 0000000..00f71dc
--- /dev/null
+++ b/core/java/android/window/ITransitionMetricsReporter.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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.window;
+
+import android.os.IBinder;
+
+/**
+ * Implemented by WM Core to know the metrics of transition that runs on a different process.
+ * @hide
+ */
+oneway interface ITransitionMetricsReporter {
+
+ /**
+ * Called when the transition animation starts.
+ *
+ * @param startTime The time when the animation started.
+ */
+ void reportAnimationStart(IBinder transitionToken, long startTime);
+}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index e65fcdd..3c7cd02 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -23,6 +23,7 @@
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.WindowContainerToken;
@@ -98,4 +99,7 @@
* this will replace the existing one if set.
*/
void registerTransitionPlayer(in ITransitionPlayer player);
+
+ /** @return An interface enabling the transition players to report its metrics. */
+ ITransitionMetricsReporter getTransitionMetricsReporter();
}
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 5950e9f..aec910b 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -136,6 +136,11 @@
/** @hide */
public static final int TYPE_PARAMETER_USE_EMPTY_SPLASH_SCREEN = 0x00000020;
/**
+ * The parameter which indicates if the activity has finished drawing.
+ * @hide
+ */
+ public static final int TYPE_PARAMETER_ACTIVITY_DRAWN = 0x00000040;
+ /**
* Application is allowed to use the legacy splash screen
* @hide
*/
diff --git a/core/java/android/window/TransitionMetrics.java b/core/java/android/window/TransitionMetrics.java
new file mode 100644
index 0000000..9a93c1a
--- /dev/null
+++ b/core/java/android/window/TransitionMetrics.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.window;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Singleton;
+
+/**
+ * A helper class for who plays transition animation can report its metrics easily.
+ * @hide
+ */
+public class TransitionMetrics {
+
+ private final ITransitionMetricsReporter mTransitionMetricsReporter;
+
+ private TransitionMetrics(ITransitionMetricsReporter reporter) {
+ mTransitionMetricsReporter = reporter;
+ }
+
+ /** Reports the current timestamp as when the transition animation starts. */
+ public void reportAnimationStart(IBinder transitionToken) {
+ try {
+ mTransitionMetricsReporter.reportAnimationStart(transitionToken,
+ SystemClock.elapsedRealtime());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Gets the singleton instance of TransitionMetrics. */
+ public static TransitionMetrics getInstance() {
+ return sTransitionMetrics.get();
+ }
+
+ private static final Singleton<TransitionMetrics> sTransitionMetrics = new Singleton<>() {
+ @Override
+ protected TransitionMetrics create() {
+ return new TransitionMetrics(WindowOrganizer.getTransitionMetricsReporter());
+ }
+ };
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 00bc202..7a960c6 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -295,6 +295,36 @@
}
/**
+ * Reparent's all children tasks or the top task of {@param currentParent} in the specified
+ * {@param windowingMode} and {@param activityType} to {@param newParent} in their current
+ * z-order.
+ *
+ * @param currentParent of the tasks to perform the operation no.
+ * {@code null} will perform the operation on the display.
+ * @param newParent for the tasks. {@code null} will perform the operation on the display.
+ * @param windowingModes of the tasks to reparent.
+ * @param activityTypes of the tasks to reparent.
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ * @param reparentTopOnly When {@code true}, only reparent the top task which fit windowingModes
+ * and activityTypes.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
+ @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
+ @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) {
+ mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
+ currentParent != null ? currentParent.asBinder() : null,
+ newParent != null ? newParent.asBinder() : null,
+ windowingModes,
+ activityTypes,
+ onTop,
+ reparentTopOnly));
+ return this;
+ }
+
+ /**
* Reparent's all children tasks of {@param currentParent} in the specified
* {@param windowingMode} and {@param activityType} to {@param newParent} in their current
* z-order.
@@ -311,13 +341,8 @@
public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
@Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
@Nullable int[] activityTypes, boolean onTop) {
- mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
- currentParent != null ? currentParent.asBinder() : null,
- newParent != null ? newParent.asBinder() : null,
- windowingModes,
- activityTypes,
- onTop));
- return this;
+ return reparentTasks(currentParent, newParent, windowingModes, activityTypes, onTop,
+ false /* reparentTopOnly */);
}
/**
@@ -948,6 +973,8 @@
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
private boolean mToTop;
+ private boolean mReparentTopOnly;
+
@Nullable
private int[] mWindowingModes;
@@ -985,13 +1012,15 @@
}
public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent,
- IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) {
+ IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop,
+ boolean reparentTopOnly) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT)
.setContainer(currentParent)
.setReparentContainer(newParent)
.setWindowingModes(windowingModes)
.setActivityTypes(activityTypes)
.setToTop(onTop)
+ .setReparentTopOnly(reparentTopOnly)
.build();
}
@@ -1040,6 +1069,7 @@
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mToTop = copy.mToTop;
+ mReparentTopOnly = copy.mReparentTopOnly;
mWindowingModes = copy.mWindowingModes;
mActivityTypes = copy.mActivityTypes;
mLaunchOptions = copy.mLaunchOptions;
@@ -1053,6 +1083,7 @@
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
mToTop = in.readBoolean();
+ mReparentTopOnly = in.readBoolean();
mWindowingModes = in.createIntArray();
mActivityTypes = in.createIntArray();
mLaunchOptions = in.readBundle();
@@ -1093,6 +1124,10 @@
return mToTop;
}
+ public boolean getReparentTopOnly() {
+ return mReparentTopOnly;
+ }
+
public int[] getWindowingModes() {
return mWindowingModes;
}
@@ -1126,12 +1161,13 @@
switch (mType) {
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
- + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
return "{SetLaunchRoot: container=" + mContainer
- + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
case HIERARCHY_OP_TYPE_REPARENT:
return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+ mReparent + "}";
@@ -1163,8 +1199,9 @@
+ " adjacentContainer=" + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
- + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mToTop=" + mToTop
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
}
}
@@ -1174,6 +1211,7 @@
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
dest.writeBoolean(mToTop);
+ dest.writeBoolean(mReparentTopOnly);
dest.writeIntArray(mWindowingModes);
dest.writeIntArray(mActivityTypes);
dest.writeBundle(mLaunchOptions);
@@ -1211,6 +1249,8 @@
private boolean mToTop;
+ private boolean mReparentTopOnly;
+
@Nullable
private int[] mWindowingModes;
@@ -1248,6 +1288,11 @@
return this;
}
+ Builder setReparentTopOnly(boolean reparentTopOnly) {
+ mReparentTopOnly = reparentTopOnly;
+ return this;
+ }
+
Builder setWindowingModes(@Nullable int[] windowingModes) {
mWindowingModes = windowingModes;
return this;
@@ -1290,6 +1335,7 @@
? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
: null;
hierarchyOp.mToTop = mToTop;
+ hierarchyOp.mReparentTopOnly = mReparentTopOnly;
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index e9b8174..4ea5ea5 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -159,7 +159,19 @@
}
}
- IWindowOrganizerController getWindowOrganizerController() {
+ /**
+ * @see TransitionMetrics
+ * @hide
+ */
+ public static ITransitionMetricsReporter getTransitionMetricsReporter() {
+ try {
+ return getWindowOrganizerController().getTransitionMetricsReporter();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ static IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6ff74e4..9b1bef06 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -441,14 +441,12 @@
private final ChooserHandler mChooserHandler = new ChooserHandler();
private class ChooserHandler extends Handler {
- private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
- private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
+ private static final int SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS = 7;
private void removeAllMessages() {
removeMessages(LIST_VIEW_UPDATE_MESSAGE);
- removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
- removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
+ removeMessages(SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS);
}
@Override
@@ -468,22 +466,23 @@
.refreshListView();
break;
- case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
- if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
- final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
- if (resultInfo.resultTargets != null) {
- ChooserListAdapter adapterForUserHandle =
- mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
- resultInfo.userHandle);
- if (adapterForUserHandle != null) {
- adapterForUserHandle.addServiceResults(
- resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1,
- mDirectShareShortcutInfoCache);
+ case SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS:
+ if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS");
+ final ServiceResultInfo[] resultInfos = (ServiceResultInfo[]) msg.obj;
+ for (ServiceResultInfo resultInfo : resultInfos) {
+ if (resultInfo.resultTargets != null) {
+ ChooserListAdapter adapterForUserHandle =
+ mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ resultInfo.userHandle);
+ if (adapterForUserHandle != null) {
+ adapterForUserHandle.addServiceResults(
+ resultInfo.originalTarget,
+ resultInfo.resultTargets, msg.arg1,
+ mDirectShareShortcutInfoCache);
+ }
}
}
- break;
- case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
logDirectShareTargetReceived(
MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
sendVoiceChoicesIfNeeded();
@@ -1954,34 +1953,44 @@
// Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
// for direct share targets. After ShareSheet is refactored we should use the
// ShareShortcutInfos directly.
+ List<ServiceResultInfo> resultRecords = new ArrayList<>();
for (int i = 0; i < chooserListAdapter.getDisplayResolveInfoCount(); i++) {
- List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
- for (int j = 0; j < resultList.size(); j++) {
- if (chooserListAdapter.getDisplayResolveInfo(i).getResolvedComponentName().equals(
- resultList.get(j).getTargetComponent())) {
- matchingShortcuts.add(resultList.get(j));
- }
- }
+ DisplayResolveInfo displayResolveInfo = chooserListAdapter.getDisplayResolveInfo(i);
+ List<ShortcutManager.ShareShortcutInfo> matchingShortcuts =
+ filterShortcutsByTargetComponentName(
+ resultList, displayResolveInfo.getResolvedComponentName());
if (matchingShortcuts.isEmpty()) {
continue;
}
List<ChooserTarget> chooserTargets = convertToChooserTarget(
matchingShortcuts, resultList, appTargets, shortcutType);
- final Message msg = Message.obtain();
- msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
- msg.obj = new ServiceResultInfo(chooserListAdapter.getDisplayResolveInfo(i),
- chooserTargets, userHandle);
- msg.arg1 = shortcutType;
- mChooserHandler.sendMessage(msg);
+ ServiceResultInfo resultRecord = new ServiceResultInfo(
+ displayResolveInfo, chooserTargets, userHandle);
+ resultRecords.add(resultRecord);
}
- sendShortcutManagerShareTargetResultCompleted();
+ sendShortcutManagerShareTargetResults(
+ shortcutType, resultRecords.toArray(new ServiceResultInfo[0]));
}
- private void sendShortcutManagerShareTargetResultCompleted() {
+ private List<ShortcutManager.ShareShortcutInfo> filterShortcutsByTargetComponentName(
+ List<ShortcutManager.ShareShortcutInfo> allShortcuts, ComponentName requiredTarget) {
+ List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
+ for (ShortcutManager.ShareShortcutInfo shortcut : allShortcuts) {
+ if (requiredTarget.equals(shortcut.getTargetComponent())) {
+ matchingShortcuts.add(shortcut);
+ }
+ }
+ return matchingShortcuts;
+ }
+
+ private void sendShortcutManagerShareTargetResults(
+ int shortcutType, ServiceResultInfo[] results) {
final Message msg = Message.obtain();
- msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+ msg.what = ChooserHandler.SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS;
+ msg.obj = results;
+ msg.arg1 = shortcutType;
mChooserHandler.sendMessage(msg);
}
diff --git a/core/java/com/android/internal/app/LaunchAfterAuthenticationActivity.java b/core/java/com/android/internal/app/LaunchAfterAuthenticationActivity.java
new file mode 100644
index 0000000..20a025a
--- /dev/null
+++ b/core/java/com/android/internal/app/LaunchAfterAuthenticationActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.internal.app;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/**
+ * Activity used to intercept lock screen intents and show the bouncer before launching the
+ * original intent.
+ */
+public class LaunchAfterAuthenticationActivity extends Activity {
+ private static final String TAG = LaunchAfterAuthenticationActivity.class.getSimpleName();
+ private static final String EXTRA_ON_SUCCESS_INTENT =
+ "com.android.internal.app.extra.ON_SUCCESS_INTENT";
+
+ /**
+ * Builds the intent used to launch this activity.
+ *
+ * @param onSuccessIntent The intent to launch after the user has authenticated.
+ */
+ public static Intent createLaunchAfterAuthenticationIntent(IntentSender onSuccessIntent) {
+ return new Intent()
+ .setClassName(/* packageName= */"android",
+ LaunchAfterAuthenticationActivity.class.getName())
+ .putExtra(EXTRA_ON_SUCCESS_INTENT, onSuccessIntent)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final IntentSender onSuccessIntent = getIntent().getParcelableExtra(
+ EXTRA_ON_SUCCESS_INTENT);
+ requestDismissKeyguardIfNeeded(onSuccessIntent);
+ }
+
+ private void requestDismissKeyguardIfNeeded(IntentSender onSuccessIntent) {
+ final KeyguardManager km = Objects.requireNonNull(getSystemService(KeyguardManager.class));
+ if (km.isKeyguardLocked()) {
+ km.requestDismissKeyguard(this,
+ new KeyguardManager.KeyguardDismissCallback() {
+ @Override
+ public void onDismissCancelled() {
+ LaunchAfterAuthenticationActivity.this.finish();
+ }
+
+ @Override
+ public void onDismissSucceeded() {
+ if (onSuccessIntent != null) {
+ onUnlocked(onSuccessIntent);
+ }
+ LaunchAfterAuthenticationActivity.this.finish();
+ }
+
+ @Override
+ public void onDismissError() {
+ Slog.e(TAG, "Error while dismissing keyguard.");
+ LaunchAfterAuthenticationActivity.this.finish();
+ }
+ });
+ } else {
+ finish();
+ }
+ }
+
+ private void onUnlocked(@NonNull IntentSender targetIntent) {
+ try {
+ targetIntent.sendIntent(
+ /* context= */ this,
+ /* code= */ 0,
+ /* intent= */null,
+ /* onFinished= */ null,
+ /* handler= */ null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.e(TAG, "Error while sending original intent", e);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java
index 049f952..f8c6640 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java
@@ -74,12 +74,7 @@
if (sRegistry == null) {
sRegistry = new WeakHashMap<>();
}
- final WeakReference<InputMethodPrivilegedOperations> previousOps =
- sRegistry.put(token, new WeakReference<>(ops));
- if (previousOps != null) {
- throw new IllegalStateException(previousOps.get() + " is already registered for "
- + " this token=" + token + " newOps=" + ops);
- }
+ sRegistry.put(token, new WeakReference<>(ops));
}
}
@@ -132,21 +127,4 @@
}
}
}
-
- /**
- * Check the given IME token registration status.
- *
- * @param token IME token
- * @return {@code true} when the IME token has already registered
- * {@link InputMethodPrivilegedOperations}, {@code false} otherwise.
- */
- @AnyThread
- public static boolean isRegistered(IBinder token) {
- synchronized (sLock) {
- if (sRegistry == null) {
- return false;
- }
- return sRegistry.containsKey(token);
- }
- }
}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 93baa19..9443070 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -31,12 +31,15 @@
* Estimates power consumed by the ambient display
*/
public class AmbientDisplayPowerCalculator extends PowerCalculator {
- private final UsageBasedPowerEstimator mPowerEstimator;
+ private final UsageBasedPowerEstimator[] mPowerEstimators;
public AmbientDisplayPowerCalculator(PowerProfile powerProfile) {
- // TODO(b/200239964): update to support multidisplay.
- mPowerEstimator = new UsageBasedPowerEstimator(
- powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0));
+ final int numDisplays = powerProfile.getNumDisplays();
+ mPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
+ for (int display = 0; display < numDisplays; display++) {
+ mPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display));
+ }
}
/**
@@ -50,8 +53,8 @@
final int powerModel = getPowerModel(measuredEnergyUC, query);
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
- final double powerMah = getMeasuredOrEstimatedPower(powerModel,
- measuredEnergyUC, mPowerEstimator, durationMs);
+ final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs,
+ measuredEnergyUC);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs)
@@ -71,9 +74,8 @@
final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC();
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
final int powerModel = getPowerModel(measuredEnergyUC);
- final double powerMah = getMeasuredOrEstimatedPower(powerModel,
- batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(),
- mPowerEstimator, durationMs);
+ final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs,
+ measuredEnergyUC);
if (powerMah > 0) {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0);
bs.usagePowerMah = powerMah;
@@ -86,4 +88,26 @@
private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
}
+
+ private double calculateTotalPower(@BatteryConsumer.PowerModel int powerModel,
+ BatteryStats batteryStats, long rawRealtimeUs, long consumptionUC) {
+ switch (powerModel) {
+ case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+ return uCtoMah(consumptionUC);
+ case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+ default:
+ return calculateEstimatedPower(batteryStats, rawRealtimeUs);
+ }
+ }
+
+ private double calculateEstimatedPower(BatteryStats batteryStats, long rawRealtimeUs) {
+ final int numDisplays = mPowerEstimators.length;
+ double power = 0;
+ for (int display = 0; display < numDisplays; display++) {
+ final long dozeTime = batteryStats.getDisplayScreenDozeTime(display, rawRealtimeUs)
+ / 1000;
+ power += mPowerEstimators[display].calculatePower(dozeTime);
+ }
+ return power;
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 63cce0a..985331c 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -678,7 +678,7 @@
* Schedule a sync because of a screen state change.
*/
Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
- boolean onBatteryScreenOff, int screenState);
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates);
Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis);
void cancelCpuSyncDueToWakelockChange();
Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
@@ -844,17 +844,91 @@
public boolean mRecordAllHistory;
boolean mNoAutoReset;
+ /**
+ * Overall screen state. For multidisplay devices, this represents the current highest screen
+ * state of the displays.
+ */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected int mScreenState = Display.STATE_UNKNOWN;
+ /**
+ * Overall screen on timer. For multidisplay devices, this represents the time spent with at
+ * least one display in the screen on state.
+ */
StopwatchTimer mScreenOnTimer;
+ /**
+ * Overall screen doze timer. For multidisplay devices, this represents the time spent with
+ * screen doze being the highest screen state.
+ */
StopwatchTimer mScreenDozeTimer;
-
+ /**
+ * Overall screen brightness bin. For multidisplay devices, this represents the current
+ * brightest screen.
+ */
int mScreenBrightnessBin = -1;
+ /**
+ * Overall screen brightness timers. For multidisplay devices, the {@link mScreenBrightnessBin}
+ * timer will be active at any given time
+ */
final StopwatchTimer[] mScreenBrightnessTimer =
new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
boolean mPretendScreenOff;
+ private static class DisplayBatteryStats {
+ /**
+ * Per display screen state.
+ */
+ public int screenState = Display.STATE_UNKNOWN;
+ /**
+ * Per display screen on timers.
+ */
+ public StopwatchTimer screenOnTimer;
+ /**
+ * Per display screen doze timers.
+ */
+ public StopwatchTimer screenDozeTimer;
+ /**
+ * Per display screen brightness bins.
+ */
+ public int screenBrightnessBin = -1;
+ /**
+ * Per display screen brightness timers.
+ */
+ public StopwatchTimer[] screenBrightnessTimers =
+ new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
+ /**
+ * Per display screen state the last time {@link #updateDisplayMeasuredEnergyStatsLocked}
+ * was called.
+ */
+ public int screenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN;
+
+ DisplayBatteryStats(Clock clock, TimeBase timeBase) {
+ screenOnTimer = new StopwatchTimer(clock, null, -1, null,
+ timeBase);
+ screenDozeTimer = new StopwatchTimer(clock, null, -1, null,
+ timeBase);
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ screenBrightnessTimers[i] = new StopwatchTimer(clock, null, -100 - i, null,
+ timeBase);
+ }
+ }
+
+ /**
+ * Reset display timers.
+ */
+ public void reset(long elapsedRealtimeUs) {
+ screenOnTimer.reset(false, elapsedRealtimeUs);
+ screenDozeTimer.reset(false, elapsedRealtimeUs);
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ screenBrightnessTimers[i].reset(false, elapsedRealtimeUs);
+ }
+ }
+ }
+
+ DisplayBatteryStats[] mPerDisplayBatteryStats;
+
+ private int mDisplayMismatchWtfCount = 0;
+
boolean mInteractive;
StopwatchTimer mInteractiveTimer;
@@ -999,8 +1073,6 @@
@GuardedBy("this")
@VisibleForTesting
protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats;
- /** Last known screen state. Needed for apportioning display energy. */
- int mScreenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN;
/** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */
@Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null;
/** Cpu Power calculator for attributing measured cpu charge consumption to uids */
@@ -4455,8 +4527,10 @@
public void setPretendScreenOff(boolean pretendScreenOff) {
if (mPretendScreenOff != pretendScreenOff) {
mPretendScreenOff = pretendScreenOff;
- noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON,
- mClock.elapsedRealtime(), mClock.uptimeMillis(), mClock.currentTimeMillis());
+ final int primaryScreenState = mPerDisplayBatteryStats[0].screenState;
+ noteScreenStateLocked(0, primaryScreenState,
+ mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ mClock.currentTimeMillis());
}
}
@@ -5054,29 +5128,158 @@
}
@GuardedBy("this")
- public void noteScreenStateLocked(int state) {
- noteScreenStateLocked(state, mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ public void noteScreenStateLocked(int display, int state) {
+ noteScreenStateLocked(display, state, mClock.elapsedRealtime(), mClock.uptimeMillis(),
mClock.currentTimeMillis());
}
@GuardedBy("this")
- public void noteScreenStateLocked(int state,
+ public void noteScreenStateLocked(int display, int displayState,
long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
- state = mPretendScreenOff ? Display.STATE_OFF : state;
-
// Battery stats relies on there being 4 states. To accommodate this, new states beyond the
// original 4 are mapped to one of the originals.
- if (state > MAX_TRACKED_SCREEN_STATE) {
- switch (state) {
- case Display.STATE_VR:
- state = Display.STATE_ON;
+ if (displayState > MAX_TRACKED_SCREEN_STATE) {
+ if (Display.isOnState(displayState)) {
+ displayState = Display.STATE_ON;
+ } else if (Display.isDozeState(displayState)) {
+ if (Display.isSuspendedState(displayState)) {
+ displayState = Display.STATE_DOZE_SUSPEND;
+ } else {
+ displayState = Display.STATE_DOZE;
+ }
+ } else if (Display.isOffState(displayState)) {
+ displayState = Display.STATE_OFF;
+ } else {
+ Slog.wtf(TAG, "Unknown screen state (not mapped): " + displayState);
+ displayState = Display.STATE_UNKNOWN;
+ }
+ }
+ // As of this point, displayState should be mapped to one of:
+ // - Display.STATE_ON,
+ // - Display.STATE_DOZE
+ // - Display.STATE_DOZE_SUSPEND
+ // - Display.STATE_OFF
+ // - Display.STATE_UNKNOWN
+
+ int state;
+ int overallBin = mScreenBrightnessBin;
+ int externalUpdateFlag = 0;
+ boolean shouldScheduleSync = false;
+ final int numDisplay = mPerDisplayBatteryStats.length;
+ if (display < 0 || display >= numDisplay) {
+ Slog.wtf(TAG, "Unexpected note screen state for display " + display + " (only "
+ + mPerDisplayBatteryStats.length + " displays exist...)");
+ return;
+ }
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ final int oldDisplayState = displayStats.screenState;
+
+ if (oldDisplayState == displayState) {
+ // Nothing changed
+ state = mScreenState;
+ } else {
+ displayStats.screenState = displayState;
+
+ // Stop timer for previous display state.
+ switch (oldDisplayState) {
+ case Display.STATE_ON:
+ displayStats.screenOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ final int bin = displayStats.screenBrightnessBin;
+ if (bin >= 0) {
+ displayStats.screenBrightnessTimers[bin].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE:
+ // Transition from doze to doze suspend can be ignored.
+ if (displayState == Display.STATE_DOZE_SUSPEND) break;
+ displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ // Transition from doze suspend to doze can be ignored.
+ if (displayState == Display.STATE_DOZE) break;
+ displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_OFF: // fallthrough
+ case Display.STATE_UNKNOWN:
+ // Not tracked by timers.
break;
default:
- Slog.wtf(TAG, "Unknown screen state (not mapped): " + state);
+ Slog.wtf(TAG,
+ "Attempted to stop timer for unexpected display state " + display);
+ }
+
+ // Start timer for new display state.
+ switch (displayState) {
+ case Display.STATE_ON:
+ displayStats.screenOnTimer.startRunningLocked(elapsedRealtimeMs);
+ final int bin = displayStats.screenBrightnessBin;
+ if (bin >= 0) {
+ displayStats.screenBrightnessTimers[bin].startRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ shouldScheduleSync = true;
break;
+ case Display.STATE_DOZE:
+ // Transition from doze suspend to doze can be ignored.
+ if (oldDisplayState == Display.STATE_DOZE_SUSPEND) break;
+ displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ // Transition from doze to doze suspend can be ignored.
+ if (oldDisplayState == Display.STATE_DOZE) break;
+ displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_OFF: // fallthrough
+ case Display.STATE_UNKNOWN:
+ // Not tracked by timers.
+ break;
+ default:
+ Slog.wtf(TAG,
+ "Attempted to start timer for unexpected display state " + displayState
+ + " for display " + display);
+ }
+
+ if (shouldScheduleSync
+ && mGlobalMeasuredEnergyStats != null
+ && mGlobalMeasuredEnergyStats.isStandardBucketSupported(
+ MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON)) {
+ // Display measured energy stats is available. Prepare to schedule an
+ // external sync.
+ externalUpdateFlag |= ExternalStatsSync.UPDATE_DISPLAY;
+ }
+
+ // Reevaluate most important display screen state.
+ state = Display.STATE_UNKNOWN;
+ for (int i = 0; i < numDisplay; i++) {
+ final int tempState = mPerDisplayBatteryStats[i].screenState;
+ if (tempState == Display.STATE_ON
+ || state == Display.STATE_ON) {
+ state = Display.STATE_ON;
+ } else if (tempState == Display.STATE_DOZE
+ || state == Display.STATE_DOZE) {
+ state = Display.STATE_DOZE;
+ } else if (tempState == Display.STATE_DOZE_SUSPEND
+ || state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_DOZE_SUSPEND;
+ } else if (tempState == Display.STATE_OFF
+ || state == Display.STATE_OFF) {
+ state = Display.STATE_OFF;
+ }
}
}
+ final boolean batteryRunning = mOnBatteryTimeBase.isRunning();
+ final boolean batteryScreenOffRunning = mOnBatteryScreenOffTimeBase.isRunning();
+
+ state = mPretendScreenOff ? Display.STATE_OFF : state;
if (mScreenState != state) {
recordDailyStatsIfNeededLocked(true, currentTimeMs);
final int oldState = mScreenState;
@@ -5130,11 +5333,11 @@
+ Display.stateToString(state));
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
- // TODO: (Probably overkill) Have mGlobalMeasuredEnergyStats store supported flags and
- // only update DISPLAY if it is. Currently overkill since CPU is scheduled anyway.
- final int updateFlag = ExternalStatsSync.UPDATE_CPU | ExternalStatsSync.UPDATE_DISPLAY;
- mExternalSync.scheduleSyncDueToScreenStateChange(updateFlag,
- mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning(), state);
+
+ // Per screen state Cpu stats needed. Prepare to schedule an external sync.
+ externalUpdateFlag |= ExternalStatsSync.UPDATE_CPU;
+ shouldScheduleSync = true;
+
if (Display.isOnState(state)) {
updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
uptimeMs * 1000, elapsedRealtimeMs * 1000);
@@ -5152,33 +5355,116 @@
updateDischargeScreenLevelsLocked(oldState, state);
}
}
+
+ // Changing display states might have changed the screen used to determine the overall
+ // brightness.
+ maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
+
+ if (shouldScheduleSync) {
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ final int[] displayStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ }
+ mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
+ batteryRunning, batteryScreenOffRunning, state, displayStates);
+ }
}
@UnsupportedAppUsage
public void noteScreenBrightnessLocked(int brightness) {
- noteScreenBrightnessLocked(brightness, mClock.elapsedRealtime(), mClock.uptimeMillis());
+ noteScreenBrightnessLocked(0, brightness);
}
- public void noteScreenBrightnessLocked(int brightness, long elapsedRealtimeMs, long uptimeMs) {
+ /**
+ * Note screen brightness change for a display.
+ */
+ public void noteScreenBrightnessLocked(int display, int brightness) {
+ noteScreenBrightnessLocked(display, brightness, mClock.elapsedRealtime(),
+ mClock.uptimeMillis());
+ }
+
+
+ /**
+ * Note screen brightness change for a display.
+ */
+ public void noteScreenBrightnessLocked(int display, int brightness, long elapsedRealtimeMs,
+ long uptimeMs) {
// Bin the brightness.
int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS);
if (bin < 0) bin = 0;
else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
- if (mScreenBrightnessBin != bin) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
- | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+
+ final int overallBin;
+
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ if (display < 0 || display >= numDisplays) {
+ Slog.wtf(TAG, "Unexpected note screen brightness for display " + display + " (only "
+ + mPerDisplayBatteryStats.length + " displays exist...)");
+ return;
+ }
+
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ final int oldBin = displayStats.screenBrightnessBin;
+ if (oldBin == bin) {
+ // Nothing changed
+ overallBin = mScreenBrightnessBin;
+ } else {
+ displayStats.screenBrightnessBin = bin;
+ if (displayStats.screenState == Display.STATE_ON) {
+ if (oldBin >= 0) {
+ displayStats.screenBrightnessTimers[oldBin].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ displayStats.screenBrightnessTimers[bin].startRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ }
+
+ maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
+ }
+
+ private int evaluateOverallScreenBrightnessBinLocked() {
+ int overallBin = -1;
+ final int numDisplays = getDisplayCount();
+ for (int display = 0; display < numDisplays; display++) {
+ final int displayBrightnessBin;
+ if (mPerDisplayBatteryStats[display].screenState == Display.STATE_ON) {
+ displayBrightnessBin = mPerDisplayBatteryStats[display].screenBrightnessBin;
+ } else {
+ displayBrightnessBin = -1;
+ }
+ if (displayBrightnessBin > overallBin) {
+ overallBin = displayBrightnessBin;
+ }
+ }
+ return overallBin;
+ }
+
+ private void maybeUpdateOverallScreenBrightness(int overallBin, long elapsedRealtimeMs,
+ long uptimeMs) {
+ if (mScreenBrightnessBin != overallBin) {
+ if (overallBin >= 0) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+ | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ if (DEBUG_HISTORY) {
+ Slog.v(TAG, "Screen brightness " + overallBin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ }
+ addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ }
if (mScreenState == Display.STATE_ON) {
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.stopRunningLocked(elapsedRealtimeMs);
}
- mScreenBrightnessTimer[bin]
- .startRunningLocked(elapsedRealtimeMs);
+ if (overallBin >= 0) {
+ mScreenBrightnessTimer[overallBin]
+ .startRunningLocked(elapsedRealtimeMs);
+ }
}
- mScreenBrightnessBin = bin;
+ mScreenBrightnessBin = overallBin;
}
}
@@ -6842,6 +7128,31 @@
return mScreenBrightnessTimer[brightnessBin];
}
+ @Override
+ public int getDisplayCount() {
+ return mPerDisplayBatteryStats.length;
+ }
+
+ @Override
+ public long getDisplayScreenOnTime(int display, long elapsedRealtimeUs) {
+ return mPerDisplayBatteryStats[display].screenOnTimer.getTotalTimeLocked(elapsedRealtimeUs,
+ STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs) {
+ return mPerDisplayBatteryStats[display].screenDozeTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs) {
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ return displayStats.screenBrightnessTimers[brightnessBin].getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED);
+ }
+
@Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -10875,6 +11186,10 @@
mScreenBrightnessTimer[i] = new StopwatchTimer(mClock, null, -100 - i, null,
mOnBatteryTimeBase);
}
+
+ mPerDisplayBatteryStats = new DisplayBatteryStats[1];
+ mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
+
mInteractiveTimer = new StopwatchTimer(mClock, null, -10, null, mOnBatteryTimeBase);
mPowerSaveModeEnabledTimer = new StopwatchTimer(mClock, null, -2, null,
mOnBatteryTimeBase);
@@ -10987,6 +11302,8 @@
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
}
+
+ setDisplayCountLocked(mPowerProfile.getNumDisplays());
}
PowerProfile getPowerProfile() {
@@ -11019,6 +11336,16 @@
mExternalSync = sync;
}
+ /**
+ * Initialize and set multi display timers and states.
+ */
+ public void setDisplayCountLocked(int numDisplays) {
+ mPerDisplayBatteryStats = new DisplayBatteryStats[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ mPerDisplayBatteryStats[i] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
+ }
+ }
+
public void updateDailyDeadlineLocked() {
// Get the current time.
long currentTimeMs = mDailyStartTimeMs = mClock.currentTimeMillis();
@@ -11502,6 +11829,11 @@
mScreenBrightnessTimer[i].reset(false, elapsedRealtimeUs);
}
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ for (int i = 0; i < numDisplays; i++) {
+ mPerDisplayBatteryStats[i].reset(elapsedRealtimeUs);
+ }
+
if (mPowerProfile != null) {
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
} else {
@@ -12785,22 +13117,43 @@
* is always 0 when the screen is not "ON" and whenever the rail energy is 0 (if supported).
* To the extent that those assumptions are violated, the algorithm will err.
*
- * @param chargeUC amount of charge (microcoulombs) used by Display since this was last called.
- * @param screenState screen state at the time this data collection was scheduled
+ * @param chargesUC amount of charge (microcoulombs) used by each Display since this was last
+ * called.
+ * @param screenStates each screen state at the time this data collection was scheduled
*/
@GuardedBy("this")
- public void updateDisplayMeasuredEnergyStatsLocked(long chargeUC, int screenState,
+ public void updateDisplayMeasuredEnergyStatsLocked(long[] chargesUC, int[] screenStates,
long elapsedRealtimeMs) {
- if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + chargeUC);
+ if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + Arrays.toString(chargesUC));
if (mGlobalMeasuredEnergyStats == null) {
return;
}
- final @StandardPowerBucket int powerBucket =
- MeasuredEnergyStats.getDisplayPowerBucket(mScreenStateAtLastEnergyMeasurement);
- mScreenStateAtLastEnergyMeasurement = screenState;
+ final int numDisplays;
+ if (mPerDisplayBatteryStats.length == screenStates.length) {
+ numDisplays = screenStates.length;
+ } else {
+ // if this point is reached, it will be reached every display state change.
+ // Rate limit the wtf logging to once every 100 display updates.
+ if (mDisplayMismatchWtfCount++ % 100 == 0) {
+ Slog.wtf(TAG, "Mismatch between PowerProfile reported display count ("
+ + mPerDisplayBatteryStats.length
+ + ") and PowerStatsHal reported display count (" + screenStates.length
+ + ")");
+ }
+ // Keep the show going, use the shorter of the two.
+ numDisplays = mPerDisplayBatteryStats.length < screenStates.length
+ ? mPerDisplayBatteryStats.length : screenStates.length;
+ }
- if (!mOnBatteryInternal || chargeUC <= 0) {
+ final int[] oldScreenStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ final int screenState = screenStates[i];
+ oldScreenStates[i] = mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement;
+ mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
+ }
+
+ if (!mOnBatteryInternal) {
// There's nothing further to update.
return;
}
@@ -12815,17 +13168,31 @@
return;
}
- mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC);
+ long totalScreenOnChargeUC = 0;
+ for (int i = 0; i < numDisplays; i++) {
+ final long chargeUC = chargesUC[i];
+ if (chargeUC <= 0) {
+ // There's nothing further to update.
+ continue;
+ }
+
+ final @StandardPowerBucket int powerBucket =
+ MeasuredEnergyStats.getDisplayPowerBucket(oldScreenStates[i]);
+ mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC);
+ if (powerBucket == MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) {
+ totalScreenOnChargeUC += chargeUC;
+ }
+ }
// Now we blame individual apps, but only if the display was ON.
- if (powerBucket != MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) {
+ if (totalScreenOnChargeUC <= 0) {
return;
}
// TODO(b/175726779): Consider unifying the code with the non-rail display power blaming.
// NOTE: fg time is NOT pooled. If two uids are both somehow in fg, then that time is
// 'double counted' and will simply exceed the realtime that elapsed.
- // If multidisplay becomes a reality, this is probably more reasonable than pooling.
+ // TODO(b/175726779): collect per display uid visibility for display power attribution.
// Collect total time since mark so that we can normalize power.
final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray();
@@ -12838,7 +13205,8 @@
if (fgTimeUs == 0) continue;
fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs);
}
- distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0);
+ distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON,
+ totalScreenOnChargeUC, fgTimeUsArray, 0);
}
/**
@@ -14769,6 +15137,11 @@
mShuttingDown = true;
}
+ @Override
+ public boolean isProcessStateDataAvailable() {
+ return trackPerProcStateCpuTimes();
+ }
+
public boolean trackPerProcStateCpuTimes() {
return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
}
@@ -14790,7 +15163,12 @@
public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets,
String[] customBucketNames) {
boolean supportedBucketMismatch = false;
- mScreenStateAtLastEnergyMeasurement = mScreenState;
+
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ for (int i = 0; i < numDisplays; i++) {
+ final int screenState = mPerDisplayBatteryStats[i].screenState;
+ mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
+ }
if (supportedStandardBuckets == null) {
if (mGlobalMeasuredEnergyStats != null) {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 615ab63..88425be 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -152,9 +152,13 @@
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ final boolean includeProcessStateData = ((query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
+ && mStats.isProcessStateDataAvailable();
final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
- mStats.getCustomEnergyConsumerNames(), includePowerModels);
+ mStats.getCustomEnergyConsumerNames(), includePowerModels,
+ includeProcessStateData);
// TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
// of stats sessions to wall-clock adjustments
batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime());
@@ -224,10 +228,13 @@
private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) {
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ final boolean includeProcessStateData = ((query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
+ && mStats.isProcessStateDataAvailable();
final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customEnergyConsumerNames, includePowerModels);
+ customEnergyConsumerNames, includePowerModels, includeProcessStateData);
if (mBatteryUsageStatsStore == null) {
Log.e(TAG, "BatteryUsageStatsStore is unavailable");
return builder.build();
@@ -238,16 +245,25 @@
if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) {
final BatteryUsageStats snapshot =
mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
- if (snapshot != null) {
- if (Arrays.equals(snapshot.getCustomPowerComponentNames(),
- customEnergyConsumerNames)) {
- builder.add(snapshot);
- } else {
- Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
- + "custom power components: "
- + Arrays.toString(snapshot.getCustomPowerComponentNames()));
- }
+ if (snapshot == null) {
+ continue;
}
+
+ if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
+ customEnergyConsumerNames)) {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+ + "custom power components: "
+ + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ continue;
+ }
+
+ if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
+ + " does not include process state data");
+ continue;
+ }
+
+ builder.add(snapshot);
}
}
return builder.build();
diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java
new file mode 100644
index 0000000..518d9ab
--- /dev/null
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Performs per-state counting of long integers over time. The tracked "value" is expected
+ * to increase monotonously. The counter keeps track of the current state. When the
+ * updateValue method is called, the delta from the previous invocation of this method
+ * and the new value is added to the counter corresponding to the current state. If the
+ * state changed in the interim, the delta is distributed proptionally.
+ *
+ * The class's behavior is illustrated by this example:
+ * <pre>
+ * // At 0 ms, the state of the tracked object is 0 and the initial tracked value is 100
+ * counter.setState(0, 0);
+ * counter.updateValue(100, 0);
+ *
+ * // At 1000 ms, the state changes to 1
+ * counter.setState(1, 1000);
+ *
+ * // At 3000 ms, the tracked value is updated to 130
+ * counter.updateValue(130, 3000);
+ *
+ * // The delta (130 - 100 = 30) is distributed between states 0 and 1 according to the time
+ * // spent in those respective states; in this specific case, 1000 and 2000 ms.
+ * long countForState0 == counter.getCount(0); // 10
+ * long countForState1 == counter.getCount(1); // 20
+ * </pre>
+ *
+ * The tracked values are expected to increase monotonically.
+ *
+ * @hide
+ */
+public final class LongMultiStateCounter implements Parcelable {
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ LongMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+
+ private final int mStateCount;
+
+ // Visible to other objects in this package so that it can be passed to @CriticalNative
+ // methods.
+ final long mNativeObject;
+
+ public LongMultiStateCounter(int stateCount) {
+ Preconditions.checkArgumentPositive(stateCount, "stateCount must be greater than 0");
+ mStateCount = stateCount;
+ mNativeObject = native_init(stateCount);
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ private LongMultiStateCounter(Parcel in) {
+ mNativeObject = native_initFromParcel(in);
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+
+ mStateCount = native_getStateCount(mNativeObject);
+ }
+
+ public int getStateCount() {
+ return mStateCount;
+ }
+
+ /**
+ * Enables or disables the counter. When the counter is disabled, it does not
+ * accumulate counts supplied by the {@link #updateValue} method.
+ */
+ public void setEnabled(boolean enabled, long timestampMs) {
+ native_setEnabled(mNativeObject, enabled, timestampMs);
+ }
+
+ /**
+ * Sets the current state to the supplied value.
+ *
+ * @param state The new state
+ * @param timestampMs The time when the state change occurred, e.g.
+ * SystemClock.elapsedRealtime()
+ */
+ public void setState(int state, long timestampMs) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ native_setState(mNativeObject, state, timestampMs);
+ }
+
+ /**
+ * Sets the new values. The delta between the previously set values and these values
+ * is distributed among the state according to the time the object spent in those states
+ * since the previous call to updateValues.
+ */
+ public void updateValue(long value, long timestampMs) {
+ native_updateValue(mNativeObject, value, timestampMs);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void addCount(long count) {
+ native_addCount(mNativeObject, count);
+ }
+
+ /**
+ * Resets the accumulated counts to 0.
+ */
+ public void reset() {
+ native_reset(mNativeObject);
+ }
+
+ /**
+ * Returns the accumulated count for the specified state.
+ */
+ public long getCount(int state) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + mStateCount + "]");
+ }
+ return native_getCount(mNativeObject, state);
+ }
+
+ @Override
+ public String toString() {
+ return native_toString(mNativeObject);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ native_writeToParcel(mNativeObject, dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<LongMultiStateCounter> CREATOR =
+ new Creator<LongMultiStateCounter>() {
+ @Override
+ public LongMultiStateCounter createFromParcel(Parcel in) {
+ return new LongMultiStateCounter(in);
+ }
+
+ @Override
+ public LongMultiStateCounter[] newArray(int size) {
+ return new LongMultiStateCounter[size];
+ }
+ };
+
+
+ @CriticalNative
+ private static native long native_init(int stateCount);
+
+ @CriticalNative
+ private static native long native_getReleaseFunc();
+
+ @CriticalNative
+ private static native void native_setEnabled(long nativeObject, boolean enabled,
+ long timestampMs);
+
+ @CriticalNative
+ private static native void native_setState(long nativeObject, int state, long timestampMs);
+
+ @CriticalNative
+ private static native void native_updateValue(long nativeObject, long value, long timestampMs);
+
+ @CriticalNative
+ private static native void native_addCount(long nativeObject, long count);
+
+ @CriticalNative
+ private static native void native_reset(long nativeObject);
+
+ @CriticalNative
+ private static native long native_getCount(long nativeObject, int state);
+
+ @FastNative
+ private native String native_toString(long nativeObject);
+
+ @FastNative
+ private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+
+ @FastNative
+ private static native long native_initFromParcel(Parcel parcel);
+
+ @CriticalNative
+ private static native int native_getStateCount(long nativeObject);
+}
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 4979ecb..93d562c 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -133,32 +133,6 @@
}
/**
- * Returns either the measured energy converted to mAh or a usage-based estimate.
- */
- protected static double getMeasuredOrEstimatedPower(@BatteryConsumer.PowerModel int powerModel,
- long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) {
- switch (powerModel) {
- case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
- return uCtoMah(measuredEnergyUC);
- case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
- default:
- return powerEstimator.calculatePower(durationMs);
- }
- }
-
- /**
- * Returns either the measured energy converted to mAh or a usage-based estimate.
- */
- protected static double getMeasuredOrEstimatedPower(
- long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) {
- if (measuredEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
- return uCtoMah(measuredEnergyUC);
- } else {
- return powerEstimator.calculatePower(durationMs);
- }
- }
-
- /**
* Prints formatted amount of power in milli-amp-hours.
*/
public static void printPowerMah(PrintWriter pw, double powerMah) {
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 72ad4e7..2b63459 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -44,8 +44,8 @@
// Minimum amount of time the screen should be on to start smearing drain to apps
public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
- private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
- private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
+ private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators;
+ private final UsageBasedPowerEstimator[] mScreenFullPowerEstimators;
private static class PowerAndDuration {
public long durationMs;
@@ -53,11 +53,16 @@
}
public ScreenPowerCalculator(PowerProfile powerProfile) {
- // TODO(b/200239964): update to support multidisplay.
- mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
- powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0));
- mScreenFullPowerEstimator = new UsageBasedPowerEstimator(
- powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
+ final int numDisplays = powerProfile.getNumDisplays();
+ mScreenOnPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
+ mScreenFullPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
+ for (int display = 0; display < numDisplays; display++) {
+ mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display));
+ mScreenFullPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL,
+ display));
+ }
}
@Override
@@ -172,7 +177,7 @@
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
default:
totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
- rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
+ rawRealtimeUs);
}
}
@@ -194,19 +199,25 @@
return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
}
- private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs,
- int statsType, long durationMs) {
- double power = mScreenOnPowerEstimator.calculatePower(durationMs);
- for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- final long brightnessTime =
- batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000;
- final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime)
- * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
- if (DEBUG && binPowerMah != 0) {
- Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
- + " power=" + formatCharge(binPowerMah));
+ private double calculateTotalPowerFromBrightness(BatteryStats batteryStats,
+ long rawRealtimeUs) {
+ final int numDisplays = mScreenOnPowerEstimators.length;
+ double power = 0;
+ for (int display = 0; display < numDisplays; display++) {
+ final long displayTime = batteryStats.getDisplayScreenOnTime(display, rawRealtimeUs)
+ / 1000;
+ power += mScreenOnPowerEstimators[display].calculatePower(displayTime);
+ for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ final long brightnessTime = batteryStats.getDisplayScreenBrightnessTime(display,
+ bin, rawRealtimeUs) / 1000;
+ final double binPowerMah = mScreenFullPowerEstimators[display].calculatePower(
+ brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ if (DEBUG && binPowerMah != 0) {
+ Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime
+ + " power=" + formatCharge(binPowerMah));
+ }
+ power += binPowerMah;
}
- power += binPowerMah;
}
return power;
}
diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
index a8003a1..d69a240 100644
--- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
@@ -20,5 +20,4 @@
void onSimSecureStateChanged(boolean simSecure);
void onInputRestrictedStateChanged(boolean inputRestricted);
void onTrustedChanged(boolean trusted);
- void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper);
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index db019a67..954204f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -84,6 +84,7 @@
Consts.TAG_WM),
WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
+ WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 5144a91..4c519f4 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -117,6 +117,11 @@
*/
public static final int ACTION_LOCKSCREEN_UNLOCK = 11;
+ /**
+ * Time it takes to switch users.
+ */
+ public static final int ACTION_USER_SWITCH = 12;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -129,7 +134,8 @@
ACTION_START_RECENTS_ANIMATION,
ACTION_ROTATE_SCREEN_SENSOR,
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
- ACTION_LOCKSCREEN_UNLOCK
+ ACTION_LOCKSCREEN_UNLOCK,
+ ACTION_USER_SWITCH
};
/** @hide */
@@ -145,7 +151,8 @@
ACTION_START_RECENTS_ANIMATION,
ACTION_ROTATE_SCREEN_SENSOR,
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
- ACTION_LOCKSCREEN_UNLOCK
+ ACTION_LOCKSCREEN_UNLOCK,
+ ACTION_USER_SWITCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -163,7 +170,8 @@
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
- FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH
};
private static LatencyTracker sLatencyTracker;
@@ -247,6 +255,8 @@
return "ACTION_ROTATE_SCREEN_SENSOR";
case 12:
return "ACTION_LOCKSCREEN_UNLOCK";
+ case 13:
+ return "ACTION_USER_SWITCH";
default:
throw new IllegalArgumentException("Invalid action");
}
@@ -424,7 +434,7 @@
// start counting timeout.
mTimeoutRunnable = timeoutAction;
BackgroundThread.getHandler()
- .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(2));
+ .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15));
}
void end() {
diff --git a/core/java/com/android/internal/view/ListViewCaptureHelper.java b/core/java/com/android/internal/view/ListViewCaptureHelper.java
index f4a5b71..11ed820 100644
--- a/core/java/com/android/internal/view/ListViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/ListViewCaptureHelper.java
@@ -23,10 +23,13 @@
import android.annotation.NonNull;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
+import java.util.function.Consumer;
+
/**
* Scroll capture support for ListView.
*
@@ -56,8 +59,8 @@
}
@Override
- public ScrollResult onScrollRequested(@NonNull ListView listView, Rect scrollBounds,
- Rect requestRect) {
+ public void onScrollRequested(@NonNull ListView listView, Rect scrollBounds,
+ Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) {
Log.d(TAG, "-----------------------------------------------------------");
Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", "
+ "requestRect=" + requestRect + ")");
@@ -69,7 +72,8 @@
if (!listView.isVisibleToUser() || listView.getChildCount() == 0) {
Log.w(TAG, "listView is empty or not visible, cannot continue");
- return result; // result.availableArea == empty Rect
+ resultConsumer.accept(result); // result.availableArea == empty Rect
+ return;
}
// Make requestRect relative to RecyclerView (from scrollBounds)
@@ -117,7 +121,7 @@
mScrollDelta, scrollBounds, requestedContainerBounds);
}
Log.d(TAG, "-----------------------------------------------------------");
- return result;
+ resultConsumer.accept(result);
}
@Override
diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
index 64622f0..8192ffd 100644
--- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
@@ -18,11 +18,14 @@
import android.annotation.NonNull;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import java.util.function.Consumer;
+
/**
* ScrollCapture for RecyclerView and <i>RecyclerView-like</i> ViewGroups.
* <p>
@@ -61,8 +64,8 @@
}
@Override
- public ScrollResult onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
- Rect requestRect) {
+ public void onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
+ Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) {
ScrollResult result = new ScrollResult();
result.requestedArea = new Rect(requestRect);
result.scrollDelta = mScrollDelta;
@@ -70,7 +73,8 @@
if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) {
Log.w(TAG, "recyclerView is empty or not visible, cannot continue");
- return result; // result.availableArea == empty Rect
+ resultConsumer.accept(result); // result.availableArea == empty Rect
+ return;
}
// move from scrollBounds-relative to parent-local coordinates
@@ -83,7 +87,8 @@
View anchor = findChildNearestTarget(recyclerView, requestedContainerBounds);
if (anchor == null) {
Log.w(TAG, "Failed to locate anchor view");
- return result; // result.availableArea == empty rect
+ resultConsumer.accept(result); // result.availableArea == empty rect
+ return;
}
Rect requestedContentBounds = new Rect(requestedContainerBounds);
@@ -113,13 +118,14 @@
if (!requestedContainerBounds.intersect(recyclerLocalVisible)) {
// Requested area is still not visible
- return result;
+ resultConsumer.accept(result);
+ return;
}
Rect available = new Rect(requestedContainerBounds);
available.offset(-scrollBounds.left, -scrollBounds.top);
available.offset(0, mScrollDelta);
result.availableArea = available;
- return result;
+ resultConsumer.accept(result);
}
/**
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
index baf725d..347ab1f 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
@@ -18,9 +18,12 @@
import android.annotation.NonNull;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.view.View;
import android.view.ViewGroup;
+import java.util.function.Consumer;
+
/**
* Provides view-specific handling to ScrollCaptureViewSupport.
*
@@ -98,21 +101,22 @@
/**
* Map the request onto the screen.
* <p>
- * Given a rect describing the area to capture, relative to scrollBounds, take actions
+ * Given a rect describing the area to capture, relative to scrollBounds, take actions
* necessary to bring the content within the rectangle into the visible area of the view if
* needed and return the resulting rectangle describing the position and bounds of the area
* which is visible.
- *
- * @param view the view being captured
+ * @param view the view being captured
* @param scrollBounds the area in which scrolling content moves, local to the {@code containing
* view}
* @param requestRect the area relative to {@code scrollBounds} which describes the location of
- * content to capture for the request
- * @return the result of the request as a {@link ScrollResult}
+ * content to capture for the request
+ * @param cancellationSignal allows for the request to be cancelled by the caller
+ * @param resultConsumer accepts the result of the request as a {@link ScrollResult}
*/
@NonNull
- ScrollResult onScrollRequested(@NonNull V view, @NonNull Rect scrollBounds,
- @NonNull Rect requestRect);
+ void onScrollRequested(@NonNull V view, @NonNull Rect scrollBounds,
+ @NonNull Rect requestRect, CancellationSignal cancellationSignal,
+ Consumer<ScrollResult> resultConsumer);
/**
* Restore the target after capture.
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index 2f25d60..94a8ae5 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -249,46 +249,38 @@
}
// Ask the view to scroll as needed to bring this area into view.
- ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
- requestRect);
+ mViewHelper.onScrollRequested(view, session.getScrollBounds(), requestRect, signal,
+ (result) -> onScrollResult(result, view, signal, onComplete));
+ }
+
+ private void onScrollResult(ScrollResult scrollResult, V view, CancellationSignal signal,
+ Consumer<Rect> onComplete) {
+
+ if (signal.isCanceled()) {
+ Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
+ return;
+ }
if (scrollResult.availableArea.isEmpty()) {
onComplete.accept(scrollResult.availableArea);
return;
}
- // For image capture, shift back by scrollDelta to arrive at the location within the view
- // where the requested content will be drawn
+ // For image capture, shift back by scrollDelta to arrive at the location
+ // within the view where the requested content will be drawn
Rect viewCaptureArea = new Rect(scrollResult.availableArea);
viewCaptureArea.offset(0, -scrollResult.scrollDelta);
- Runnable captureAction = () -> {
- if (signal.isCanceled()) {
- Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
- } else {
- int result = mRenderer.renderView(view, viewCaptureArea);
- switch (result) {
- case HardwareRenderer.SYNC_OK:
- case HardwareRenderer.SYNC_REDRAW_REQUESTED:
- /* Frame synced, buffer will be produced... notify client. */
- onComplete.accept(new Rect(scrollResult.availableArea));
- return;
- case HardwareRenderer.SYNC_FRAME_DROPPED:
- Log.e(TAG, "syncAndDraw(): SYNC_FRAME_DROPPED !");
- break;
- case HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND:
- Log.e(TAG, "syncAndDraw(): SYNC_LOST_SURFACE !");
- break;
- case HardwareRenderer.SYNC_CONTEXT_IS_STOPPED:
- Log.e(TAG, "syncAndDraw(): SYNC_CONTEXT_IS_STOPPED !");
- break;
- }
- // No buffer will be produced.
- onComplete.accept(new Rect(/* empty */));
- }
- };
-
- view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis);
+ int result = mRenderer.renderView(view, viewCaptureArea);
+ if (result == HardwareRenderer.SYNC_OK
+ || result == HardwareRenderer.SYNC_REDRAW_REQUESTED) {
+ /* Frame synced, buffer will be produced... notify client. */
+ onComplete.accept(new Rect(scrollResult.availableArea));
+ } else {
+ // No buffer will be produced.
+ Log.e(TAG, "syncAndDraw(): SyncAndDrawResult = " + result);
+ onComplete.accept(new Rect(/* empty */));
+ }
}
@Override
diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
index db7881f..c8ff283 100644
--- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
@@ -19,10 +19,13 @@
import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import java.util.function.Consumer;
+
/**
* ScrollCapture for ScrollView and <i>ScrollView-like</i> ViewGroups.
* <p>
@@ -60,8 +63,8 @@
}
}
- public ScrollResult onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
- Rect requestRect) {
+ public void onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
+ Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) {
/*
+---------+ <----+ Content [25,25 - 275,1025] (w=250,h=1000)
| |
@@ -105,7 +108,8 @@
final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
if (contentView == null) {
// No child view? Cannot continue.
- return result;
+ resultConsumer.accept(result);
+ return;
}
// 1) Translate request rect to make it relative to container view
@@ -155,7 +159,8 @@
if (!view.getChildVisibleRect(contentView, available, offset)) {
available.setEmpty();
result.availableArea = available;
- return result;
+ resultConsumer.accept(result);
+ return;
}
// Transform back from global to content-view local
available.offset(-offset.x, -offset.y);
@@ -174,7 +179,7 @@
available.offset(0, scrollDelta);
result.availableArea = new Rect(available);
- return result;
+ resultConsumer.accept(result);
}
public void onPrepareForEnd(@NonNull ViewGroup view) {
diff --git a/core/java/com/android/internal/view/WebViewCaptureHelper.java b/core/java/com/android/internal/view/WebViewCaptureHelper.java
index 37ce782..086e00c 100644
--- a/core/java/com/android/internal/view/WebViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/WebViewCaptureHelper.java
@@ -23,8 +23,11 @@
import android.annotation.NonNull;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.webkit.WebView;
+import java.util.function.Consumer;
+
/**
* ScrollCapture for WebView.
*/
@@ -51,8 +54,9 @@
@NonNull
@Override
- public ScrollResult onScrollRequested(@NonNull WebView view, @NonNull Rect scrollBounds,
- @NonNull Rect requestRect) {
+ public void onScrollRequested(@NonNull WebView view, @NonNull Rect scrollBounds,
+ @NonNull Rect requestRect, CancellationSignal cancellationSignal,
+ Consumer<ScrollResult> resultConsumer) {
int scrollDelta = view.getScrollY() - mOriginScrollY;
@@ -64,7 +68,7 @@
mWebViewBounds.set(0, 0, view.getWidth(), view.getHeight());
if (!view.isVisibleToUser()) {
- return result;
+ resultConsumer.accept(result);
}
// Map the request into local coordinates
@@ -88,7 +92,7 @@
result.availableArea = new Rect(mRequestWebViewLocal);
result.availableArea.offset(0, result.scrollDelta);
}
- return result;
+ resultConsumer.accept(result);
}
@Override
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 945adb7..1a1a8ba 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -218,6 +218,7 @@
"com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
"com_android_internal_os_KernelSingleUidTimeReader.cpp",
"com_android_internal_os_LongArrayMultiStateCounter.cpp",
+ "com_android_internal_os_LongMultiStateCounter.cpp",
"com_android_internal_os_Zygote.cpp",
"com_android_internal_os_ZygoteCommandBuffer.cpp",
"com_android_internal_os_ZygoteInit.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 91179c2..5c9b6df 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -205,6 +205,7 @@
extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
extern int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv* env);
+extern int register_com_android_internal_os_LongMultiStateCounter(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
@@ -1588,6 +1589,7 @@
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
+ REG_JNI(register_com_android_internal_os_LongMultiStateCounter),
REG_JNI(register_com_android_internal_os_Zygote),
REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer),
REG_JNI(register_com_android_internal_os_ZygoteInit),
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index d4fb3e3..a7362ab 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -30,43 +30,6 @@
namespace android {
-struct {
- jmethodID onTransactionComplete;
-} gTransactionCompleteCallback;
-
-class TransactionCompleteCallbackWrapper : public LightRefBase<TransactionCompleteCallbackWrapper> {
-public:
- explicit TransactionCompleteCallbackWrapper(JNIEnv* env, jobject jobject) {
- env->GetJavaVM(&mVm);
- mTransactionCompleteObject = env->NewGlobalRef(jobject);
- LOG_ALWAYS_FATAL_IF(!mTransactionCompleteObject, "Failed to make global ref");
- }
-
- ~TransactionCompleteCallbackWrapper() {
- if (mTransactionCompleteObject) {
- getenv()->DeleteGlobalRef(mTransactionCompleteObject);
- mTransactionCompleteObject = nullptr;
- }
- }
-
- void onTransactionComplete(int64_t frameNr) {
- if (mTransactionCompleteObject) {
- getenv()->CallVoidMethod(mTransactionCompleteObject,
- gTransactionCompleteCallback.onTransactionComplete, frameNr);
- }
- }
-
-private:
- JavaVM* mVm;
- jobject mTransactionCompleteObject;
-
- JNIEnv* getenv() {
- JNIEnv* env;
- mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
- return env;
- }
-};
-
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
jlong width, jlong height, jint format) {
String8 str8;
@@ -119,21 +82,6 @@
queue->mergeWithNextTransaction(transaction, framenumber);
}
-static void nativeSetTransactionCompleteCallback(JNIEnv* env, jclass clazz, jlong ptr,
- jlong frameNumber,
- jobject transactionCompleteCallback) {
- sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
- if (transactionCompleteCallback == nullptr) {
- queue->setTransactionCompleteCallback(frameNumber, nullptr);
- } else {
- sp<TransactionCompleteCallbackWrapper> wrapper =
- new TransactionCompleteCallbackWrapper{env, transactionCompleteCallback};
- queue->setTransactionCompleteCallback(frameNumber, [wrapper](int64_t frameNr) {
- wrapper->onTransactionComplete(frameNr);
- });
- }
-}
-
static jlong nativeGetLastAcquiredFrameNum(JNIEnv* env, jclass clazz, jlong ptr) {
sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
return queue->getLastAcquiredFrameNum();
@@ -153,9 +101,6 @@
{"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction},
{"nativeUpdate", "(JJJJIJ)V", (void*)nativeUpdate},
{"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction},
- {"nativeSetTransactionCompleteCallback",
- "(JJLandroid/graphics/BLASTBufferQueue$TransactionCompleteCallback;)V",
- (void*)nativeSetTransactionCompleteCallback},
{"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
{"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
// clang-format on
@@ -165,11 +110,6 @@
int res = jniRegisterNativeMethods(env, "android/graphics/BLASTBufferQueue",
gMethods, NELEM(gMethods));
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
-
- jclass transactionCompleteClass =
- FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionCompleteCallback");
- gTransactionCompleteCallback.onTransactionComplete =
- GetMethodIDOrDie(env, transactionCompleteClass, "onTransactionComplete", "(J)V");
return 0;
}
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 0909ce7..2f12289 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -147,8 +147,7 @@
virtual void onFrameAvailable(const BufferItem& item);
private:
- static JNIEnv* getJNIEnv(bool* needsDetach);
- static void detachJNI();
+ static JNIEnv* getJNIEnv();
jobject mWeakThiz;
jclass mClazz;
@@ -160,58 +159,40 @@
mClazz((jclass)env->NewGlobalRef(clazz))
{}
-JNIEnv* JNISurfaceTextureContext::getJNIEnv(bool* needsDetach) {
- *needsDetach = false;
+JNIEnv* JNISurfaceTextureContext::getJNIEnv() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
if (env == NULL) {
JavaVMAttachArgs args = {
JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL };
JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->AttachCurrentThread(&env, (void*) &args);
+ int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
if (result != JNI_OK) {
ALOGE("thread attach failed: %#x", result);
return NULL;
}
- *needsDetach = true;
}
return env;
}
-void JNISurfaceTextureContext::detachJNI() {
- JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->DetachCurrentThread();
- if (result != JNI_OK) {
- ALOGE("thread detach failed: %#x", result);
- }
-}
-
JNISurfaceTextureContext::~JNISurfaceTextureContext()
{
- bool needsDetach = false;
- JNIEnv* env = getJNIEnv(&needsDetach);
+ JNIEnv* env = getJNIEnv();
if (env != NULL) {
env->DeleteGlobalRef(mWeakThiz);
env->DeleteGlobalRef(mClazz);
} else {
ALOGW("leaking JNI object references");
}
- if (needsDetach) {
- detachJNI();
- }
}
void JNISurfaceTextureContext::onFrameAvailable(const BufferItem& /* item */)
{
- bool needsDetach = false;
- JNIEnv* env = getJNIEnv(&needsDetach);
+ JNIEnv* env = getJNIEnv();
if (env != NULL) {
env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
} else {
ALOGW("onFrameAvailable event will not posted");
}
- if (needsDetach) {
- detachJNI();
- }
}
// ----------------------------------------------------------------------------
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index aadd320..be9aaaf 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -596,13 +596,10 @@
jlong otherNativePtr)
{
Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
- if (thisParcel == NULL) {
- return 0;
- }
+ LOG_ALWAYS_FATAL_IF(thisParcel == nullptr, "Should not be null");
+
Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
- if (otherParcel == NULL) {
- return thisParcel->getOpenAshmemSize();
- }
+ LOG_ALWAYS_FATAL_IF(otherParcel == nullptr, "Should not be null");
return thisParcel->compareData(*otherParcel);
}
@@ -638,6 +635,22 @@
return ret;
}
+static jboolean android_os_Parcel_hasFileDescriptorsInRange(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint offset,
+ jint length) {
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel != NULL) {
+ bool result;
+ status_t err = parcel->hasFileDescriptorsInRange(offset, length, &result);
+ if (err != NO_ERROR) {
+ signalExceptionForError(env, clazz, err);
+ return JNI_FALSE;
+ }
+ return result ? JNI_TRUE : JNI_FALSE;
+ }
+ return JNI_FALSE;
+}
+
// String tries to allocate itself on the stack, within a known size, but will
// make a heap allocation if not.
template <size_t StackReserve>
@@ -727,11 +740,11 @@
return Parcel::getGlobalAllocCount();
}
-static jlong android_os_Parcel_getBlobAshmemSize(jlong nativePtr)
+static jlong android_os_Parcel_getOpenAshmemSize(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
- return parcel->getBlobAshmemSize();
+ return parcel->getOpenAshmemSize();
}
return 0;
}
@@ -831,6 +844,7 @@
{"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom},
// @CriticalNative
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
+ {"nativeHasFileDescriptorsInRange", "(JII)Z", (void*)android_os_Parcel_hasFileDescriptorsInRange},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
@@ -838,7 +852,7 @@
{"getGlobalAllocCount", "()J", (void*)android_os_Parcel_getGlobalAllocCount},
// @CriticalNative
- {"nativeGetBlobAshmemSize", "(J)J", (void*)android_os_Parcel_getBlobAshmemSize},
+ {"nativeGetOpenAshmemSize", "(J)J", (void*)android_os_Parcel_getOpenAshmemSize},
// @CriticalNative
{"nativeReadCallingWorkSourceUid", "(J)I", (void*)android_os_Parcel_readCallingWorkSourceUid},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index dae844f..3bc3336 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1870,16 +1870,12 @@
if (surface == nullptr) {
return;
}
- surface->setTransformHint(
- ui::Transform::toRotationFlags(static_cast<ui::Rotation>(transformHint)));
+ surface->setTransformHint(transformHint);
}
static jint nativeGetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl));
- ui::Transform::RotationFlags transformHintRotationFlags =
- static_cast<ui::Transform::RotationFlags>(surface->getTransformHint());
-
- return toRotationInt(ui::Transform::toRotation((transformHintRotationFlags)));
+ return surface->getTransformHint();
}
static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
new file mode 100644
index 0000000..9ffc757
--- /dev/null
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_parcel.h>
+#include <android/binder_parcel_jni.h>
+#include <android/binder_parcel_utils.h>
+#include <android_runtime/Log.h>
+
+#include <cstring>
+
+#include "MultiStateCounter.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+namespace battery {
+
+typedef battery::MultiStateCounter<int64_t> LongMultiStateCounter;
+
+template <>
+bool LongMultiStateCounter::delta(const int64_t &previousValue, const int64_t &newValue,
+ int64_t *outValue) const {
+ *outValue = newValue - previousValue;
+ return *outValue >= 0;
+}
+
+template <>
+void LongMultiStateCounter::add(int64_t *value1, const int64_t &value2, const uint64_t numerator,
+ const uint64_t denominator) const {
+ if (numerator != denominator) {
+ // The caller ensures that denominator != 0
+ *value1 += value2 * numerator / denominator;
+ } else {
+ *value1 += value2;
+ }
+}
+
+template <>
+std::string LongMultiStateCounter::valueToString(const int64_t &v) const {
+ return std::to_string(v);
+}
+
+} // namespace battery
+
+static inline battery::LongMultiStateCounter *asLongMultiStateCounter(const jlong nativePtr) {
+ return reinterpret_cast<battery::LongMultiStateCounter *>(nativePtr);
+}
+
+static jlong native_init(jint stateCount) {
+ battery::LongMultiStateCounter *counter = new battery::LongMultiStateCounter(stateCount, 0);
+ return reinterpret_cast<jlong>(counter);
+}
+
+static void native_dispose(void *nativePtr) {
+ delete reinterpret_cast<battery::LongMultiStateCounter *>(nativePtr);
+}
+
+static jlong native_getReleaseFunc() {
+ return reinterpret_cast<jlong>(native_dispose);
+}
+
+static void native_setEnabled(jlong nativePtr, jboolean enabled, jlong timestamp) {
+ asLongMultiStateCounter(nativePtr)->setEnabled(enabled, timestamp);
+}
+
+static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
+ asLongMultiStateCounter(nativePtr)->setState(state, timestamp);
+}
+
+static void native_updateValue(jlong nativePtr, jlong value, jlong timestamp) {
+ asLongMultiStateCounter(nativePtr)->updateValue((int64_t)value, timestamp);
+}
+
+static void native_addCount(jlong nativePtr, jlong count) {
+ asLongMultiStateCounter(nativePtr)->addValue(count);
+}
+
+static void native_reset(jlong nativePtr) {
+ asLongMultiStateCounter(nativePtr)->reset();
+}
+
+static jlong native_getCount(jlong nativePtr, jint state) {
+ return asLongMultiStateCounter(nativePtr)->getCount(state);
+}
+
+static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) {
+ return env->NewStringUTF(asLongMultiStateCounter(nativePtr)->toString().c_str());
+}
+
+static void throwWriteRE(JNIEnv *env, binder_status_t status) {
+ ALOGE("Could not write LongMultiStateCounter to Parcel, status = %d", status);
+ jniThrowRuntimeException(env, "Could not write LongMultiStateCounter to Parcel");
+}
+
+#define THROW_ON_WRITE_ERROR(expr) \
+ { \
+ binder_status_t status = expr; \
+ if (status != STATUS_OK) { \
+ throwWriteRE(env, status); \
+ } \
+ }
+
+static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
+ jint flags) {
+ battery::LongMultiStateCounter *counter = asLongMultiStateCounter(nativePtr);
+ AParcel *parcel = AParcel_fromJavaParcel(env, jParcel);
+
+ uint16_t stateCount = counter->getStateCount();
+ THROW_ON_WRITE_ERROR(AParcel_writeInt32(parcel, stateCount));
+
+ for (battery::state_t state = 0; state < stateCount; state++) {
+ THROW_ON_WRITE_ERROR(AParcel_writeInt64(parcel, counter->getCount(state)));
+ }
+}
+
+static void throwReadRE(JNIEnv *env, binder_status_t status) {
+ ALOGE("Could not read LongMultiStateCounter from Parcel, status = %d", status);
+ jniThrowRuntimeException(env, "Could not read LongMultiStateCounter from Parcel");
+}
+
+#define THROW_ON_READ_ERROR(expr) \
+ { \
+ binder_status_t status = expr; \
+ if (status != STATUS_OK) { \
+ throwReadRE(env, status); \
+ } \
+ }
+
+static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
+ AParcel *parcel = AParcel_fromJavaParcel(env, jParcel);
+
+ int32_t stateCount;
+ THROW_ON_READ_ERROR(AParcel_readInt32(parcel, &stateCount));
+
+ battery::LongMultiStateCounter *counter = new battery::LongMultiStateCounter(stateCount, 0);
+
+ for (battery::state_t state = 0; state < stateCount; state++) {
+ int64_t value;
+ THROW_ON_READ_ERROR(AParcel_readInt64(parcel, &value));
+ counter->setValue(state, value);
+ }
+
+ return reinterpret_cast<jlong>(counter);
+}
+
+static jint native_getStateCount(jlong nativePtr) {
+ return asLongMultiStateCounter(nativePtr)->getStateCount();
+}
+
+static const JNINativeMethod g_methods[] = {
+ // @CriticalNative
+ {"native_init", "(I)J", (void *)native_init},
+ // @CriticalNative
+ {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc},
+ // @CriticalNative
+ {"native_setEnabled", "(JZJ)V", (void *)native_setEnabled},
+ // @CriticalNative
+ {"native_setState", "(JIJ)V", (void *)native_setState},
+ // @CriticalNative
+ {"native_updateValue", "(JJJ)V", (void *)native_updateValue},
+ // @CriticalNative
+ {"native_addCount", "(JJ)V", (void *)native_addCount},
+ // @CriticalNative
+ {"native_reset", "(J)V", (void *)native_reset},
+ // @CriticalNative
+ {"native_getCount", "(JI)J", (void *)native_getCount},
+ // @FastNative
+ {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
+ // @FastNative
+ {"native_writeToParcel", "(JLandroid/os/Parcel;I)V", (void *)native_writeToParcel},
+ // @FastNative
+ {"native_initFromParcel", "(Landroid/os/Parcel;)J", (void *)native_initFromParcel},
+ // @CriticalNative
+ {"native_getStateCount", "(J)I", (void *)native_getStateCount},
+};
+
+int register_com_android_internal_os_LongMultiStateCounter(JNIEnv *env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/LongMultiStateCounter", g_methods,
+ NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 44ea23f..78650ed 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -8,9 +8,6 @@
yro@google.com
zhouwenjie@google.com
-# Settings UI
-per-file settings_enums.proto=tmfang@google.com
-
# Frameworks
ogunwale@google.com
jjaggi@google.com
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 8e33561..40e3f60 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -69,6 +69,7 @@
// know what activity types to check for when invoking splitscreen multi-window.
optional bool is_home_recents_component = 6;
repeated IdentifierProto pending_activities = 7 [deprecated=true];
+ optional int32 default_min_size_resizable_task = 8;
}
message BarControllerProto {
@@ -365,6 +366,7 @@
optional bool translucent = 30;
optional bool pip_auto_enter_enabled = 31;
optional bool in_size_compat_mode = 32;
+ optional float min_aspect_ratio = 33;
}
/* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 612dfd0..b4fa652 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -709,7 +709,7 @@
<protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
<protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
- <protected-broadcast android:name="android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER" />
+ <protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -2011,6 +2011,14 @@
android:label="@string/permlab_uwb_ranging"
android:protectionLevel="dangerous" />
+ <!-- Required to be able to advertise and connect to nearby devices via Wi-Fi.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_nearby_wifi_devices"
+ android:label="@string/permlab_nearby_wifi_devices"
+ android:protectionLevel="dangerous" />
+
<!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
user from using them until they are unsuspended.
@hide
@@ -2968,6 +2976,17 @@
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
android:protectionLevel="normal" />
+ <!-- Allows application to request to be associated with a virtual display capable of streaming
+ Android applications
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows a companion app to associate to Wi-Fi.
<p>Only for use by a single pre-approved app.
@hide
@@ -4918,15 +4937,15 @@
android:protectionLevel="signature|privileged" />
<!-- An application needs this permission for
- {@link android.provider.Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK} to show its
- {@link android.app.Activity} in 2-pane of Settings app. -->
- <permission android:name="android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK"
+ {@link android.provider.Settings#ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY} to show its
+ {@link android.app.Activity} embedded in Settings app. -->
+ <permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK"
android:protectionLevel="signature|preinstalled" />
<!-- @SystemApi {@link android.app.Activity} should require this permission to ensure that only
- the settings app can embed it in a 2-pane window.
+ the settings app can embed it in a multi pane window.
@hide -->
- <permission android:name="android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS"
+ <permission android:name="android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"
android:protectionLevel="signature" />
<!-- @SystemApi Allows applications to set a live wallpaper.
@@ -5325,6 +5344,15 @@
android:description="@string/permdesc_startViewPermissionUsage"
android:protectionLevel="signature|installer" />
+ <!--
+ Allows the holder to start the screen with a list of app features.
+ <p>Protection level: signature|installer
+ -->
+ <permission android:name="android.permission.START_VIEW_APP_FEATURES"
+ android:label="@string/permlab_startViewAppFeatures"
+ android:description="@string/permdesc_startViewAppFeatures"
+ android:protectionLevel="signature|installer" />
+
<!-- Allows an application to query whether DO_NOT_ASK_CREDENTIALS_ON_BOOT
flag is set.
@hide -->
@@ -5398,6 +5426,12 @@
<permission android:name="android.permission.VIEW_INSTANT_APPS"
android:protectionLevel="signature|preinstalled" />
+ <!-- Allows an application to interact with the currently active
+ {@link com.android.server.communal.CommunalManagerService}.
+ @hide -->
+ <permission android:name="android.permission.WRITE_COMMUNAL_STATE"
+ android:protectionLevel="signature" />
+
<!-- Allows the holder to manage whether the system can bind to services
provided by instant apps. This permission is intended to protect
test/development fucntionality and should be used only in such cases.
@@ -6104,6 +6138,12 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.LaunchAfterAuthenticationActivity"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
<activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true">
@@ -6291,7 +6331,7 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service android:name="com.android.server.pm.BackgroundDexOptService"
+ <service android:name="com.android.server.pm.BackgroundDexOptJobService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1b67328..14a1ae5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1457,7 +1457,7 @@
<string name="usb_power_notification_message" msgid="7284765627437897702">"جارٍ شحن الجهاز المتصل. انقر لعرض خيارات أكثر."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"تم اكتشاف ملحق صوتي تناظري"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"الجهاز الذي تم توصيله بالهاتف غير متوافق معه. انقر للحصول على المزيد من المعلومات."</string>
- <string name="adb_active_notification_title" msgid="408390247354560331">"تم توصيل أداة تصحيح أخطاء الجهاز عبر USB"</string>
+ <string name="adb_active_notification_title" msgid="408390247354560331">"تم توصيل USB لتصحيح أخطاء الجهاز"</string>
<string name="adb_active_notification_message" msgid="5617264033476778211">"انقر لإيقاف تصحيح أخطاء الجهاز عبر USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"اختيار إيقاف تصحيح أخطاء USB."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"تم تفعيل ميزة \"تصحيح الأخطاء اللاسلكي\"."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 2e8bf70..6aa9330 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -299,7 +299,7 @@
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्या तपशीलांसाठी टॅप करा"</string>
<string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
<string name="safeMode" msgid="8974401416068943888">"सुरक्षित मोड"</string>
- <string name="android_system_label" msgid="5974767339591067210">"Android सिस्टम"</string>
+ <string name="android_system_label" msgid="5974767339591067210">"Android सिस्टीम"</string>
<string name="user_owner_label" msgid="8628726904184471211">"वैयक्तिक प्रोफाइलवर स्विच करा"</string>
<string name="managed_profile_label" msgid="7316778766973512382">"कार्य प्रोफाइलवर स्विच करा"</string>
<string name="permgrouplab_contacts" msgid="4254143639307316920">"संपर्क"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 4cfcd80..7f7c6b6 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -360,7 +360,7 @@
<string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ਐਪ ਨੂੰ ਆਉਣ ਵਾਲੀ ਫ਼ੋਨ ਕਾਲ ਦਾ ਜਵਾਬ ਦੇਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦੀ ਹੈ।"</string>
<string name="permlab_receiveSms" msgid="505961632050451881">"ਲਿਖਤ ਸੁਨੇਹੇ (SMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="permdesc_receiveSms" msgid="1797345626687832285">"ਐਪ ਨੂੰ SMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string>
- <string name="permlab_receiveMms" msgid="4000650116674380275">"ਟੈਕਸਟ ਸੁਨੇਹੇ (MMS) ਪੜ੍ਹੋ"</string>
+ <string name="permlab_receiveMms" msgid="4000650116674380275">"ਲਿਖਤ ਸੁਨੇਹੇ (MMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="permdesc_receiveMms" msgid="958102423732219710">"ਐਪ ਨੂੰ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਨੂੰ ਅੱਗੇ ਭੇਜੋ"</string>
<string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਹਨਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਚੇਤਨਾਵਾਂ ਤੁਹਾਨੂੰ ਸੰਕਟਕਾਲੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸੰਕਟਕਾਲੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
@@ -376,7 +376,7 @@
<string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
<string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"ਇਹ ਐਪ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
<string name="permdesc_readSms" product="default" msgid="774753371111699782">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
- <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ਟੈਕਸਟ ਸੁਨੇਹੇ (WAP) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
+ <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ਲਿਖਤ ਸੁਨੇਹੇ (WAP) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="permdesc_receiveWapPush" msgid="1638677888301778457">"ਐਪ ਨੂੰ WAP ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸ ਇਜਾਜ਼ਤ ਵਿੱਚ ਸ਼ਾਮਲ ਹੈ ਐਪ ਦੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰਨ ਅਤੇ ਮਿਟਾਉਣ ਦੀ ਸਮਰੱਥਾ।"</string>
<string name="permlab_getTasks" msgid="7460048811831750262">"ਚੱਲ ਰਹੇ ਐਪਸ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="permdesc_getTasks" msgid="7388138607018233726">"ਐਪ ਨੂੰ ਵਰਤਮਾਨ ਵਿੱਚ ਅਤੇ ਹੁਣੇ ਜਿਹੇ ਚੱਲ ਰਹੇ ਕੰਮਾਂ ਬਾਰੇ ਵਿਸਤ੍ਰਿਤ ਜਾਣਕਾਰੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਐਪ ਨੂੰ ਇਸ ਬਾਰੇ ਜਾਣਕਾਰੀ ਖੋਜਣ ਦੀ ਆਗਿਆ ਦੇ ਸਕਦਾ ਹੈ ਕਿ ਡੀਵਾਈਸ ਤੇ ਕਿਹੜੀਆਂ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।"</string>
@@ -1050,7 +1050,7 @@
<string name="searchview_description_query" msgid="7430242366971716338">"ਖੋਜ ਪੁੱਛਗਿੱਛ"</string>
<string name="searchview_description_clear" msgid="1989371719192982900">"ਸਵਾਲ ਹਟਾਓ"</string>
<string name="searchview_description_submit" msgid="6771060386117334686">"ਸਵਾਲ ਪ੍ਰਸਤੁਤ ਕਰੋ"</string>
- <string name="searchview_description_voice" msgid="42360159504884679">"ਵੌਇਸ ਖੋਜ"</string>
+ <string name="searchview_description_voice" msgid="42360159504884679">"ਅਵਾਜ਼ੀ ਖੋਜ"</string>
<string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"ਕੀ ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
<string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> \'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ\' ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਣਾ ਚਾਹੁੰਦੀ ਹੈ। ਜਦੋਂ \'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ\' ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਵੇਰਵੇ ਸੁਣ ਜਾਂ ਦੇਖ ਸਕਦੇ ਹੋ ਕਿ ਤੁਹਾਡੀ ਉਂਗਲੀ ਦੇ ਹੇਠਾਂ ਕੀ ਹੈ ਜਾਂ ਟੈਬਲੈੱਟ ਨਾਲ ਇੰਟਰੈਕਟ ਕਰਨ ਲਈ ਸੰਕੇਤਾਂ ਦੀ ਪਾਲਣਾ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ਸਪਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਚਾਹੁੰਦਾ ਹੈ। ਜਦੋਂ ਸਪਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਵੇਰਵੇ ਸੁਣ ਜਾਂ ਦੇਖ ਸਕਦੇ ਹੋ ਤਿ ਤੁਹਾਡੀ ਉਂਗਲੀ ਦੇ ਹੇਠਾਂ ਕੀ ਹੈ ਜਾਂ ਫ਼ੋਨ ਨਾਲ ਇੰਟਰੈਕਟ ਕਰਨ ਲਈ ਸੰਕੇਤ ਪਰਫੌਰਮ ਕਰ ਸਕਦੇ ਹੋ।"</string>
@@ -2015,7 +2015,7 @@
<string name="app_category_image" msgid="7307840291864213007">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਚਿੱਤਰ"</string>
<string name="app_category_social" msgid="2278269325488344054">"ਸਮਾਜਕ ਅਤੇ ਸੰਚਾਰ"</string>
<string name="app_category_news" msgid="1172762719574964544">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string>
- <string name="app_category_maps" msgid="6395725487922533156">"Maps ਅਤੇ ਨੈਵੀਗੇਸ਼ਨ"</string>
+ <string name="app_category_maps" msgid="6395725487922533156">"ਨਕਸ਼ੇ ਅਤੇ ਨੈਵੀਗੇਸ਼ਨ"</string>
<string name="app_category_productivity" msgid="1844422703029557883">"ਉਤਪਾਦਕਤਾ"</string>
<string name="app_category_accessibility" msgid="6643521607848547683">"ਪਹੁੰਚਯੋਗਤਾ"</string>
<string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"ਡੀਵਾਈਸ ਸਟੋਰੇਜ"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 2b5ea2e..8f8325a 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -176,10 +176,10 @@
<string name="contentServiceSync" msgid="2341041749565687871">"ஒத்திசை"</string>
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ஒத்திசைக்க முடியவில்லை"</string>
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"அதிகளவிலான <xliff:g id="CONTENT_TYPE">%s</xliff:g> உள்ளடக்க வகைகளை நீக்க முயன்றுள்ளீர்கள்."</string>
- <string name="low_memory" product="tablet" msgid="5557552311566179924">"டேப்லெட் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை அழிக்கவும்."</string>
- <string name="low_memory" product="watch" msgid="3479447988234030194">"வாட்ச் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை நீக்கவும்."</string>
+ <string name="low_memory" product="tablet" msgid="5557552311566179924">"டேப்லெட் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை அழிக்கவும்."</string>
+ <string name="low_memory" product="watch" msgid="3479447988234030194">"வாட்ச் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை நீக்கவும்."</string>
<string name="low_memory" product="tv" msgid="6663680413790323318">"Android TVயின் சேமிப்பிடம் நிரம்பிவிட்டது. இடத்தைக் காலியாக்க சில ஃபைல்களை நீக்கவும்."</string>
- <string name="low_memory" product="default" msgid="2539532364144025569">"மொபைல் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை அழிக்கவும்."</string>
+ <string name="low_memory" product="default" msgid="2539532364144025569">"மொபைல் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை அழிக்கவும்."</string>
<plurals name="ssl_ca_cert_warning" formatted="false" msgid="2288194355006173029">
<item quantity="other">சான்றிதழ் அங்கீகாரங்கள் நிறுவப்பட்டன</item>
<item quantity="one">சான்றிதழ் அங்கீகாரம் நிறுவப்பட்டது</item>
@@ -311,7 +311,7 @@
<string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS அனுப்பலாம், வந்த SMSகளைப் பார்க்கலாம்"</string>
<string name="permgrouplab_storage" msgid="1938416135375282333">"ஃபைல்களும் மீடியாவும்"</string>
- <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் கோப்புகளை அணுக வேண்டும்"</string>
+ <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் ஃபைல்களை அணுக வேண்டும்"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"மைக்ரோஃபோன்"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"ஒலிப் பதிவு செய்யலாம்"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"உடல் செயல்பாடுகள்"</string>
@@ -1418,7 +1418,7 @@
<string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"அமைக்கத் தேர்ந்தெடுங்கள்"</string>
<string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"சாதனத்தை ரீஃபார்மேட் செய்ய வேண்டியிருக்கும். வெளியேற்ற தட்டவும்."</string>
<string name="ext_media_ready_notification_message" msgid="777258143284919261">"படங்களையும் மீடியாவையும் மாற்றலாம்"</string>
- <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"மீடியா கோப்புகளை உலாவுக"</string>
+ <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"மீடியா ஃபைல்களை உலாவுக"</string>
<string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"<xliff:g id="NAME">%s</xliff:g> இல் சிக்கல்"</string>
<string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> வேலை செய்யவில்லை"</string>
<string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"சரிசெய்ய, தட்டவும்"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2680c31..b063140 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1051,6 +1051,39 @@
-->
<integer name="config_shortPressOnSleepBehavior">0</integer>
+ <!-- Control the behavior when the user long presses the stem primary button.
+ Stem primary button is only used on watch form factor. If a device is not
+ a watch, setting this config is no-op.
+ 0 - Nothing
+ 1 - Launch voice assistant
+ -->
+ <integer name="config_longPressOnStemPrimaryBehavior">0</integer>
+
+ <!-- Control the behavior when the user double presses the stem primary button.
+ Stem primary button is only used on watch form factor. If a device is not
+ a watch, setting this config is no-op.
+ 0 - Nothing
+ 1 - Switch to the recent app
+ -->
+ <integer name="config_doublePressOnStemPrimaryBehavior">0</integer>
+
+ <!-- Control the behavior when the user triple presses the stem primary button.
+ Stem primary button is only used on watch form factor. If a device is not
+ a watch, setting this config is no-op.
+ 0 - Nothing
+ 1 - Toggle accessibility
+ -->
+ <integer name="config_triplePressOnStemPrimaryBehavior">0</integer>
+
+ <!-- Control the behavior when the user short presses the stem primary button.
+ Stem primary button is only used on watch form factor. If a device is not
+ a watch, setting this config is no-op.
+ 0 - Nothing
+ 1 - Go to launch all apps
+ -->
+ <integer name="config_shortPressOnStemPrimaryBehavior">0</integer>
+
+
<!-- Time to wait while a button is pressed before triggering a very long press. -->
<integer name="config_veryLongPressTimeout">3500</integer>
@@ -4065,7 +4098,7 @@
<string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
<!-- URI for default ringtone sound file to be used for silent ringer vibration -->
- <string translatable="false" name="config_defaultRingtoneVibrationSound">/product/media/audio/ui/AttentionalHaptics.ogg</string>
+ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
<!-- Default number of notifications from the same app before they are automatically grouped by the OS -->
<integer translatable="false" name="config_autoGroupAtCount">4</integer>
@@ -5104,6 +5137,97 @@
<bool name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed">true</bool>
<bool name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default">true</bool>
+ <!-- Which Short Audio Descriptors a TV should query via CEC -->
+ <bool name="config_cecQuerySadLpcm_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadLpcmEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadLpcmEnabled_default">true</bool>
+ <bool name="config_cecQuerySadLpcmDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadLpcmDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadDd_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadDdEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDdEnabled_default">true</bool>
+ <bool name="config_cecQuerySadDdDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDdDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadMpeg1_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadMpeg1Enabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMpeg1Enabled_default">true</bool>
+ <bool name="config_cecQuerySadMpeg1Disabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMpeg1Disabled_default">false</bool>
+
+ <bool name="config_cecQuerySadMp3_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadMp3Enabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMp3Enabled_default">true</bool>
+ <bool name="config_cecQuerySadMp3Disabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMp3Disabled_default">false</bool>
+
+ <bool name="config_cecQuerySadMpeg2_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadMpeg2Enabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMpeg2Enabled_default">true</bool>
+ <bool name="config_cecQuerySadMpeg2Disabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMpeg2Disabled_default">false</bool>
+
+ <bool name="config_cecQuerySadAac_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadAacEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadAacEnabled_default">true</bool>
+ <bool name="config_cecQuerySadAacDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadAacDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadDts_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadDtsEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDtsEnabled_default">true</bool>
+ <bool name="config_cecQuerySadDtsDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDtsDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadAtrac_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadAtracEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadAtracEnabled_default">true</bool>
+ <bool name="config_cecQuerySadAtracDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadAtracDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadOnebitaudio_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadOnebitaudioEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadOnebitaudioEnabled_default">true</bool>
+ <bool name="config_cecQuerySadOnebitaudioDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadOnebitaudioDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadDdp_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadDdpEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDdpEnabled_default">true</bool>
+ <bool name="config_cecQuerySadDdpDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDdpDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadDtshd_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadDtshdEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDtshdEnabled_default">true</bool>
+ <bool name="config_cecQuerySadDtshdDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDtshdDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadTruehd_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadTruehdEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadTruehdEnabled_default">true</bool>
+ <bool name="config_cecQuerySadTruehdDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadTruehdDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadDst_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadDstEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDstEnabled_default">true</bool>
+ <bool name="config_cecQuerySadDstDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadDstDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadWmapro_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadWmaproEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadWmaproEnabled_default">true</bool>
+ <bool name="config_cecQuerySadWmaproDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadWmaproDisabled_default">false</bool>
+
+ <bool name="config_cecQuerySadMax_userConfigurable">true</bool>
+ <bool name="config_cecQuerySadMaxEnabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMaxEnabled_default">true</bool>
+ <bool name="config_cecQuerySadMaxDisabled_allowed">true</bool>
+ <bool name="config_cecQuerySadMaxDisabled_default">false</bool>
+
<!-- Whether app hibernation deletes OAT artifact files as part of global hibernation. -->
<bool name="config_hibernationDeletesOatArtifactsEnabled">true</bool>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 84f82fd..747a918 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -263,6 +263,18 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL}. -->
<item type="id" name="accessibilityActionDragCancel" />
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SWIPE_LEFT}. -->
+ <item type="id" name="accessibilityActionSwipeLeft" />
+
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SWIPE_RIGHT}. -->
+ <item type="id" name="accessibilityActionSwipeRight" />
+
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SWIPE_UP}. -->
+ <item type="id" name="accessibilityActionSwipeUp" />
+
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SWIPE_DOWN}. -->
+ <item type="id" name="accessibilityActionSwipeDown" />
+
<!-- View tag for remote views to store the index of the next child when adding nested remote views dynamically. -->
<item type="id" name="remote_views_next_child" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a6e42bd..ed49fe4 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3306,6 +3306,10 @@
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
+ <public name="accessibilityActionSwipeLeft" />
+ <public name="accessibilityActionSwipeRight" />
+ <public name="accessibilityActionSwipeUp" />
+ <public name="accessibilityActionSwipeDown" />
</staging-public-group>
<staging-public-group type="style" first-id="0x0dfd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index dbb2b1a..391c7dc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1493,6 +1493,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]-->
<string name="permdesc_uwb_ranging">Allow the app to determine relative position between nearby Ultra-Wideband devices</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]-->
+ <string name="permlab_nearby_wifi_devices">interact with nearby Wi\u2011Fi devices</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]-->
+ <string name="permdesc_nearby_wifi_devices">Allows the app to advertise, connect, and determine the relative position of nearby Wi\u2011Fi devices</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1921,6 +1926,11 @@
<string name="permdesc_startViewPermissionUsage">Allows the holder to start the permission usage for an app. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_startViewAppFeatures">start view app features</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permdesc_startViewAppFeatures">Allows the holder to start viewing the features info for an app.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
<string name="permlab_highSamplingRateSensors">access sensor data at a high sampling rate</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_highSamplingRateSensors">Allows the app to sample sensor data at a rate greater than 200 Hz</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 253cd47..846ec60 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -459,6 +459,10 @@
<java-symbol type="integer" name="config_toastDefaultGravity" />
<java-symbol type="integer" name="config_triplePressOnPowerBehavior" />
<java-symbol type="integer" name="config_shortPressOnSleepBehavior" />
+ <java-symbol type="integer" name="config_longPressOnStemPrimaryBehavior" />
+ <java-symbol type="integer" name="config_shortPressOnStemPrimaryBehavior" />
+ <java-symbol type="integer" name="config_doublePressOnStemPrimaryBehavior" />
+ <java-symbol type="integer" name="config_triplePressOnStemPrimaryBehavior" />
<java-symbol type="integer" name="config_windowOutsetBottom" />
<java-symbol type="integer" name="db_connection_pool_size" />
<java-symbol type="integer" name="db_journal_size_limit" />
@@ -4432,6 +4436,97 @@
<java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed" />
<java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default" />
+ <!-- Which Short Audio Descriptors a TV should query via CEC -->
+ <java-symbol type="bool" name="config_cecQuerySadLpcm_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadLpcmEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadLpcmEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadLpcmDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadLpcmDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadDd_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadDdEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDdEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadDdDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDdDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadMpeg1_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg1Enabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg1Enabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg1Disabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg1Disabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadMp3_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadMp3Enabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMp3Enabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadMp3Disabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMp3Disabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadMpeg2_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg2Enabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg2Enabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg2Disabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMpeg2Disabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadAac_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadAacEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadAacEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadAacDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadAacDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadDts_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadDtsEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDtsEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadDtsDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDtsDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadAtrac_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadAtracEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadAtracEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadAtracDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadAtracDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadOnebitaudio_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadOnebitaudioEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadOnebitaudioEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadOnebitaudioDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadOnebitaudioDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadDdp_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadDdpEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDdpEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadDdpDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDdpDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadDtshd_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadDtshdEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDtshdEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadDtshdDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDtshdDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadTruehd_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadTruehdEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadTruehdEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadTruehdDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadTruehdDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadDst_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadDstEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDstEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadDstDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadDstDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadWmapro_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadWmaproEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadWmaproEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadWmaproDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadWmaproDisabled_default" />
+
+ <java-symbol type="bool" name="config_cecQuerySadMax_userConfigurable" />
+ <java-symbol type="bool" name="config_cecQuerySadMaxEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMaxEnabled_default" />
+ <java-symbol type="bool" name="config_cecQuerySadMaxDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecQuerySadMaxDisabled_default" />
+
<!-- Ids for RemoteViews -->
<java-symbol type="id" name="remote_views_next_child" />
<java-symbol type="id" name="remote_views_stable_id" />
diff --git a/core/tests/companiontests/Android.bp b/core/tests/companiontests/Android.bp
new file mode 100644
index 0000000..d31b8f4
--- /dev/null
+++ b/core/tests/companiontests/Android.bp
@@ -0,0 +1,21 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "CompanionTests",
+ // Include all test java files.
+ srcs: ["src/**/*.java"],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: ["junit"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/core/tests/companiontests/AndroidManifest.xml b/core/tests/companiontests/AndroidManifest.xml
new file mode 100644
index 0000000..f436d97
--- /dev/null
+++ b/core/tests/companiontests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.companion.tests"
+ android:sharedUserId="android.uid.system" >
+
+ <application >
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.companion.CompanionTestRunner"
+ android:targetPackage="com.android.companion.tests"
+ android:label="Companion Tests" />
+
+</manifest>
diff --git a/core/tests/companiontests/src/android/companion/BluetoothDeviceFilterUtilsTest.java b/core/tests/companiontests/src/android/companion/BluetoothDeviceFilterUtilsTest.java
new file mode 100644
index 0000000..1ddbbd8
--- /dev/null
+++ b/core/tests/companiontests/src/android/companion/BluetoothDeviceFilterUtilsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 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.companion;
+
+import android.os.ParcelUuid;
+import android.test.InstrumentationTestCase;
+
+public class BluetoothDeviceFilterUtilsTest extends InstrumentationTestCase {
+ private static final String TAG = "BluetoothDeviceFilterUtilsTest";
+
+ private final ParcelUuid mServiceUuid =
+ ParcelUuid.fromString("F0FFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
+ private final ParcelUuid mNonMatchingDeviceUuid =
+ ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
+ private final ParcelUuid mMatchingDeviceUuid =
+ ParcelUuid.fromString("F0FFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
+ private final ParcelUuid mMaskUuid =
+ ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
+ private final ParcelUuid mMatchingMaskUuid =
+ ParcelUuid.fromString("F0FFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public void testUuidsMaskedEquals() {
+ assertFalse(BluetoothDeviceFilterUtils.uuidsMaskedEquals(
+ mNonMatchingDeviceUuid.getUuid(),
+ mServiceUuid.getUuid(),
+ mMaskUuid.getUuid()));
+
+ assertTrue(BluetoothDeviceFilterUtils.uuidsMaskedEquals(
+ mMatchingDeviceUuid.getUuid(),
+ mServiceUuid.getUuid(),
+ mMaskUuid.getUuid()));
+
+ assertTrue(BluetoothDeviceFilterUtils.uuidsMaskedEquals(
+ mNonMatchingDeviceUuid.getUuid(),
+ mServiceUuid.getUuid(),
+ mMatchingMaskUuid.getUuid()));
+ }
+}
diff --git a/core/tests/companiontests/src/android/companion/CompanionTestRunner.java b/core/tests/companiontests/src/android/companion/CompanionTestRunner.java
new file mode 100644
index 0000000..caa2c68
--- /dev/null
+++ b/core/tests/companiontests/src/android/companion/CompanionTestRunner.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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.companion;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import junit.framework.TestSuite;
+
+
+/**
+ * Instrumentation test runner for Companion tests.
+ */
+public class CompanionTestRunner extends InstrumentationTestRunner {
+ private static final String TAG = "CompanionTestRunner";
+
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(BluetoothDeviceFilterUtilsTest.class);
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return CompanionTestRunner.class.getClassLoader();
+ }
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 93e4a29..bcd794e 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -54,7 +54,6 @@
"print-test-util-lib",
"testng",
"servicestests-utils",
- "AppSearchTestUtils",
],
libs: [
diff --git a/core/tests/coretests/src/android/app/time/OWNERS b/core/tests/coretests/src/android/app/time/OWNERS
index 8f80897..292cb72 100644
--- a/core/tests/coretests/src/android/app/time/OWNERS
+++ b/core/tests/coretests/src/android/app/time/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /core/java/android/app/time/OWNERS
diff --git a/core/tests/coretests/src/android/app/timedetector/OWNERS b/core/tests/coretests/src/android/app/timedetector/OWNERS
index 8f80897..c612473 100644
--- a/core/tests/coretests/src/android/app/timedetector/OWNERS
+++ b/core/tests/coretests/src/android/app/timedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
include /core/java/android/app/timedetector/OWNERS
diff --git a/core/tests/coretests/src/android/app/timezone/OWNERS b/core/tests/coretests/src/android/app/timezone/OWNERS
index 8f80897..381ecf1 100644
--- a/core/tests/coretests/src/android/app/timezone/OWNERS
+++ b/core/tests/coretests/src/android/app/timezone/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+include /core/java/android/app/timezone/OWNERS
diff --git a/core/tests/coretests/src/android/app/timezonedetector/OWNERS b/core/tests/coretests/src/android/app/timezonedetector/OWNERS
index 8f80897..2e9c324 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/OWNERS
+++ b/core/tests/coretests/src/android/app/timezonedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /core/java/android/app/timezonedetector/OWNERS
diff --git a/core/tests/coretests/src/android/net/OWNERS b/core/tests/coretests/src/android/net/OWNERS
index aa87958..4e5136f 100644
--- a/core/tests/coretests/src/android/net/OWNERS
+++ b/core/tests/coretests/src/android/net/OWNERS
@@ -1 +1,3 @@
include /services/core/java/com/android/server/net/OWNERS
+
+per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/tests/coretests/src/android/net/SntpClientTest.java b/core/tests/coretests/src/android/net/SntpClientTest.java
index bf9978c..b400b9b 100644
--- a/core/tests/coretests/src/android/net/SntpClientTest.java
+++ b/core/tests/coretests/src/android/net/SntpClientTest.java
@@ -22,7 +22,10 @@
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import android.net.sntp.Duration64;
+import android.net.sntp.Timestamp64;
import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
@@ -38,7 +41,13 @@
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import java.util.Arrays;
+import java.util.Random;
+import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
public class SntpClientTest {
@@ -54,41 +63,232 @@
//
// Server, Leap indicator: (0), Stratum 2 (secondary reference), poll 6 (64s), precision -20
// Root Delay: 0.005447, Root dispersion: 0.002716, Reference-ID: 221.253.71.41
- // Reference Timestamp: 3653932102.507969856 (2015/10/15 14:08:22)
- // Originator Timestamp: 3653932113.576327741 (2015/10/15 14:08:33)
- // Receive Timestamp: 3653932113.581012725 (2015/10/15 14:08:33)
- // Transmit Timestamp: 3653932113.581012725 (2015/10/15 14:08:33)
+ // Reference Timestamp:
+ // d9ca9446.820a5000 / ERA0: 2015-10-15 21:08:22 UTC / ERA1: 2151-11-22 03:36:38 UTC
+ // Originator Timestamp:
+ // d9ca9451.938a3771 / ERA0: 2015-10-15 21:08:33 UTC / ERA1: 2151-11-22 03:36:49 UTC
+ // Receive Timestamp:
+ // d9ca9451.94bd3fff / ERA0: 2015-10-15 21:08:33 UTC / ERA1: 2151-11-22 03:36:49 UTC
+ // Transmit Timestamp:
+ // d9ca9451.94bd4001 / ERA0: 2015-10-15 21:08:33 UTC / ERA1: 2151-11-22 03:36:49 UTC
+ //
// Originator - Receive Timestamp: +0.004684958
// Originator - Transmit Timestamp: +0.004684958
- private static final String WORKING_VERSION4 =
- "240206ec" +
- "00000165" +
- "000000b2" +
- "ddfd4729" +
- "d9ca9446820a5000" +
- "d9ca9451938a3771" +
- "d9ca945194bd3fff" +
- "d9ca945194bd4001";
+ private static final String LATE_ERA_RESPONSE =
+ "240206ec"
+ + "00000165"
+ + "000000b2"
+ + "ddfd4729"
+ + "d9ca9446820a5000"
+ + "d9ca9451938a3771"
+ + "d9ca945194bd3fff"
+ + "d9ca945194bd4001";
+
+ /** This is the actual UTC time in the server if it is in ERA0 */
+ private static final Instant LATE_ERA0_SERVER_TIME =
+ calculateIdealServerTime("d9ca9451.94bd3fff", "d9ca9451.94bd4001", 0);
+
+ /**
+ * This is the Unix epoch time matches the originate timestamp from {@link #LATE_ERA_RESPONSE}
+ * when interpreted as an ERA0 timestamp.
+ */
+ private static final Instant LATE_ERA0_REQUEST_TIME =
+ Timestamp64.fromString("d9ca9451.938a3771").toInstant(0);
+
+ // A tweaked version of the ERA0 response to represent an ERA 1 response.
+ //
+ // Server, Leap indicator: (0), Stratum 2 (secondary reference), poll 6 (64s), precision -20
+ // Root Delay: 0.005447, Root dispersion: 0.002716, Reference-ID: 221.253.71.41
+ // Reference Timestamp:
+ // 1db2d246.820a5000 / ERA0: 1915-10-16 21:08:22 UTC / ERA1: 2051-11-22 03:36:38 UTC
+ // Originate Timestamp:
+ // 1db2d251.938a3771 / ERA0: 1915-10-16 21:08:33 UTC / ERA1: 2051-11-22 03:36:49 UTC
+ // Receive Timestamp:
+ // 1db2d251.94bd3fff / ERA0: 1915-10-16 21:08:33 UTC / ERA1: 2051-11-22 03:36:49 UTC
+ // Transmit Timestamp:
+ // 1db2d251.94bd4001 / ERA0: 1915-10-16 21:08:33 UTC / ERA1: 2051-11-22 03:36:49 UTC
+ //
+ // Originate - Receive Timestamp: +0.004684958
+ // Originate - Transmit Timestamp: +0.004684958
+ private static final String EARLY_ERA_RESPONSE =
+ "240206ec"
+ + "00000165"
+ + "000000b2"
+ + "ddfd4729"
+ + "1db2d246820a5000"
+ + "1db2d251938a3771"
+ + "1db2d25194bd3fff"
+ + "1db2d25194bd4001";
+
+ /** This is the actual UTC time in the server if it is in ERA0 */
+ private static final Instant EARLY_ERA1_SERVER_TIME =
+ calculateIdealServerTime("1db2d251.94bd3fff", "1db2d251.94bd4001", 1);
+
+ /**
+ * This is the Unix epoch time matches the originate timestamp from {@link #EARLY_ERA_RESPONSE}
+ * when interpreted as an ERA1 timestamp.
+ */
+ private static final Instant EARLY_ERA1_REQUEST_TIME =
+ Timestamp64.fromString("1db2d251.938a3771").toInstant(1);
private SntpTestServer mServer;
private SntpClient mClient;
private Network mNetwork;
+ private Supplier<Instant> mSystemTimeSupplier;
+ private Random mRandom;
+ @SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
+ mServer = new SntpTestServer();
+
// A mock network has NETID_UNSET, which allows the test to run, with a loopback server,
// even w/o external networking.
mNetwork = mock(Network.class, CALLS_REAL_METHODS);
- mServer = new SntpTestServer();
- mClient = new SntpClient();
+ mRandom = mock(Random.class);
+
+ mSystemTimeSupplier = mock(Supplier.class);
+ // Returning zero means the "randomized" bottom bits of the clients transmit timestamp /
+ // server's originate timestamp will be zeros.
+ when(mRandom.nextInt()).thenReturn(0);
+ mClient = new SntpClient(mSystemTimeSupplier, mRandom);
}
+ /** Tests when the client and server are in ERA0. b/199481251. */
@Test
- public void testBasicWorkingSntpClientQuery() throws Exception {
- mServer.setServerReply(HexEncoding.decode(WORKING_VERSION4.toCharArray(), false));
+ public void testRequestTime_era0ClientEra0RServer() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
+ mServer.setServerReply(HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false));
assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
assertEquals(1, mServer.numRequestsReceived());
assertEquals(1, mServer.numRepliesSent());
+
+ checkRequestTimeCalcs(LATE_ERA0_REQUEST_TIME, LATE_ERA0_SERVER_TIME, mClient);
+ }
+
+ /** Tests when the client is behind the server and in the previous ERA. b/199481251. */
+ @Test
+ public void testRequestTime_era0ClientEra1Server() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
+ mServer.setServerReply(HexEncoding.decode(EARLY_ERA_RESPONSE.toCharArray(), false));
+ assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
+ assertEquals(1, mServer.numRequestsReceived());
+ assertEquals(1, mServer.numRepliesSent());
+
+ checkRequestTimeCalcs(LATE_ERA0_REQUEST_TIME, EARLY_ERA1_SERVER_TIME, mClient);
+
+ }
+
+ /** Tests when the client is ahead of the server and in the next ERA. b/199481251. */
+ @Test
+ public void testRequestTime_era1ClientEra0Server() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(EARLY_ERA1_REQUEST_TIME);
+
+ mServer.setServerReply(HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false));
+ assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
+ assertEquals(1, mServer.numRequestsReceived());
+ assertEquals(1, mServer.numRepliesSent());
+
+ checkRequestTimeCalcs(EARLY_ERA1_REQUEST_TIME, LATE_ERA0_SERVER_TIME, mClient);
+ }
+
+ /** Tests when the client and server are in ERA1. b/199481251. */
+ @Test
+ public void testRequestTime_era1ClientEra1Server() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(EARLY_ERA1_REQUEST_TIME);
+
+ mServer.setServerReply(HexEncoding.decode(EARLY_ERA_RESPONSE.toCharArray(), false));
+ assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
+ assertEquals(1, mServer.numRequestsReceived());
+ assertEquals(1, mServer.numRepliesSent());
+
+ checkRequestTimeCalcs(EARLY_ERA1_REQUEST_TIME, EARLY_ERA1_SERVER_TIME, mClient);
+ }
+
+ private static void checkRequestTimeCalcs(
+ Instant clientTime, Instant serverTime, SntpClient client) {
+ // The tests don't attempt to control the elapsed time tracking, which influences the
+ // round trip time (i.e. time spent in due to the network), but they control everything
+ // else, so assertions are allowed some slop and round trip time just has to be >= 0.
+ assertTrue("getRoundTripTime()=" + client.getRoundTripTime(),
+ client.getRoundTripTime() >= 0);
+
+ // Calculate the ideal offset if nothing took any time.
+ long expectedOffset = serverTime.toEpochMilli() - clientTime.toEpochMilli();
+ long allowedSlop = (client.getRoundTripTime() / 2) + 1; // +1 to allow for truncation loss.
+ assertNearlyEquals(expectedOffset, client.getClockOffset(), allowedSlop);
+ assertNearlyEquals(clientTime.toEpochMilli() + expectedOffset,
+ client.getNtpTime(), allowedSlop);
+ }
+
+ /**
+ * Unit tests for the low-level offset calculations. More targeted / easier to write than the
+ * end-to-end tests above that simulate the server. b/199481251.
+ */
+ @Test
+ public void testCalculateClockOffset() {
+ Instant era0Time1 = utcInstant(2021, 10, 5, 2, 2, 2, 2);
+ // Confirm what happens when the client and server are completely in sync.
+ checkCalculateClockOffset(era0Time1, era0Time1);
+
+ Instant era0Time2 = utcInstant(2021, 10, 6, 1, 1, 1, 1);
+ checkCalculateClockOffset(era0Time1, era0Time2);
+ checkCalculateClockOffset(era0Time2, era0Time1);
+
+ Instant era1Time1 = utcInstant(2061, 10, 5, 2, 2, 2, 2);
+ checkCalculateClockOffset(era1Time1, era1Time1);
+
+ Instant era1Time2 = utcInstant(2061, 10, 6, 1, 1, 1, 1);
+ checkCalculateClockOffset(era1Time1, era1Time2);
+ checkCalculateClockOffset(era1Time2, era1Time1);
+
+ // Cross-era calcs (requires they are still within 68 years of each other).
+ checkCalculateClockOffset(era0Time1, era1Time1);
+ checkCalculateClockOffset(era1Time1, era0Time1);
+ }
+
+ private void checkCalculateClockOffset(Instant clientTime, Instant serverTime) {
+ // The expected (ideal) offset is the difference between the client and server clocks. NTP
+ // assumes delays are symmetric, i.e. that the server time is between server
+ // receive/transmit time, client time is between request/response time, and send networking
+ // delay == receive networking delay.
+ Duration expectedOffset = Duration.between(clientTime, serverTime);
+
+ // Try simulating various round trip delays, including zero.
+ for (long totalElapsedTimeMillis : Arrays.asList(0, 20, 200, 2000, 20000)) {
+ // Simulate that a 10% of the elapsed time is due to time spent in the server, the rest
+ // is network / client processing time.
+ long simulatedServerElapsedTimeMillis = totalElapsedTimeMillis / 10;
+ long simulatedClientElapsedTimeMillis = totalElapsedTimeMillis;
+
+ // Create some symmetrical timestamps.
+ Timestamp64 clientRequestTimestamp = Timestamp64.fromInstant(
+ clientTime.minusMillis(simulatedClientElapsedTimeMillis / 2));
+ Timestamp64 clientResponseTimestamp = Timestamp64.fromInstant(
+ clientTime.plusMillis(simulatedClientElapsedTimeMillis / 2));
+ Timestamp64 serverReceiveTimestamp = Timestamp64.fromInstant(
+ serverTime.minusMillis(simulatedServerElapsedTimeMillis / 2));
+ Timestamp64 serverTransmitTimestamp = Timestamp64.fromInstant(
+ serverTime.plusMillis(simulatedServerElapsedTimeMillis / 2));
+
+ Duration actualOffset = SntpClient.calculateClockOffset(
+ clientRequestTimestamp, serverReceiveTimestamp,
+ serverTransmitTimestamp, clientResponseTimestamp);
+
+ // We allow up to 1ms variation because NTP types are lossy and the simulated elapsed
+ // time millis may not divide exactly.
+ int allowedSlopMillis = 1;
+ assertNearlyEquals(
+ expectedOffset.toMillis(), actualOffset.toMillis(), allowedSlopMillis);
+ }
+ }
+
+ private static Instant utcInstant(
+ int year, int monthOfYear, int day, int hour, int minute, int second, int nanos) {
+ return LocalDateTime.of(year, monthOfYear, day, hour, minute, second, nanos)
+ .toInstant(ZoneOffset.UTC);
}
@Test
@@ -98,6 +298,8 @@
@Test
public void testTimeoutFailure() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
mServer.clearServerReply();
assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
assertEquals(1, mServer.numRequestsReceived());
@@ -106,7 +308,9 @@
@Test
public void testIgnoreLeapNoSync() throws Exception {
- final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
+ final byte[] reply = HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false);
reply[0] |= (byte) 0xc0;
mServer.setServerReply(reply);
assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
@@ -116,7 +320,9 @@
@Test
public void testAcceptOnlyServerAndBroadcastModes() throws Exception {
- final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
+ final byte[] reply = HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false);
for (int i = 0; i <= 7; i++) {
final String logMsg = "mode: " + i;
reply[0] &= (byte) 0xf8;
@@ -140,10 +346,12 @@
@Test
public void testAcceptableStrataOnly() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
final int STRATUM_MIN = 1;
final int STRATUM_MAX = 15;
- final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+ final byte[] reply = HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false);
for (int i = 0; i < 256; i++) {
final String logMsg = "stratum: " + i;
reply[1] = (byte) i;
@@ -162,7 +370,9 @@
@Test
public void testZeroTransmitTime() throws Exception {
- final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
+ final byte[] reply = HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false);
Arrays.fill(reply, TRANSMIT_TIME_OFFSET, TRANSMIT_TIME_OFFSET + 8, (byte) 0x00);
mServer.setServerReply(reply);
assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
@@ -170,6 +380,19 @@
assertEquals(1, mServer.numRepliesSent());
}
+ @Test
+ public void testNonMatchingOriginateTime() throws Exception {
+ when(mSystemTimeSupplier.get()).thenReturn(LATE_ERA0_REQUEST_TIME);
+
+ final byte[] reply = HexEncoding.decode(LATE_ERA_RESPONSE.toCharArray(), false);
+ mServer.setServerReply(reply);
+ mServer.setGenerateValidOriginateTimestamp(false);
+
+ assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork));
+ assertEquals(1, mServer.numRequestsReceived());
+ assertEquals(1, mServer.numRepliesSent());
+ }
+
private static class SntpTestServer {
private final Object mLock = new Object();
@@ -177,6 +400,7 @@
private final InetAddress mAddress;
private final int mPort;
private byte[] mReply;
+ private boolean mGenerateValidOriginateTimestamp = true;
private int mRcvd;
private int mSent;
private Thread mListeningThread;
@@ -201,10 +425,16 @@
synchronized (mLock) {
mRcvd++;
if (mReply == null) { continue; }
- // Copy transmit timestamp into originate timestamp.
- // TODO: bounds checking.
- System.arraycopy(ntpMsg.getData(), TRANSMIT_TIME_OFFSET,
- mReply, ORIGINATE_TIME_OFFSET, 8);
+ if (mGenerateValidOriginateTimestamp) {
+ // Copy the transmit timestamp into originate timestamp: This is
+ // validated by well-behaved clients.
+ System.arraycopy(ntpMsg.getData(), TRANSMIT_TIME_OFFSET,
+ mReply, ORIGINATE_TIME_OFFSET, 8);
+ } else {
+ // Fill it with junk instead.
+ Arrays.fill(mReply, ORIGINATE_TIME_OFFSET,
+ ORIGINATE_TIME_OFFSET + 8, (byte) 0xFF);
+ }
ntpMsg.setData(mReply);
ntpMsg.setLength(mReply.length);
try {
@@ -245,9 +475,38 @@
}
}
+ /**
+ * Controls the test server's behavior of copying the client's transmit timestamp into the
+ * response's originate timestamp (which is required of a real server).
+ */
+ public void setGenerateValidOriginateTimestamp(boolean enabled) {
+ synchronized (mLock) {
+ mGenerateValidOriginateTimestamp = enabled;
+ }
+ }
+
public InetAddress getAddress() { return mAddress; }
public int getPort() { return mPort; }
public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } }
public int numRepliesSent() { synchronized (mLock) { return mSent; } }
}
+
+ /**
+ * Generates the "real" server time assuming it is exactly between the receive and transmit
+ * timestamp and in the NTP era specified.
+ */
+ private static Instant calculateIdealServerTime(String receiveTimestampString,
+ String transmitTimestampString, int era) {
+ Timestamp64 receiveTimestamp = Timestamp64.fromString(receiveTimestampString);
+ Timestamp64 transmitTimestamp = Timestamp64.fromString(transmitTimestampString);
+ Duration serverProcessingTime =
+ Duration64.between(receiveTimestamp, transmitTimestamp).toDuration();
+ return receiveTimestamp.toInstant(era)
+ .plusMillis(serverProcessingTime.dividedBy(2).toMillis());
+ }
+
+ private static void assertNearlyEquals(long expected, long actual, long allowedSlop) {
+ assertTrue("expected=" + expected + ", actual=" + actual + ", allowedSlop=" + allowedSlop,
+ actual >= expected - allowedSlop && actual <= expected + allowedSlop);
+ }
}
diff --git a/core/tests/coretests/src/android/net/sntp/Duration64Test.java b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
new file mode 100644
index 0000000..60b69f6
--- /dev/null
+++ b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2021 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.net.sntp;
+
+import static android.net.sntp.Timestamp64.NANOS_PER_SECOND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+public class Duration64Test {
+
+ @Test
+ public void testBetween_rangeChecks() {
+ long maxDuration64Seconds = Timestamp64.MAX_SECONDS_IN_ERA / 2;
+
+ Timestamp64 zeroNoFrac = Timestamp64.fromComponents(0, 0);
+ assertEquals(Duration64.ZERO, Duration64.between(zeroNoFrac, zeroNoFrac));
+
+ {
+ Timestamp64 ceilNoFrac = Timestamp64.fromComponents(maxDuration64Seconds, 0);
+ assertEquals(Duration64.ZERO, Duration64.between(ceilNoFrac, ceilNoFrac));
+
+ long expectedNanos = maxDuration64Seconds * NANOS_PER_SECOND;
+ assertEquals(Duration.ofNanos(expectedNanos),
+ Duration64.between(zeroNoFrac, ceilNoFrac).toDuration());
+ assertEquals(Duration.ofNanos(-expectedNanos),
+ Duration64.between(ceilNoFrac, zeroNoFrac).toDuration());
+ }
+
+ {
+ // This value is the largest fraction of a second representable. It is 1-(1/2^32)), and
+ // so numerically larger than 999_999_999 nanos.
+ int fractionBits = 0xFFFF_FFFF;
+ Timestamp64 ceilWithFrac = Timestamp64
+ .fromComponents(maxDuration64Seconds, fractionBits);
+ assertEquals(Duration64.ZERO, Duration64.between(ceilWithFrac, ceilWithFrac));
+
+ long expectedNanos = maxDuration64Seconds * NANOS_PER_SECOND + 999_999_999;
+ assertEquals(
+ Duration.ofNanos(expectedNanos),
+ Duration64.between(zeroNoFrac, ceilWithFrac).toDuration());
+ // The -1 nanos demonstrates asymmetry due to the way Duration64 has different
+ // precision / range of sub-second fractions.
+ assertEquals(
+ Duration.ofNanos(-expectedNanos - 1),
+ Duration64.between(ceilWithFrac, zeroNoFrac).toDuration());
+ }
+ }
+
+ @Test
+ public void testBetween_smallSecondsOnly() {
+ long expectedNanos = 5L * NANOS_PER_SECOND;
+ assertEquals(Duration.ofNanos(expectedNanos),
+ Duration64.between(Timestamp64.fromComponents(5, 0),
+ Timestamp64.fromComponents(10, 0))
+ .toDuration());
+ assertEquals(Duration.ofNanos(-expectedNanos),
+ Duration64.between(Timestamp64.fromComponents(10, 0),
+ Timestamp64.fromComponents(5, 0))
+ .toDuration());
+ }
+
+ @Test
+ public void testBetween_smallSecondsAndFraction() {
+ // Choose a nanos values we know can be represented exactly with fixed point binary (1/2
+ // second, 1/4 second, etc.).
+ {
+ long expectedNanos = 5L * NANOS_PER_SECOND + 500_000_000L;
+ int fractionHalfSecond = 0x8000_0000;
+ assertEquals(Duration.ofNanos(expectedNanos),
+ Duration64.between(
+ Timestamp64.fromComponents(5, 0),
+ Timestamp64.fromComponents(10, fractionHalfSecond)).toDuration());
+ assertEquals(Duration.ofNanos(-expectedNanos),
+ Duration64.between(
+ Timestamp64.fromComponents(10, fractionHalfSecond),
+ Timestamp64.fromComponents(5, 0)).toDuration());
+ }
+
+ {
+ long expectedNanos = 5L * NANOS_PER_SECOND + 250_000_000L;
+ int fractionHalfSecond = 0x8000_0000;
+ int fractionQuarterSecond = 0x4000_0000;
+
+ assertEquals(Duration.ofNanos(expectedNanos),
+ Duration64.between(
+ Timestamp64.fromComponents(5, fractionQuarterSecond),
+ Timestamp64.fromComponents(10, fractionHalfSecond)).toDuration());
+ assertEquals(Duration.ofNanos(-expectedNanos),
+ Duration64.between(
+ Timestamp64.fromComponents(10, fractionHalfSecond),
+ Timestamp64.fromComponents(5, fractionQuarterSecond)).toDuration());
+ }
+
+ }
+
+ @Test
+ public void testBetween_sameEra0() {
+ int arbitraryEra0Year = 2021;
+ Instant one = utcInstant(arbitraryEra0Year, 1, 1, 0, 0, 0, 500);
+ assertNtpEraOfInstant(one, 0);
+
+ checkDuration64Behavior(one, one);
+
+ Instant two = utcInstant(arbitraryEra0Year + 1, 1, 1, 0, 0, 0, 250);
+ assertNtpEraOfInstant(two, 0);
+
+ checkDuration64Behavior(one, two);
+ checkDuration64Behavior(two, one);
+ }
+
+ @Test
+ public void testBetween_sameEra1() {
+ int arbitraryEra1Year = 2037;
+ Instant one = utcInstant(arbitraryEra1Year, 1, 1, 0, 0, 0, 500);
+ assertNtpEraOfInstant(one, 1);
+
+ checkDuration64Behavior(one, one);
+
+ Instant two = utcInstant(arbitraryEra1Year + 1, 1, 1, 0, 0, 0, 250);
+ assertNtpEraOfInstant(two, 1);
+
+ checkDuration64Behavior(one, two);
+ checkDuration64Behavior(two, one);
+ }
+
+ /**
+ * Tests that two timestamps can originate from times in different eras, and the works
+ * calculation still works providing the two times aren't more than 68 years apart (half of the
+ * 136 years representable using an unsigned 32-bit seconds representation).
+ */
+ @Test
+ public void testBetween_adjacentEras() {
+ int yearsSeparation = 68;
+
+ // This year just needs to be < 68 years before the end of NTP timestamp era 0.
+ int arbitraryYearInEra0 = 2021;
+
+ Instant one = utcInstant(arbitraryYearInEra0, 1, 1, 0, 0, 0, 500);
+ assertNtpEraOfInstant(one, 0);
+
+ checkDuration64Behavior(one, one);
+
+ Instant two = utcInstant(arbitraryYearInEra0 + yearsSeparation, 1, 1, 0, 0, 0, 250);
+ assertNtpEraOfInstant(two, 1);
+
+ checkDuration64Behavior(one, two);
+ checkDuration64Behavior(two, one);
+ }
+
+ /**
+ * This test confirms that duration calculations fail in the expected fashion if two
+ * Timestamp64s are more than 2^31 seconds apart.
+ *
+ * <p>The types / math specified by NTP for timestamps deliberately takes place in 64-bit signed
+ * arithmetic for the bits used to represent timestamps (32-bit unsigned integer seconds,
+ * 32-bits fixed point for fraction of seconds). Timestamps can therefore represent ~136 years
+ * of seconds.
+ * When subtracting one timestamp from another, we end up with a signed 32-bit seconds value.
+ * This means the max duration representable is ~68 years before numbers will over or underflow.
+ * i.e. the client and server are in the same or adjacent NTP eras and the difference in their
+ * clocks isn't more than ~68 years. >= ~68 years and things break down.
+ */
+ @Test
+ public void testBetween_tooFarApart() {
+ int tooManyYearsSeparation = 68 + 1;
+
+ Instant one = utcInstant(2021, 1, 1, 0, 0, 0, 500);
+ assertNtpEraOfInstant(one, 0);
+ Instant two = utcInstant(2021 + tooManyYearsSeparation, 1, 1, 0, 0, 0, 250);
+ assertNtpEraOfInstant(two, 1);
+
+ checkDuration64OverflowBehavior(one, two);
+ checkDuration64OverflowBehavior(two, one);
+ }
+
+ private static void checkDuration64Behavior(Instant one, Instant two) {
+ // This is the answer if we perform the arithmetic in a lossless fashion.
+ Duration expectedDuration = Duration.between(one, two);
+ Duration64 expectedDuration64 = Duration64.fromDuration(expectedDuration);
+
+ // Sub-second precision is limited in Timestamp64, so we can lose 1ms.
+ assertEqualsOrSlightlyLessThan(
+ expectedDuration.toMillis(), expectedDuration64.toDuration().toMillis());
+
+ Timestamp64 one64 = Timestamp64.fromInstant(one);
+ Timestamp64 two64 = Timestamp64.fromInstant(two);
+
+ // This is the answer if we perform the arithmetic in a lossy fashion.
+ Duration64 actualDuration64 = Duration64.between(one64, two64);
+ assertEquals(expectedDuration64.getSeconds(), actualDuration64.getSeconds());
+ assertEqualsOrSlightlyLessThan(expectedDuration64.getNanos(), actualDuration64.getNanos());
+ }
+
+ private static void checkDuration64OverflowBehavior(Instant one, Instant two) {
+ // This is the answer if we perform the arithmetic in a lossless fashion.
+ Duration trueDuration = Duration.between(one, two);
+
+ // Confirm the maths is expected to overflow / underflow.
+ assertTrue(trueDuration.getSeconds() > Integer.MAX_VALUE / 2
+ || trueDuration.getSeconds() < Integer.MIN_VALUE / 2);
+
+ // Now perform the arithmetic as specified for NTP: do subtraction using the 64-bit
+ // timestamp.
+ Timestamp64 one64 = Timestamp64.fromInstant(one);
+ Timestamp64 two64 = Timestamp64.fromInstant(two);
+
+ Duration64 actualDuration64 = Duration64.between(one64, two64);
+ assertNotEquals(trueDuration.getSeconds(), actualDuration64.getSeconds());
+ }
+
+ /**
+ * Asserts the instant provided is in the specified NTP timestamp era. Used to confirm /
+ * document values picked for tests have the properties needed.
+ */
+ private static void assertNtpEraOfInstant(Instant one, int ntpEra) {
+ long expectedSeconds = one.getEpochSecond();
+
+ // The conversion to Timestamp64 is lossy (it loses the era). We then supply the expected
+ // era. If the era was correct, we will end up with the value we started with (modulo nano
+ // precision loss). If the era is wrong, we won't.
+ Instant roundtrippedInstant = Timestamp64.fromInstant(one).toInstant(ntpEra);
+
+ long actualSeconds = roundtrippedInstant.getEpochSecond();
+ assertEquals(expectedSeconds, actualSeconds);
+ }
+
+ /**
+ * Used to account for the fact that NTP types used 32-bit fixed point storage, so cannot store
+ * all values precisely. The value we get out will always be the value we put in, or one that is
+ * one unit smaller (due to truncation).
+ */
+ private static void assertEqualsOrSlightlyLessThan(long expected, long actual) {
+ assertTrue("expected=" + expected + ", actual=" + actual,
+ expected == actual || expected == actual - 1);
+ }
+
+ private static Instant utcInstant(
+ int year, int monthOfYear, int day, int hour, int minute, int second, int nanos) {
+ return LocalDateTime.of(year, monthOfYear, day, hour, minute, second, nanos)
+ .toInstant(ZoneOffset.UTC);
+ }
+}
diff --git a/core/tests/coretests/src/android/net/sntp/OWNERS b/core/tests/coretests/src/android/net/sntp/OWNERS
new file mode 100644
index 0000000..232c2eb
--- /dev/null
+++ b/core/tests/coretests/src/android/net/sntp/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/net/sntp/OWNERS
diff --git a/core/tests/coretests/src/android/net/sntp/PredictableRandom.java b/core/tests/coretests/src/android/net/sntp/PredictableRandom.java
new file mode 100644
index 0000000..bb2922b
--- /dev/null
+++ b/core/tests/coretests/src/android/net/sntp/PredictableRandom.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.net.sntp;
+
+import java.util.Random;
+
+class PredictableRandom extends Random {
+ private int[] mIntSequence = new int[] { 1 };
+ private int mIntPos = 0;
+
+ public void setIntSequence(int[] intSequence) {
+ this.mIntSequence = intSequence;
+ }
+
+ @Override
+ public int nextInt() {
+ int value = mIntSequence[mIntPos++];
+ mIntPos %= mIntSequence.length;
+ return value;
+ }
+}
diff --git a/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
new file mode 100644
index 0000000..1b1c500
--- /dev/null
+++ b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2021 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.net.sntp;
+
+import static android.net.sntp.Timestamp64.NANOS_PER_SECOND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class Timestamp64Test {
+
+ @Test
+ public void testFromComponents() {
+ long minNtpEraSeconds = 0;
+ long maxNtpEraSeconds = 0xFFFFFFFFL;
+
+ expectIllegalArgumentException(() -> Timestamp64.fromComponents(minNtpEraSeconds - 1, 0));
+ expectIllegalArgumentException(() -> Timestamp64.fromComponents(maxNtpEraSeconds + 1, 0));
+
+ assertComponentCreation(minNtpEraSeconds, 0);
+ assertComponentCreation(maxNtpEraSeconds, 0);
+ assertComponentCreation(maxNtpEraSeconds, Integer.MIN_VALUE);
+ assertComponentCreation(maxNtpEraSeconds, Integer.MAX_VALUE);
+ }
+
+ private static void assertComponentCreation(long ntpEraSeconds, int fractionBits) {
+ Timestamp64 value = Timestamp64.fromComponents(ntpEraSeconds, fractionBits);
+ assertEquals(ntpEraSeconds, value.getEraSeconds());
+ assertEquals(fractionBits, value.getFractionBits());
+ }
+
+ @Test
+ public void testEqualsAndHashcode() {
+ assertEqualsAndHashcode(0, 0);
+ assertEqualsAndHashcode(1, 0);
+ assertEqualsAndHashcode(0, 1);
+ }
+
+ private static void assertEqualsAndHashcode(int eraSeconds, int fractionBits) {
+ Timestamp64 one = Timestamp64.fromComponents(eraSeconds, fractionBits);
+ Timestamp64 two = Timestamp64.fromComponents(eraSeconds, fractionBits);
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ @Test
+ public void testStringForm() {
+ expectIllegalArgumentException(() -> Timestamp64.fromString(""));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("."));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("1234567812345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678?12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678..12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("1.12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12.12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("123456.12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("1234567.12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678.1"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678.12"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678.123456"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678.1234567"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("X2345678.12345678"));
+ expectIllegalArgumentException(() -> Timestamp64.fromString("12345678.X2345678"));
+
+ assertStringCreation("00000000.00000000", 0, 0);
+ assertStringCreation("00000001.00000001", 1, 1);
+ assertStringCreation("ffffffff.ffffffff", 0xFFFFFFFFL, 0xFFFFFFFF);
+ }
+
+ private static void assertStringCreation(
+ String string, long expectedSeconds, int expectedFractionBits) {
+ Timestamp64 timestamp64 = Timestamp64.fromString(string);
+ assertEquals(string, timestamp64.toString());
+ assertEquals(expectedSeconds, timestamp64.getEraSeconds());
+ assertEquals(expectedFractionBits, timestamp64.getFractionBits());
+ }
+
+ @Test
+ public void testStringForm_lenientHexCasing() {
+ Timestamp64 mixedCaseValue = Timestamp64.fromString("AaBbCcDd.EeFf1234");
+ assertEquals(0xAABBCCDDL, mixedCaseValue.getEraSeconds());
+ assertEquals(0xEEFF1234, mixedCaseValue.getFractionBits());
+ }
+
+ @Test
+ public void testFromInstant_secondsHandling() {
+ final int era0 = 0;
+ final int eraNeg1 = -1;
+ final int eraNeg2 = -2;
+ final int era1 = 1;
+
+ assertInstantCreationOnlySeconds(-Timestamp64.OFFSET_1900_TO_1970, 0, era0);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 - Timestamp64.SECONDS_IN_ERA, 0, eraNeg1);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 + Timestamp64.SECONDS_IN_ERA, 0, era1);
+
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 - 1, Timestamp64.MAX_SECONDS_IN_ERA, -1);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 - Timestamp64.SECONDS_IN_ERA - 1,
+ Timestamp64.MAX_SECONDS_IN_ERA, eraNeg2);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 + Timestamp64.SECONDS_IN_ERA - 1,
+ Timestamp64.MAX_SECONDS_IN_ERA, era0);
+
+ assertInstantCreationOnlySeconds(-Timestamp64.OFFSET_1900_TO_1970 + 1, 1, era0);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 - Timestamp64.SECONDS_IN_ERA + 1, 1, eraNeg1);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.OFFSET_1900_TO_1970 + Timestamp64.SECONDS_IN_ERA + 1, 1, era1);
+
+ assertInstantCreationOnlySeconds(0, Timestamp64.OFFSET_1900_TO_1970, era0);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.SECONDS_IN_ERA, Timestamp64.OFFSET_1900_TO_1970, eraNeg1);
+ assertInstantCreationOnlySeconds(
+ Timestamp64.SECONDS_IN_ERA, Timestamp64.OFFSET_1900_TO_1970, era1);
+
+ assertInstantCreationOnlySeconds(1, Timestamp64.OFFSET_1900_TO_1970 + 1, era0);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.SECONDS_IN_ERA + 1, Timestamp64.OFFSET_1900_TO_1970 + 1, eraNeg1);
+ assertInstantCreationOnlySeconds(
+ Timestamp64.SECONDS_IN_ERA + 1, Timestamp64.OFFSET_1900_TO_1970 + 1, era1);
+
+ assertInstantCreationOnlySeconds(-1, Timestamp64.OFFSET_1900_TO_1970 - 1, era0);
+ assertInstantCreationOnlySeconds(
+ -Timestamp64.SECONDS_IN_ERA - 1, Timestamp64.OFFSET_1900_TO_1970 - 1, eraNeg1);
+ assertInstantCreationOnlySeconds(
+ Timestamp64.SECONDS_IN_ERA - 1, Timestamp64.OFFSET_1900_TO_1970 - 1, era1);
+ }
+
+ private static void assertInstantCreationOnlySeconds(
+ long epochSeconds, long expectedNtpEraSeconds, int ntpEra) {
+ int nanosOfSecond = 0;
+ Instant instant = Instant.ofEpochSecond(epochSeconds, nanosOfSecond);
+ Timestamp64 timestamp = Timestamp64.fromInstant(instant);
+ assertEquals(expectedNtpEraSeconds, timestamp.getEraSeconds());
+
+ int expectedFractionBits = 0;
+ assertEquals(expectedFractionBits, timestamp.getFractionBits());
+
+ // Confirm the Instant can be round-tripped if we know the era. Also assumes the nanos can
+ // be stored precisely; 0 can be.
+ Instant roundTrip = timestamp.toInstant(ntpEra);
+ assertEquals(instant, roundTrip);
+ }
+
+ @Test
+ public void testFromInstant_fractionHandling() {
+ // Try some values we know can be represented exactly.
+ assertInstantCreationOnlyFractionExact(0x0, 0);
+ assertInstantCreationOnlyFractionExact(0x80000000, 500_000_000L);
+ assertInstantCreationOnlyFractionExact(0x40000000, 250_000_000L);
+
+ // Test the limits of precision.
+ assertInstantCreationOnlyFractionExact(0x00000006, 1L);
+ assertInstantCreationOnlyFractionExact(0x00000005, 1L);
+ assertInstantCreationOnlyFractionExact(0x00000004, 0L);
+ assertInstantCreationOnlyFractionExact(0x00000002, 0L);
+ assertInstantCreationOnlyFractionExact(0x00000001, 0L);
+
+ // Confirm nanosecond storage / precision is within 1ns.
+ final boolean exhaustive = false;
+ for (int i = 0; i < NANOS_PER_SECOND; i++) {
+ Instant instant = Instant.ofEpochSecond(0, i);
+ Instant roundTripped = Timestamp64.fromInstant(instant).toInstant(0);
+ assertNanosWithTruncationAllowed(i, roundTripped);
+ if (!exhaustive) {
+ i += 999_999;
+ }
+ }
+ }
+
+ @SuppressWarnings("JavaInstantGetSecondsGetNano")
+ private static void assertInstantCreationOnlyFractionExact(
+ int fractionBits, long expectedNanos) {
+ Timestamp64 timestamp64 = Timestamp64.fromComponents(0, fractionBits);
+
+ final int ntpEra = 0;
+ Instant instant = timestamp64.toInstant(ntpEra);
+
+ assertEquals(expectedNanos, instant.getNano());
+ }
+
+ @SuppressWarnings("JavaInstantGetSecondsGetNano")
+ private static void assertNanosWithTruncationAllowed(long expectedNanos, Instant instant) {
+ // Allow for < 1ns difference due to truncation.
+ long actualNanos = instant.getNano();
+ assertTrue("expectedNanos=" + expectedNanos + ", actualNanos=" + actualNanos,
+ actualNanos == expectedNanos || actualNanos == expectedNanos - 1);
+ }
+
+ @SuppressWarnings("JavaInstantGetSecondsGetNano")
+ @Test
+ public void testMillisRandomizationConstant() {
+ // Mathematically, we can say that to represent 1000 different values, we need 10 binary
+ // digits (2^10 = 1024). The same is true whether we're dealing with integers or fractions.
+ // Unfortunately, for fractions those 1024 values do not correspond to discrete decimal
+ // values. Discrete millisecond values as fractions (e.g. 0.001 - 0.999) cannot be
+ // represented exactly except where the value can also be represented as some combination of
+ // powers of -2. When we convert back and forth, we truncate, so millisecond decimal
+ // fraction N represented as a binary fraction will always be equal to or lower than N. If
+ // we are truncating correctly it will never be as low as (N-0.001). N -> [N-0.001, N].
+
+ // We need to keep 10 bits to hold millis (inaccurately, since there are numbers that
+ // cannot be represented exactly), leaving us able to randomize the remaining 22 bits of the
+ // fraction part without significantly affecting the number represented.
+ assertEquals(22, Timestamp64.SUB_MILLIS_BITS_TO_RANDOMIZE);
+
+ // Brute force proof that randomization logic will keep the timestamp within the range
+ // [N-0.001, N] where x is in milliseconds.
+ int smallFractionRandomizedLow = 0;
+ int smallFractionRandomizedHigh = 0b00000000_00111111_11111111_11111111;
+ int largeFractionRandomizedLow = 0b11111111_11000000_00000000_00000000;
+ int largeFractionRandomizedHigh = 0b11111111_11111111_11111111_11111111;
+
+ long smallLowNanos = Timestamp64.fromComponents(
+ 0, smallFractionRandomizedLow).toInstant(0).getNano();
+ long smallHighNanos = Timestamp64.fromComponents(
+ 0, smallFractionRandomizedHigh).toInstant(0).getNano();
+ long smallDelta = smallHighNanos - smallLowNanos;
+ long millisInNanos = 1_000_000_000 / 1_000;
+ assertTrue(smallDelta >= 0 && smallDelta < millisInNanos);
+
+ long largeLowNanos = Timestamp64.fromComponents(
+ 0, largeFractionRandomizedLow).toInstant(0).getNano();
+ long largeHighNanos = Timestamp64.fromComponents(
+ 0, largeFractionRandomizedHigh).toInstant(0).getNano();
+ long largeDelta = largeHighNanos - largeLowNanos;
+ assertTrue(largeDelta >= 0 && largeDelta < millisInNanos);
+
+ PredictableRandom random = new PredictableRandom();
+ random.setIntSequence(new int[] { 0xFFFF_FFFF });
+ Timestamp64 zero = Timestamp64.fromComponents(0, 0);
+ Timestamp64 zeroWithFractionRandomized = zero.randomizeSubMillis(random);
+ assertEquals(zero.getEraSeconds(), zeroWithFractionRandomized.getEraSeconds());
+ assertEquals(smallFractionRandomizedHigh, zeroWithFractionRandomized.getFractionBits());
+ }
+
+ @Test
+ public void testRandomizeLowestBits() {
+ Random random = new Random(1);
+ {
+ int fractionBits = 0;
+ expectIllegalArgumentException(
+ () -> Timestamp64.randomizeLowestBits(random, fractionBits, -1));
+ expectIllegalArgumentException(
+ () -> Timestamp64.randomizeLowestBits(random, fractionBits, 0));
+ expectIllegalArgumentException(
+ () -> Timestamp64.randomizeLowestBits(random, fractionBits, Integer.SIZE));
+ expectIllegalArgumentException(
+ () -> Timestamp64.randomizeLowestBits(random, fractionBits, Integer.SIZE + 1));
+ }
+
+ // Check the behavior looks correct from a probabilistic point of view.
+ for (int input : new int[] { 0, 0xFFFFFFFF }) {
+ for (int bitCount = 1; bitCount < Integer.SIZE; bitCount++) {
+ int upperBitMask = 0xFFFFFFFF << bitCount;
+ int expectedUpperBits = input & upperBitMask;
+
+ Set<Integer> values = new HashSet<>();
+ values.add(input);
+
+ int trials = 100;
+ for (int i = 0; i < trials; i++) {
+ int outputFractionBits =
+ Timestamp64.randomizeLowestBits(random, input, bitCount);
+
+ // Record the output value for later analysis.
+ values.add(outputFractionBits);
+
+ // Check upper bits did not change.
+ assertEquals(expectedUpperBits, outputFractionBits & upperBitMask);
+ }
+
+ // It's possible to be more rigorous here, perhaps with a histogram. As bitCount
+ // rises, values.size() quickly trend towards the value of trials + 1. For now, this
+ // mostly just guards against a no-op implementation.
+ assertTrue(bitCount + ":" + values.size(), values.size() > 1);
+ }
+ }
+ }
+
+ private static void expectIllegalArgumentException(Runnable r) {
+ try {
+ r.run();
+ fail();
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 9a9b474..a42285e 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -2,11 +2,11 @@
per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
# Haptics
-per-file CombinedVibrationEffectTest.java = michaelwr@google.com
-per-file ExternalVibrationTest.java = michaelwr@google.com
-per-file VibrationEffectTest.java = michaelwr@google.com
-per-file VibratorInfoTest.java = michaelwr@google.com
-per-file VibratorTest.java = michaelwr@google.com
+per-file CombinedVibrationEffectTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file ExternalVibrationTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibrationEffectTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibratorInfoTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibratorTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
# Power
-per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
+per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/service/timezone/OWNERS b/core/tests/coretests/src/android/service/timezone/OWNERS
new file mode 100644
index 0000000..8116388
--- /dev/null
+++ b/core/tests/coretests/src/android/service/timezone/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 847766
+include /core/java/android/service/timezone/OWNERS
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
new file mode 100644
index 0000000..c8de190
--- /dev/null
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.timezone;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class TimeZoneProviderEventTest {
+
+ @Test
+ public void isEquivalentToAndEquals() {
+ TimeZoneProviderEvent fail1v1 =
+ TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one");
+ assertEquals(fail1v1, fail1v1);
+ assertIsEquivalentTo(fail1v1, fail1v1);
+ assertNotEquals(fail1v1, null);
+ assertNotEquivalentTo(fail1v1, null);
+
+ {
+ TimeZoneProviderEvent fail1v2 =
+ TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one");
+ assertEquals(fail1v1, fail1v2);
+ assertIsEquivalentTo(fail1v1, fail1v2);
+
+ TimeZoneProviderEvent fail2 =
+ TimeZoneProviderEvent.createPermanentFailureEvent(2222L, "two");
+ assertNotEquals(fail1v1, fail2);
+ assertIsEquivalentTo(fail1v1, fail2);
+ }
+
+ TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(1111L);
+ assertEquals(uncertain1v1, uncertain1v1);
+ assertIsEquivalentTo(uncertain1v1, uncertain1v1);
+ assertNotEquals(uncertain1v1, null);
+ assertNotEquivalentTo(uncertain1v1, null);
+
+ {
+ TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(1111L);
+ assertEquals(uncertain1v1, uncertain1v2);
+ assertIsEquivalentTo(uncertain1v1, uncertain1v2);
+
+ TimeZoneProviderEvent uncertain2 = TimeZoneProviderEvent.createUncertainEvent(2222L);
+ assertNotEquals(uncertain1v1, uncertain2);
+ assertIsEquivalentTo(uncertain1v1, uncertain2);
+ }
+
+ TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(1111L)
+ .setTimeZoneIds(Collections.singletonList("Europe/London"))
+ .build();
+ TimeZoneProviderEvent certain1v1 =
+ TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1);
+ assertEquals(certain1v1, certain1v1);
+ assertIsEquivalentTo(certain1v1, certain1v1);
+ assertNotEquals(certain1v1, null);
+ assertNotEquivalentTo(certain1v1, null);
+
+ {
+ // Same suggestion, same time.
+ TimeZoneProviderEvent certain1v2 =
+ TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1);
+ assertEquals(certain1v1, certain1v2);
+ assertIsEquivalentTo(certain1v1, certain1v2);
+
+ // Same suggestion, different time.
+ TimeZoneProviderEvent certain1v3 =
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1);
+ assertNotEquals(certain1v1, certain1v3);
+ assertIsEquivalentTo(certain1v1, certain1v3);
+
+ // suggestion1 is equivalent to suggestion2, but not equal
+ TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(2222L)
+ .setTimeZoneIds(Collections.singletonList("Europe/London"))
+ .build();
+ assertNotEquals(suggestion1, suggestion2);
+ TimeZoneProviderSuggestionTest.assertIsEquivalentTo(suggestion1, suggestion2);
+ TimeZoneProviderEvent certain2 =
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion2);
+ assertNotEquals(certain1v1, certain2);
+ assertIsEquivalentTo(certain1v1, certain2);
+
+ // suggestion3 is not equivalent to suggestion1
+ TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Collections.singletonList("Europe/Paris"))
+ .build();
+ TimeZoneProviderEvent certain3 =
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion3);
+ assertNotEquals(certain1v1, certain3);
+ assertNotEquivalentTo(certain1v1, certain3);
+ }
+
+ assertNotEquals(fail1v1, uncertain1v1);
+ assertNotEquivalentTo(fail1v1, uncertain1v1);
+
+ assertNotEquals(fail1v1, certain1v1);
+ assertNotEquivalentTo(fail1v1, certain1v1);
+ }
+
+ @Test
+ public void testParcelable_failureEvent() {
+ TimeZoneProviderEvent event =
+ TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "failure reason");
+ assertRoundTripParcelable(event);
+ }
+
+ @Test
+ public void testParcelable_uncertain() {
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L);
+ assertRoundTripParcelable(event);
+ }
+
+ @Test
+ public void testParcelable_suggestion() {
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
+ .build();
+ TimeZoneProviderEvent event =
+ TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion);
+ assertRoundTripParcelable(event);
+ }
+
+ private static void assertNotEquivalentTo(
+ TimeZoneProviderEvent one, TimeZoneProviderEvent two) {
+ if (one == null && two == null) {
+ fail("null arguments");
+ }
+ if (one != null) {
+ assertFalse("one=" + one + ", two=" + two, one.isEquivalentTo(two));
+ }
+ if (two != null) {
+ assertFalse("one=" + one + ", two=" + two, two.isEquivalentTo(one));
+ }
+ }
+
+ private static void assertIsEquivalentTo(TimeZoneProviderEvent one, TimeZoneProviderEvent two) {
+ if (one == null || two == null) {
+ fail("null arguments");
+ }
+ assertTrue("one=" + one + ", two=" + two, one.isEquivalentTo(two));
+ assertTrue("one=" + one + ", two=" + two, two.isEquivalentTo(one));
+ }
+}
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java
new file mode 100644
index 0000000..e800739
--- /dev/null
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+/** Tests for non-API methods */
+public class TimeZoneProviderSuggestionTest {
+
+ @Test
+ public void isEquivalentToAndEquals() {
+ TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(1111L)
+ .setTimeZoneIds(Collections.singletonList("Europe/London"))
+ .build();
+ assertEquals(suggestion1, suggestion1);
+ assertIsEquivalentTo(suggestion1, suggestion1);
+ assertNotEquals(suggestion1, null);
+ assertNotEquivalentTo(suggestion1, null);
+
+ // Same time zone IDs, different time.
+ TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(2222L)
+ .setTimeZoneIds(Collections.singletonList("Europe/London"))
+ .build();
+ assertNotEquals(suggestion1, suggestion2);
+ assertIsEquivalentTo(suggestion1, suggestion2);
+
+ // Different time zone IDs.
+ TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(1111L)
+ .setTimeZoneIds(Collections.singletonList("Europe/Paris"))
+ .build();
+ assertNotEquals(suggestion1, suggestion3);
+ assertNotEquivalentTo(suggestion1, suggestion3);
+ }
+
+ static void assertNotEquivalentTo(
+ TimeZoneProviderSuggestion one, TimeZoneProviderSuggestion two) {
+ if (one == null && two == null) {
+ fail("null arguments");
+ }
+ if (one != null) {
+ assertFalse("one=" + one + ", two=" + two, one.isEquivalentTo(two));
+ }
+ if (two != null) {
+ assertFalse("one=" + one + ", two=" + two, two.isEquivalentTo(one));
+ }
+ }
+
+ static void assertIsEquivalentTo(
+ TimeZoneProviderSuggestion one, TimeZoneProviderSuggestion two) {
+ if (one == null || two == null) {
+ fail("null arguments");
+ }
+ assertTrue("one=" + one + ", two=" + two, one.isEquivalentTo(two));
+ assertTrue("one=" + one + ", two=" + two, two.isEquivalentTo(one));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index d76037e..130f552 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -38,26 +38,28 @@
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0, 10.0);
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0, 10.0)
+ .setNumDisplays(1);
@Test
public void testMeasuredEnergyBasedModel() {
mStatsRule.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
- stats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON, 0);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000},
+ new int[]{Display.STATE_ON}, 0);
- stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
30 * MINUTE_IN_MS);
- stats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_DOZE,
- 30 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000},
+ new int[]{Display.STATE_DOZE}, 30 * MINUTE_IN_MS);
- stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
120 * MINUTE_IN_MS);
- stats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_OFF,
- 120 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000},
+ new int[]{Display.STATE_OFF}, 120 * MINUTE_IN_MS);
AmbientDisplayPowerCalculator calculator =
new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
@@ -75,12 +77,73 @@
}
@Test
+ public void testMeasuredEnergyBasedModel_multiDisplay() {
+ mStatsRule.initMeasuredEnergyStatsLocked()
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 1, 20.0)
+ .setNumDisplays(2);
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+
+ final int[] screenStates = new int[] {Display.STATE_OFF, Display.STATE_OFF};
+
+ stats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
+ stats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0);
+
+ // Switch display0 to doze
+ screenStates[0] = Display.STATE_DOZE;
+ stats.noteScreenStateLocked(0, screenStates[0], 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ 30 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200, 300},
+ screenStates, 30 * MINUTE_IN_MS);
+
+ // Switch display1 to doze
+ screenStates[1] = Display.STATE_DOZE;
+ stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
+ 90 * MINUTE_IN_MS);
+ // 100,000,000 uC should be attributed to display 0 doze here.
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000, 700_000_000},
+ screenStates, 90 * MINUTE_IN_MS);
+
+ // Switch display0 to off
+ screenStates[0] = Display.STATE_OFF;
+ stats.noteScreenStateLocked(0, screenStates[0], 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ 120 * MINUTE_IN_MS);
+ // 40,000,000 and 70,000,000 uC should be attributed to display 0 and 1 doze here.
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{40_000_000, 70_000_000},
+ screenStates, 120 * MINUTE_IN_MS);
+
+ // Switch display1 to off
+ screenStates[1] = Display.STATE_OFF;
+ stats.noteScreenStateLocked(1, screenStates[1], 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
+ 150 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100, 90_000_000}, screenStates,
+ 150 * MINUTE_IN_MS);
+ // 90,000,000 uC should be attributed to display 1 doze here.
+
+ AmbientDisplayPowerCalculator calculator =
+ new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(120 * MINUTE_IN_MS);
+ // 100,000,000 + 40,000,000 + 70,000,000 + 90,000,000 uC / 1000 (micro-/milli-) / 3600
+ // (seconds/hour) = 27.777778 mAh
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isWithin(PRECISION).of(83.33333);
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+ }
+
+ @Test
public void testPowerProfileBasedModel() {
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
- stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
30 * MINUTE_IN_MS);
- stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
120 * MINUTE_IN_MS);
AmbientDisplayPowerCalculator calculator =
@@ -96,4 +159,36 @@
assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
+
+ @Test
+ public void testPowerProfileBasedModel_multiDisplay() {
+ mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 1, 20.0)
+ .setNumDisplays(2);
+
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ stats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0);
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ 30 * MINUTE_IN_MS);
+ stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
+ 90 * MINUTE_IN_MS);
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ 120 * MINUTE_IN_MS);
+ stats.noteScreenStateLocked(1, Display.STATE_OFF, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
+ 150 * MINUTE_IN_MS);
+
+ AmbientDisplayPowerCalculator calculator =
+ new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+ // Duration should only be the union of
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(120 * MINUTE_IN_MS);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isWithin(PRECISION).of(35.0);
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
index 9c641e6..2262c05 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
@@ -18,15 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
-import android.content.Context;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Process;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import libcore.testing.io.TestIoUtils;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,16 +43,8 @@
@Before
public void setup() {
- Context context = InstrumentationRegistry.getContext();
-
- File historyDir = new File(context.getDataDir(), BatteryStatsHistory.HISTORY_DIR);
- String[] files = historyDir.list();
- if (files != null) {
- for (int i = 0; i < files.length; i++) {
- new File(historyDir, files[i]).delete();
- }
- }
- historyDir.delete();
+ final File historyDir =
+ TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index e8e4330..b655369 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -16,9 +16,13 @@
package com.android.internal.os;
+import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
+
import android.app.ActivityManager;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
@@ -37,8 +41,10 @@
import junit.framework.TestCase;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.IntConsumer;
/**
* Test various BatteryStatsImpl noteStart methods.
@@ -317,18 +323,130 @@
public void testNoteScreenStateLocked() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- bi.noteScreenStateLocked(Display.STATE_ON);
- bi.noteScreenStateLocked(Display.STATE_DOZE);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_DOZE);
- bi.noteScreenStateLocked(Display.STATE_ON);
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_ON);
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_OFF);
+ assertEquals(Display.STATE_OFF, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_VR note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_ON_SUSPEND note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Transition from ON to ON state should not cause an External Sync
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
+ }
+
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly for
+ * multi display devices
+ */
+ @SmallTest
+ public void testNoteScreenStateLocked_multiDisplay() throws Exception {
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setDisplayCountLocked(2);
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_OFF, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_VR note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_ON_SUSPEND note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Transition from ON to ON state should not cause an External Sync
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(1, Display.STATE_DOZE);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ // Should remain STATE_ON since display0 is still on.
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Overall screen state did not change, so no need to sync CPU stats.
+ assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ // Overall screen state did not change, so no need to sync CPU stats.
+ assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
}
/*
@@ -352,32 +470,317 @@
bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
// Turn on display at 200us
clocks.realtime = clocks.uptime = 200;
- bi.noteScreenStateLocked(Display.STATE_ON);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000));
clocks.realtime = clocks.uptime = 310;
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000));
clocks.realtime = clocks.uptime = 400;
- bi.noteScreenStateLocked(Display.STATE_DOZE);
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000));
+ assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000));
clocks.realtime = clocks.uptime = 1000;
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
assertEquals(1290_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1500_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1500_000));
+ }
+
+ /*
+ * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly for multi display
+ * devices.
+ */
+ @SmallTest
+ public void testNoteScreenStateTimersLocked_multiDisplay() throws Exception {
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setDisplayCountLocked(2);
+
+ clocks.realtime = clocks.uptime = 100;
+ // Device startup, setOnBatteryLocked calls updateTimebases
+ bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
+ // Turn on display at 200us
+ clocks.realtime = clocks.uptime = 200;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+ assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 250_000));
+
+ clocks.realtime = clocks.uptime = 310;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 350_000));
+
+ clocks.realtime = clocks.uptime = 400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000));
+ assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 500_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 500_000));
+
+ clocks.realtime = clocks.uptime = 1000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(1000_000, bi.computeBatteryRealtime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(890_000, bi.computeBatteryScreenOffRealtime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(600_000, bi.getScreenDozeTime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1100_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1100_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1100_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 1100_000));
+
+ clocks.realtime = clocks.uptime = 1200;
+ // Change state of second display to doze
+ bi.noteScreenStateLocked(1, Display.STATE_DOZE);
+ assertEquals(1150_000, bi.computeBatteryRealtime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(1040_000, bi.computeBatteryScreenOffRealtime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(650_000, bi.getScreenDozeTime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1250_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1250_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1250_000));
+ assertEquals(50_000, bi.getDisplayScreenDozeTime(1, 1250_000));
+
+ clocks.realtime = clocks.uptime = 1310;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(1250_000, bi.computeBatteryRealtime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(1100_000, bi.computeBatteryScreenOffRealtime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(150_000, bi.getScreenOnTime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(710_000, bi.getScreenDozeTime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(150_000, bi.getDisplayScreenOnTime(0, 1350_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1350_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1350_000));
+ assertEquals(150_000, bi.getDisplayScreenDozeTime(1, 1350_000));
+
+ clocks.realtime = clocks.uptime = 1400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(1200_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(810_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 1500_000));
+ assertEquals(700_000, bi.getDisplayScreenDozeTime(0, 1500_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1500_000));
+ assertEquals(300_000, bi.getDisplayScreenDozeTime(1, 1500_000));
+
+ clocks.realtime = clocks.uptime = 2000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(2000_000, bi.computeBatteryRealtime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(1800_000, bi.computeBatteryScreenOffRealtime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getScreenOnTime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(1410_000, bi.getScreenDozeTime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2100_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2100_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 2100_000));
+ assertEquals(900_000, bi.getDisplayScreenDozeTime(1, 2100_000));
+
+
+ clocks.realtime = clocks.uptime = 2200;
+ // Change state of second display to on
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+ assertEquals(2150_000, bi.computeBatteryRealtime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(250_000, bi.getScreenOnTime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2250_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2250_000));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(1, 2250_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2250_000));
+
+ clocks.realtime = clocks.uptime = 2310;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(2250_000, bi.computeBatteryRealtime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(350_000, bi.getScreenOnTime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(240_000, bi.getDisplayScreenOnTime(0, 2350_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2350_000));
+ assertEquals(150_000, bi.getDisplayScreenOnTime(1, 2350_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2350_000));
+
+ clocks.realtime = clocks.uptime = 2400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(2400_000, bi.computeBatteryRealtime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(500_000, bi.getScreenOnTime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.getDisplayScreenOnTime(0, 2500_000));
+ assertEquals(1300_000, bi.getDisplayScreenDozeTime(0, 2500_000));
+ assertEquals(300_000, bi.getDisplayScreenOnTime(1, 2500_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2500_000));
+
+ clocks.realtime = clocks.uptime = 3000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(3000_000, bi.computeBatteryRealtime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1100_000, bi.getScreenOnTime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.getDisplayScreenOnTime(0, 3100_000));
+ assertEquals(1800_000, bi.getDisplayScreenDozeTime(0, 3100_000));
+ assertEquals(900_000, bi.getDisplayScreenOnTime(1, 3100_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 3100_000));
+ }
+
+
+ /**
+ * Test BatteryStatsImpl.noteScreenBrightnessLocked updates timers correctly.
+ */
+ @SmallTest
+ public void testScreenBrightnessLocked_multiDisplay() throws Exception {
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ final int numDisplay = 2;
+ bi.setDisplayCountLocked(numDisplay);
+
+
+ final long[] overallExpected = new long[NUM_SCREEN_BRIGHTNESS_BINS];
+ final long[][] perDisplayExpected = new long[numDisplay][NUM_SCREEN_BRIGHTNESS_BINS];
+ class Bookkeeper {
+ public long currentTimeMs = 100;
+ public int overallActiveBin = -1;
+ public int[] perDisplayActiveBin = new int[numDisplay];
+ }
+ final Bookkeeper bk = new Bookkeeper();
+ Arrays.fill(bk.perDisplayActiveBin, -1);
+
+ IntConsumer incrementTime = inc -> {
+ bk.currentTimeMs += inc;
+ if (bk.overallActiveBin >= 0) {
+ overallExpected[bk.overallActiveBin] += inc;
+ }
+ for (int i = 0; i < numDisplay; i++) {
+ final int bin = bk.perDisplayActiveBin[i];
+ if (bin >= 0) {
+ perDisplayExpected[i][bin] += inc;
+ }
+ }
+ clocks.realtime = clocks.uptime = bk.currentTimeMs;
+ };
+
+ bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+
+ incrementTime.accept(100);
+ bi.noteScreenBrightnessLocked(0, 25);
+ bi.noteScreenBrightnessLocked(1, 25);
+ // floor(25/256*5) = bin 0
+ bk.overallActiveBin = 0;
+ bk.perDisplayActiveBin[0] = 0;
+ bk.perDisplayActiveBin[1] = 0;
+
+ incrementTime.accept(50);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(13);
+ bi.noteScreenBrightnessLocked(0, 100);
+ // floor(25/256*5) = bin 1
+ bk.overallActiveBin = 1;
+ bk.perDisplayActiveBin[0] = 1;
+
+ incrementTime.accept(44);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(22);
+ bi.noteScreenBrightnessLocked(1, 200);
+ // floor(200/256*5) = bin 3
+ bk.overallActiveBin = 3;
+ bk.perDisplayActiveBin[1] = 3;
+
+ incrementTime.accept(33);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(77);
+ bi.noteScreenBrightnessLocked(0, 150);
+ // floor(150/256*5) = bin 2
+ // Overall active bin should not change
+ bk.perDisplayActiveBin[0] = 2;
+
+ incrementTime.accept(88);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(11);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+ // Display 1 should timers should stop incrementing
+ // Overall active bin should fallback to display 0's bin
+ bk.overallActiveBin = 2;
+ bk.perDisplayActiveBin[1] = -1;
+
+ incrementTime.accept(99);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(200);
+ bi.noteScreenBrightnessLocked(0, 255);
+ // floor(150/256*5) = bin 4
+ bk.overallActiveBin = 4;
+ bk.perDisplayActiveBin[0] = 4;
+
+ incrementTime.accept(300);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(200);
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ // No displays are on. No brightness timers should be active.
+ bk.overallActiveBin = -1;
+ bk.perDisplayActiveBin[0] = -1;
+
+ incrementTime.accept(300);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(400);
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+ // Display 1 turned back on.
+ bk.overallActiveBin = 3;
+ bk.perDisplayActiveBin[1] = 3;
+
+ incrementTime.accept(500);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(600);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ // Display 0 turned back on.
+ bk.overallActiveBin = 4;
+ bk.perDisplayActiveBin[0] = 4;
+
+ incrementTime.accept(700);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
}
@SmallTest
@@ -593,7 +996,7 @@
bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
clocks.realtime = 0;
- int screen = Display.STATE_OFF;
+ int[] screen = new int[]{Display.STATE_OFF};
boolean battery = false;
final int uid1 = 10500;
@@ -603,35 +1006,35 @@
long globalDoze = 0;
// Case A: uid1 off, uid2 off, battery off, screen off
- bi.updateTimeBasesLocked(battery, screen, clocks.realtime*1000, 0);
+ bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0);
bi.setOnBatteryInternal(battery);
- bi.updateDisplayMeasuredEnergyStatsLocked(500_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{500_000}, screen, clocks.realtime);
checkMeasuredCharge("A", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case B: uid1 off, uid2 off, battery ON, screen off
clocks.realtime += 17;
battery = true;
- bi.updateTimeBasesLocked(battery, screen, clocks.realtime*1000, 0);
+ bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0);
bi.setOnBatteryInternal(battery);
clocks.realtime += 19;
- bi.updateDisplayMeasuredEnergyStatsLocked(510_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{510_000}, screen, clocks.realtime);
checkMeasuredCharge("B", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case C: uid1 ON, uid2 off, battery on, screen off
clocks.realtime += 18;
setFgState(uid1, true, bi);
clocks.realtime += 18;
- bi.updateDisplayMeasuredEnergyStatsLocked(520_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{520_000}, screen, clocks.realtime);
checkMeasuredCharge("C", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case D: uid1 on, uid2 off, battery on, screen ON
clocks.realtime += 17;
- screen = Display.STATE_ON;
- bi.updateDisplayMeasuredEnergyStatsLocked(521_000, screen, clocks.realtime);
+ screen[0] = Display.STATE_ON;
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{521_000}, screen, clocks.realtime);
blame1 += 0; // Screen had been off during the measurement period
checkMeasuredCharge("D.1", uid1, blame1, uid2, blame2, globalDoze, bi);
clocks.realtime += 101;
- bi.updateDisplayMeasuredEnergyStatsLocked(530_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{530_000}, screen, clocks.realtime);
blame1 += 530_000;
checkMeasuredCharge("D.2", uid1, blame1, uid2, blame2, globalDoze, bi);
@@ -639,33 +1042,33 @@
clocks.realtime += 20;
setFgState(uid2, true, bi);
clocks.realtime += 40;
- bi.updateDisplayMeasuredEnergyStatsLocked(540_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{540_000}, screen, clocks.realtime);
// In the past 60ms, sum of fg is 20+40+40=100ms. uid1 is blamed for 60/100; uid2 for 40/100
blame1 += 540_000 * (20 + 40) / (20 + 40 + 40);
- blame2 += 540_000 * ( 0 + 40) / (20 + 40 + 40);
+ blame2 += 540_000 * (0 + 40) / (20 + 40 + 40);
checkMeasuredCharge("E", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case F: uid1 on, uid2 OFF, battery on, screen on
clocks.realtime += 40;
setFgState(uid2, false, bi);
clocks.realtime += 120;
- bi.updateDisplayMeasuredEnergyStatsLocked(550_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{550_000}, screen, clocks.realtime);
// In the past 160ms, sum f fg is 200ms. uid1 is blamed for 40+120 of it; uid2 for 40 of it.
blame1 += 550_000 * (40 + 120) / (40 + 40 + 120);
- blame2 += 550_000 * (40 + 0 ) / (40 + 40 + 120);
+ blame2 += 550_000 * (40 + 0) / (40 + 40 + 120);
checkMeasuredCharge("F", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case G: uid1 on, uid2 off, battery on, screen DOZE
clocks.realtime += 5;
- screen = Display.STATE_DOZE;
- bi.updateDisplayMeasuredEnergyStatsLocked(570_000, screen, clocks.realtime);
+ screen[0] = Display.STATE_DOZE;
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{570_000}, screen, clocks.realtime);
blame1 += 570_000; // All of this pre-doze time is blamed on uid1.
checkMeasuredCharge("G", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case H: uid1 on, uid2 off, battery on, screen ON
clocks.realtime += 6;
- screen = Display.STATE_ON;
- bi.updateDisplayMeasuredEnergyStatsLocked(580_000, screen, clocks.realtime);
+ screen[0] = Display.STATE_ON;
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{580_000}, screen, clocks.realtime);
blame1 += 0; // The screen had been doze during the energy period
globalDoze += 580_000;
checkMeasuredCharge("H", uid1, blame1, uid2, blame2, globalDoze, bi);
@@ -820,4 +1223,19 @@
assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
}
+
+ private void checkScreenBrightnesses(long[] overallExpected, long[][] perDisplayExpected,
+ BatteryStatsImpl bi, long currentTimeMs) {
+ final int numDisplay = bi.getDisplayCount();
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ for (int display = 0; display < numDisplay; display++) {
+ assertEquals("Failure for display " + display + " screen brightness bin " + bin,
+ perDisplayExpected[display][bin] * 1000,
+ bi.getDisplayScreenBrightnessTime(display, bin, currentTimeMs * 1000));
+ }
+ assertEquals("Failure for overall screen brightness bin " + bin,
+ overallExpected[bin] * 1000,
+ bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 90a9572..92c2d43 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -64,6 +64,8 @@
KernelSingleProcessCpuThreadReaderTest.class,
KernelSingleUidTimeReaderTest.class,
KernelWakelockReaderTest.class,
+ LongArrayMultiStateCounterTest.class,
+ LongMultiStateCounterTest.class,
LongSamplingCounterTest.class,
LongSamplingCounterArrayTest.class,
MobileRadioPowerCalculatorTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 1bb6792..f75a6df 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -118,6 +118,12 @@
return this;
}
+ public BatteryUsageStatsRule setNumDisplays(int value) {
+ when(mPowerProfile.getNumDisplays()).thenReturn(value);
+ mBatteryStats.setDisplayCountLocked(value);
+ return this;
+ }
+
/** Call only after setting the power profile information. */
public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() {
return initMeasuredEnergyStatsLocked(new String[0]);
@@ -191,8 +197,10 @@
final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames();
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ final boolean includeProcessStateData = (query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customPowerComponentNames, includePowerModels);
+ customPowerComponentNames, includePowerModels, includeProcessStateData);
SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
for (int i = 0; i < uidStats.size(); i++) {
builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index b44de3b..4733f86 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -16,9 +16,12 @@
package com.android.internal.os;
+import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
import static android.os.BatteryConsumer.POWER_MODEL_MEASURED_ENERGY;
-import static android.os.BatteryConsumer.POWER_MODEL_POWER_PROFILE;
import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +30,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
import android.os.Parcel;
@@ -68,6 +72,12 @@
}
@Test
+ public void testBuilder_noProcessStateData() {
+ BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(false).build();
+ assertBatteryUsageStats1(batteryUsageStats, false);
+ }
+
+ @Test
public void testParcelability_smallNumberOfUids() {
final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats1(true).build();
final Parcel parcel = Parcel.obtain();
@@ -142,9 +152,14 @@
assertThat(dump).contains("Computed drain: 30000");
assertThat(dump).contains("actual drain: 1000-2000");
assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
+ assertThat(dump).contains("cpu(fg): 2333 apps: 1333 duration: 3s 332ms");
+ assertThat(dump).contains("cpu(bg): 2444 apps: 1444 duration: 4s 442ms");
+ assertThat(dump).contains("cpu(fgs): 2555 apps: 1555 duration: 5s 552ms");
assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
- assertThat(dump).contains("UID 271: 1200 ( screen=300 cpu=400 FOO=500 )");
- assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 FOO=20.0 )");
+ assertThat(dump).contains("UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 ( screen=300 "
+ + "cpu=400 (600ms) cpu:fg=1777 (7s 771ms) cpu:bg=1888 (8s 881ms) "
+ + "cpu:fgs=1999 (9s 991ms) FOO=500 )");
+ assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 (30ms) FOO=20.0 )");
}
@Test
@@ -160,10 +175,10 @@
@Test
public void testAdd() {
final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
- final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[] {"FOO"}).build();
+ final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
final BatteryUsageStats sum =
- new BatteryUsageStats.Builder(new String[] {"FOO"}, true)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
.add(stats1)
.add(stats2)
.build();
@@ -175,12 +190,16 @@
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 2124, null,
- 5321, 7432, 423, POWER_MODEL_POWER_PROFILE, 745, POWER_MODEL_UNDEFINED,
- 956, 1167, 1478);
+ 5321, 7432, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745,
+ POWER_MODEL_UNDEFINED,
+ 956, 1167, 1478,
+ true, 3554, 3776, 3998, 3554, 15542, 3776, 17762, 3998, 19982);
} else if (uidBatteryConsumer.getUid() == APP_UID2) {
assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
- 1111, 2222, 333, POWER_MODEL_POWER_PROFILE, 444, POWER_MODEL_POWER_PROFILE,
- 555, 666, 777);
+ 1111, 2222, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+ 555, 666, 777,
+ true, 1777, 1888, 1999, 1777, 7771, 1888, 8881, 1999, 9991);
} else {
fail("Unexpected UID " + uidBatteryConsumer.getUid());
}
@@ -198,8 +217,17 @@
@Test
public void testAdd_customComponentMismatch() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[] {"FOO"}, true);
- final BatteryUsageStats stats = buildBatteryUsageStats2(new String[] {"BAR"}).build();
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+ final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
+
+ assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+ }
+
+ @Test
+ public void testAdd_processStateDataMismatch() {
+ final BatteryUsageStats.Builder builder =
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+ final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
}
@@ -227,7 +255,7 @@
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[] {"FOO"}, true)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
@@ -236,16 +264,19 @@
addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo",
1000, 2000,
- 300, POWER_MODEL_POWER_PROFILE, 400, POWER_MODEL_POWER_PROFILE, 500, 600, 800);
+ 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800,
+ 1777, 7771, 1888, 8881, 1999, 9991);
addAggregateBatteryConsumer(builder,
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, 0,
- 10100, 10200, 10300, 10400);
+ 10100, 10200, 10300, 10400,
+ 1333, 3331, 1444, 4441, 1555, 5551);
addAggregateBatteryConsumer(builder,
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, 30000,
- 20100, 20200, 20300, 20400);
-
+ 20100, 20200, 20300, 20400,
+ 2333, 3332, 2444, 4442, 2555, 5552);
if (includeUserBatteryConsumer) {
builder.getOrCreateUserBatteryConsumerBuilder(USER_ID)
@@ -261,43 +292,55 @@
return builder;
}
- private BatteryUsageStats.Builder buildBatteryUsageStats2(String[] customPowerComponentNames) {
+ private BatteryUsageStats.Builder buildBatteryUsageStats2(String[] customPowerComponentNames,
+ boolean includeProcessStateData) {
final MockClock clocks = new MockClock();
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(customPowerComponentNames, true)
- .setDischargePercentage(30)
- .setDischargedPowerRange(1234, 2345)
- .setStatsStartTimestamp(2000)
- .setStatsEndTimestamp(5000);
+ new BatteryUsageStats.Builder(customPowerComponentNames, true,
+ includeProcessStateData);
+ builder.setDischargePercentage(30)
+ .setDischargedPowerRange(1234, 2345)
+ .setStatsStartTimestamp(2000)
+ .setStatsEndTimestamp(5000);
addUidBatteryConsumer(builder, batteryStats, APP_UID1, null,
4321, 5432,
- 123, POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_MEASURED_ENERGY, 456, 567, 678);
+ 123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_MEASURED_ENERGY,
+ 456, 567, 678,
+ 1777, 7771, 1888, 8881, 1999, 9991);
addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar",
1111, 2222,
- 333, POWER_MODEL_POWER_PROFILE, 444, POWER_MODEL_POWER_PROFILE, 555, 666, 777);
+ 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777,
+ 1777, 7771, 1888, 8881, 1999, 9991);
addAggregateBatteryConsumer(builder,
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, 0,
- 10123, 10234, 10345, 10456);
+ 10123, 10234, 10345, 10456,
+ 4333, 3334, 5444, 4445, 6555, 5556);
addAggregateBatteryConsumer(builder,
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, 12345,
- 20111, 20222, 20333, 20444);
+ 20111, 20222, 20333, 20444,
+ 7333, 3337, 8444, 4448, 9555, 5559);
return builder;
}
private void addUidBatteryConsumer(BatteryUsageStats.Builder builder,
MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain,
- int timeInStateForeground, int timeInStateBackground, int screenPower,
- int screenPowerModel, int cpuPower, int cpuPowerModel, int customComponentPower,
- int cpuDuration, int customComponentDuration) {
+ int timeInStateForeground, int timeInStateBackground, double screenPower,
+ int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower,
+ int cpuDuration, int customComponentDuration, double cpuPowerForeground,
+ int cpuDurationForeground, double cpuPowerBackground, int cpuDurationBackground,
+ double cpuPowerFgs, int cpuDurationFgs) {
final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(uid);
- builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid)
+ final UidBatteryConsumer.Builder uidBuilder =
+ builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid);
+ uidBuilder
.setPackageWithHighestDrain(packageWithHighestDrain)
.setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, timeInStateForeground)
.setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, timeInStateBackground)
@@ -311,21 +354,68 @@
BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
.setUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
+ if (builder.isProcessStateDataNeeded()) {
+ final BatteryConsumer.Key cpuFgKey = uidBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ final BatteryConsumer.Key cpuBgKey = uidBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
+ final BatteryConsumer.Key cpuFgsKey = uidBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+ uidBuilder
+ .setConsumedPower(cpuFgKey, cpuPowerForeground,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .setUsageDurationMillis(cpuFgKey, cpuDurationForeground)
+ .setConsumedPower(cpuBgKey, cpuPowerBackground,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .setUsageDurationMillis(cpuBgKey, cpuDurationBackground)
+ .setConsumedPower(cpuFgsKey, cpuPowerFgs,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs);
+ }
}
private void addAggregateBatteryConsumer(BatteryUsageStats.Builder builder, int scope,
double consumedPower, int cpuPower, int customComponentPower, int cpuDuration,
- int customComponentDuration) {
- builder.getAggregateBatteryConsumerBuilder(scope)
- .setConsumedPower(consumedPower)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
- .setConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower)
- .setUsageDurationMillis(
- BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
- .setUsageDurationForCustomComponentMillis(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
+ int customComponentDuration, double cpuPowerForeground, long cpuDurationForeground,
+ double cpuPowerBackground, long cpuDurationBackground, double cpuPowerFgs,
+ long cpuDurationFgs) {
+ final AggregateBatteryConsumer.Builder aggBuilder =
+ builder.getAggregateBatteryConsumerBuilder(scope)
+ .setConsumedPower(consumedPower)
+ .setConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
+ .setConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ customComponentPower)
+ .setUsageDurationMillis(
+ BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
+ .setUsageDurationForCustomComponentMillis(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ customComponentDuration);
+ if (builder.isProcessStateDataNeeded()) {
+ final BatteryConsumer.Key cpuFgKey = aggBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ final BatteryConsumer.Key cpuBgKey = aggBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
+ final BatteryConsumer.Key cpuFgsKey = aggBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+ aggBuilder
+ .setConsumedPower(cpuFgKey, cpuPowerForeground,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .setUsageDurationMillis(cpuFgKey, cpuDurationForeground)
+ .setConsumedPower(cpuBgKey, cpuPowerBackground,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .setUsageDurationMillis(cpuBgKey, cpuDurationBackground)
+ .setConsumedPower(cpuFgsKey, cpuPowerFgs,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs);
+ }
}
public void assertBatteryUsageStats1(BatteryUsageStats batteryUsageStats,
@@ -338,8 +428,10 @@
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
- 1000, 2000, 300, POWER_MODEL_POWER_PROFILE, 400, POWER_MODEL_POWER_PROFILE,
- 500, 600, 800);
+ 1000, 2000, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+ 500, 600, 800,
+ true, 1777, 1888, 1999, 1777, 7771, 1888, 8881, 1999, 9991);
} else {
fail("Unexpected UID " + uidBatteryConsumer.getUid());
}
@@ -384,10 +476,13 @@
}
private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer,
- int consumedPower, String packageWithHighestDrain, int timeInStateForeground,
- int timeInStateBackground, int screenPower, int screenPowerModel, int cpuPower,
- int cpuPowerModel, int customComponentPower, int cpuDuration,
- int customComponentDuration) {
+ double consumedPower, String packageWithHighestDrain, int timeInStateForeground,
+ int timeInStateBackground, int screenPower, int screenPowerModel, double cpuPower,
+ int cpuPowerModel, double customComponentPower, int cpuDuration,
+ int customComponentDuration, boolean processStateDataIncluded,
+ double totalPowerForeground, double totalPowerBackground, double totalPowerFgs,
+ double cpuPowerForeground, int cpuDurationForeground, double cpuPowerBackground,
+ int cpuDurationBackground, double cpuPowerFgs, int cpuDurationFgs) {
assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(consumedPower);
assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo(
packageWithHighestDrain);
@@ -413,6 +508,58 @@
assertThat(uidBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
assertThat(uidBatteryConsumer.getCustomPowerComponentName(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
+
+ if (processStateDataIncluded) {
+ assertThat(uidBatteryConsumer.getConsumedPower(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY,
+ PROCESS_STATE_FOREGROUND)))
+ .isEqualTo(totalPowerForeground);
+ assertThat(uidBatteryConsumer.getConsumedPower(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY,
+ PROCESS_STATE_BACKGROUND)))
+ .isEqualTo(totalPowerBackground);
+ assertThat(uidBatteryConsumer.getConsumedPower(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY,
+ PROCESS_STATE_FOREGROUND_SERVICE)))
+ .isEqualTo(totalPowerFgs);
+ }
+
+ final BatteryConsumer.Key cpuFgKey = uidBatteryConsumer.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ if (processStateDataIncluded) {
+ assertThat(cpuFgKey).isNotNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuFgKey))
+ .isEqualTo(cpuPowerForeground);
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgKey))
+ .isEqualTo(cpuDurationForeground);
+ } else {
+ assertThat(cpuFgKey).isNull();
+ }
+
+ final BatteryConsumer.Key cpuBgKey = uidBatteryConsumer.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_BACKGROUND);
+ if (processStateDataIncluded) {
+ assertThat(cpuBgKey).isNotNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuBgKey))
+ .isEqualTo(cpuPowerBackground);
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBgKey))
+ .isEqualTo(cpuDurationBackground);
+ } else {
+ assertThat(cpuBgKey).isNull();
+ }
+
+ final BatteryConsumer.Key cpuFgsKey = uidBatteryConsumer.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+ if (processStateDataIncluded) {
+ assertThat(cpuFgsKey).isNotNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuFgsKey))
+ .isEqualTo(cpuPowerFgs);
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgsKey))
+ .isEqualTo(cpuDurationFgs);
+ } else {
+ assertThat(cpuFgsKey).isNotNull();
+ }
}
private void assertUserBatteryConsumer(UserBatteryConsumer userBatteryConsumer,
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
new file mode 100644
index 0000000..8540adb
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LongMultiStateCounterTest {
+
+ @Test
+ public void setStateAndUpdateValue() {
+ LongMultiStateCounter counter = new LongMultiStateCounter(2);
+
+ counter.updateValue(0, 1000);
+ counter.setState(0, 1000);
+ counter.setState(1, 2000);
+ counter.setState(0, 4000);
+ counter.updateValue(100, 9000);
+
+ assertThat(counter.getCount(0)).isEqualTo(75);
+ assertThat(counter.getCount(1)).isEqualTo(25);
+
+ assertThat(counter.toString()).isEqualTo("[0: 75, 1: 25] updated: 9000 currentState: 0");
+ }
+
+ @Test
+ public void setEnabled() {
+ LongMultiStateCounter counter = new LongMultiStateCounter(2);
+ counter.setState(0, 1000);
+ counter.updateValue(0, 1000);
+ counter.updateValue(100, 2000);
+
+ assertThat(counter.getCount(0)).isEqualTo(100);
+
+ counter.setEnabled(false, 3000);
+
+ // Partially included, because the counter is disabled after the previous update
+ counter.updateValue(200, 4000);
+
+ // Count only 50%, because the counter was disabled for 50% of the time
+ assertThat(counter.getCount(0)).isEqualTo(150);
+
+ // Not counted because the counter is disabled
+ counter.updateValue(250, 5000);
+
+ counter.setEnabled(true, 6000);
+
+ counter.updateValue(300, 7000);
+
+ // Again, take 50% of the delta
+ assertThat(counter.getCount(0)).isEqualTo(175);
+ }
+
+ @Test
+ public void reset() {
+ LongMultiStateCounter counter = new LongMultiStateCounter(2);
+ counter.setState(0, 1000);
+ counter.updateValue(0, 1000);
+ counter.updateValue(100, 2000);
+
+ assertThat(counter.getCount(0)).isEqualTo(100);
+
+ counter.reset();
+
+ assertThat(counter.getCount(0)).isEqualTo(0);
+
+ counter.updateValue(200, 3000);
+ counter.updateValue(300, 4000);
+
+ assertThat(counter.getCount(0)).isEqualTo(100);
+ }
+
+ @Test
+ public void parceling() {
+ LongMultiStateCounter counter = new LongMultiStateCounter(2);
+ counter.updateValue(0, 1000);
+ counter.setState(0, 1000);
+ counter.updateValue(100, 2000);
+ counter.setState(1, 2000);
+ counter.updateValue(101, 3000);
+
+ assertThat(counter.getCount(0)).isEqualTo(100);
+ assertThat(counter.getCount(1)).isEqualTo(1);
+
+ Parcel parcel = Parcel.obtain();
+ counter.writeToParcel(parcel, 0);
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+
+ parcel = Parcel.obtain();
+ parcel.unmarshall(bytes, 0, bytes.length);
+ parcel.setDataPosition(0);
+
+ LongMultiStateCounter newCounter = LongMultiStateCounter.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(newCounter.getCount(0)).isEqualTo(100);
+ assertThat(newCounter.getCount(1)).isEqualTo(1);
+
+ // ==== Verify that the counter keeps accumulating after unparceling.
+
+ // State, last update timestamp and current counts are undefined at this point.
+ newCounter.setState(0, 100);
+ newCounter.updateValue(300, 100);
+
+ // A new base state and counters are established; we can continue accumulating deltas
+ newCounter.updateValue(316, 200);
+
+ assertThat(newCounter.getCount(0)).isEqualTo(116);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index b31587b..c24dc67 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -39,6 +39,7 @@
public class MockBatteryStatsImpl extends BatteryStatsImpl {
public boolean mForceOnBattery;
private NetworkStats mNetworkStats;
+ private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync();
MockBatteryStatsImpl() {
this(new MockClock());
@@ -52,7 +53,7 @@
super(clock, historyDirectory);
initTimersAndCounters();
- setExternalStatsSyncLocked(new DummyExternalStatsSync());
+ setExternalStatsSyncLocked(mExternalStatsSync);
informThatAllExternalStatsAreFlushed();
// A no-op handler.
@@ -185,7 +186,15 @@
return mPendingUids;
}
+ public int getAndClearExternalStatsSyncFlags() {
+ final int flags = mExternalStatsSync.flags;
+ mExternalStatsSync.flags = 0;
+ return flags;
+ }
+
private class DummyExternalStatsSync implements ExternalStatsSync {
+ public int flags = 0;
+
@Override
public Future<?> scheduleSync(String reason, int flags) {
return null;
@@ -219,8 +228,9 @@
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(
- int flag, boolean onBattery, boolean onBatteryScreenOff, int screenState) {
+ public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
+ flags |= flag;
return null;
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 50e0a15..eee5d57 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -42,24 +42,27 @@
private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43;
private static final long MINUTE_IN_MS = 60 * 1000;
private static final long MINUTE_IN_US = 60 * 1000 * 1000;
+ private static final long HOUR_IN_MS = 60 * MINUTE_IN_MS;
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0, 36.0)
- .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 48.0);
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 48.0)
+ .setNumDisplays(1);
@Test
public void testMeasuredEnergyBasedModel() {
mStatsRule.initMeasuredEnergyStatsLocked();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(0, Display.STATE_ON, 0);
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{0},
+ new int[]{Display.STATE_ON}, 0);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
0, 0);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_ON,
- 15 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000},
+ new int[]{Display.STATE_ON}, 15 * MINUTE_IN_MS);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
@@ -67,16 +70,16 @@
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON,
- 60 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000},
+ new int[]{Display.STATE_ON}, 60 * MINUTE_IN_MS);
- batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_DOZE,
- 120 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000},
+ new int[]{Display.STATE_DOZE}, 120 * MINUTE_IN_MS);
mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
@@ -129,24 +132,122 @@
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
+
@Test
- public void testPowerProfileBasedModel() {
+ public void testMeasuredEnergyBasedModel_multiDisplay() {
+ mStatsRule.initMeasuredEnergyStatsLocked()
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 1, 100.0)
+ .setNumDisplays(2);
+
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
- batteryStats.noteScreenBrightnessLocked(255, 0, 0);
- setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
- 0, 0);
+ final int[] screenStates = new int[]{Display.STATE_ON, Display.STATE_OFF};
- batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
- batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
+ batteryStats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0);
+
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
- batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ screenStates[0] = Display.STATE_OFF;
+ screenStates[1] = Display.STATE_ON;
+ batteryStats.noteScreenStateLocked(0, screenStates[0],
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(1, screenStates[1], 80 * MINUTE_IN_MS,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{600_000_000, 500},
+ screenStates, 80 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenBrightnessLocked(1, 25, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS);
+
+ screenStates[1] = Display.STATE_OFF;
+ batteryStats.noteScreenStateLocked(1, screenStates[1], 110 * MINUTE_IN_MS,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{700, 800_000_000},
+ screenStates, 110 * MINUTE_IN_MS);
+
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+
+ mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+
+ ScreenPowerCalculator calculator =
+ new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ // (600000000 + 800000000) uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(388.88888);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(20 * MINUTE_IN_MS);
+
+ // Uid1 ran for 20 out of 80 min during the first Display update.
+ // It also ran for 5 out of 45 min during the second Display update:
+ // Uid1 charge = 20 / 80 * 600000000 mAs = 41.66666 mAh
+ assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(41.66666);
+ assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(90 * MINUTE_IN_MS);
+
+ // Uid2 ran for 60 out of 80 min during the first Display update.
+ // It also ran for all of the second Display update:
+ // Uid1 charge = 60 / 80 * 600000000 + 800000000 mAs = 347.22222 mAh
+ assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(347.22222);
+ assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(388.88888);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ }
+
+ @Test
+ public void testPowerProfileBasedModel() {
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
+ 0, 0);
+
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
@@ -197,6 +298,95 @@
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
+
+ @Test
+ public void testPowerProfileBasedModel_multiDisplay() {
+ mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 1, 100.0)
+ .setNumDisplays(2);
+
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
+ 0, 0);
+
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(1, Display.STATE_ON, 80 * MINUTE_IN_MS,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 20, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 110 * MINUTE_IN_MS,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+
+ mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+ ScreenPowerCalculator calculator =
+ new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ // First display consumed 92 mAh.
+ // Second display ran for 0.5 hours at a base drain rate of 60 mA.
+ // 6 minutes (0.1 hours) spent in the first brightness level which drains an extra 10 mA.
+ // 12 minutes (0.2 hours) spent in the fifth brightness level which drains an extra 90 mA.
+ // 12 minutes (0.2 hours) spent in the second brightness level which drains an extra 30 mA.
+ // 92 + 60 * 0.5 + 10 * 0.1 + 90 * 0.2 + 30 * 0.2 = 147
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(147);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(20 * MINUTE_IN_MS);
+
+ // Uid1 took 20 out of the total of 110 min of foreground activity
+ // Uid1 charge = 20 / 110 * 147.0 = 23.0 mAh
+ assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(26.72727);
+ assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(90 * MINUTE_IN_MS);
+
+ // Uid2 took 90 out of the total of 110 min of foreground activity
+ // Uid2 charge = 90 / 110 * 92.0 = 69.0 mAh
+ assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(120.272727);
+ assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(147);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ }
+
private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
long uptimeMs) {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
diff --git a/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java
index 42b2b16..c76e24c 100644
--- a/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
@@ -38,7 +39,6 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
-import androidx.test.annotation.UiThreadTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;
@@ -47,6 +47,10 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* This test contains a set of operations designed to verify the behavior of a
* ScrollCaptureViewHelper implementation. Subclasses define and initialize
@@ -88,6 +92,7 @@
private T mTarget;
private Rect mScrollBounds;
private H mHelper;
+ private CancellationSignal mCancellationSignal;
private Instrumentation mInstrumentation;
@@ -96,6 +101,7 @@
mInstrumentation = InstrumentationRegistry.getInstrumentation();
Context context = mInstrumentation.getTargetContext();
mWm = context.getSystemService(WindowManager.class);
+ mCancellationSignal = new CancellationSignal();
// Instantiate parent view on the main thread
mInstrumentation.runOnMainSync(() -> mContentRoot = new FrameLayout(context));
@@ -157,7 +163,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_up_fromTop() {
initHelper(ScrollPosition.TOP);
@@ -168,7 +173,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_down_fromTop() {
initHelper(ScrollPosition.TOP);
Rect request = new Rect(0, WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT + CAPTURE_HEIGHT);
@@ -182,7 +186,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_up_fromMiddle() {
initHelper(ScrollPosition.MIDDLE);
@@ -197,7 +200,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_down_fromMiddle() {
initHelper(ScrollPosition.MIDDLE);
@@ -212,7 +214,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_up_fromBottom() {
initHelper(ScrollPosition.BOTTOM);
@@ -227,7 +228,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_down_fromBottom() {
initHelper(ScrollPosition.BOTTOM);
@@ -242,7 +242,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_offTopEdge() {
initHelper(ScrollPosition.TOP);
@@ -262,7 +261,6 @@
}
@Test
- @UiThreadTest
public void onScrollRequested_offBottomEdge() {
initHelper(ScrollPosition.BOTTOM);
@@ -279,7 +277,7 @@
}
@After
- public final void removeWindow() throws InterruptedException {
+ public final void removeWindow() {
mInstrumentation.runOnMainSync(() -> {
if (mContentRoot != null && mContentRoot.isAttachedToWindow()) {
mWm.removeViewImmediate(mContentRoot);
@@ -288,17 +286,36 @@
}
private void initHelper(ScrollPosition position) {
- setInitialScrollPosition(mTarget, position);
mHelper = createHelper();
- mScrollBounds = mHelper.onComputeScrollBounds(mTarget);
- mHelper.onPrepareForStart(mTarget, mScrollBounds);
+ mInstrumentation.runOnMainSync(() -> {
+ setInitialScrollPosition(mTarget, position);
+ mScrollBounds = mHelper.onComputeScrollBounds(mTarget);
+ mHelper.onPrepareForStart(mTarget, mScrollBounds);
+ });
}
@NonNull
- private ScrollResult requestScrollSync(H helper, Rect scrollBounds, Rect request) {
- helper.onPrepareForStart(mTarget, scrollBounds);
- ScrollResult result = helper.onScrollRequested(mTarget, scrollBounds, request);
-
+ private ScrollResult requestScrollSync(H helper, Rect scrollBounds, Rect request) {
+ AtomicReference<ScrollResult> resultRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ mInstrumentation.runOnMainSync(() -> {
+ helper.onPrepareForStart(mTarget, scrollBounds);
+ helper.onScrollRequested(mTarget, scrollBounds, request, mCancellationSignal,
+ (result) -> {
+ resultRef.set(result);
+ latch.countDown();
+ });
+ });
+ try {
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ mCancellationSignal.cancel();
+ fail("Timeout waiting for ScrollResult");
+ }
+ } catch (InterruptedException e) {
+ mCancellationSignal.cancel();
+ fail("Interrupted!");
+ }
+ ScrollResult result = resultRef.get();
assertNotNull(result);
return result;
}
diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java
index 699008b..8409958 100644
--- a/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java
+++ b/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.view.View;
import android.view.ViewGroup;
@@ -28,6 +29,8 @@
import org.junit.Test;
+import java.util.function.Consumer;
+
public class ScrollCaptureViewSupportTest {
ScrollCaptureViewHelper<View> mViewHelper = new ScrollCaptureViewHelper<View>() {
@@ -42,9 +45,10 @@
@NonNull
@Override
- public ScrollResult onScrollRequested(@NonNull View view, @NonNull Rect scrollBounds,
- @NonNull Rect requestRect) {
- return new ScrollResult();
+ public void onScrollRequested(@NonNull View view, @NonNull Rect scrollBounds,
+ @NonNull Rect requestRect, CancellationSignal signal,
+ Consumer<ScrollResult> resultConsumer) {
+ resultConsumer.accept(new ScrollResult());
}
@Override
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index 08761f6..6340980 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -27,7 +27,6 @@
libs: ["tradefed"],
test_suites: [
"device-tests",
- "general-tests",
],
target_required: [
"OverlayHostTests_NonPlatformSignatureOverlay",
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
index 15fb76d..b453cde9 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -23,7 +23,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
include $(BUILD_PACKAGE)
@@ -34,7 +34,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_CERTIFICATE := platform
LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
@@ -47,7 +47,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_CERTIFICATE := platform
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
index 3921251..77fc887 100644
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
@@ -22,7 +22,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
LOCAL_USE_AAPT2 := true
LOCAL_AAPT_FLAGS := --no-resource-removal
@@ -37,7 +37,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_CERTIFICATE := platform
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
@@ -52,7 +52,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_CERTIFICATE := platform
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
@@ -69,7 +69,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
@@ -83,7 +83,7 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4c930d1..25fb223 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -501,6 +501,8 @@
<permission name="android.permission.UPDATE_DEVICE_STATS" />
<!-- Permission required for GTS test - PendingSystemUpdateTest -->
<permission name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE" />
+ <!-- Permission required for GTS test - GtsAssistIntentTestCases -->
+ <permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 909ca39..6e92755 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -595,6 +595,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1478175541": {
+ "message": "No longer animating wallpaper targets!",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-1474602871": {
"message": "Launch on display check: disallow launch on virtual display for not-embedded activity.",
"level": "DEBUG",
@@ -1135,6 +1141,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-863438038": {
+ "message": "Aborting Transition: %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-861859917": {
"message": "Attempted to add window to a display that does not exist: %d. Aborting.",
"level": "WARN",
@@ -1621,6 +1633,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-360208282": {
+ "message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-354571697": {
"message": "Existence Changed in transition %d: %s",
"level": "VERBOSE",
@@ -1675,6 +1693,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-304728471": {
+ "message": "New wallpaper: target=%s prev=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-302468788": {
"message": "Expected target rootTask=%s to be top most but found rootTask=%s",
"level": "WARN",
@@ -1693,6 +1717,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-275077723": {
+ "message": "New animation: %s old animation: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-262984451": {
"message": "Relaunch failed %s",
"level": "INFO",
@@ -1747,6 +1777,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-182877285": {
+ "message": "Wallpaper layer changed: assigning layers + relayout",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -2005,6 +2041,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "114070759": {
+ "message": "New wallpaper target: %s prevTarget: %s caller=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"115358443": {
"message": "Focus changing: %s -> %s",
"level": "INFO",
@@ -2347,6 +2389,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "422634333": {
+ "message": "First draw done in potential wallpaper target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"424524729": {
"message": "Attempted to add wallpaper window with unknown token %s. Aborting.",
"level": "WARN",
@@ -2371,12 +2419,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "457951957": {
- "message": "\tNot visible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"463993897": {
"message": "Aborted waiting for drawn: %s",
"level": "WARN",
@@ -2425,6 +2467,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowContainerThumbnail.java"
},
+ "535103992": {
+ "message": "Wallpaper may change! Adjusting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"539077569": {
"message": "Clear freezing of %s force=%b",
"level": "VERBOSE",
@@ -2653,6 +2701,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "733466617": {
+ "message": "Wallpaper token %s visible=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+ },
"736692676": {
"message": "Config is relaunching %s",
"level": "VERBOSE",
@@ -2989,6 +3043,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1178653181": {
+ "message": "Old wallpaper still the target.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1186730970": {
"message": " no common mode yet, so set it",
"level": "VERBOSE",
@@ -3667,6 +3727,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "1984843251": {
+ "message": "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1995093920": {
"message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
"level": "VERBOSE",
@@ -3697,6 +3763,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "2024493888": {
+ "message": "\tWallpaper of display=%s is not visible",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+ "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+ },
"2028163120": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s isEntrance=%s Callers=%s",
"level": "VERBOSE",
@@ -3721,12 +3793,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "2057434754": {
- "message": "\tvisible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"2060978050": {
"message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
"level": "WARN",
@@ -3867,6 +3933,9 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WALLPAPER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WINDOW_INSETS": {
"tag": "WindowManager"
},
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index e369acc..9af508a 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -36,22 +36,9 @@
int format, long transactionPtr);
private static native void nativeMergeWithNextTransaction(long ptr, long transactionPtr,
long frameNumber);
- private static native void nativeSetTransactionCompleteCallback(long ptr, long frameNumber,
- TransactionCompleteCallback callback);
private static native long nativeGetLastAcquiredFrameNum(long ptr);
private static native void nativeApplyPendingTransactions(long ptr, long frameNumber);
- /**
- * Callback sent to {@link #setTransactionCompleteCallback(long, TransactionCompleteCallback)}
- */
- public interface TransactionCompleteCallback {
- /**
- * Invoked when the transaction has completed.
- * @param frameNumber The frame number of the buffer that was in that transaction
- */
- void onTransactionComplete(long frameNumber);
- }
-
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@PixelFormat.Format int format) {
@@ -104,16 +91,6 @@
nativeUpdate(mNativeObject, sc.mNativeObject, width, height, format, 0);
}
- /**
- * Set a callback when the transaction with the frame number has been completed.
- * @param frameNumber The frame number to get the transaction complete callback for
- * @param completeCallback The callback that should be invoked.
- */
- public void setTransactionCompleteCallback(long frameNumber,
- @Nullable TransactionCompleteCallback completeCallback) {
- nativeSetTransactionCompleteCallback(mNativeObject, frameNumber, completeCallback);
- }
-
@Override
protected void finalize() throws Throwable {
try {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index e6f8388..62959b7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -28,7 +28,7 @@
* an OEM by overriding this method.
*/
public static SidecarInterface getSidecarImpl(Context context) {
- return new SampleSidecarImpl(context);
+ return new SampleSidecarImpl(context.getApplicationContext());
}
/**
@@ -36,6 +36,6 @@
* @return API version string in MAJOR.MINOR.PATCH-description format.
*/
public static String getApiVersion() {
- return "0.1.0-settings_sample";
+ return "1.0.0-reference";
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
new file mode 100644
index 0000000..14ba9df
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/** Display area organizer for the root display areas */
+public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
+
+ private static final String TAG = RootDisplayAreaOrganizer.class.getSimpleName();
+
+ /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
+ private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
+ /** Display area leashes, which is mapped by display IDs. */
+ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
+
+ public RootDisplayAreaOrganizer(Executor executor) {
+ super(executor);
+ List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
+ for (int i = infos.size() - 1; i >= 0; --i) {
+ onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
+ }
+ }
+
+ public void attachToDisplayArea(int displayId, SurfaceControl.Builder b) {
+ final SurfaceControl sc = mLeashes.get(displayId);
+ if (sc != null) {
+ b.setParent(sc);
+ }
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
+ @NonNull SurfaceControl leash) {
+ if (displayAreaInfo.featureId != FEATURE_ROOT) {
+ throw new IllegalArgumentException(
+ "Unknown feature: " + displayAreaInfo.featureId
+ + "displayAreaInfo:" + displayAreaInfo);
+ }
+
+ final int displayId = displayAreaInfo.displayId;
+ if (mDisplayAreasInfo.get(displayId) != null) {
+ throw new IllegalArgumentException(
+ "Duplicate DA for displayId: " + displayId
+ + " displayAreaInfo:" + displayAreaInfo
+ + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
+ }
+
+ mDisplayAreasInfo.put(displayId, displayAreaInfo);
+ mLeashes.put(displayId, leash);
+ }
+
+ @Override
+ public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ if (mDisplayAreasInfo.get(displayId) == null) {
+ throw new IllegalArgumentException(
+ "onDisplayAreaVanished() Unknown DA displayId: " + displayId
+ + " displayAreaInfo:" + displayAreaInfo
+ + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
+ }
+
+ mDisplayAreasInfo.remove(displayId);
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ if (mDisplayAreasInfo.get(displayId) == null) {
+ throw new IllegalArgumentException(
+ "onDisplayAreaInfoChanged() Unknown DA displayId: " + displayId
+ + " displayAreaInfo:" + displayAreaInfo
+ + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
+ }
+
+ mDisplayAreasInfo.put(displayId, displayAreaInfo);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+
+ @Override
+ public String toString() {
+ return TAG + "#" + mDisplayAreasInfo.size();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index 10d7725..6a252e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -232,7 +232,7 @@
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
- onLayoutChanged(mSplitLayout);
+ onLayoutSizeChanged(mSplitLayout);
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
@@ -313,13 +313,19 @@
}
@Override
- public void onLayoutChanging(SplitLayout layout) {
+ public void onLayoutPositionChanging(SplitLayout layout) {
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
@Override
- public void onLayoutChanged(SplitLayout layout) {
+ public void onLayoutSizeChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t ->
+ layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
+ }
+
+ @Override
+ public void onLayoutSizeChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
mSyncQueue.queue(wct);
@@ -328,9 +334,9 @@
}
@Override
- public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+ public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyLayoutShifted(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
+ layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
mController.getTaskOrganizer().applyTransaction(wct);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 05ebbba..8d43f13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -121,7 +121,7 @@
@Nullable
private Icon mIcon;
private boolean mIsBubble;
- private boolean mIsVisuallyInterruptive;
+ private boolean mIsTextChanged;
private boolean mIsClearable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
@@ -342,12 +342,12 @@
}
/**
- * Sets whether this bubble is considered visually interruptive. This method is purely for
+ * Sets whether this bubble is considered text changed. This method is purely for
* testing.
*/
@VisibleForTesting
- void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
- mIsVisuallyInterruptive = visuallyInterruptive;
+ void setTextChangedForTest(boolean textChanged) {
+ mIsTextChanged = textChanged;
}
/**
@@ -454,7 +454,7 @@
mFlyoutMessage = extractFlyoutMessage(entry);
if (entry.getRanking() != null) {
mShortcutInfo = entry.getRanking().getConversationShortcutInfo();
- mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
+ mIsTextChanged = entry.getRanking().isTextChanged();
if (entry.getRanking().getChannel() != null) {
mIsImportantConversation =
entry.getRanking().getChannel().isImportantConversation();
@@ -495,8 +495,8 @@
return mIcon;
}
- boolean isVisuallyInterruptive() {
- return mIsVisuallyInterruptive;
+ boolean isTextChanged() {
+ return mIsTextChanged;
}
/**
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 c126f32..b6d65be 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
@@ -939,7 +939,7 @@
public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
// If this is an interruptive notif, mark that it's interrupted
mSysuiProxy.setNotificationInterruption(notif.getKey());
- if (!notif.getRanking().visuallyInterruptive()
+ if (!notif.getRanking().isTextChanged()
&& (notif.getBubbleMetadata() != null
&& !notif.getBubbleMetadata().getAutoExpandBubble())
&& mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index bef26bf..519a856 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -323,7 +323,7 @@
}
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
- suppressFlyout |= !bubble.isVisuallyInterruptive();
+ suppressFlyout |= !bubble.isTextChanged();
if (prevBubble == null) {
// Create a new bubble
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 d590ab1..300319a 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
@@ -120,8 +120,6 @@
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
- private static final int MANAGE_MENU_SCRIM_ANIM_DURATION = 150;
-
private static final float SCRIM_ALPHA = 0.6f;
/**
@@ -894,6 +892,7 @@
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
afterExpandedViewAnimation();
+ showManageMenu(mShowingManage);
} /* after */);
final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
getBubbleIndex(mExpandedBubble));
@@ -1253,9 +1252,6 @@
mRelativeStackPositionBeforeRotation = new RelativeStackPosition(
mPositioner.getRestingPosition(),
mStackAnimationController.getAllowableStackPositionRegion());
- mManageMenu.setVisibility(View.INVISIBLE);
- mShowingManage = false;
-
addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
}
@@ -2555,16 +2551,19 @@
invalidate();
}
- private void showManageMenu(boolean show) {
+ /** Hide or show the manage menu for the currently expanded bubble. */
+ @VisibleForTesting
+ public void showManageMenu(boolean show) {
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
// bubble. If we end up in this state, just hide the menu immediately.
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
+ mManageMenuScrim.setVisibility(INVISIBLE);
+ mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
return;
}
-
if (show) {
mManageMenuScrim.setVisibility(VISIBLE);
mManageMenuScrim.setTranslationZ(mManageMenu.getElevation() - 1f);
@@ -2576,8 +2575,8 @@
}
};
+ mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
- .setDuration(MANAGE_MENU_SCRIM_ANIM_DURATION)
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
.alpha(show ? SCRIM_ALPHA : 0f)
.withEndAction(endAction)
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 9b7eb2f..c82249b 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
@@ -284,6 +284,8 @@
void onStackExpandChanged(boolean shouldExpand);
+ void onManageMenuExpandChanged(boolean menuExpanded);
+
void onUnbubbleConversation(String key);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 596a2f4..5b3ce2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -291,13 +291,13 @@
void updateDivideBounds(int position) {
updateBounds(position);
mSplitWindowManager.setResizingSplits(true);
- mSplitLayoutHandler.onLayoutChanging(this);
+ mSplitLayoutHandler.onLayoutSizeChanging(this);
}
void setDividePosition(int position) {
mDividePosition = position;
updateBounds(mDividePosition);
- mSplitLayoutHandler.onLayoutChanged(this);
+ mSplitLayoutHandler.onLayoutSizeChanged(this);
mSplitWindowManager.setResizingSplits(false);
}
@@ -451,7 +451,7 @@
* Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
* restore shifted configuration bounds if it's no longer shifted.
*/
- public void applyLayoutShifted(WindowContainerTransaction wct, int offsetX, int offsetY,
+ public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY,
ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
if (offsetX == 0 && offsetY == 0) {
wct.setBounds(taskInfo1.token, mBounds1);
@@ -492,19 +492,43 @@
/** Calls when dismissing split. */
void onSnappedToDismiss(boolean snappedToEnd);
- /** Calls when the bounds is changing due to animation or dragging divider bar. */
- void onLayoutChanging(SplitLayout layout);
-
- /** Calls when the target bounds changed. */
- void onLayoutChanged(SplitLayout layout);
+ /**
+ * Calls when resizing the split bounds.
+ *
+ * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+ * SurfaceControl, SurfaceControl)
+ */
+ void onLayoutSizeChanging(SplitLayout layout);
/**
- * Notifies when the layout shifted. So the layout handler can shift configuration
- * bounds correspondingly to make sure client apps won't get configuration changed or
- * relaunch. If the layout is no longer shifted, layout handler should restore shifted
- * configuration bounds.
+ * Calls when finish resizing the split bounds.
+ *
+ * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo,
+ * ActivityManager.RunningTaskInfo)
+ * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+ * SurfaceControl, SurfaceControl)
*/
- void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout);
+ void onLayoutSizeChanged(SplitLayout layout);
+
+ /**
+ * Calls when re-positioning the split bounds. Like moving split bounds while showing IME
+ * panel.
+ *
+ * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+ * SurfaceControl, SurfaceControl)
+ */
+ void onLayoutPositionChanging(SplitLayout layout);
+
+ /**
+ * Notifies the target offset for shifting layout. So layout handler can shift configuration
+ * bounds correspondingly to make sure client apps won't get configuration changed or
+ * relaunched. If the layout is no longer shifted, layout handler should restore shifted
+ * configuration bounds.
+ *
+ * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int,
+ * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo)
+ */
+ void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout);
/** Calls when user double tapped on the divider bar. */
default void onDoubleTappedDivider() {
@@ -674,9 +698,9 @@
// changed or relaunch. This is required to make sure client apps will calculate
// insets properly after layout shifted.
if (mTargetYOffset == 0) {
- mSplitLayoutHandler.onLayoutShifted(0, 0, SplitLayout.this);
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
} else {
- mSplitLayoutHandler.onLayoutShifted(0, mTargetYOffset - mLastYOffset,
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
SplitLayout.this);
}
}
@@ -695,7 +719,7 @@
public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
if (displayId != mDisplayId) return;
onProgress(getProgress(imeTop));
- mSplitLayoutHandler.onLayoutChanging(SplitLayout.this);
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -703,7 +727,7 @@
SurfaceControl.Transaction t) {
if (displayId != mDisplayId || cancel) return;
onProgress(1.0f);
- mSplitLayoutHandler.onLayoutChanging(SplitLayout.this);
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -713,7 +737,7 @@
if (!controlling && mImeShown) {
reset();
mSplitWindowManager.setInteractive(true);
- mSplitLayoutHandler.onLayoutChanging(SplitLayout.this);
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java
new file mode 100644
index 0000000..defbd5a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.displayareahelper;
+
+import android.view.SurfaceControl;
+
+import java.util.function.Consumer;
+
+/**
+ * Interface that allows to perform various display area related actions
+ */
+public interface DisplayAreaHelper {
+
+ /**
+ * Updates SurfaceControl builder to reparent it to the root display area
+ * @param displayId id of the display to which root display area it should be reparented to
+ * @param builder surface control builder that should be updated
+ * @param onUpdated callback that is invoked after updating the builder, called on
+ * the shell main thread
+ */
+ default void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder,
+ Consumer<SurfaceControl.Builder> onUpdated) {
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java
new file mode 100644
index 0000000..ef9ad6d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.displayareahelper;
+
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+public class DisplayAreaHelperController implements DisplayAreaHelper {
+
+ private final Executor mExecutor;
+ private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+
+ public DisplayAreaHelperController(Executor executor,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ mExecutor = executor;
+ mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+ }
+
+ @Override
+ public void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder,
+ Consumer<SurfaceControl.Builder> onUpdated) {
+ mExecutor.execute(() -> {
+ mRootDisplayAreaOrganizer.attachToDisplayArea(displayId, builder);
+ onUpdated.accept(builder);
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 9686776..291cbb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -282,6 +282,7 @@
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
+ mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
}
@@ -349,6 +350,10 @@
}
}
+ public ActivityManager.RunningTaskInfo getTaskInfo() {
+ return mTaskInfo;
+ }
+
public SurfaceControl getSurfaceControl() {
return mLeash;
}
@@ -716,6 +721,9 @@
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
}
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mPipTransitionController.forceFinishTransition();
+ }
final PipAnimationController.PipTransitionAnimator<?> animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6fec1fb..328f3ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -32,17 +32,18 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -57,11 +58,14 @@
*/
public class PipTransition extends PipTransitionController {
+ private static final String TAG = PipTransition.class.getSimpleName();
+
private final PipTransitionState mPipTransitionState;
private final int mEnterExitAnimationDuration;
private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private Rect mExitDestinationBounds = new Rect();
+ private IBinder mExitTransition = null;
public PipTransition(Context context,
PipBoundsState pipBoundsState,
@@ -96,7 +100,7 @@
public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
if (destinationBounds != null) {
mExitDestinationBounds.set(destinationBounds);
- mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+ mExitTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
} else {
mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this);
}
@@ -109,14 +113,19 @@
@android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
@android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (info.getType() == TRANSIT_EXIT_PIP && info.getChanges().size() == 1) {
- final TransitionInfo.Change change = info.getChanges().get(0);
- mFinishCallback = finishCallback;
- startTransaction.apply();
- boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(),
- new Rect(mExitDestinationBounds));
- mExitDestinationBounds.setEmpty();
- return success;
+ if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) {
+ mExitTransition = null;
+ if (info.getChanges().size() == 1) {
+ final TransitionInfo.Change change = info.getChanges().get(0);
+ mFinishCallback = finishCallback;
+ startTransaction.apply();
+ boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(),
+ new Rect(mExitDestinationBounds));
+ mExitDestinationBounds.setEmpty();
+ return success;
+ } else {
+ Log.e(TAG, "Got an exit-pip transition with unexpected change-list");
+ }
}
if (info.getType() == TRANSIT_REMOVE_PIP) {
@@ -183,26 +192,58 @@
}
@Override
+ public void onTransitionMerged(@NonNull IBinder transition) {
+ if (transition != mExitTransition) {
+ return;
+ }
+ // This means an expand happened before enter-pip finished and we are now "merging" a
+ // no-op transition that happens to match our exit-pip.
+ boolean cancelled = false;
+ if (mPipAnimationController.getCurrentAnimator() != null) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ cancelled = true;
+ }
+ // Unset exitTransition AFTER cancel so that finishResize knows we are merging.
+ mExitTransition = null;
+ if (!cancelled) return;
+ final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
+ if (taskInfo != null) {
+ startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+ new Rect(mExitDestinationBounds));
+ }
+ mExitDestinationBounds.setEmpty();
+ }
+
+ @Override
public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
- SurfaceControl.Transaction tx) {
+ @Nullable SurfaceControl.Transaction tx) {
if (isInPipDirection(direction)) {
mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
}
- WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareFinishResizeTransaction(taskInfo, destinationBounds,
- direction, tx, wct);
- mFinishCallback.onTransitionFinished(wct, new WindowContainerTransactionCallback() {
- @Override
- public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) {
- t.merge(tx);
- t.apply();
+ // If there is an expected exit transition, then the exit will be "merged" into this
+ // transition so don't fire the finish-callback in that case.
+ if (mExitTransition == null && mFinishCallback != null) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareFinishResizeTransaction(taskInfo, destinationBounds,
+ direction, wct);
+ if (tx != null) {
+ wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
- });
+ mFinishCallback.onTransitionFinished(wct, null /* wctCallback */);
+ mFinishCallback = null;
+ }
finishResizeForMenu(destinationBounds);
}
+ @Override
+ public void forceFinishTransition() {
+ if (mFinishCallback == null) return;
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCallback */);
+ mFinishCallback = null;
+ }
+
private boolean startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
final Rect destinationBounds) {
PipAnimationController.PipTransitionAnimator animator =
@@ -243,7 +284,7 @@
startTransaction.merge(tx);
startTransaction.apply();
mPipBoundsState.setBounds(destinationBounds);
- onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx);
+ onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
mFinishCallback = null;
mPipTransitionState.setInSwipePipToHomeTransition(false);
@@ -292,7 +333,6 @@
private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
- SurfaceControl.Transaction tx,
WindowContainerTransaction wct) {
Rect taskBounds = null;
if (isInPipDirection(direction)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index dbf603c..376f329 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -49,6 +49,7 @@
protected final Transitions mTransitions;
private final Handler mMainHandler;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+ protected PipTaskOrganizer mPipOrganizer;
protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
@@ -103,6 +104,13 @@
// Default implementation does nothing.
}
+ /**
+ * Called when the transition animation can't continue (eg. task is removed during
+ * animation)
+ */
+ public void forceFinishTransition() {
+ }
+
public PipTransitionController(PipBoundsState pipBoundsState,
PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController, Transitions transitions,
@@ -119,6 +127,10 @@
}
}
+ void setPipOrganizer(PipTaskOrganizer pto) {
+ mPipOrganizer = pto;
+ }
+
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 7f82ebd..a47a152 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -49,25 +49,24 @@
return mIsActive;
}
- void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
wct.setBounds(rootToken, rootBounds)
.setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
- .setLaunchRoot(
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES)
- .reparentTasks(
- null /* currentParent */,
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES,
- true /* onTop */)
// Moving the root task to top after the child tasks were re-parented , or the root
// task cannot be visible and focused.
.reorder(rootToken, true /* onTop */);
+ if (includingTopTask) {
+ wct.reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */,
+ true /* reparentTopOnly */);
+ }
mIsActive = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 0d52719..ec71fbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.splitscreen;
+import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -213,7 +215,11 @@
options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
try {
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ final int result =
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
+ mStageCoordinator.evictOccludedChildren(position);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
@@ -229,6 +235,7 @@
mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
options, user);
+ mStageCoordinator.evictOccludedChildren(position);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
@@ -272,6 +279,10 @@
Slog.e(TAG, "Error finishing legacy transition: ", e);
}
}
+
+ // Launching a new app into a specific split evicts tasks previously in the same
+ // split.
+ mStageCoordinator.evictOccludedChildren(position);
}
};
WindowContainerTransaction wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 414b4e4..0cff18e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -263,7 +263,7 @@
@SplitPosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
setSideStagePosition(sideStagePosition, wct);
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
mSideStage.addTask(task, getSideStageBounds(), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
@@ -299,7 +299,7 @@
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -368,7 +368,7 @@
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -394,6 +394,12 @@
TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
}
+ void evictOccludedChildren(@SplitPosition int position) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ (position == mSideStagePosition ? mSideStage : mMainStage).evictOccludedChildren(wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
Bundle resolveStartStage(@SplitScreen.StageType int stage,
@SplitPosition int position, @androidx.annotation.Nullable Bundle options,
@androidx.annotation.Nullable WindowContainerTransaction wct) {
@@ -471,7 +477,7 @@
if (mSideStageListener.mVisible && updateBounds) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
- onLayoutChanged(mSplitLayout);
+ onLayoutSizeChanged(mSplitLayout);
} else {
updateWindowBounds(mSplitLayout, wct);
updateUnfoldBounds();
@@ -756,7 +762,7 @@
} else if (isSideStage) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make sure the main stage is active.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
}
@@ -799,13 +805,18 @@
}
@Override
- public void onLayoutChanging(SplitLayout layout) {
+ public void onLayoutPositionChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ }
+
+ @Override
+ public void onLayoutSizeChanging(SplitLayout layout) {
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
mSideStage.setOutlineVisibility(false);
}
@Override
- public void onLayoutChanged(SplitLayout layout) {
+ public void onLayoutSizeChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
updateUnfoldBounds();
@@ -859,13 +870,13 @@
}
@Override
- public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+ public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
bottomRightStage.mRootTaskInfo);
mTaskOrganizer.applyTransaction(wct);
}
@@ -897,7 +908,7 @@
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
&& mMainStage.isActive()) {
- onLayoutChanged(mSplitLayout);
+ onLayoutSizeChanged(mSplitLayout);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 84d570f..071badf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -68,6 +68,7 @@
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
void onRootTaskVanished();
+
void onNoLongerSupportMultiWindow();
}
@@ -247,6 +248,15 @@
wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
}
+ void evictOccludedChildren(WindowContainerTransaction wct) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ if (!taskInfo.isVisible) {
+ wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+ }
+ }
+ }
+
void setVisibility(boolean visible, WindowContainerTransaction wct) {
wct.reorder(mRootTaskInfo.token, visible /* onTop */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
new file mode 100644
index 0000000..45f6d3c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.window.RemoteTransition;
+
+import com.android.wm.shell.stagesplit.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
+ * to indicate leaving no top task after leaving split-screen.
+ */
+ oneway void exitSplitScreen(int toTopTaskId) = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+
+ /**
+ * Starts tasks simultaneously in one transition.
+ */
+ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
+ in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+
+ /**
+ * Version of startTasks using legacy transition system.
+ */
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Blocking call that notifies and gets additional split-screen targets when entering
+ * recents (for example: the dividerBar).
+ * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+ * @param appTargets apps that will be re-parented to display area
+ */
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ in RemoteAnimationTarget[] appTargets) = 12;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
new file mode 100644
index 0000000..46e4299
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
+ void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
new file mode 100644
index 0000000..83855be
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Main stage for split-screen mode. When split-screen is active all standard activity types launch
+ * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * @see StageCoordinator
+ */
+class MainStage extends StageTaskListener {
+ private static final String TAG = MainStage.class.getSimpleName();
+
+ private boolean mIsActive = false;
+
+ MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ if (mIsActive) return;
+
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
+ .setLaunchRoot(
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES)
+ .reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+
+ mIsActive = true;
+ }
+
+ void deactivate(WindowContainerTransaction wct) {
+ deactivate(wct, false /* toTop */);
+ }
+
+ void deactivate(WindowContainerTransaction wct, boolean toTop) {
+ if (!mIsActive) return;
+ mIsActive = false;
+
+ if (mRootTaskInfo == null) return;
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setLaunchRoot(
+ rootToken,
+ null,
+ null)
+ .reparentTasks(
+ rootToken,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop)
+ // We want this re-order to the bottom regardless since we are re-parenting
+ // all its tasks.
+ .reorder(rootToken, false /* onTop */);
+ }
+
+ void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds)
+ .setWindowingMode(mRootTaskInfo.token, windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
new file mode 100644
index 0000000..8fbad52
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
+ * the consideration of display insets like status bar, navigation bar and display cutout.
+ */
+class OutlineManager extends WindowlessWindowManager {
+ private static final String WINDOW_NAME = "SplitOutlineLayer";
+ private final Context mContext;
+ private final Rect mRootBounds = new Rect();
+ private final Rect mTempRect = new Rect();
+ private final Rect mLastOutlineBounds = new Rect();
+ private final InsetsState mInsetsState = new InsetsState();
+ private final int mExpandedTaskBarHeight;
+ private OutlineView mOutlineView;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mHostLeash;
+ private SurfaceControl mLeash;
+
+ OutlineManager(Context context, Configuration configuration) {
+ super(configuration, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ b.setParent(mHostLeash);
+ }
+
+ void inflate(SurfaceControl rootLeash, Rect rootBounds) {
+ if (mLeash != null || mViewHost != null) return;
+
+ mHostLeash = rootLeash;
+ mRootBounds.set(rootBounds);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
+ final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_outline, null);
+ mOutlineView = rootLayout.findViewById(R.id.split_outline);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.width = mRootBounds.width();
+ lp.height = mRootBounds.height();
+ lp.token = new Binder();
+ lp.setTitle(WINDOW_NAME);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+ // TRUSTED_OVERLAY for windowless window without input channel.
+ mViewHost.setView(rootLayout, lp);
+ mLeash = getSurfaceControl(mViewHost.getWindowToken());
+
+ drawOutline();
+ }
+
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ mRootBounds.setEmpty();
+ mLastOutlineBounds.setEmpty();
+ mOutlineView = null;
+ mHostLeash = null;
+ mLeash = null;
+ }
+
+ @Nullable
+ SurfaceControl getOutlineLeash() {
+ return mLeash;
+ }
+
+ void setVisibility(boolean visible) {
+ if (mOutlineView != null) {
+ mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ void setRootBounds(Rect rootBounds) {
+ if (mViewHost == null || mViewHost.getView() == null) {
+ return;
+ }
+
+ if (!mRootBounds.equals(rootBounds)) {
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = rootBounds.width();
+ lp.height = rootBounds.height();
+ mViewHost.relayout(lp);
+ mRootBounds.set(rootBounds);
+ drawOutline();
+ }
+ }
+
+ void onInsetsChanged(InsetsState insetsState) {
+ if (!mInsetsState.equals(insetsState)) {
+ mInsetsState.set(insetsState);
+ drawOutline();
+ }
+ }
+
+ private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
+ outBounds.set(rootBounds);
+ final InsetsSource taskBarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ // Only insets the divider bar with task bar when it's expanded so that the rounded corners
+ // will be drawn against task bar.
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
+ }
+
+ // Offset the coordinate from screen based to surface based.
+ outBounds.offset(-rootBounds.left, -rootBounds.top);
+ }
+
+ void drawOutline() {
+ if (mOutlineView == null) {
+ return;
+ }
+
+ computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
+ if (mTempRect.equals(mLastOutlineBounds)) {
+ return;
+ }
+
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
+ lp.leftMargin = mTempRect.left;
+ lp.topMargin = mTempRect.top;
+ lp.width = mTempRect.width();
+ lp.height = mTempRect.height();
+ mOutlineView.setLayoutParams(lp);
+ mLastOutlineBounds.set(mTempRect);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
new file mode 100644
index 0000000..92b1381
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+/** View for drawing split outline. */
+public class OutlineView extends View {
+ private final Paint mPaint = new Paint();
+ private final Path mPath = new Path();
+ private final float[] mRadii = new float[8];
+
+ public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(
+ getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
+ mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ // TODO(b/200850654): match the screen corners with the actual display decor.
+ mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
+ mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
+ mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
+ mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
+ }
+
+ private int getCornerRadius(@RoundedCorner.Position int position) {
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
+ return roundedCorner == null ? 0 : roundedCorner.getRadius();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ mPath.reset();
+ mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawPath(mPath, mPaint);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
new file mode 100644
index 0000000..55c4f3a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
+ * here. All other task are launch in the {@link MainStage}.
+ *
+ * @see StageCoordinator
+ */
+class SideStage extends StageTaskListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ private static final String TAG = SideStage.class.getSimpleName();
+ private final Context mContext;
+ private OutlineManager mOutlineManager;
+
+ SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ mContext = context;
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
+ WindowContainerTransaction wct) {
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .reparent(task.token, rootToken, true /* onTop*/)
+ // Moving the root task to top after the child tasks were reparented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+ }
+
+ boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ // No matter if the root task is empty or not, moving the root to bottom because it no
+ // longer preserves visible child task.
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ if (mChildrenTaskInfo.size() == 0) return false;
+ wct.reparentTasks(
+ mRootTaskInfo.token,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop);
+ return true;
+ }
+
+ boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+ final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ if (task == null) return false;
+ wct.reparent(task.token, newParent, false /* onTop */);
+ return true;
+ }
+
+ @Nullable
+ public SurfaceControl getOutlineLeash() {
+ return mOutlineManager.getOutlineLeash();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ super.onTaskAppeared(taskInfo, leash);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
+ enableOutline(true);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ super.onTaskInfoChanged(taskInfo);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
+ }
+ }
+
+ private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
+ return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
+ }
+
+ void enableOutline(boolean enable) {
+ if (mOutlineManager == null) {
+ return;
+ }
+
+ if (enable) {
+ if (mRootTaskInfo != null) {
+ mOutlineManager.inflate(mRootLeash,
+ mRootTaskInfo.configuration.windowConfiguration.getBounds());
+ }
+ } else {
+ mOutlineManager.release();
+ }
+ }
+
+ void setOutlineVisibility(boolean visible) {
+ mOutlineManager.setVisibility(visible);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mOutlineManager.onInsetsChanged(insetsState);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsChanged(insetsState);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
new file mode 100644
index 0000000..aec81a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to engage split-screen feature.
+ * TODO: Figure out which of these are actually needed outside of the Shell
+ */
+@ExternalThread
+public interface SplitScreen {
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ int STAGE_TYPE_SIDE = 1;
+
+ @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+ STAGE_TYPE_UNDEFINED,
+ STAGE_TYPE_MAIN,
+ STAGE_TYPE_SIDE
+ })
+ @interface StageType {}
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ interface SplitScreenListener {
+ default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+ default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitVisibilityChanged(boolean visible) {}
+ }
+
+ /** Registers listener that gets split screen callback. */
+ void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+ @NonNull Executor executor);
+
+ /** Unregisters listener that gets split screen callback. */
+ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
+
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ void onKeyguardVisibilityChanged(boolean showing);
+
+ /** Get a string representation of a stage type */
+ static String stageTypeToString(@StageType int stage) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
+ case STAGE_TYPE_MAIN: return "MAIN";
+ case STAGE_TYPE_SIDE: return "SIDE";
+ default: return "UNKNOWN(" + stage + ")";
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
new file mode 100644
index 0000000..94db9cd9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.transition.LegacyTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Provider;
+
+/**
+ * Class manages split-screen multitasking mode and implements the main interface
+ * {@link SplitScreen}.
+ * @see StageCoordinator
+ */
+// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
+ private static final String TAG = SplitScreenController.class.getSimpleName();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final SplitScreenImpl mImpl = new SplitScreenImpl();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final SplitscreenEventLogger mLogger;
+ private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
+
+ private StageCoordinator mStageCoordinator;
+
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mUnfoldControllerProvider = unfoldControllerProvider;
+ mLogger = new SplitscreenEventLogger();
+ }
+
+ public SplitScreen asSplitScreen() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ public void onOrganizerRegistered() {
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mUnfoldControllerProvider);
+ }
+ }
+
+ public boolean isSplitScreenVisible() {
+ return mStageCoordinator.isSplitScreenVisible();
+ }
+
+ public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
+ final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("Unknown taskId" + taskId);
+ }
+ return moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ return mStageCoordinator.moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean removeFromSideStage(int taskId) {
+ return mStageCoordinator.removeFromSideStage(taskId);
+ }
+
+ public void setSideStageOutline(boolean enable) {
+ mStageCoordinator.setSideStageOutline(enable);
+ }
+
+ public void setSideStagePosition(@SplitPosition int sideStagePosition) {
+ mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
+ }
+
+ public void setSideStageVisibility(boolean visible) {
+ mStageCoordinator.setSideStageVisibility(visible);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ moveToSideStage(taskId,
+ leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+
+ public void exitSplitScreen(int toTopTaskId, int exitReason) {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
+
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mStageCoordinator.onKeyguardOccludedChanged(occluded);
+ }
+
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ }
+
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ }
+
+ public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
+ }
+
+ public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.registerSplitScreenListener(listener);
+ }
+
+ public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.unregisterSplitScreenListener(listener);
+ }
+
+ public void startTask(int taskId, @SplitScreen.StageType int stage,
+ @SplitPosition int position, @Nullable Bundle options) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ }
+
+ public void startShortcut(String packageName, String shortcutId,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, UserHandle user) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ options, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, stage, position, options);
+ return;
+ }
+ mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
+ null /* remote */);
+ }
+
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ }
+
+ t.apply();
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+ }
+ };
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
+ if (!isSplitScreenVisible()) return null;
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("RecentsAnimationSplitTasks")
+ .setHidden(false)
+ .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+ mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
+ SurfaceControl sc = builder.build();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+
+ // Ensure that we order these in the parent in the right z-order as their previous order
+ Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
+ int layer = 1;
+ for (RemoteAnimationTarget appTarget : apps) {
+ transaction.reparent(appTarget.leash, sc);
+ transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+ appTarget.screenSpaceBounds.top);
+ transaction.setLayer(appTarget.leash, layer++);
+ }
+ transaction.apply();
+ transaction.close();
+ return new RemoteAnimationTarget[]{
+ mStageCoordinator.getDividerBarLegacyTarget(),
+ mStageCoordinator.getOutlineLegacyTarget()};
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ if (mStageCoordinator != null) {
+ mStageCoordinator.dump(pw, prefix);
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class SplitScreenImpl implements SplitScreen {
+ private ISplitScreenImpl mISplitScreen;
+ private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+ private final SplitScreenListener mListener = new SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+ });
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+ });
+ }
+ }
+
+ @Override
+ public void onSplitVisibilityChanged(boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+ });
+ }
+ }
+ };
+
+ @Override
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
+ }
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+
+ @Override
+ public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+ if (mExecutors.containsKey(listener)) return;
+
+ mMainExecutor.execute(() -> {
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.registerSplitScreenListener(mListener);
+ }
+
+ mExecutors.put(listener, executor);
+ });
+
+ executor.execute(() -> {
+ mStageCoordinator.sendStatusToListener(listener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mMainExecutor.execute(() -> {
+ mExecutors.remove(listener);
+
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.unregisterSplitScreenListener(mListener);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+ });
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void exitSplitScreen(int toTopTaskId) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen(toTopTaskId,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
+ });
+ }
+
+ @Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
+ }
+
+ @Override
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
+ }
+
+ @Override
+ public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
+ }
+
+ @Override
+ public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+ mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ adapter));
+ }
+
+ @Override
+ public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
+ sideTaskId, sideOptions, sidePosition, remoteTransition));
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ @Nullable Bundle options, UserHandle user) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
+ }
+
+ @Override
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+ executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+ (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
+ true /* blocking */);
+ return out[0];
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
new file mode 100644
index 0000000..af9a5aa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.transition.OneShotRemoteHandler;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/** Manages transition animations for split-screen. */
+class SplitScreenTransitions {
+ private static final String TAG = "SplitScreenTransitions";
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+ private final TransactionPool mTransactionPool;
+ private final Transitions mTransitions;
+ private final Runnable mOnFinish;
+
+ IBinder mPendingDismiss = null;
+ IBinder mPendingEnter = null;
+
+ private IBinder mAnimatingTransition = null;
+ private OneShotRemoteHandler mRemoteHandler = null;
+
+ private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
+ if (wct != null || wctCB != null) {
+ throw new UnsupportedOperationException("finish transactions not supported yet.");
+ }
+ onFinish();
+ };
+
+ /** Keeps track of currently running animations */
+ private final ArrayList<Animator> mAnimations = new ArrayList<>();
+
+ private Transitions.TransitionFinishCallback mFinishCallback = null;
+ private SurfaceControl.Transaction mFinishTransaction;
+
+ SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+ @NonNull Runnable onFinishCallback) {
+ mTransactionPool = pool;
+ mTransitions = transitions;
+ mOnFinish = onFinishCallback;
+ }
+
+ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+ mRemoteFinishCB);
+ mRemoteHandler = null;
+ return;
+ }
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ }
+
+ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
+ @NonNull WindowContainerToken sideRoot) {
+ mFinishTransaction = mTransactionPool.acquire();
+
+ // Play some place-holder fade animations
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ if (mode == TRANSIT_CHANGE) {
+ if (change.getParent() != null) {
+ // This is probably reparented, so we want the parent to be immediately visible
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ t.show(parentChange.getLeash());
+ t.setAlpha(parentChange.getLeash(), 1.f);
+ // and then animate this layer outside the parent (since, for example, this is
+ // the home task animating from fullscreen to part-screen).
+ t.reparent(leash, info.getRootLeash());
+ t.setLayer(leash, info.getChanges().size() - i);
+ // build the finish reparent/reposition
+ mFinishTransaction.reparent(leash, parentChange.getLeash());
+ mFinishTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ }
+ // TODO(shell-transitions): screenshot here
+ final Rect startBounds = new Rect(change.getStartAbsBounds());
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing split via snap which means the still-visible task has been
+ // dragged to its end position at animation start so reflect that here.
+ startBounds.offsetTo(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ }
+ final Rect endBounds = new Rect(change.getEndAbsBounds());
+ startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ startExampleResizeAnimation(leash, startBounds, endBounds);
+ }
+ if (change.getParent() != null) {
+ continue;
+ }
+
+ if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ || sideRoot.equals(change.getContainer()))) {
+ t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+ change.getStartAbsBounds().height());
+ }
+ boolean isOpening = isOpeningType(info.getType());
+ if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+ // fade in
+ startExampleAnimation(leash, true /* show */);
+ } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+ // fade out
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing via snap-to-top/bottom means that the dismissed task is already
+ // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
+ // and don't animate it so it doesn't pop-in when reparented.
+ t.setAlpha(leash, 0.f);
+ } else {
+ startExampleAnimation(leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish();
+ }
+
+ /** Starts a transition to enter split with a remote transition animator. */
+ IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
+ @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
+ @NonNull Transitions.TransitionHandler handler) {
+ if (remoteTransition != null) {
+ // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ }
+ final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
+ mPendingEnter = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.setTransition(transition);
+ }
+ return transition;
+ }
+
+ /** Starts a transition for dismissing split after dragging the divider to a screen edge */
+ IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
+ @NonNull Transitions.TransitionHandler handler) {
+ final IBinder transition = mTransitions.startTransition(
+ TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
+ mPendingDismiss = transition;
+ return transition;
+ }
+
+ void onFinish() {
+ if (!mAnimations.isEmpty()) return;
+ mOnFinish.run();
+ if (mFinishTransaction != null) {
+ mFinishTransaction.apply();
+ mTransactionPool.release(mFinishTransaction);
+ mFinishTransaction = null;
+ }
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ mFinishCallback = null;
+ if (mAnimatingTransition == mPendingEnter) {
+ mPendingEnter = null;
+ }
+ if (mAnimatingTransition == mPendingDismiss) {
+ mPendingDismiss = null;
+ }
+ mAnimatingTransition = null;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
+ @NonNull Rect startBounds, @NonNull Rect endBounds) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setWindowCrop(leash,
+ (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
+ (int) (startBounds.height() * (1.f - fraction)
+ + endBounds.height() * fraction));
+ transaction.setPosition(leash,
+ startBounds.left * (1.f - fraction) + endBounds.left * fraction,
+ startBounds.top * (1.f - fraction) + endBounds.top * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setWindowCrop(leash, 0, 0);
+ transaction.setPosition(leash, endBounds.left, endBounds.top);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
new file mode 100644
index 0000000..aab7902
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class SplitscreenEventLogger {
+
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // The instance id for the current splitscreen session (from start to end)
+ private InstanceId mLoggerSessionId;
+
+ // Drag info
+ private @SplitPosition int mDragEnterPosition;
+ private InstanceId mDragEnterSessionId;
+
+ // For deduping async events
+ private int mLastMainStagePosition = -1;
+ private int mLastMainStageUid = -1;
+ private int mLastSideStagePosition = -1;
+ private int mLastSideStageUid = -1;
+ private float mLastSplitRatio = -1f;
+
+ public SplitscreenEventLogger() {
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Return whether a splitscreen session has started.
+ */
+ public boolean hasStartedSession() {
+ return mLoggerSessionId != null;
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from a drag.
+ */
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ mDragEnterPosition = position;
+ mDragEnterSessionId = dragSessionId;
+ }
+
+ /**
+ * Logs when the user enters splitscreen.
+ */
+ public void logEnter(float splitRatio,
+ @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ mLoggerSessionId = mIdSequence.newInstanceId();
+ int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
+ ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ updateSplitRatioState(splitRatio);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
+ enterReason,
+ 0 /* exitReason */,
+ splitRatio,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the user exits splitscreen. Only one of the main or side stages should be
+ * specified to indicate which position was focused as a part of exiting (both can be unset).
+ */
+ public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
+ && sideStagePosition != SPLIT_POSITION_UNDEFINED)
+ || (mainStageUid != 0 && sideStageUid != 0)) {
+ throw new IllegalArgumentException("Only main or side stage should be set");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
+ 0 /* enterReason */,
+ exitReason,
+ 0f /* splitRatio */,
+ getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid,
+ getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+
+ // Reset states
+ mLoggerSessionId = null;
+ mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
+ mDragEnterSessionId = null;
+ mLastMainStagePosition = -1;
+ mLastMainStageUid = -1;
+ mLastSideStagePosition = -1;
+ mLastSideStageUid = -1;
+ }
+
+ /**
+ * Logs when an app in the main stage changes.
+ */
+ public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
+ isLandscape), mainStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ 0 /* sideStagePosition */,
+ 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when an app in the side stage changes.
+ */
+ public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
+ isLandscape), sideStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ 0 /* mainStagePosition */,
+ 0 /* mainStageUid */,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the splitscreen ratio changes.
+ */
+ public void logResize(float splitRatio) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (splitRatio <= 0f || splitRatio >= 1f) {
+ // Don't bother reporting resizes that end up dismissing the split, that will be logged
+ // via the exit event
+ return;
+ }
+ if (!updateSplitRatioState(splitRatio)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ mLastSplitRatio,
+ 0 /* mainStagePosition */, 0 /* mainStageUid */,
+ 0 /* sideStagePosition */, 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the apps in splitscreen are swapped.
+ */
+ public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
+ boolean changed = (mLastMainStagePosition != mainStagePosition)
+ || (mLastMainStageUid != mainStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastMainStagePosition = mainStagePosition;
+ mLastMainStageUid = mainStageUid;
+ return true;
+ }
+
+ private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
+ boolean changed = (mLastSideStagePosition != sideStagePosition)
+ || (mLastSideStageUid != sideStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastSideStagePosition = sideStagePosition;
+ mLastSideStageUid = sideStageUid;
+ return true;
+ }
+
+ private boolean updateSplitRatioState(float splitRatio) {
+ boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
+ if (!changed) {
+ return false;
+ }
+
+ mLastSplitRatio = splitRatio;
+ return true;
+ }
+
+ public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
+ }
+ }
+
+ private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
+ }
+ }
+
+ private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
new file mode 100644
index 0000000..574e379
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -0,0 +1,1330 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.isClosingType;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
+ * {@link SideStage} stages.
+ * Some high-level rules:
+ * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * least one child task.
+ * - The {@link MainStage} should only have children if the coordinator is active.
+ * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
+ * and {@link SideStage} are visible.
+ * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
+ * {@link #onStageHasChildrenChanged(StageListenerImpl).}
+ */
+class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+
+ private static final String TAG = StageCoordinator.class.getSimpleName();
+
+ /** internal value for mDismissTop that represents no dismiss */
+ private static final int NO_DISMISS = -2;
+
+ private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
+ private final MainStage mMainStage;
+ private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mMainUnfoldController;
+ private final SideStage mSideStage;
+ private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mSideUnfoldController;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+ private final int mDisplayId;
+ private SplitLayout mSplitLayout;
+ private boolean mDividerVisible;
+ private final SyncTransactionQueue mSyncQueue;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private final Context mContext;
+ private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SplitScreenTransitions mSplitTransitions;
+ private final SplitscreenEventLogger mLogger;
+ private boolean mExitSplitScreenOnHide;
+ private boolean mKeyguardOccluded;
+
+ // TODO(b/187041611): remove this flag after totally deprecated legacy split
+ /** Whether the device is supporting legacy split or not. */
+ private boolean mUseLegacySplit;
+
+ @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+
+ /** The target stage to dismiss to when unlock after folded. */
+ @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+
+ private final Runnable mOnTransitionAnimationComplete = () -> {
+ // If still playing, let it finish.
+ if (!isSplitScreenVisible()) {
+ // Update divider state after animation so that it is still around and positioned
+ // properly for the animation itself.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ }
+ mDismissTop = NO_DISMISS;
+ };
+
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
+ };
+
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mLogger = logger;
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
+ mMainStage = new MainStage(
+ mTaskOrganizer,
+ mDisplayId,
+ mMainStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mMainUnfoldController);
+ mSideStage = new SideStage(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ mSideStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mSideUnfoldController);
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
+ mRootTDAOrganizer.registerListener(displayId, this);
+ final DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+ new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = mainStage;
+ mSideStage = sideStage;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mRootTDAOrganizer.registerListener(displayId, this);
+ mSplitLayout = splitLayout;
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mLogger = logger;
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ SplitScreenTransitions getSplitTransitions() {
+ return mSplitTransitions;
+ }
+
+ boolean isSplitScreenVisible() {
+ return mSideStageListener.mVisible && mMainStageListener.mVisible;
+ }
+
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(sideStagePosition, wct);
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.addTask(task, getSideStageBounds(), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
+ return true;
+ }
+
+ boolean removeFromSideStage(int taskId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ /**
+ * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
+ * {@link SideStage} no longer has children.
+ */
+ final boolean result = mSideStage.removeTask(taskId,
+ mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+ wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return result;
+ }
+
+ void setSideStageOutline(boolean enable) {
+ mSideStage.enableOutline(enable);
+ }
+
+ /** Starts 2 tasks in one transition. */
+ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ }
+
+ /** Starts 2 tasks in one legacy transition. */
+ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+ finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @androidx.annotation.Nullable Bundle options,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ }
+
+ Bundle resolveStartStage(@SplitScreen.StageType int stage,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
+ @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ if (position == getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ setSideStagePosition(position, wct);
+ } else {
+ position = getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ setSideStagePosition(sideStagePosition, wct);
+ } else {
+ position = getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
+ }
+
+ @SplitPosition
+ int getSideStagePosition() {
+ return mSideStagePosition;
+ }
+
+ @SplitPosition
+ int getMainStagePosition() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
+ void setSideStagePosition(@SplitPosition int sideStagePosition,
+ @Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+ }
+
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
+ if (mSideStagePosition == sideStagePosition) return;
+ mSideStagePosition = sideStagePosition;
+ sendOnStagePositionChanged();
+
+ if (mSideStageListener.mVisible && updateBounds) {
+ if (wct == null) {
+ // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutSizeChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
+ }
+ }
+ }
+
+ void setSideStageVisibility(boolean visible) {
+ if (mSideStageListener.mVisible == visible) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.setVisibility(visible, wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ void onKeyguardOccludedChanged(boolean occluded) {
+ // Do not exit split directly, because it needs to wait for task info update to determine
+ // which task should remain on top after split dismissed.
+ mKeyguardOccluded = occluded;
+ }
+
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!showing && mMainStage.isActive()
+ && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
+ }
+ }
+
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mExitSplitScreenOnHide = exitSplitScreenOnHide;
+ }
+
+ void exitSplitScreen(int toTopTaskId, int exitReason) {
+ StageTaskListener childrenToTop = null;
+ if (mMainStage.containsTask(toTopTaskId)) {
+ childrenToTop = mMainStage;
+ } else if (mSideStage.containsTask(toTopTaskId)) {
+ childrenToTop = mSideStage;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (childrenToTop != null) {
+ childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
+ }
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void applyExitSplitScreen(
+ StageTaskListener childrenToTop,
+ WindowContainerTransaction wct, int exitReason) {
+ mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
+ mMainStage.deactivate(wct, childrenToTop == mMainStage);
+ mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ // Hide divider and reset its position.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (childrenToTop != null) {
+ logExitToStage(exitReason, childrenToTop == mMainStage);
+ } else {
+ logExit(exitReason);
+ }
+ }
+
+ /**
+ * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
+ * an existing WindowContainerTransaction (rather than applying immediately). This is intended
+ * to be used when exiting split might be bundled with other window operations.
+ */
+ void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
+ @NonNull WindowContainerTransaction wct) {
+ mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
+ mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+ }
+
+ void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ outTopOrLeftBounds.set(mSplitLayout.getBounds1());
+ outBottomOrRightBounds.set(mSplitLayout.getBounds2());
+ }
+
+ private void addActivityOptions(Bundle opts, StageTaskListener stage) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ }
+
+ void updateActivityOptions(Bundle opts, @SplitPosition int position) {
+ addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
+ }
+
+ void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ sendStatusToListener(listener);
+ }
+
+ void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
+ private void sendOnStagePositionChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ }
+ }
+
+ private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+ boolean present, boolean visible) {
+ int stage;
+ if (present) {
+ stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ } else {
+ // No longer on any stage
+ stage = STAGE_TYPE_UNDEFINED;
+ }
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ } else {
+ mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
+ }
+ }
+
+ private void sendSplitVisibilityChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onSplitVisibilityChanged(mDividerVisible);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ }
+ }
+
+ private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
+ if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
+ mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+
+ // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
+ // split to prevent new split behavior confusing users.
+ if (!mUseLegacySplit) {
+ wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void onStageRootTaskVanished(StageListenerImpl stageListener) {
+ if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Deactivate the main stage if it no longer has a root task.
+ mMainStage.deactivate(wct);
+
+ if (!mUseLegacySplit) {
+ wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void setDividerVisibility(boolean visible) {
+ if (mDividerVisible == visible) return;
+ mDividerVisible = visible;
+ if (visible) {
+ mSplitLayout.init();
+ updateUnfoldBounds();
+ } else {
+ mSplitLayout.release();
+ }
+ sendSplitVisibilityChanged();
+ }
+
+ private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ final boolean sideStageVisible = mSideStageListener.mVisible;
+ final boolean mainStageVisible = mMainStageListener.mVisible;
+ final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+ final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+ final boolean sameVisibility = sideStageVisible == mainStageVisible;
+ // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+ // got one stage visibility changed for a moment and it will cause flicker.
+ if (sameVisibility) {
+ setDividerVisibility(bothStageVisible);
+ }
+
+ if (bothStageInvisible) {
+ if (mExitSplitScreenOnHide
+ // Don't dismiss staged split when both stages are not visible due to sleeping display,
+ // like the cases keyguard showing or screen off.
+ || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+ exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ }
+ } else if (mKeyguardOccluded) {
+ // At least one of the stages is visible while keyguard occluded. Dismiss split because
+ // there's show-when-locked activity showing on top of keyguard. Also make sure the
+ // task contains show-when-locked activity remains on top after split dismissed.
+ final StageTaskListener toTop =
+ mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+ exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ // Same above, we only set root tasks and divider leash visibility when both stage
+ // change to visible or invisible to avoid flicker.
+ if (sameVisibility) {
+ t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
+ .setVisibility(mMainStage.mRootLeash, bothStageVisible);
+ applyDividerVisibility(t);
+ applyOutlineVisibility(t);
+ }
+ });
+ }
+
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
+ }
+
+ private void applyOutlineVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
+ if (outlineLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
+ } else {
+ t.hide(outlineLeash);
+ }
+ }
+
+ private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ final boolean hasChildren = stageListener.mHasChildren;
+ final boolean isSideStage = stageListener == mSideStageListener;
+ if (!hasChildren) {
+ if (isSideStage && mMainStageListener.mVisible) {
+ // Exit to main stage if side stage no longer has children.
+ exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ } else if (!isSideStage && mSideStageListener.mVisible) {
+ // Exit to side stage if main stage no longer has children.
+ exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ }
+ } else if (isSideStage) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make sure the main stage is active.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
+ && mSideStageListener.mHasChildren) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+ }
+
+ @VisibleForTesting
+ IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
+ return mSplitTransitions.startSnapToDismiss(wct, this);
+ }
+
+ @Override
+ public void onSnappedToDismiss(boolean bottomOrRight) {
+ final boolean mainStageToTop =
+ bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ onSnappedToDismissTransition(mainStageToTop);
+ return;
+ }
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
+ }
+
+ @Override
+ public void onDoubleTappedDivider() {
+ setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ @Override
+ public void onLayoutPositionChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ }
+
+ @Override
+ public void onLayoutSizeChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(false);
+ }
+
+ @Override
+ public void onLayoutSizeChanged(SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateWindowBounds(layout, wct);
+ updateUnfoldBounds();
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(true);
+ mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
+ }
+
+ private void updateUnfoldBounds() {
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ }
+ }
+
+ /**
+ * Populates `wct` with operations that match the split windows to the current layout.
+ * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
+ */
+ private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
+ }
+
+ void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
+ bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
+ }
+
+ @Override
+ public int getSplitItemPosition(WindowContainerToken token) {
+ if (token == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
+ return getMainStagePosition();
+ } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
+ return getSideStagePosition();
+ }
+
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ @Override
+ public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ throw new IllegalStateException("Well that was unexpected...");
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+ && mMainStage.isActive()) {
+ onLayoutSizeChanged(mSplitLayout);
+ }
+ }
+
+ private void onFoldedStateChanged(boolean folded) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (!folded) return;
+
+ if (mMainStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+ }
+
+ private Rect getSideStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ }
+
+ private Rect getMainStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ }
+
+ /**
+ * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
+ * this task (yet) so this can also be used to identify which stage to put a task into.
+ */
+ private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
+ // TODO(b/184679596): Find a way to either include task-org information in the transition,
+ // or synchronize task-org callbacks so we can use stage.containsTask
+ if (mMainStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
+ return mMainStage;
+ } else if (mSideStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+ return mSideStage;
+ }
+ return null;
+ }
+
+ @SplitScreen.StageType
+ private int getStageType(StageTaskListener stage) {
+ return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask == null) {
+ // still want to monitor everything while in split-screen, so return non-null.
+ return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+ }
+
+ WindowContainerTransaction out = null;
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitScreenVisible()) {
+ // try to handle everything while in split-screen, so return a WCT even if it's empty.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
+ mMainStage.getChildCount(), mSideStage.getChildCount());
+ out = new WindowContainerTransaction();
+ final StageTaskListener stage = getStageOfTask(triggerTask);
+ if (stage != null) {
+ // dismiss split if the last task in one of the stages is going away
+ if (isClosingType(type) && stage.getChildCount() == 1) {
+ // The top should be the opposite side that is closing:
+ mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ }
+ } else {
+ if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
+ // Going home so dismiss both.
+ mDismissTop = STAGE_TYPE_UNDEFINED;
+ }
+ }
+ if (mDismissTop != NO_DISMISS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Dismiss from request. toTop=%s",
+ stageTypeToString(mDismissTop));
+ prepareExitSplitScreen(mDismissTop, out);
+ mSplitTransitions.mPendingDismiss = transition;
+ }
+ } else {
+ // Not in split mode, so look for an open into a split stage just so we can whine and
+ // complain about how this isn't a supported operation.
+ if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
+ if (getStageOfTask(triggerTask) != null) {
+ throw new IllegalStateException("Entering split implicitly with only one task"
+ + " isn't supported.");
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition != mSplitTransitions.mPendingDismiss
+ && transition != mSplitTransitions.mPendingEnter) {
+ // Not entering or exiting, so just do some house-keeping and validation.
+
+ // If we're not in split-mode, just abort so something else can handle it.
+ if (!isSplitScreenVisible()) return false;
+
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final StageTaskListener stage = getStageOfTask(taskInfo);
+ if (stage == null) continue;
+ if (isOpeningType(change.getMode())) {
+ if (!stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ } else if (isClosingType(change.getMode())) {
+ if (stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ }
+ }
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ // TODO(shell-transitions): Implement a fallback behavior for now.
+ throw new IllegalStateException("Somehow removed the last task in a stage"
+ + " outside of a proper transition");
+ // This can happen in some pathological cases. For example:
+ // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
+ // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
+ // In this case, the result *should* be that we leave split.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ }
+
+ // Use normal animations.
+ return false;
+ }
+
+ boolean shouldAnimate = true;
+ if (mSplitTransitions.mPendingEnter == transition) {
+ shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingDismiss == transition) {
+ shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+ }
+ if (!shouldAnimate) return false;
+
+ mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ return true;
+ }
+
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
+ // First, verify that we actually have opened 2 apps in split.
+ TransitionInfo.Change mainChild = null;
+ TransitionInfo.Change sideChild = null;
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
+ if (stageType == STAGE_TYPE_MAIN) {
+ mainChild = change;
+ } else if (stageType == STAGE_TYPE_SIDE) {
+ sideChild = change;
+ }
+ }
+ if (mainChild == null || sideChild == null) {
+ throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ + " 2 tasks in transition. Possibly one of them failed to launch");
+ // TODO: fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
+ }
+
+ // Update local states (before animating).
+ setDividerVisibility(true);
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
+ null /* wct */);
+ setSplitsVisible(true);
+
+ addDividerBarToTransition(info, t, true /* show */);
+
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ + " to have been called with " + mainChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ + " to have been called with " + sideChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ return true;
+ } else {
+ // TODO: other entry method animations
+ throw new RuntimeException("Unsupported split-entry");
+ }
+ }
+
+ private boolean startPendingDismissAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (mMainStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+ if (mSideStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+
+ // Update local states.
+ setSplitsVisible(false);
+ // Wait until after animation to update divider
+
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
+ }
+
+ if (mDismissTop == STAGE_TYPE_UNDEFINED) {
+ // Going home (dismissing both splits)
+
+ // TODO: Have a proper remote for this. Until then, though, reset state and use the
+ // normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false);
+ mSplitTransitions.mPendingDismiss = null;
+ return false;
+ }
+
+ addDividerBarToTransition(info, t, false /* show */);
+ // We're dismissing split by moving the other one to fullscreen.
+ // Since we don't have any animations for this yet, just use the internal example
+ // animations.
+ return true;
+ }
+
+ private void addDividerBarToTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, boolean show) {
+ final SurfaceControl leash = mSplitLayout.getDividerLeash();
+ final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ barChange.setStartAbsBounds(bounds);
+ barChange.setEndAbsBounds(bounds);
+ barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
+ barChange.setFlags(FLAG_IS_DIVIDER_BAR);
+ // Technically this should be order-0, but this is running after layer assignment
+ // and it's a special case, so just add to end.
+ info.addChange(barChange);
+ // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+ if (show) {
+ t.setAlpha(leash, 1.f);
+ t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setPosition(leash, bounds.left, bounds.top);
+ t.show(leash);
+ }
+ }
+
+ RemoteAnimationTarget getDividerBarLegacyTarget() {
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ RemoteAnimationTarget getOutlineLegacyTarget() {
+ final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
+ // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
+ // distinguish as a split auxiliary target in Launcher.
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "MainStage");
+ pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStage");
+ mSideStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+ }
+
+ /**
+ * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
+ * This is intended for batch use, so it assumes other state management logic is already
+ * handled.
+ */
+ private void setSplitsVisible(boolean visible) {
+ mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
+ mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is next entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mLogger.enterRequestedByDrag(position, dragSessionId);
+ }
+
+ /**
+ * Logs the exit of splitscreen.
+ */
+ private void logExit(int exitReason) {
+ mLogger.logExit(exitReason,
+ SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
+ SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ /**
+ * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
+ * executed.
+ */
+ private void logExitToStage(int exitReason, boolean toMainStage) {
+ mLogger.logExit(exitReason,
+ toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
+ toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
+ !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
+ !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
+ boolean mHasRootTask = false;
+ boolean mVisible = false;
+ boolean mHasChildren = false;
+
+ @Override
+ public void onRootTaskAppeared() {
+ mHasRootTask = true;
+ StageCoordinator.this.onStageRootTaskAppeared(this);
+ }
+
+ @Override
+ public void onStatusChanged(boolean visible, boolean hasChildren) {
+ if (!mHasRootTask) return;
+
+ if (mHasChildren != hasChildren) {
+ mHasChildren = hasChildren;
+ StageCoordinator.this.onStageHasChildrenChanged(this);
+ }
+ if (mVisible != visible) {
+ mVisible = visible;
+ StageCoordinator.this.onStageVisibilityChanged(this);
+ }
+ }
+
+ @Override
+ public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
+ }
+
+ @Override
+ public void onRootTaskVanished() {
+ reset();
+ StageCoordinator.this.onStageRootTaskVanished(this);
+ }
+
+ @Override
+ public void onNoLongerSupportMultiWindow() {
+ if (mMainStage.isActive()) {
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ }
+ }
+
+ private void reset() {
+ mHasRootTask = false;
+ mVisible = false;
+ mHasChildren = false;
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+ pw.println(prefix + "mVisible=" + mVisible);
+ pw.println(prefix + "mHasChildren=" + mHasChildren);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
new file mode 100644
index 0000000..8b36c94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class that handle common task org. related for split-screen stages.
+ * Note that this class and its sub-class do not directly perform hierarchy operations.
+ * They only serve to hold a collection of tasks and provide APIs like
+ * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
+ * to perform operations in-sync with other containers.
+ *
+ * @see StageCoordinator
+ */
+class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = StageTaskListener.class.getSimpleName();
+
+ protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ protected static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ public interface StageListenerCallbacks {
+ void onRootTaskAppeared();
+
+ void onStatusChanged(boolean visible, boolean hasChildren);
+
+ void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
+
+ void onRootTaskVanished();
+ void onNoLongerSupportMultiWindow();
+ }
+
+ private final StageListenerCallbacks mCallbacks;
+ private final SurfaceSession mSurfaceSession;
+ protected final SyncTransactionQueue mSyncQueue;
+
+ protected ActivityManager.RunningTaskInfo mRootTaskInfo;
+ protected SurfaceControl mRootLeash;
+ protected SurfaceControl mDimLayer;
+ protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
+ private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+
+ private final StageTaskUnfoldController mStageTaskUnfoldController;
+
+ StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ mCallbacks = callbacks;
+ mSyncQueue = syncQueue;
+ mSurfaceSession = surfaceSession;
+ mStageTaskUnfoldController = stageTaskUnfoldController;
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
+
+ int getChildCount() {
+ return mChildrenTaskInfo.size();
+ }
+
+ boolean containsTask(int taskId) {
+ return mChildrenTaskInfo.contains(taskId);
+ }
+
+ /**
+ * Returns the top activity uid for the top child task.
+ */
+ int getTopChildTaskUid() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.topActivityInfo == null) {
+ continue;
+ }
+ return info.topActivityInfo.applicationInfo.uid;
+ }
+ return 0;
+ }
+
+ /** @return {@code true} if this listener contains the currently focused task. */
+ boolean isFocused() {
+ if (mRootTaskInfo == null) {
+ return false;
+ }
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
+ mRootLeash = leash;
+ mRootTaskInfo = taskInfo;
+ mCallbacks.onRootTaskAppeared();
+ sendStatusChanged();
+ mSyncQueue.runInSync(t -> {
+ t.hide(mRootLeash);
+ mDimLayer =
+ SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
+ });
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ final int taskId = taskInfo.taskId;
+ mChildrenLeashes.put(taskId, leash);
+ mChildrenTaskInfo.put(taskId, taskInfo);
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (!taskInfo.supportsMultiWindow) {
+ // Leave split screen if the task no longer supports multi window.
+ mCallbacks.onNoLongerSupportMultiWindow();
+ return;
+ }
+ if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+ taskInfo.isVisible);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ updateChildTaskSurface(
+ taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ }
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskId = taskInfo.taskId;
+ if (mRootTaskInfo.taskId == taskId) {
+ mCallbacks.onRootTaskVanished();
+ mSyncQueue.runInSync(t -> t.remove(mDimLayer));
+ mRootTaskInfo = null;
+ } else if (mChildrenTaskInfo.contains(taskId)) {
+ mChildrenTaskInfo.remove(taskId);
+ mChildrenLeashes.remove(taskId);
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskVanished(taskInfo);
+ }
+ }
+
+ @Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mRootTaskInfo.taskId == taskId) {
+ b.setParent(mRootLeash);
+ } else if (mChildrenLeashes.contains(taskId)) {
+ b.setParent(mChildrenLeashes.get(taskId));
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
+ void setBounds(Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds);
+ }
+
+ void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ if (!containsTask(taskId)) {
+ return;
+ }
+ wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
+ }
+
+ void setVisibility(boolean visible, WindowContainerTransaction wct) {
+ wct.reorder(mRootTaskInfo.token, visible /* onTop */);
+ }
+
+ void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+ @SplitScreen.StageType int stage) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ int taskId = mChildrenTaskInfo.keyAt(i);
+ listener.onTaskStageChanged(taskId, stage,
+ mChildrenTaskInfo.get(taskId).isVisible);
+ }
+ }
+
+ private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, boolean firstAppeared) {
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ t.setWindowCrop(leash, null);
+ t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+
+ private void sendStatusChanged() {
+ mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+ }
+
+ @Override
+ @CallSuper
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
new file mode 100644
index 0000000..62b9da6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final UnfoldBackgroundController mBackgroundController;
+ private final Executor mExecutor;
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final Rect mStageBounds = new Rect();
+ private final TransactionPool mTransactionPool;
+
+ private InsetsSource mTaskbarInsetsSource;
+ private boolean mBothStagesVisible;
+
+ public StageTaskUnfoldController(@NonNull Context context,
+ @NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mBackgroundController = backgroundController;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ /**
+ * Called when split screen task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext context = new AnimationContext(leash);
+ mAnimationContextByTaskId.put(taskInfo.taskId, context);
+ }
+
+ /**
+ * Called when a split screen task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ resetSurface(transaction, context);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mBackgroundController.ensureBackground(transaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ resetTransformations();
+ }
+
+ /**
+ * Called when split screen visibility changes
+ * @param bothStagesVisible true if both stages of the split screen are visible
+ */
+ public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+ mBothStagesVisible = bothStagesVisible;
+ if (!bothStagesVisible) {
+ resetTransformations();
+ }
+ }
+
+ /**
+ * Called when split screen stage bounds changed
+ * @param bounds new bounds for this stage
+ */
+ public void onLayoutChanged(Rect bounds) {
+ mStageBounds.set(bounds);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ private void resetTransformations() {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ mBackgroundController.removeBackground(transaction);
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+ transaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+
+ private AnimationContext(SurfaceControl leash) {
+ this.mLeash = leash;
+ update();
+ }
+
+ private void update() {
+ mStartCropRect.set(mStageBounds);
+
+ if (mTaskbarInsetsSource != null) {
+ // Only insets the cropping window with taskbar when taskbar is expanded
+ if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(mTaskbarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ // Offset to surface coordinates as layout bounds are in screen coordinates
+ mStartCropRect.offsetTo(0, 0);
+
+ mEndCropRect.set(mStartCropRect);
+
+ int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+ int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+ mStartCropRect.inset(margin, margin, margin, margin);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index f0685a8..38122ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -38,6 +38,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -50,6 +51,8 @@
*/
public class SplashscreenIconDrawableFactory {
+ private static final String TAG = "SplashscreenIconDrawableFactory";
+
/**
* @return An array containing the foreground drawable at index 0 and if needed a background
* drawable at index 1.
@@ -282,7 +285,12 @@
if (startListener != null) {
startListener.run();
}
- mAnimatableIcon.start();
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ animation.cancel();
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index bde2b5f..05ba74a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -23,6 +23,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
@@ -41,7 +42,7 @@
/**
* Algorithm for determining the type of a new starting window on handheld devices.
- * At the moment also used on Android Auto.
+ * At the moment also used on Android Auto and Wear OS.
*/
public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
private static final String TAG = PhoneStartingWindowTypeAlgorithm.class.getSimpleName();
@@ -58,6 +59,7 @@
(parameter & TYPE_PARAMETER_USE_EMPTY_SPLASH_SCREEN) != 0;
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
+ final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
@@ -68,11 +70,14 @@
+ " activityCreated:" + activityCreated
+ " useEmptySplashScreen:" + useEmptySplashScreen
+ " legacySplashScreen:" + legacySplashScreen
+ + " activityDrawn:" + activityDrawn
+ " topIsHome:" + topIsHome);
}
if (!topIsHome) {
- if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
+ if (!processRunning
+ || newTask
+ || (taskSwitch && (!activityCreated || !activityDrawn))) {
return useEmptySplashScreen
? STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
: legacySplashScreen
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 663d647..7abda99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -70,6 +70,7 @@
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.TransitionInfo;
+import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -362,6 +363,7 @@
}
}
startTransaction.apply();
+ TransitionMetrics.getInstance().reportAnimationStart(transition);
// run finish now in-case there are no animations
onAnimFinish.run();
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 2720157..c369831 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -44,6 +44,7 @@
import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
+import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
@@ -192,6 +193,8 @@
public void register(ShellTaskOrganizer taskOrganizer) {
if (mPlayerImpl == null) return;
taskOrganizer.registerTransitionPlayer(mPlayerImpl);
+ // Pre-load the instance.
+ TransitionMetrics.getInstance();
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
index b63d9ff..4d87ec9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
@@ -43,4 +43,4 @@
} while (SystemClock.uptimeMillis() - startTime < timeout)
return (false to null)
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 322d8b5..b432bb6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -22,11 +22,11 @@
import android.content.Context
import android.os.ServiceManager
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.Flicker
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
@@ -34,7 +34,6 @@
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.flicker.repetitions
import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
-import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -49,13 +48,9 @@
protected val notifyManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE))
- protected val packageManager = context.getPackageManager()
- protected val uid = packageManager.getApplicationInfo(
+ protected val uid = context.packageManager.getApplicationInfo(
testApp.component.packageName, 0).uid
- protected lateinit var addBubbleBtn: UiObject2
- protected lateinit var cancelAllBtn: UiObject2
-
protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
@JvmOverloads
@@ -69,10 +64,8 @@
notifyManager.setBubblesAllowed(testApp.component.packageName,
uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
testApp.launchViaIntent(wmHelper)
- addBubbleBtn = device.wait(Until.findObject(
- By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
- cancelAllBtn = device.wait(Until.findObject(
- By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
+ waitAndGetAddBubbleBtn()
+ waitAndGetCancelAllBtn()
}
}
@@ -86,13 +79,10 @@
}
}
- @FlakyTest
- @Test
- fun testAppIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- }
- }
+ protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? = device.wait(Until.findObject(
+ By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
+ protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? = device.wait(Until.findObject(
+ By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index bfdcb36..2614b4a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -20,6 +20,7 @@
import android.graphics.Point
import android.util.DisplayMetrics
import android.view.WindowManager
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -28,6 +29,7 @@
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -44,22 +46,31 @@
@Group4
class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
- val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- val displaySize = DisplayMetrics()
+ private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private val displaySize = DisplayMetrics()
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ get() = buildTransition {
setup {
eachRun {
- addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Add Bubble not found")
}
}
transitions {
- wm?.run { wm.getDefaultDisplay().getMetrics(displaySize) } ?: error("WM not found")
+ wm.run { wm.getDefaultDisplay().getMetrics(displaySize) }
val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels)
val showBubble = device.wait(Until.findObject(
By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found")
}
}
+
+ @FlakyTest
+ @Test
+ fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 42eeadf..2679c10 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.bubble
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -24,6 +25,7 @@
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -43,17 +45,25 @@
class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ get() = buildTransition {
setup {
test {
- addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Add Bubble not found")
}
}
transitions {
val showBubble = device.wait(Until.findObject(
By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT)
showBubble?.run { showBubble.click() } ?: error("Bubble notify not found")
- device.pressBack()
}
}
+
+ @FlakyTest
+ @Test
+ fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
new file mode 100644
index 0000000..9c172a2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:LaunchBubbleFromLockScreen`
+ *
+ * Actions:
+ * Launch an bubble from notification on lock screen
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition {
+ setup {
+ eachRun {
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Bubble widget not found")
+ wmHelper.waitFor("noAppWindowsOnTop") {
+ it.wmState.topVisibleAppWindow.isEmpty()
+ }
+ }
+ }
+ transitions {
+ val notification = device.wait(Until.findObject(
+ By.text("BubbleChat")), FIND_OBJECT_TIMEOUT)
+ notification?.click() ?: error("Notification not found")
+ instrumentation.uiAutomation.syncInputTransactions()
+ val showBubble = device.wait(Until.findObject(
+ By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT)
+ showBubble?.click() ?: error("Bubble notify not found")
+ instrumentation.uiAutomation.syncInputTransactions()
+ val cancelAllBtn = waitAndGetCancelAllBtn()
+ cancelAllBtn?.click() ?: error("Cancel widget not found")
+ }
+ }
+
+ @FlakyTest
+ @Test
+ fun testAppIsVisibleAtEnd() {
+ testSpec.assertLayersEnd {
+ this.isVisible(testApp.component)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index 47e8c0c..7b25908 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,12 +16,14 @@
package com.android.wm.shell.flicker.bubble
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -40,9 +42,18 @@
class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ get() = buildTransition {
transitions {
- addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Bubble widget not found")
}
}
+
+ @FlakyTest
+ @Test
+ fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index 194e28f..19edd6f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -25,6 +26,7 @@
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -42,10 +44,11 @@
class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ get() = buildTransition {
setup {
test {
for (i in 1..3) {
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
}
val showBubble = device.wait(Until.findObject(
@@ -63,4 +66,12 @@
}
}
}
+
+ @FlakyTest
+ @Test
+ fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 091022a..bc701d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -131,7 +131,7 @@
NotificationListenerService.Ranking ranking =
mock(NotificationListenerService.Ranking.class);
- when(ranking.visuallyInterruptive()).thenReturn(true);
+ when(ranking.isTextChanged()).thenReturn(true);
mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
mMainExecutor);
@@ -1014,15 +1014,15 @@
}
private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
- sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */);
+ sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
}
private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime,
- boolean visuallyInterruptive) {
+ boolean textChanged) {
setPostTime(entry, postTime);
// BubbleController calls this:
Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
- b.setVisuallyInterruptiveForTest(visuallyInterruptive);
+ b.setTextChangedForTest(textChanged);
// And then this
mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
true /* showInShade */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index defa58d..b4caeb5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -95,13 +95,13 @@
@Test
public void testUpdateDivideBounds() {
mSplitLayout.updateDivideBounds(anyInt());
- verify(mSplitLayoutHandler).onLayoutChanging(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
}
@Test
public void testSetDividePosition() {
mSplitLayout.setDividePosition(anyInt());
- verify(mSplitLayoutHandler).onLayoutChanged(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 12b547a..2bcc45e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -62,7 +62,8 @@
@Test
public void testActiveDeactivate() {
- mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct);
+ mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
+ true /* reparent */);
assertThat(mMainStage.isActive()).isTrue();
mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index be103863..05496b0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -316,7 +316,8 @@
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
- mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction());
+ mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
+ true /* includingTopTask */);
}
private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a39d331..cd29220 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -18,9 +18,11 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -111,7 +113,8 @@
mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT);
- verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
+ verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class),
+ eq(true /* includingTopTask */));
verify(mSideStage).addTask(eq(task), any(Rect.class),
any(WindowContainerTransaction.class));
}
@@ -130,7 +133,7 @@
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
clearInvocations(mMainUnfoldController, mSideUnfoldController);
- mStageCoordinator.onLayoutChanged(mSplitLayout);
+ mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
verify(mMainUnfoldController).onLayoutChanged(mBounds2);
verify(mSideUnfoldController).onLayoutChanged(mBounds1);
@@ -142,7 +145,7 @@
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
clearInvocations(mMainUnfoldController, mSideUnfoldController);
- mStageCoordinator.onLayoutChanged(mSplitLayout);
+ mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
verify(mMainUnfoldController).onLayoutChanged(mBounds1);
verify(mSideUnfoldController).onLayoutChanged(mBounds2);
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 0cde3d1..22904a0 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
#include "utils/ByteOrder.h"
@@ -600,6 +601,7 @@
return base::unexpected(result.error());
}
+ bool overlaid = false;
if (!stop_at_first_match && !ignore_configuration && !apk_assets_[result->cookie]->IsLoader()) {
for (const auto& id_map : package_group.overlays_) {
auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
@@ -616,6 +618,27 @@
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, String8(), result->cookie});
+ if (auto path = apk_assets_[result->cookie]->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
+ overlaid = true;
}
continue;
}
@@ -646,6 +669,9 @@
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(),
overlay_result->cookie});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
+ overlaid = true;
}
}
}
@@ -654,6 +680,10 @@
last_resolution_.cookie = result->cookie;
last_resolution_.type_string_ref = result->type_string_ref;
last_resolution_.entry_string_ref = result->entry_string_ref;
+ last_resolution_.best_config_name = result->config.toString();
+ if (!overlaid) {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
}
return result;
@@ -671,8 +701,6 @@
uint32_t best_offset = 0U;
uint32_t type_flags = 0U;
- std::vector<Resolution::Step> resolution_steps;
-
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
@@ -725,7 +753,7 @@
resolution_type = Resolution::Step::Type::OVERLAID;
} else {
if (UNLIKELY(logging_enabled)) {
- resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
+ last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
this_config.toString(),
cookie});
}
@@ -742,7 +770,7 @@
if (!offset.has_value()) {
if (UNLIKELY(logging_enabled)) {
- resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+ last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
this_config.toString(),
cookie});
}
@@ -806,6 +834,8 @@
last_resolution_.steps.clear();
last_resolution_.type_string_ref = StringPoolRef();
last_resolution_.entry_string_ref = StringPoolRef();
+ last_resolution_.best_config_name.clear();
+ last_resolution_.best_package_name.clear();
}
void AssetManager2::SetResourceResolutionLoggingEnabled(bool enabled) {
@@ -865,6 +895,10 @@
}
}
+ log_stream << "\nBest matching is from "
+ << (last_resolution_.best_config_name.isEmpty() ? "default"
+ : last_resolution_.best_config_name)
+ << " configuration of " << last_resolution_.best_package_name;
return log_stream.str();
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 7d01395..a3b42df 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -486,6 +486,12 @@
// Steps taken to resolve last resource.
std::vector<Step> steps;
+
+ // The configuration name of the best resource found.
+ String8 best_config_name;
+
+ // The package name of the best resource found.
+ String8 best_package_name;
};
// Record of the last resolved resource's resolution path.
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 3c4ee4e..4394740 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -766,7 +766,9 @@
auto result = assetmanager.GetLastResourceResolution();
EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
"\tFor config - de\n"
- "\tFound initial: basic/basic.apk", result);
+ "\tFound initial: basic/basic.apk\n"
+ "Best matching is from default configuration of com.android.basic",
+ result);
}
TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
@@ -787,7 +789,9 @@
EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
"\tFor config - de\n"
"\tFound initial: basic/basic.apk\n"
- "\tFound better: basic/basic_de_fr.apk - de", result);
+ "\tFound better: basic/basic_de_fr.apk - de\n"
+ "Best matching is from de configuration of com.android.basic",
+ result);
}
TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
diff --git a/libs/androidfw/tests/PosixUtils_test.cpp b/libs/androidfw/tests/PosixUtils_test.cpp
index c7b3eba..8c49350 100644
--- a/libs/androidfw/tests/PosixUtils_test.cpp
+++ b/libs/androidfw/tests/PosixUtils_test.cpp
@@ -30,14 +30,14 @@
const auto result = ExecuteBinary({"/bin/date", "--help"});
ASSERT_THAT(result, NotNull());
ASSERT_EQ(result->status, 0);
- ASSERT_EQ(result->stdout_str.find("usage: date "), 0);
+ ASSERT_GE(result->stdout_str.find("usage: date "), 0);
}
TEST(PosixUtilsTest, RelativePathToBinary) {
const auto result = ExecuteBinary({"date", "--help"});
ASSERT_THAT(result, NotNull());
ASSERT_EQ(result->status, 0);
- ASSERT_EQ(result->stdout_str.find("usage: date "), 0);
+ ASSERT_GE(result->stdout_str.find("usage: date "), 0);
}
TEST(PosixUtilsTest, BadParameters) {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5fa0922..38b5715 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -18,15 +18,16 @@
#include <apex/window.h>
#include <fcntl.h>
+#include <gui/TraceUtils.h>
#include <strings.h>
#include <sys/stat.h>
+#include <ui/Fence.h>
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <functional>
-#include <gui/TraceUtils.h>
#include "../Properties.h"
#include "AnimationContext.h"
#include "Frame.h"
@@ -509,6 +510,8 @@
Frame frame = mRenderPipeline->getFrame();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
+ ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
+
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
&(profiler()));
@@ -739,6 +742,9 @@
instance->mRenderThread.getASurfaceControlFunctions();
nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
+ if (gpuCompleteTime == Fence::SIGNAL_TIME_PENDING) {
+ gpuCompleteTime = -1;
+ }
uint64_t frameNumber = functions.getFrameNumberFunc(stats);
FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
diff --git a/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl b/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
index 94668a3..6d69038 100644
--- a/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
+++ b/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
@@ -48,4 +48,13 @@
@nullable AudioConfig audioConfig;
/** Additional data. */
byte[] data;
+ /**
+ * If true, recognition is still active after this event.
+ * For compatibility with earlier versions of this data type, when the status field is set to
+ * RecognitionStatus.FORCED, the value of this field should be treated as 'true', regardless of
+ * the actual value.
+ * When the status is RecognitionStatus.ABORTED or RecognitionStatus.FAILURE, this must be set
+ * to false.
+ */
+ boolean recognitionStillActive;
}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
index e6cfb6b..0209602 100644
--- a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
@@ -43,4 +43,5 @@
boolean triggerInData;
@nullable android.media.audio.common.AudioConfig audioConfig;
byte[] data;
+ boolean recognitionStillActive;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 669bfc5..d061bd3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5233,21 +5233,6 @@
}
}
- /**
- * @hide
- * Notifies AudioService that it is connected to an A2DP device that supports absolute volume,
- * so that AudioService can send volume change events to the A2DP device, rather than handling
- * them.
- */
- public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
- final IAudioService service = getService();
- try {
- service.avrcpSupportsAbsoluteVolume(address, support);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
/**
* {@hide}
*/
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 3cb6454..0450a80 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1805,9 +1805,15 @@
return false;
}
final int channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
- final int channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
- ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
- : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ final int channelCountLimit;
+ try {
+ channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
+ ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
+ : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ } catch (IllegalArgumentException iae) {
+ loge("Unsupported encoding " + iae);
+ return false;
+ }
if (channelCount > channelCountLimit) {
loge("Channel configuration contains too many channels for encoding "
+ encoding + "(" + channelCount + " > " + channelCountLimit + ")");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7604b360..5b05cd3 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -38,6 +38,7 @@
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -179,8 +180,6 @@
int getEncodedSurroundMode(int targetSdkVersion);
- oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
-
void setSpeakerphoneOn(IBinder cb, boolean on);
boolean isSpeakerphoneOn();
@@ -449,4 +448,10 @@
void setSpatializerParameter(int key, in byte[] value);
void getSpatializerParameter(int key, inout byte[] value);
+
+ int getSpatializerOutput();
+
+ void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+ void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index b59c71f..742207b 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -39,6 +39,7 @@
MediaRouterClientState getState(IMediaRouterClient client);
boolean isPlaybackActive(IMediaRouterClient client);
+ void setBluetoothA2dpOn(IMediaRouterClient client, boolean on);
void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl
new file mode 100644
index 0000000..57572a8
--- /dev/null
+++ b/media/java/android/media/ISpatializerOutputCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer output changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerOutputCallback {
+
+ void dispatchSpatializerOutputChanged(int output);
+}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 16543b0..08a648a 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -744,6 +744,19 @@
public static final String KEY_CHANNEL_MASK = "channel-mask";
/**
+ * A key describing the maximum number of channels that can be output by an audio decoder.
+ * By default, the decoder will output the same number of channels as present in the encoded
+ * stream, if supported. Set this value to limit the number of output channels, and use
+ * the downmix information in the stream, if available.
+ * <p>Values larger than the number of channels in the content to decode behave like the number
+ * of channels in the content (if applicable), for instance passing 99 for a 5.1 audio stream
+ * behaves like passing 6.
+ * <p>This key is only used during decoding.
+ */
+ public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT =
+ "max-output-channel_count";
+
+ /**
* A key describing the number of frames to trim from the start of the decoded audio stream.
* The associated value is an integer.
*/
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 83bc38b2..1077275 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -158,18 +158,10 @@
* the user supplied callback method OnErrorListener.onError() will be
* invoked by the internal player engine and the object will be
* transfered to the <em>Error</em> state. </li>
- * <li>It is also recommended that once
- * a MediaPlayer object is no longer being used, call {@link #release()} immediately
- * so that resources used by the internal player engine associated with the
- * MediaPlayer object can be released immediately. Resource may include
- * singleton resources such as hardware acceleration components and
- * failure to call {@link #release()} may cause subsequent instances of
- * MediaPlayer objects to fallback to software implementations or fail
- * altogether. Once the MediaPlayer
- * object is in the <em>End</em> state, it can no longer be used and
- * there is no way to bring it back to any other state. </li>
- * <li>Furthermore,
- * the MediaPlayer objects created using <code>new</code> is in the
+ * <li>You must call {@link #release()} once you have finished using an instance to release
+ * acquired resources, such as memory and codecs. Once you have called {@link #release}, you
+ * must no longer interact with the released instance.
+ * <li>MediaPlayer objects created using <code>new</code> is in the
* <em>Idle</em> state, while those created with one
* of the overloaded convenient <code>create</code> methods are <em>NOT</em>
* in the <em>Idle</em> state. In fact, the objects are in the <em>Prepared</em>
@@ -407,7 +399,7 @@
* <tr><td>release </p></td>
* <td>any </p></td>
* <td>{} </p></td>
- * <td>After {@link #release()}, the object is no longer available. </p></td></tr>
+ * <td>After {@link #release()}, you must not interact with the object. </p></td></tr>
* <tr><td>reset </p></td>
* <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
* PlaybackCompleted, Error}</p></td>
@@ -657,11 +649,13 @@
private ProvisioningThread mDrmProvisioningThread;
/**
- * Default constructor. Consider using one of the create() methods for
- * synchronously instantiating a MediaPlayer from a Uri or resource.
- * <p>When done with the MediaPlayer, you should call {@link #release()},
- * to free the resources. If not released, too many MediaPlayer instances may
- * result in an exception.</p>
+ * Default constructor.
+ *
+ * <p>Consider using one of the create() methods for synchronously instantiating a MediaPlayer
+ * from a Uri or resource.
+ *
+ * <p>You must call {@link #release()} when you are finished using the instantiated instance.
+ * Doing so frees any resources you have previously acquired.
*/
public MediaPlayer() {
this(AudioSystem.AUDIO_SESSION_ALLOCATE);
@@ -873,9 +867,10 @@
/**
* Convenience method to create a MediaPlayer for a given Uri.
* On success, {@link #prepare()} will already have been called and must not be called again.
- * <p>When done with the MediaPlayer, you should call {@link #release()},
- * to free the resources. If not released, too many MediaPlayer instances will
- * result in an exception.</p>
+ *
+ * <p>You must call {@link #release()} when you are finished using the created instance. Doing
+ * so frees any resources you have previously acquired.
+ *
* <p>Note that since {@link #prepare()} is called automatically in this method,
* you cannot change the audio
* session ID (see {@link #setAudioSessionId(int)}) or audio attributes
@@ -892,9 +887,10 @@
/**
* Convenience method to create a MediaPlayer for a given Uri.
* On success, {@link #prepare()} will already have been called and must not be called again.
- * <p>When done with the MediaPlayer, you should call {@link #release()},
- * to free the resources. If not released, too many MediaPlayer instances will
- * result in an exception.</p>
+ *
+ * <p>You must call {@link #release()} when you are finished using the created instance. Doing
+ * so frees any resources you have previously acquired.
+ *
* <p>Note that since {@link #prepare()} is called automatically in this method,
* you cannot change the audio
* session ID (see {@link #setAudioSessionId(int)}) or audio attributes
@@ -955,9 +951,10 @@
/**
* Convenience method to create a MediaPlayer for a given resource id.
* On success, {@link #prepare()} will already have been called and must not be called again.
- * <p>When done with the MediaPlayer, you should call {@link #release()},
- * to free the resources. If not released, too many MediaPlayer instances will
- * result in an exception.</p>
+ *
+ * <p>You must call {@link #release()} when you are finished using the created instance. Doing
+ * so frees any resources you have previously acquired.
+ *
* <p>Note that since {@link #prepare()} is called automatically in this method,
* you cannot change the audio
* session ID (see {@link #setAudioSessionId(int)}) or audio attributes
@@ -2157,21 +2154,8 @@
/**
* Releases resources associated with this MediaPlayer object.
- * It is considered good practice to call this method when you're
- * done using the MediaPlayer. In particular, whenever an Activity
- * of an application is paused (its onPause() method is called),
- * or stopped (its onStop() method is called), this method should be
- * invoked to release the MediaPlayer object, unless the application
- * has a special need to keep the object around. In addition to
- * unnecessary resources (such as memory and instances of codecs)
- * being held, failure to call this method immediately if a
- * MediaPlayer object is no longer needed may also lead to
- * continuous battery consumption for mobile devices, and playback
- * failure for other applications if no multiple instances of the
- * same codec are supported on a device. Even if multiple instances
- * of the same codec are supported, some performance degradation
- * may be expected when unnecessary multiple instances are used
- * at the same time.
+ *
+ * <p>You must call this method once the instance is no longer required.
*/
public void release() {
baseRelease();
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 7e9d2d8..9c9e83b 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -106,7 +106,7 @@
@IntDef({
TYPE_UNKNOWN, TYPE_BUILTIN_SPEAKER, TYPE_WIRED_HEADSET,
TYPE_WIRED_HEADPHONES, TYPE_BLUETOOTH_A2DP, TYPE_HDMI, TYPE_USB_DEVICE,
- TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID,
+ TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID, TYPE_BLE_HEADSET,
TYPE_REMOTE_TV, TYPE_REMOTE_SPEAKER, TYPE_GROUP})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
@@ -202,6 +202,14 @@
public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
/**
+ * A route type describing a BLE HEADSET.
+ *
+ * @see #getType
+ * @hide
+ */
+ public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
+
+ /**
* A route type indicating the presentation of the media is happening on a TV.
*
* @see #getType
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index e432eb6..13c1498 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -1078,7 +1078,8 @@
&& (types & ROUTE_TYPE_LIVE_AUDIO) != 0
&& (route.isBluetooth() || route.isDefault())) {
try {
- sStatic.mAudioService.setBluetoothA2dpOn(route.isBluetooth());
+ sStatic.mMediaRouterService.setBluetoothA2dpOn(sStatic.mClient,
+ route.isBluetooth());
} catch (RemoteException e) {
Log.e(TAG, "Error changing Bluetooth A2DP state", e);
}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 8b1624b..e6fff39 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -63,7 +64,9 @@
/**
* Returns whether spatialization is enabled or not.
* A false value can originate for instance from the user electing to
- * disable the feature.<br>
+ * disable the feature, or when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
+ * <br>
* Note that this state reflects a platform-wide state of the "desire" to use spatialization,
* but availability of the audio processing is still dictated by the compatibility between
* the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
@@ -85,7 +88,10 @@
* incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
* Note that spatialization can be available, but disabled by the user, in which case this
* method would still return {@code true}, whereas {@link #isEnabled()}
- * would return {@code false}.
+ * would return {@code false}.<br>
+ * Also when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
+ * the return value will be false.
* @return {@code true} if the spatializer effect is available and capable
* of processing the audio for the current configuration of the device,
* {@code false} otherwise.
@@ -293,6 +299,24 @@
@HeadTrackingModeSet int mode);
}
+
+ /**
+ * @hide
+ * An interface to be notified of changes to the output stream used by the spatializer
+ * effect.
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnSpatializerOutputChangedListener {
+ /**
+ * Called when the id of the output stream of the spatializer effect changed.
+ * @param spatializer the {@code Spatializer} instance whose output is updated
+ * @param output the id of the output stream, or 0 when there is no spatializer output
+ */
+ void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
+ @IntRange(from = 0) int output);
+ }
+
/**
* @hide
* An interface to be notified of updates to the head to soundstage pose, as represented by the
@@ -839,6 +863,73 @@
}
}
+ /**
+ * @hide
+ * Returns the id of the output stream used for the spatializer effect playback
+ * @return id of the output stream, or 0 if no spatializer playback is active
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @IntRange(from = 0) int getOutput() {
+ try {
+ return mAm.getService().getSpatializerOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSpatializerOutput", e);
+ return 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the listener to receive spatializer effect output updates
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnSpatializerOutputChangedListener()
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnSpatializerOutputChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSpatializerOutputChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mOutputListenerLock) {
+ if (mOutputListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mOutputListener =
+ new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
+ mOutputDispatcher = new SpatializerOutputDispatcherStub();
+ try {
+ mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) {
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for spatializer effect output updates
+ * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnSpatializerOutputChangedListener() {
+ synchronized (mOutputListenerLock) {
+ if (mOutputDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) { }
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+
//-----------------------------------------------------------------------------
// callback helper definitions
@@ -964,4 +1055,35 @@
}
}
}
+
+ //-----------------------------------------------------------------------------
+ // output callback management and stub
+ private final Object mOutputListenerLock = new Object();
+ /**
+ * Listener for output updates
+ */
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
+
+ private final class SpatializerOutputDispatcherStub
+ extends ISpatializerOutputCallback.Stub {
+
+ @Override
+ public void dispatchSpatializerOutputChanged(int output) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnSpatializerOutputChangedListener> listener;
+ synchronized (mOutputListenerLock) {
+ listener = mOutputListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onSpatializerOutputChanged(Spatializer.this, output));
+ }
+ }
+ }
}
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index fa3dc72..6112290 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -504,8 +504,22 @@
/**
* Sets target mix role of the mixing rule.
*
- * <p>The mix role indicates playback streams will be captured or recording source will be
- * injected. If not specified, the mix role will be decided automatically when
+ * As each mixing rule is intended to be associated with an {@link AudioMix},
+ * explicitly setting the role of a mixing rule allows this {@link Builder} to
+ * verify validity of the mixing rules to be validated.<br>
+ * The mix role allows distinguishing between:
+ * <ul>
+ * <li>audio framework mixers that will mix / sample-rate convert / reformat the audio
+ * signal of any audio player (e.g. a {@link android.media.MediaPlayer}) that matches
+ * the selection rules defined in the object being built. Use
+ * {@link AudioMixingRule#MIX_ROLE_PLAYERS} for such an {@code AudioMixingRule}</li>
+ * <li>audio framework mixers that will be used as the injection point (after sample-rate
+ * conversion and reformatting of the audio signal) into any audio recorder (e.g. a
+ * {@link android.media.AudioRecord}) that matches the selection rule defined in the
+ * object being built. Use {@link AudioMixingRule#MIX_ROLE_INJECTOR} for such an
+ * {@code AudioMixingRule}.</li>
+ * </ul>
+ * <p>If not specified, the mix role will be decided automatically when
* {@link #addRule(AudioAttributes, int)} or {@link #addMixRule(int, Object)} be called.
*
* @param mixRole integer value of {@link #MIX_ROLE_PLAYERS} or {@link #MIX_ROLE_INJECTOR}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 20fa53d..bc00c40 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -657,8 +657,9 @@
parcel.setDataPosition(0);
Bundle out = parcel.readBundle(null);
- // Calling Bundle#size() will trigger Bundle#unparcel().
- out.size();
+ for (String key : out.keySet()) {
+ out.get(key);
+ }
} catch (BadParcelableException e) {
Log.d(TAG, "Custom parcelable in bundle.", e);
return true;
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 30a14c8..a0f6fb9 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1658,6 +1658,25 @@
*/
String COLUMN_CONTENT_ID = "content_id";
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>Should be empty if this program is not live.
+ *
+ * <p>Type: INTEGER (long)
+ * @see #COLUMN_LIVE
+ */
+ String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>Should be empty if this program is not live.
+ *
+ * <p>Type: INTEGER (long)
+ * @see #COLUMN_LIVE
+ */
+ String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
}
/** Column definitions for the TV channels table. */
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
new file mode 100644
index 0000000..4197934
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+/**
+ * Interface a client of the ITvIAppManager implements, to identify itself and receive information
+ * about changes to the state of each TV interactive application service.
+ * @hide
+ */
+oneway interface ITvIAppClient {
+ void onSessionCreated(in String iAppServiceId, IBinder token, int seq);
+ void onSessionReleased(int seq);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
new file mode 100644
index 0000000..a435e20
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.media.tv.interactive.ITvIAppClient;
+
+/**
+ * Interface to the TV interactive app service.
+ * @hide
+ */
+interface ITvIAppManager {
+ void startIApp(in IBinder sessionToken, int userId);
+ void createSession(
+ in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
+ void releaseSession(in IBinder sessionToken, int userId);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
new file mode 100644
index 0000000..c4f82eb
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.media.tv.interactive.ITvIAppSessionCallback;
+
+/**
+ * Top-level interface to a TV IApp component (implemented in a Service). It's used for
+ * TvIAppManagerService to communicate with TvIAppService.
+ * @hide
+ */
+oneway interface ITvIAppService {
+ void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
new file mode 100644
index 0000000..8d49bc2
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+/**
+ * Helper interface for ITvIAppService to allow the TvIAppService to notify the
+ * TvIAppManagerService.
+ * @hide
+ */
+oneway interface ITvIAppServiceCallback {
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
new file mode 100644
index 0000000..0cbdc8e
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+/**
+ * Sub-interface of ITvIAppService.aidl which is created per session and has its own context.
+ * @hide
+ */
+oneway interface ITvIAppSession {
+ void startIApp();
+ void release();
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
new file mode 100644
index 0000000..b3b317e
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.media.tv.interactive.ITvIAppSession;
+
+/**
+ * Helper interface for ITvIAppSession to allow TvIAppService to notify the system service when
+ * there is a related event.
+ * @hide
+ */
+oneway interface ITvIAppSessionCallback {
+ void onSessionCreated(in ITvIAppSession session);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
new file mode 100644
index 0000000..8b8c8c9
--- /dev/null
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Central system API to the overall TV interactive application framework (TIAF) architecture, which
+ * arbitrates interaction between applications and interactive apps.
+ * @hide
+ */
+@SystemService("tv_interactive_app")
+public final class TvIAppManager {
+ private static final String TAG = "TvIAppManager";
+
+ private final ITvIAppManager mService;
+ private final int mUserId;
+
+ // A mapping from the sequence number of a session to its SessionCallbackRecord.
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+ new SparseArray<>();
+
+ // A sequence number for the next session to be created. Should be protected by a lock
+ // {@code mSessionCallbackRecordMap}.
+ private int mNextSeq;
+
+ private final ITvIAppClient mClient;
+
+ public TvIAppManager(ITvIAppManager service, int userId) {
+ mService = service;
+ mUserId = userId;
+ mClient = new ITvIAppClient.Stub() {
+ @Override
+ public void onSessionCreated(String iAppServiceId, IBinder token, int seq) {
+ // TODO: use InputChannel for input events
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for " + token);
+ return;
+ }
+ Session session = null;
+ if (token != null) {
+ session = new Session(token, mService, mUserId, seq,
+ mSessionCallbackRecordMap);
+ } else {
+ mSessionCallbackRecordMap.delete(seq);
+ }
+ record.postSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ mSessionCallbackRecordMap.delete(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq:" + seq);
+ return;
+ }
+ record.mSession.releaseInternal();
+ record.postSessionReleased();
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates a {@link Session} for a given TV interactive application.
+ *
+ * <p>The number of sessions that can be created at the same time is limited by the capability
+ * of the given interactive application.
+ *
+ * @param iAppServiceId The ID of the interactive application.
+ * @param type the type of the interactive application.
+ * @param callback A callback used to receive the created session.
+ * @param handler A {@link Handler} that the session creation will be delivered to.
+ * @hide
+ */
+ public void createSession(@NonNull String iAppServiceId, int type,
+ @NonNull final SessionCallback callback, @NonNull Handler handler) {
+ createSessionInternal(iAppServiceId, type, callback, handler);
+ }
+
+ private void createSessionInternal(String iAppServiceId, int type, SessionCallback callback,
+ Handler handler) {
+ Preconditions.checkNotNull(iAppServiceId);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
+ synchronized (mSessionCallbackRecordMap) {
+ int seq = mNextSeq++;
+ mSessionCallbackRecordMap.put(seq, record);
+ try {
+ mService.createSession(mClient, iAppServiceId, type, seq, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * The Session provides the per-session functionality of interactive app.
+ */
+ public static final class Session {
+ private final ITvIAppManager mService;
+ private final int mUserId;
+ private final int mSeq;
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
+
+ private IBinder mToken;
+
+ private Session(IBinder token, ITvIAppManager service, int userId, int seq,
+ SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
+ mToken = token;
+ mService = service;
+ mUserId = userId;
+ mSeq = seq;
+ mSessionCallbackRecordMap = sessionCallbackRecordMap;
+ }
+
+ void startIApp() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.startIApp(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Releases this session.
+ */
+ public void release() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.releaseSession(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ releaseInternal();
+ }
+
+ private void releaseInternal() {
+ mToken = null;
+ synchronized (mSessionCallbackRecordMap) {
+ mSessionCallbackRecordMap.delete(mSeq);
+ }
+ }
+ }
+
+ private static final class SessionCallbackRecord {
+ private final SessionCallback mSessionCallback;
+ private final Handler mHandler;
+ private Session mSession;
+
+ SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
+ mSessionCallback = sessionCallback;
+ mHandler = handler;
+ }
+
+ void postSessionCreated(final Session session) {
+ mSession = session;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionCreated(session);
+ }
+ });
+ }
+
+ void postSessionReleased() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionReleased(mSession);
+ }
+ });
+ }
+ }
+
+ /**
+ * Interface used to receive the created session.
+ * @hide
+ */
+ public abstract static class SessionCallback {
+ /**
+ * This is called after {@link TvIAppManager#createSession} has been processed.
+ *
+ * @param session A {@link TvIAppManager.Session} instance created. This can be {@code null}
+ * if the creation request failed.
+ */
+ public void onSessionCreated(@Nullable Session session) {
+ }
+
+ /**
+ * This is called when {@link TvIAppManager.Session} is released.
+ * This typically happens when the process hosting the session has crashed or been killed.
+ *
+ * @param session the {@link TvIAppManager.Session} instance released.
+ */
+ public void onSessionReleased(@NonNull Session session) {
+ }
+ }
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
new file mode 100644
index 0000000..f363728
--- /dev/null
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The TvIAppService class represents a TV interactive applications RTE.
+ * @hide
+ */
+public abstract class TvIAppService extends Service {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvIAppService";
+
+ private final Handler mServiceHandler = new ServiceHandler();
+
+ /**
+ * This is the interface name that a service implementing an environment to run Tv IApp should
+ * say that it support -- that is, this is the action it uses for its intent filter. To be
+ * supported, the service must also require the BIND_TV_IAPP permission so that other
+ * applications cannot abuse it.
+ */
+ public static final String SERVICE_INTERFACE = "android.media.tv.TvIAppService";
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() {
+
+ @Override
+ public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) {
+ if (cb == null) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = cb;
+ args.arg2 = iAppServiceId;
+ args.arg3 = type;
+ mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
+ .sendToTarget();
+ }
+ };
+ return tvIAppServiceBinder;
+ }
+
+
+ /**
+ * Returns a concrete implementation of {@link Session}.
+ *
+ * <p>May return {@code null} if this TV IApp service fails to create a session for some
+ * reason.
+ *
+ * @param iAppServiceId The ID of the TV IApp associated with the session.
+ * @param type The type of the TV IApp associated with the session.
+ */
+ @Nullable
+ public abstract Session onCreateSession(@NonNull String iAppServiceId, int type);
+
+ /**
+ * Base class for derived classes to implement to provide a TV interactive app session.
+ */
+ public abstract static class Session implements KeyEvent.Callback {
+ private final Object mLock = new Object();
+ // @GuardedBy("mLock")
+ private ITvIAppSessionCallback mSessionCallback;
+ // @GuardedBy("mLock")
+ private final List<Runnable> mPendingActions = new ArrayList<>();
+
+ /**
+ * Starts TvIAppService session.
+ */
+ public void onStartIApp() {
+ }
+
+ /**
+ * Releases TvIAppService session.
+ */
+ public void onRelease() {
+ }
+
+ void startIApp() {
+ onStartIApp();
+ }
+ void release() {
+ onRelease();
+ }
+
+ private void initialize(ITvIAppSessionCallback callback) {
+ synchronized (mLock) {
+ mSessionCallback = callback;
+ for (Runnable runnable : mPendingActions) {
+ runnable.run();
+ }
+ mPendingActions.clear();
+ }
+ }
+ }
+
+ /**
+ * Implements the internal ITvIAppSession interface.
+ */
+ public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub {
+ private final Session mSessionImpl;
+
+ public ITvIAppSessionWrapper(Session mSessionImpl) {
+ this.mSessionImpl = mSessionImpl;
+ }
+
+ @Override
+ public void startIApp() {
+ mSessionImpl.startIApp();
+ }
+
+ @Override
+ public void release() {
+ mSessionImpl.release();
+ }
+ }
+
+ @SuppressLint("HandlerLeak")
+ private final class ServiceHandler extends Handler {
+ private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_NOTIFY_SESSION_CREATED = 2;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_CREATE_SESSION: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg1;
+ String iAppServiceId = (String) args.arg2;
+ int type = (int) args.arg3;
+ args.recycle();
+ Session sessionImpl = onCreateSession(iAppServiceId, type);
+ if (sessionImpl == null) {
+ try {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated", e);
+ }
+ return;
+ }
+ ITvIAppSession stub = new ITvIAppSessionWrapper(sessionImpl);
+
+ SomeArgs someArgs = SomeArgs.obtain();
+ someArgs.arg1 = sessionImpl;
+ someArgs.arg2 = stub;
+ someArgs.arg3 = cb;
+ mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
+ someArgs).sendToTarget();
+ return;
+ }
+ case DO_NOTIFY_SESSION_CREATED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Session sessionImpl = (Session) args.arg1;
+ ITvIAppSession stub = (ITvIAppSession) args.arg2;
+ ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg3;
+ try {
+ cb.onSessionCreated(stub);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated", e);
+ }
+ if (sessionImpl != null) {
+ sessionImpl.initialize(cb);
+ }
+ args.recycle();
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
+ }
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java
new file mode 100644
index 0000000..f56ea0a
--- /dev/null
+++ b/media/java/android/media/tv/interactive/TvIAppView.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.content.Context;
+import android.media.tv.interactive.TvIAppManager.Session;
+import android.media.tv.interactive.TvIAppManager.SessionCallback;
+import android.os.Handler;
+import android.util.Log;
+import android.view.ViewGroup;
+
+/**
+ * Displays contents of interactive TV applications.
+ * @hide
+ */
+public class TvIAppView extends ViewGroup {
+ private static final String TAG = "TvIAppView";
+ private static final boolean DEBUG = false;
+
+ private final TvIAppManager mTvIAppManager;
+ private final Handler mHandler = new Handler();
+ private Session mSession;
+ private MySessionCallback mSessionCallback;
+
+ public TvIAppView(Context context) {
+ super(context, /* attrs = */null, /* defStyleAttr = */0);
+ mTvIAppManager = (TvIAppManager) getContext().getSystemService("tv_interactive_app");
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+ }
+ }
+
+ /**
+ * Prepares the interactive application.
+ */
+ public void prepareIApp(String iAppServiceId, int type) {
+ // TODO: document and handle the cases that this method is called multiple times.
+ if (DEBUG) {
+ Log.d(TAG, "prepareIApp");
+ }
+ mSessionCallback = new MySessionCallback(iAppServiceId, type);
+ if (mTvIAppManager != null) {
+ mTvIAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler);
+ }
+ }
+
+ /**
+ * Starts the interactive application.
+ */
+ public void startIApp() {
+ if (DEBUG) {
+ Log.d(TAG, "startIApp");
+ }
+ if (mSession != null) {
+ mSession.startIApp();
+ }
+ }
+
+ private class MySessionCallback extends SessionCallback {
+ final String mIAppServiceId;
+ int mType;
+
+ MySessionCallback(String iAppServiceId, int type) {
+ mIAppServiceId = iAppServiceId;
+ mType = type;
+ }
+
+ @Override
+ public void onSessionCreated(Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionCreated()");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSessionCreated - session already created");
+ // This callback is obsolete.
+ if (session != null) {
+ session.release();
+ }
+ return;
+ }
+ mSession = session;
+ if (session != null) {
+ // TODO: handle SurfaceView and InputChannel.
+ } else {
+ // Failed to create
+ // Todo: forward error to Tv App
+ mSessionCallback = null;
+ }
+ }
+
+ @Override
+ public void onSessionReleased(Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionReleased()");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSessionReleased - session not created");
+ return;
+ }
+ mSessionCallback = null;
+ mSession = null;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b4ae1fb..9dc4949 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -449,6 +449,43 @@
mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
}
+ /**
+ * Checks if there is an unused frontend resource available.
+ *
+ * @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
+ * query to be done for.
+ */
+ public boolean hasUnusedFrontend(int frontendType) {
+ return mTunerResourceManager.hasUnusedFrontend(frontendType);
+ }
+
+ /**
+ * Checks if the calling Tuner object has the lowest priority as a client to
+ * {@link TunerResourceManager}
+ *
+ * <p>The priority comparison is done against the current holders of the frontend resource.
+ *
+ * <p>The behavior of this function is independent of the availability of unused resources.
+ *
+ * <p>The function returns {@code true} in any of the following sceanrios:
+ * <ul>
+ * <li>The caller has the priority <= other clients</li>
+ * <li>No one is holding the frontend resource of the specified type</li>
+ * <li>The caller is the only one who is holding the resource</li>
+ * <li>The frontend resource of the specified type does not exist</li>
+ *
+ * </ul>
+ * @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
+ * query to be done for.
+ *
+ * @return {@code false} only if someone else with strictly lower priority is holding the
+ * resourece.
+ * {@code true} otherwise.
+ */
+ public boolean isLowestPriority(int frontendType) {
+ return mTunerResourceManager.isLowestPriority(mClientId, frontendType);
+ }
+
private long mNativeContext; // used by native jMediaTuner
/**
@@ -1615,4 +1652,9 @@
}
mLnb = null;
}
+
+ /** @hide */
+ public int getClientId() {
+ return mClientId;
+ }
}
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 6f7adbc..244fd0e 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -179,6 +179,96 @@
}
/**
+ * Checks if there is an unused frontend resource available.
+ *
+ * @param frontendType The frontend type for the query to be done for.
+ */
+ public boolean hasUnusedFrontend(int frontendType) {
+ boolean result = false;
+ try {
+ result = mService.hasUnusedFrontend(frontendType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the client has the lowest priority among the clients that are holding
+ * the frontend resource of the specified type.
+ *
+ * <p> When this function returns false, it means that there is at least one client with the
+ * strictly lower priority (than clientId) that is reclaimable by the system.
+ *
+ * @param clientId The client ID to be checked the priority for.
+ * @param frontendType The specific frontend type to be checked for.
+ *
+ * @return false if there is another client holding the frontend resource of the specified type
+ * that can be reclaimed. Otherwise true.
+ */
+ public boolean isLowestPriority(int clientId, int frontendType) {
+ boolean result = false;
+ try {
+ result = mService.isLowestPriority(clientId, frontendType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Stores the frontend resource map if it was stored before.
+ *
+ * <p>This API is only for testing purpose and should be used in pair with
+ * restoreResourceMap(), which allows testing of {@link Tuner} APIs
+ * that behave differently based on different sets of resource map.
+ *
+ * @param resourceType The resource type to store the map for.
+ */
+ public void storeResourceMap(int resourceType) {
+ try {
+ mService.storeResourceMap(resourceType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears the frontend resource map.
+ *
+ * <p>This API is only for testing purpose and should be called right after
+ * storeResourceMap(), so TRMService#removeFrontendResource() does not
+ * get called in TRMService#setFrontendInfoListInternal() for custom frontend
+ * resource map creation.
+ *
+ * @param resourceType The resource type to clear the map for.
+ */
+ public void clearResourceMap(int resourceType) {
+ try {
+ mService.clearResourceMap(resourceType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Restores Frontend resource map for the later restore.
+ *
+ * <p>This API is only for testing purpose and should be used in pair with
+ * storeResourceMap(), which allows testing of {@link Tuner} APIs
+ * that behave differently based on different sets of resource map.
+ *
+ * @param resourceType The resource type to restore the map for.
+ */
+ public void restoreResourceMap(int resourceType) {
+ try {
+ mService.restoreResourceMap(resourceType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Updates the current TRM of the TunerHAL Frontend information.
*
* <p><strong>Note:</strong> This update must happen before the first
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index a1f6687..7bc5058 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -83,6 +83,30 @@
boolean updateClientPriority(in int clientId, in int priority, in int niceValue);
/*
+ * Checks if there is any unused frontend resource of the specified type.
+ *
+ * @param frontendType the specific type of frontend resource to be checked for.
+ *
+ * @return true if there is any unused resource of the specified type.
+ */
+ boolean hasUnusedFrontend(in int frontendType);
+
+ /*
+ * Checks if the client has the lowest priority among the clients that are holding
+ * the frontend resource of the specified type.
+ *
+ * <p> When this function returns false, it means that there is at least one client with the
+ * strictly lower priority (than clientId) that is reclaimable by the system.
+ *
+ * @param clientId The client ID to be checked the priority for.
+ * @param frontendType The specific frontend type to be checked for.
+ *
+ * @return false if there is another client holding the frontend resource of the specified type
+ * that can be reclaimed. Otherwise true.
+ */
+ boolean isLowestPriority(in int clientId, in int frontendType);
+
+ /*
* Updates the available Frontend resources information on the current device.
*
* <p><strong>Note:</strong> This update must happen before the first
@@ -354,4 +378,38 @@
*/
boolean isHigherPriority(in ResourceClientProfile challengerProfile,
in ResourceClientProfile holderProfile);
+
+ /*
+ * Stores Frontend resource map for the later restore.
+ *
+ * <p>This is API is only for testing purpose and should be used in pair with
+ * restoreResourceMap(), which allows testing of {@link Tuner} APIs
+ * that behave differently based on different sets of resource map.
+ *
+ * @param resourceType The resource type to store the map for.
+ */
+ void storeResourceMap(in int resourceType);
+
+ /*
+ * Clears the frontend resource map.
+ *
+ * <p>This is API is only for testing purpose and should be called right after
+ * storeResourceMap(), so TRMService#removeFrontendResource() does not
+ * get called in TRMService#setFrontendInfoListInternal() for custom frontend
+ * resource map creation.
+ *
+ * @param resourceType The resource type to clear the map for.
+ */
+ void clearResourceMap(in int resourceType);
+
+ /*
+ * Restores Frontend resource map if it was stored before.
+ *
+ * <p>This is API is only for testing purpose and should be used in pair with
+ * storeResourceMap(), which allows testing of {@link Tuner} APIs
+ * that behave differently based on different sets of resource map.
+ *
+ * @param resourceType The resource type to restore the map for.
+ */
+ void restoreResourceMap(in int resourceType);
}
diff --git a/packages/PrintSpooler/res/values-pa/strings.xml b/packages/PrintSpooler/res/values-pa/strings.xml
index 601fa83..ddcec40 100644
--- a/packages/PrintSpooler/res/values-pa/strings.xml
+++ b/packages/PrintSpooler/res/values-pa/strings.xml
@@ -50,8 +50,8 @@
<string name="search" msgid="5421724265322228497">"ਖੋਜੋ"</string>
<string name="all_printers_label" msgid="3178848870161526399">"ਸਾਰੇ ਪ੍ਰਿੰਟਰ"</string>
<string name="add_print_service_label" msgid="5356702546188981940">"ਸੇਵਾ ਸ਼ਾਮਲ ਕਰੋ"</string>
- <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ਖੋਜ ਬਾਕਸ ਦਿਖਾਇਆ"</string>
- <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ਖੋਜ ਬਾਕਸ ਲੁਕਾਇਆ"</string>
+ <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ਖੋਜ ਬਾਕਸ ਦਿਖਾਇਆ ਗਿਆ"</string>
+ <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ਖੋਜ ਬਾਕਸ ਲੁਕਾਇਆ ਗਿਆ"</string>
<string name="print_add_printer" msgid="1088656468360653455">"ਪ੍ਰਿੰਟਰ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="print_select_printer" msgid="7388760939873368698">"ਪ੍ਰਿੰਟਰ ਚੁਣੋ"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"ਪ੍ਰਿੰਟਰ ਭੁੱਲੋ"</string>
diff --git a/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml b/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml
new file mode 100644
index 0000000..5b52a04
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index ccdfec3..475ce35 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -191,11 +191,11 @@
<string name="tts_default_lang_title" msgid="4698933575028098940">"ਭਾਸ਼ਾ"</string>
<string name="tts_lang_use_system" msgid="6312945299804012406">"ਸਿਸਟਮ ਭਾਸ਼ਾ ਵਰਤੋ"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"ਭਾਸ਼ਾ ਨਹੀਂ ਚੁਣੀ"</string>
- <string name="tts_default_lang_summary" msgid="9042620014800063470">"ਬੋਲੇ ਗਏ ਟੈਕਸਟ ਲਈ ਭਾਸ਼ਾ-ਵਿਸ਼ੇਸ਼ ਵੌਇਸ ਸੈਟ ਕਰਦਾ ਹੈ"</string>
+ <string name="tts_default_lang_summary" msgid="9042620014800063470">"ਬੋਲੀ ਗਈ ਲਿਖਤ ਲਈ ਭਾਸ਼ਾ-ਵਿਸ਼ੇਸ਼ ਅਵਾਜ਼ ਸੈੱਟ ਕਰਦਾ ਹੈ"</string>
<string name="tts_play_example_title" msgid="1599468547216481684">"ਇੱਕ ਉਦਾਹਰਨ ਲਈ ਸੁਣੋ"</string>
<string name="tts_play_example_summary" msgid="634044730710636383">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਦਾ ਇੱਕ ਛੋਟਾ ਪ੍ਰਦਰਸ਼ਨ ਪਲੇ ਕਰੋ"</string>
- <string name="tts_install_data_title" msgid="1829942496472751703">"ਵੌਇਸ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
- <string name="tts_install_data_summary" msgid="3608874324992243851">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਲਈ ਲੋੜੀਂਦਾ ਵੌਇਸ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="tts_install_data_title" msgid="1829942496472751703">"ਅਵਾਜ਼ੀ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="tts_install_data_summary" msgid="3608874324992243851">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਲਈ ਲੋੜੀਂਦਾ ਅਵਾਜ਼ੀ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
<string name="tts_engine_security_warning" msgid="3372432853837988146">"ਇਹ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਇੰਜਣ ਉਹ ਸਭ ਲਿਖਤ ਇਕੱਤਰ ਕਰਨ ਵਿੱਚ ਸਮਰੱਥ ਹੋ ਸਕਦਾ ਹੈ, ਜੋ ਬੋਲਿਆ ਜਾਏਗਾ, ਨਿੱਜੀ ਡਾਟਾ ਸਮੇਤ ਜਿਵੇਂ ਪਾਸਵਰਡ ਅਤੇ ਕ੍ਰੈਡਿਟ ਕਾਰਡ ਨੰਬਰ। ਇਹ <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> ਇੰਜਣ ਤੋਂ ਆਉਂਦਾ ਹੈ। ਕੀ ਇਸ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਇੰਜਣ ਦੀ ਵਰਤੋਂ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈੈ?"</string>
<string name="tts_engine_network_required" msgid="8722087649733906851">"ਇਸ ਭਾਸ਼ਾ ਲਈ ਲਿਖਤ ਤੋਂ ਬੋਲੀ ਆਊਟਪੁੱਟ ਲਈ ਇੱਕ ਚਾਲੂ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨ ਦੀ ਲੋੜ ਹੈ।"</string>
<string name="tts_default_sample_string" msgid="6388016028292967973">"ਇਹ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਦਾ ਇੱਕ ਉਦਾਹਰਨ ਹੈ"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cab7cee..8e20e02 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -257,8 +257,12 @@
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing Aid profile. -->
<string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the LE_AUDIO profile. -->
+ <string name="bluetooth_profile_le_audio">LE_AUDIO</string>
<!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
<string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the LE_AUDIO checkbox preference when LE_AUDIO is connected. -->
+ <string name="bluetooth_le_audio_profile_summary_connected">Connected to LE_AUDIO</string>
<!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. -->
<string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
@@ -299,6 +303,8 @@
<string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
<!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. -->
<string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the LE_AUDIO checkbox preference that describes how checking it will set the LE_AUDIO profile as preferred. -->
+ <string name="bluetooth_le_audio_profile_summary_use_for">Use for LE_AUDIO</string>
<!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] -->
<string name="bluetooth_pairing_accept">Pair</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index c4cbc2b..5f2bef7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -16,10 +16,15 @@
package com.android.settingslib.applications;
+import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+/**
+ * A class for applying config changes and determing if doing so resulting in any "interesting"
+ * changes.
+ */
public class InterestingConfigChanges {
private final Configuration mLastConfiguration = new Configuration();
private final int mFlags;
@@ -35,6 +40,14 @@
mFlags = flags;
}
+ /**
+ * Applies the given config change and returns whether an "interesting" change happened.
+ *
+ * @param res The source of the new config to apply
+ *
+ * @return Whether interesting changes occurred
+ */
+ @SuppressLint("NewApi")
public boolean applyNewConfig(Resources res) {
int configChanges = mLastConfiguration.updateFrom(
Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 8750309..58d2185 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -22,6 +22,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -116,6 +117,8 @@
addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
new ActiveDeviceChangedHandler());
+ addHandler(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED,
+ new ActiveDeviceChangedHandler());
// Headset state changed broadcasts
addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
@@ -127,9 +130,6 @@
addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());
- addHandler(BluetoothCsipSetCoordinator.ACTION_CSIS_SET_MEMBER_AVAILABLE,
- new SetMemberAvailableHandler());
-
registerAdapterIntentReceiver();
}
@@ -455,6 +455,9 @@
bluetoothProfile = BluetoothProfile.HEADSET;
} else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) {
bluetoothProfile = BluetoothProfile.HEARING_AID;
+ } else if (Objects.equals(action,
+ BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED)) {
+ bluetoothProfile = BluetoothProfile.LE_AUDIO;
} else {
Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
return;
@@ -515,29 +518,4 @@
dispatchAudioModeChanged();
}
}
-
- private class SetMemberAvailableHandler implements Handler {
- @Override
- public void onReceive(Context context, Intent intent, BluetoothDevice device) {
- final String action = intent.getAction();
- if (device == null) {
- Log.e(TAG, "SetMemberAvailableHandler: device is null");
- return;
- }
-
- if (action == null) {
- Log.e(TAG, "SetMemberAvailableHandler: action is null");
- return;
- }
-
- final int groupId = intent.getIntExtra(BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_ID,
- BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.e(TAG, "SetMemberAvailableHandler: Invalid group id");
- return;
- }
-
- mDeviceManager.onSetMemberAppear(device, groupId);
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 7272a66..912b468 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
@@ -109,11 +110,14 @@
private boolean mIsActiveDeviceA2dp = false;
private boolean mIsActiveDeviceHeadset = false;
private boolean mIsActiveDeviceHearingAid = false;
+ private boolean mIsActiveDeviceLeAudio = false;
// Media profile connect state
private boolean mIsA2dpProfileConnectedFail = false;
private boolean mIsHeadsetProfileConnectedFail = false;
private boolean mIsHearingAidProfileConnectedFail = false;
+ private boolean mIsLeAudioProfileConnectedFail = false;
private boolean mUnpairing;
+
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
// Group member devices for the coordinated set
@@ -134,6 +138,9 @@
case BluetoothProfile.HEARING_AID:
mIsHearingAidProfileConnectedFail = true;
break;
+ case BluetoothProfile.LE_AUDIO:
+ mIsLeAudioProfileConnectedFail = true;
+ break;
default:
Log.w(TAG, "handleMessage(): unknown message : " + msg.what);
break;
@@ -268,6 +275,9 @@
case BluetoothProfile.HEARING_AID:
mIsHearingAidProfileConnectedFail = isFailed;
break;
+ case BluetoothProfile.LE_AUDIO:
+ mIsLeAudioProfileConnectedFail = isFailed;
+ break;
default:
Log.w(TAG, "setProfileConnectedStatus(): unknown profile id : " + profileId);
break;
@@ -544,6 +554,13 @@
result = true;
}
}
+ LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile();
+ if ((leAudioProfile != null) && isConnectedProfile(leAudioProfile)) {
+ if (leAudioProfile.setActiveDevice(getDevice())) {
+ Log.i(TAG, "OnPreferenceClickListener: LeAudio active device=" + this);
+ result = true;
+ }
+ }
return result;
}
@@ -622,6 +639,10 @@
changed = (mIsActiveDeviceHearingAid != isActive);
mIsActiveDeviceHearingAid = isActive;
break;
+ case BluetoothProfile.LE_AUDIO:
+ changed = (mIsActiveDeviceLeAudio != isActive);
+ mIsActiveDeviceLeAudio = isActive;
+ break;
default:
Log.w(TAG, "onActiveDeviceChanged: unknown profile " + bluetoothProfile +
" isActive " + isActive);
@@ -653,6 +674,8 @@
return mIsActiveDeviceHeadset;
case BluetoothProfile.HEARING_AID:
return mIsActiveDeviceHearingAid;
+ case BluetoothProfile.LE_AUDIO:
+ return mIsActiveDeviceLeAudio;
default:
Log.w(TAG, "getActiveDevice: unknown profile " + bluetoothProfile);
break;
@@ -747,6 +770,10 @@
if (hearingAidProfile != null) {
mIsActiveDeviceHearingAid = hearingAidProfile.getActiveDevices().contains(mDevice);
}
+ LeAudioProfile leAudio = mProfileManager.getLeAudioProfile();
+ if (leAudio != null) {
+ mIsActiveDeviceLeAudio = leAudio.getActiveDevices().contains(mDevice);
+ }
}
/**
@@ -987,6 +1014,7 @@
boolean a2dpConnected = true; // A2DP is connected
boolean hfpConnected = true; // HFP is connected
boolean hearingAidConnected = true; // Hearing Aid is connected
+ boolean leAudioConnected = true; // LeAudio is connected
int leftBattery = -1;
int rightBattery = -1;
@@ -1018,6 +1046,8 @@
hfpConnected = false;
} else if (profile instanceof HearingAidProfile) {
hearingAidConnected = false;
+ } else if (profile instanceof LeAudioProfile) {
+ leAudioConnected = false;
}
}
break;
@@ -1060,7 +1090,8 @@
// 1. Hearing Aid device active.
// 2. Headset device active with in-calling state.
// 3. A2DP device active without in-calling state.
- if (a2dpConnected || hfpConnected || hearingAidConnected) {
+ // 4. Le Audio device active
+ if (a2dpConnected || hfpConnected || hearingAidConnected || leAudioConnected) {
final boolean isOnCall = Utils.isAudioModeOngoingCall(mContext);
if ((mIsActiveDeviceHearingAid)
|| (mIsActiveDeviceHeadset && isOnCall)
@@ -1095,7 +1126,8 @@
private boolean isProfileConnectedFail() {
return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail
- || (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail);
+ || (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail)
+ || mIsLeAudioProfileConnectedFail;
}
/**
@@ -1106,6 +1138,7 @@
boolean a2dpNotConnected = false; // A2DP is preferred but not connected
boolean hfpNotConnected = false; // HFP is preferred but not connected
boolean hearingAidNotConnected = false; // Hearing Aid is preferred but not connected
+ boolean leAudioNotConnected = false; // LeAudio is preferred but not connected
synchronized (mProfileLock) {
for (LocalBluetoothProfile profile : getProfiles()) {
@@ -1131,6 +1164,8 @@
hfpNotConnected = true;
} else if (profile instanceof HearingAidProfile) {
hearingAidNotConnected = true;
+ } else if (profile instanceof LeAudioProfile) {
+ leAudioNotConnected = true;
}
}
break;
@@ -1169,6 +1204,11 @@
return mContext.getString(R.string.bluetooth_connected, activeDeviceString);
}
+ if (!leAudioNotConnected && mIsActiveDeviceLeAudio) {
+ activeDeviceString = activeDeviceStringsArray[1];
+ return mContext.getString(R.string.bluetooth_connected, activeDeviceString);
+ }
+
if (profileConnected) {
if (a2dpNotConnected && hfpNotConnected) {
if (batteryLevelPercentageString != null) {
@@ -1238,6 +1278,15 @@
BluetoothProfile.STATE_CONNECTED;
}
+ /**
+ * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio device
+ */
+ public boolean isConnectedLeAudioDevice() {
+ LeAudioProfile leAudio = mProfileManager.getLeAudioProfile();
+ return leAudio != null && leAudio.getConnectionStatus(mDevice) ==
+ BluetoothProfile.STATE_CONNECTED;
+ }
+
private boolean isConnectedSapDevice() {
SapProfile sapProfile = mProfileManager.getSapProfile();
return sapProfile != null && sapProfile.getConnectionStatus(mDevice)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 1f75ae3..b429fe6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -340,22 +340,24 @@
/**
* Called when we found a set member of a group. The function will check the {@code groupId} if
- * it exists and if there is a ongoing pair, the device would be ignored.
+ * it exists and the bond state of the device is BOND_NOE, and if there isn't any ongoing pair
+ * , and then return {@code true} to pair the device automatically.
*
* @param device The found device
* @param groupId The group id of the found device
+ *
+ * @return {@code true}, if the device should pair automatically; Otherwise, return
+ * {@code false}.
*/
- public synchronized void onSetMemberAppear(BluetoothDevice device, int groupId) {
- Log.d(TAG, "onSetMemberAppear, groupId: " + groupId + " device: " + device.toString());
-
- if (mOngoingSetMemberPair != null) {
- Log.d(TAG, "Ongoing set memberPairing in process, drop it!");
- return;
+ public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
+ if (mOngoingSetMemberPair != null || device.getBondState() != BluetoothDevice.BOND_NONE
+ || !mCsipDeviceManager.isExistedGroupId(groupId)) {
+ return false;
}
- if (mCsipDeviceManager.onSetMemberAppear(device, groupId)) {
- mOngoingSetMemberPair = device;
- }
+ Log.d(TAG, "Bond " + device.getName() + " by CSIP");
+ mOngoingSetMemberPair = device;
+ return true;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 347e14b..1d29966 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -240,22 +240,15 @@
}
/**
- * Called when we found a set member of a group. The function will check bond state, and
- * the {@code groupId} if it exists, and then create the bond.
+ * Check if the {@code groupId} is existed.
*
- * @param device The found device
- * @param groupId The group id of the found device
+ * @param groupId The group id
*
- * @return {@code true}, if the we create bond with the device. Otherwise, return
- * {@code false}.
+ * @return {@code true}, if we could find a device with this {@code groupId}; Otherwise,
+ * return {@code false}.
*/
- public boolean onSetMemberAppear(BluetoothDevice device, int groupId) {
- if (device.getBondState() != BluetoothDevice.BOND_NONE) {
- return false;
- }
-
+ public boolean isExistedGroupId(int groupId) {
if (getCachedDevice(groupId) != null) {
- device.createBond(BluetoothDevice.TRANSPORT_LE);
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
new file mode 100644
index 0000000..209507a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -0,0 +1,264 @@
+/* Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA
+- www.ehima.com
+*/
+
+/* 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.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.Build;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class LeAudioProfile implements LocalBluetoothProfile {
+ private static final String TAG = "LeAudioProfile";
+ private static boolean DEBUG = true;
+
+ private Context mContext;
+
+ private BluetoothLeAudio mService;
+ private boolean mIsProfileReady;
+
+ private final CachedBluetoothDeviceManager mDeviceManager;
+
+ static final String NAME = "LE_AUDIO";
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final BluetoothAdapter mBluetoothAdapter;
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 1;
+
+ // These callbacks run on the main thread.
+ private final class LeAudioServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG,"Bluetooth service connected");
+ }
+ mService = (BluetoothLeAudio) proxy;
+ // We just bound to the service, so refresh the UI for any connected LeAudio devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ if (DEBUG) {
+ Log.d(TAG, "LeAudioProfile found new device: " + nextDevice);
+ }
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(LeAudioProfile.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady = true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) {
+ Log.d(TAG,"Bluetooth service disconnected");
+ }
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady = false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ @Override
+ public int getProfileId() {
+ return BluetoothProfile.LE_AUDIO;
+ }
+
+ LeAudioProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mContext = context;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mBluetoothAdapter.getProfileProxy(
+ context, new LeAudioServiceListener(),
+ BluetoothProfile.LE_AUDIO);
+ }
+
+ public boolean accessProfileEnabled() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ /*
+ * @hide
+ */
+ public boolean connect(BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.connect(device);
+ }
+
+ /*
+ * @hide
+ */
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.disconnect(device);
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ public boolean setActiveDevice(BluetoothDevice device) {
+ if (mBluetoothAdapter == null) {
+ return false;
+ }
+ return device == null
+ ? mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_ALL)
+ : mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_ALL);
+ }
+
+ public List<BluetoothDevice> getActiveDevices() {
+ if (mService == null) {
+ return new ArrayList<>();
+ }
+ return mService.getActiveDevices();
+ }
+
+ @Override
+ public boolean isEnabled(BluetoothDevice device) {
+ if (mService == null || device == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+ }
+
+ @Override
+ public int getConnectionPolicy(BluetoothDevice device) {
+ if (mService == null || device == null) {
+ return CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
+ }
+
+ @Override
+ public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+ boolean isEnabled = false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ if (enabled) {
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ }
+ } else {
+ isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ }
+
+ return isEnabled;
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_le_audio;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_le_audio_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_le_audio_profile_summary_connected;
+
+ default:
+ return BluetoothUtils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_le_audio;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ protected void finalize() {
+ if (DEBUG) {
+ Log.d(TAG, "finalize()");
+ }
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.LE_AUDIO,
+ mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up LeAudio proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index bcb3455..3347920 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -24,6 +24,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothHidDevice;
import android.bluetooth.BluetoothHidHost;
import android.bluetooth.BluetoothMap;
@@ -102,6 +103,7 @@
private PbapServerProfile mPbapProfile;
private HearingAidProfile mHearingAidProfile;
private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
+ private LeAudioProfile mLeAudioProfile;
private SapProfile mSapProfile;
private VolumeControlProfile mVolumeControlProfile;
@@ -232,6 +234,14 @@
// Note: no event handler for VCP, only for being connectable.
mProfileNameMap.put(VolumeControlProfile.NAME, mVolumeControlProfile);
}
+ if (mLeAudioProfile == null && supportedList.contains(BluetoothProfile.LE_AUDIO)) {
+ if (DEBUG) {
+ Log.d(TAG, "Adding local LE_AUDIO profile");
+ }
+ mLeAudioProfile = new LeAudioProfile(mContext, mDeviceManager, this);
+ addProfile(mLeAudioProfile, LeAudioProfile.NAME,
+ BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+ }
if (mCsipSetCoordinatorProfile == null
&& supportedList.contains(BluetoothProfile.CSIP_SET_COORDINATOR)) {
if (DEBUG) {
@@ -487,6 +497,10 @@
return mHearingAidProfile;
}
+ public LeAudioProfile getLeAudioProfile() {
+ return mLeAudioProfile;
+ }
+
SapProfile getSapProfile() {
return mSapProfile;
}
@@ -614,6 +628,11 @@
removedProfiles.remove(mHearingAidProfile);
}
+ if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
+ profiles.add(mLeAudioProfile);
+ removedProfiles.remove(mLeAudioProfile);
+ }
+
if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) {
profiles.add(mSapProfile);
removedProfiles.remove(mSapProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index dd8f604..aede665 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -21,6 +21,7 @@
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
@@ -482,6 +483,7 @@
break;
case TYPE_HEARING_AID:
case TYPE_BLUETOOTH_A2DP:
+ case TYPE_BLE_HEADSET:
final BluetoothDevice device =
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress());
final CachedBluetoothDevice cachedDevice =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 22001c9..865c2f0b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -38,6 +38,7 @@
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import com.android.settingslib.bluetooth.LeAudioProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -440,6 +441,7 @@
private boolean isActiveDevice(CachedBluetoothDevice device) {
boolean isActiveDeviceA2dp = false;
boolean isActiveDeviceHearingAid = false;
+ boolean isActiveLeAudio = false;
final A2dpProfile a2dpProfile = mLocalBluetoothManager.getProfileManager().getA2dpProfile();
if (a2dpProfile != null) {
isActiveDeviceA2dp = device.getDevice().equals(a2dpProfile.getActiveDevice());
@@ -453,7 +455,15 @@
}
}
- return isActiveDeviceA2dp || isActiveDeviceHearingAid;
+ if (!isActiveDeviceA2dp && !isActiveDeviceHearingAid) {
+ final LeAudioProfile leAudioProfile = mLocalBluetoothManager.getProfileManager()
+ .getLeAudioProfile();
+ if (leAudioProfile != null) {
+ isActiveLeAudio = leAudioProfile.getActiveDevices().contains(device.getDevice());
+ }
+ }
+
+ return isActiveDeviceA2dp || isActiveDeviceHearingAid || isActiveLeAudio;
}
private Collection<DeviceCallback> getCallbacks() {
@@ -479,9 +489,9 @@
@Override
public void onDeviceListAdded(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
+ Collections.sort(devices, COMPARATOR);
mMediaDevices.clear();
mMediaDevices.addAll(devices);
- Collections.sort(devices, COMPARATOR);
// Add disconnected bluetooth devices only when phone output device is available.
for (MediaDevice device : devices) {
final int type = device.getDeviceType();
@@ -526,7 +536,7 @@
if (cachedDevice != null) {
if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
&& !cachedDevice.isConnected()
- && isA2dpOrHearingAidDevice(cachedDevice)) {
+ && isMediaDevice(cachedDevice)) {
deviceCount++;
cachedBluetoothDeviceList.add(cachedDevice);
if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) {
@@ -550,9 +560,10 @@
return new ArrayList<>(mDisconnectedMediaDevices);
}
- private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) {
+ private boolean isMediaDevice(CachedBluetoothDevice device) {
for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
- if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) {
+ if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile ||
+ profile instanceof LeAudioProfile) {
return true;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index f21c359..a49d7f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -29,6 +29,7 @@
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -122,6 +123,7 @@
break;
case TYPE_HEARING_AID:
case TYPE_BLUETOOTH_A2DP:
+ case TYPE_BLE_HEADSET:
mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
break;
case TYPE_UNKNOWN:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
index e887c450..f50802a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
@@ -52,6 +52,7 @@
when(mDevice.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+ when(mDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true);
mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null);
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index a9bc3be..713faaa 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -242,9 +242,6 @@
<!-- Default for Settings.Secure.AWARE_LOCK_ENABLED -->
<bool name="def_aware_lock_enabled">false</bool>
- <!-- Default for setting for Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED -->
- <bool name="def_hdmiControlAutoDeviceOff">true</bool>
-
<!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
<bool name="def_swipe_bottom_to_notification_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index cbee982..5d75d4f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -800,15 +800,6 @@
GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_SPHAL_LIBRARIES);
p.end(gpuToken);
- final long hdmiToken = p.start(GlobalSettingsProto.HDMI);
- dumpSetting(s, p,
- Settings.Global.HDMI_CONTROL_ENABLED,
- GlobalSettingsProto.Hdmi.CONTROL_ENABLED);
- dumpSetting(s, p,
- Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
- GlobalSettingsProto.Hdmi.CONTROL_AUTO_DEVICE_OFF_ENABLED);
- p.end(hdmiToken);
-
dumpSetting(s, p,
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ebb9e85..ea46ef1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4948,16 +4948,7 @@
if (currentVersion == 190) {
// Version 190: get HDMI auto device off from overlay
- final SettingsState globalSettings = getGlobalSettingsLocked();
- final Setting currentSetting = globalSettings.getSettingLocked(
- Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED);
- if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
- Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
- getContext().getResources().getBoolean(
- R.bool.def_hdmiControlAutoDeviceOff) ? "1" : "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
+ // HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED settings option was removed
currentVersion = 191;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3c700dc..82012d9 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -299,8 +299,6 @@
Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
Settings.Global.GNSS_SATELLITE_BLOCKLIST,
Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
- Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
- Settings.Global.HDMI_CONTROL_ENABLED,
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
Settings.Global.HIDDEN_API_POLICY,
Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 7d06620..41193e6a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -583,6 +583,9 @@
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE" />
+ <!-- Permission required to run GtsAssistantTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 8d02af1..95ce423 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -149,6 +149,9 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
+ <!-- Communal mode -->
+ <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
+
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
@@ -183,6 +186,9 @@
<permission android:name="com.android.systemui.permission.PLUGIN"
android:protectionLevel="signature" />
+ <permission android:name="com.android.systemui.permission.FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
@@ -209,8 +215,11 @@
<!-- TV picture-in-picture -->
<uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" />
- <!-- DND access -->
+ <!-- notifications & DND access -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
+ <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
+ <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index f7e0d58..3ccf5e4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -209,8 +209,13 @@
val heightRatio = state.height.toFloat() / ghostedViewState.height
val scale = min(widthRatio, heightRatio)
+ if (ghostedView.parent is ViewGroup) {
+ // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted
+ // view is still attached to a ViewGroup, otherwise calculateMatrix will throw.
+ GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
+ }
+
launchContainer.getLocationOnScreen(launchContainerLocation)
- GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
ghostViewMatrix.postScale(
scale, scale,
ghostedViewState.centerX - launchContainerLocation[0],
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index a3d924f..8063483 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -197,26 +197,6 @@
return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
}
- /**
- * Interpolate alpha for notifications background scrim during shade expansion.
- * @param fraction Shade expansion fraction
- * @param forUiContent If we want the alpha of the scrims, or ui that's on top of them.
- */
- public static float getNotificationScrimAlpha(float fraction, boolean forUiContent) {
- if (forUiContent) {
- fraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction);
- } else {
- fraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction);
- }
- fraction = fraction * 1.2f - 0.2f;
- if (fraction <= 0) {
- return 0;
- } else {
- final float oneMinusFrac = 1f - fraction;
- return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * oneMinusFrac * oneMinusFrac)));
- }
- }
-
// Create the default emphasized interpolator
private static PathInterpolator createEmphasizedInterpolator() {
Path path = new Path();
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
new file mode 100644
index 0000000..0ee2bfe
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
@@ -0,0 +1,37 @@
+package com.android.systemui.animation
+
+import android.util.MathUtils
+
+object ShadeInterpolation {
+
+ /**
+ * Interpolate alpha for notification background scrim during shade expansion.
+ * @param fraction Shade expansion fraction
+ */
+ @JvmStatic
+ fun getNotificationScrimAlpha(fraction: Float): Float {
+ val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction)
+ return interpolateEaseInOut(mappedFraction)
+ }
+
+ /**
+ * Interpolate alpha for shade content during shade expansion.
+ * @param fraction Shade expansion fraction
+ */
+ @JvmStatic
+ fun getContentAlpha(fraction: Float): Float {
+ val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction)
+ return interpolateEaseInOut(mappedFraction)
+ }
+
+ private fun interpolateEaseInOut(fraction: Float): Float {
+ val mappedFraction = fraction * 1.2f - 0.2f
+ return if (mappedFraction <= 0) {
+ 0f
+ } else {
+ val oneMinusFrac = 1f - mappedFraction
+ (1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble())))
+ .toFloat()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index e026012..1844288 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -249,6 +249,9 @@
val population = populationByColor[entry.key]!!
val cam = camByColor[entry.key]!!
val hue = cam.hue.roundToInt() % 360
+ if (cam.chroma <= MIN_CHROMA) {
+ continue
+ }
huePopulation[hue] = huePopulation[hue] + population
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
index d5b9243..fa60bc9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -16,8 +16,113 @@
package com.android.systemui.flags;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* List of {@link Flag} objects for use in SystemUI.
+ *
+ * Flag Ids are integers.
+ * Ids must be unique. This is enforced in a unit test.
+ * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related featurs with
+ * a comment. This is purely for organizational purposes.
+ *
+ * On public release builds, flags will always return their default value. There is no way to
+ * change their value on release builds.
+ *
+ * See {@link FeatureFlagManager} for instructions on flipping the flags via adb.
*/
public class Flags {
+ public static final BooleanFlag TEAMFOOD = new BooleanFlag(1, false);
+
+ /***************************************/
+ // 100 - notification
+ public static final BooleanFlag NEW_NOTIFICATION_PIPELINE =
+ new BooleanFlag(100, true);
+
+ public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING =
+ new BooleanFlag(101, false);
+
+ public static final BooleanFlag NOTIFICATION_UPDATES =
+ new BooleanFlag(102, true);
+
+ /***************************************/
+ // 200 - keyguard/lockscreen
+ public static final BooleanFlag KEYGUARD_LAYOUT =
+ new BooleanFlag(200, true);
+
+ public static final BooleanFlag LOCKSCREEN_ANIMATIONS =
+ new BooleanFlag(201, true);
+
+ public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
+ new BooleanFlag(202, true);
+
+ /***************************************/
+ // 300 - power menu
+ public static final BooleanFlag POWER_MENU_LITE =
+ new BooleanFlag(300, true);
+
+ /***************************************/
+ // 400 - smartspace
+ public static final BooleanFlag SMARTSPACE_DEDUPING =
+ new BooleanFlag(400, true);
+
+ public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
+ new BooleanFlag(401, false);
+
+ /***************************************/
+ // 500 - quick settings
+ public static final BooleanFlag NEW_USER_SWITCHER =
+ new BooleanFlag(500, true);
+
+ /***************************************/
+ // 600- status bar
+ public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
+ new BooleanFlag(501, false);
+
+ /***************************************/
+ // 700 - dialer/calls
+ public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
+ new BooleanFlag(600, true);
+
+ public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE =
+ new BooleanFlag(601, true);
+
+ public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
+ new BooleanFlag(602, true);
+
+ // Pay no attention to the reflection behind the curtain.
+ // ========================== Curtain ==========================
+ // | |
+ // | . . . . . . . . . . . . . . . . . . . |
+ private static Map<Integer, Flag<?>> sFlagMap;
+ static Map<Integer, Flag<?>> collectFlags() {
+ if (sFlagMap != null) {
+ return sFlagMap;
+ }
+ Map<Integer, Flag<?>> flags = new HashMap<>();
+
+ Field[] fields = Flags.class.getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ Flag<?> flag = (Flag<?>) field.get(null);
+ flags.put(flag.getId(), flag);
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ sFlagMap = flags;
+
+ return sFlagMap;
+ }
+ // | . . . . . . . . . . . . . . . . . . . |
+ // | |
+ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+
}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 4adb546..6114728 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -37,9 +37,6 @@
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
--keep class com.android.systemui.util.InjectionInflationController$ViewInstanceCreator {
- *;
-}
-keep class androidx.core.app.CoreComponentFactory
-keep public class * extends com.android.systemui.SystemUI {
diff --git a/packages/SystemUI/res/anim/fp_to_unlock.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml
similarity index 87%
rename from packages/SystemUI/res/anim/fp_to_unlock.xml
rename to packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml
index a5f75b6..b93ccc6 100644
--- a/packages/SystemUI/res/anim/fp_to_unlock.xml
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml
@@ -19,15 +19,15 @@
<group android:name="_R_G">
<group android:name="_R_G_L_1_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
<group android:name="_R_G_L_1_G" android:translateX="30.75" android:translateY="25.75">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
- <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
- <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
- <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
+ <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
+ <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
+ <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
</group>
</group>
<group android:name="_R_G_L_0_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
<group android:name="_R_G_L_0_G" android:translateX="47.357" android:translateY="53.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
- <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#b7f29f" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
</group>
</group>
</group>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml b/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml
new file mode 100644
index 0000000..2063d21
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G_L_0_G" android:translateX="3.75" android:translateY="8.25">
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock.xml
new file mode 100644
index 0000000..14a8d0b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G_N_10_N_11_T_0"
+ android:translateX="-27.5"
+ android:translateY="-17.5">
+ <group android:name="_R_G_L_2_G_N_10_T_1"
+ android:translateX="50.25"
+ android:translateY="61">
+ <group android:name="_R_G_L_2_G_N_10_T_0"
+ android:translateX="-13.75"
+ android:translateY="-7.5">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="-0.375"
+ android:translateY="-22.375">
+ <path android:name="_R_G_L_2_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c "/>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="_R_G_L_1_G_N_10_N_11_T_0"
+ android:translateX="-27.5"
+ android:translateY="-17.5">
+ <group android:name="_R_G_L_1_G_N_10_T_1"
+ android:translateX="50.25"
+ android:translateY="61">
+ <group android:name="_R_G_L_1_G_N_10_T_0"
+ android:translateX="-13.75"
+ android:translateY="-7.5">
+ <group android:name="_R_G_L_1_G"
+ android:translateX="5"
+ android:translateY="-22.5">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 "/>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="_R_G_L_0_G_N_10_N_11_T_0"
+ android:translateX="-27.5"
+ android:translateY="-17.5">
+ <group android:name="_R_G_L_0_G_N_10_T_1"
+ android:translateX="50.25"
+ android:translateY="61">
+ <group android:name="_R_G_L_0_G_N_10_T_0"
+ android:translateX="-13.75"
+ android:translateY="-7.5">
+ <group android:name="_R_G_L_0_G"
+ android:translateX="11"
+ android:translateY="-0.25"
+ android:pivotX="2.75"
+ android:pivotY="2.75"
+ android:scaleX="1"
+ android:scaleY="1">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml
new file mode 100644
index 0000000..cdae306
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125">
+ <path
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:pathData=" M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 " />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml b/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml
new file mode 100644
index 0000000..54242781
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:translateX="8.625"
+ android:translateY="13.625">
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c "/>
+ </group>
+ <group android:translateX="14"
+ android:translateY="13.5">
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 "/>
+ </group>
+ <group android:translateX="20"
+ android:translateY="35.75">
+ <path
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml b/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml
new file mode 100644
index 0000000..d35f695
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "/>
+ </group>
+ <group android:name="_R_G_L_1_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:strokeAlpha="1"
+ android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "/>
+ </group>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:strokeAlpha="1"
+ android:pathData=" M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "
+ android:valueTo="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="1.5"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "
+ android:valueTo="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="1.5"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "
+ android:valueTo="M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="517"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml b/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml
new file mode 100644
index 0000000..8a728ee
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "/>
+ </group>
+ <group android:name="_R_G_L_1_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "/>
+ </group>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "
+ android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="2"
+ android:valueTo="1.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "
+ android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="2"
+ android:valueTo="1.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "
+ android:valueTo="M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="517"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res/anim/lock_to_unlock.xml b/packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml
similarity index 91%
rename from packages/SystemUI/res/anim/lock_to_unlock.xml
rename to packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml
index 76f7a05..ab7e9d9 100644
--- a/packages/SystemUI/res/anim/lock_to_unlock.xml
+++ b/packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml
@@ -21,7 +21,7 @@
<group android:name="_R_G_L_2_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_2_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_2_G" android:translateX="-0.375" android:translateY="-22.375">
- <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
+ <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
</group>
</group>
</group>
@@ -30,7 +30,7 @@
<group android:name="_R_G_L_1_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_1_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_1_G" android:translateX="5" android:translateY="-22.5">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
</group>
</group>
</group>
@@ -39,7 +39,7 @@
<group android:name="_R_G_L_0_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_0_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_0_G" android:translateX="11" android:translateY="-0.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#b7f29f" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
</group>
</group>
</group>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
new file mode 100644
index 0000000..7f0f68f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+<animated-selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <!--
+ State corresponds with the following icons:
+ state_first => lock icon
+ state_middle => fingerprint icon
+ state_last => unlocked icon
+
+ state_single
+ = true => AOD
+ = false => LS
+ -->
+
+ <item
+ android:id="@+id/locked"
+ android:drawable="@drawable/ic_lock"
+ android:state_first="true"
+ android:state_single="false"/>
+
+ <item
+ android:id="@+id/locked_fp"
+ android:state_middle="true"
+ android:state_single="false"
+ android:drawable="@drawable/ic_fingerprint" />
+
+ <item
+ android:id="@+id/unlocked"
+ android:state_last="true"
+ android:state_single="false"
+ android:drawable="@drawable/ic_unlocked" />
+
+ <item
+ android:id="@+id/locked_aod"
+ android:state_first="true"
+ android:state_single="true"
+ android:drawable="@drawable/ic_lock_aod" />
+
+ <item
+ android:id="@+id/no_icon"
+ android:drawable="@color/transparent" />
+
+ <transition
+ android:fromId="@id/locked"
+ android:toId="@id/unlocked"
+ android:drawable="@drawable/lock_to_unlock" />
+
+ <transition
+ android:fromId="@id/locked_fp"
+ android:toId="@id/unlocked"
+ android:drawable="@drawable/fp_to_unlock" />
+
+ <transition
+ android:fromId="@id/unlocked"
+ android:toId="@id/locked_fp"
+ android:drawable="@drawable/unlock_to_fp" />
+
+ <transition
+ android:fromId="@id/locked_aod"
+ android:toId="@id/locked"
+ android:drawable="@drawable/lock_aod_to_ls" />
+
+ <transition
+ android:fromId="@id/locked"
+ android:toId="@id/locked_aod"
+ android:drawable="@drawable/lock_ls_to_aod" />
+</animated-selector>
diff --git a/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml b/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml
new file mode 100644
index 0000000..620c71a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G"
+ android:translateX="3.75"
+ android:translateY="8.25">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/>
+ <path android:name="_R_G_L_1_G_D_1_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="0"
+ android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/>
+ <path android:name="_R_G_L_1_G_D_2_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/>
+ <path android:name="_R_G_L_1_G_D_3_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/>
+ </group>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="20.357"
+ android:translateY="35.75"
+ android:pivotX="2.75"
+ android:pivotY="2.75"
+ android:scaleX="1"
+ android:scaleY="1">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "
+ android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "
+ android:valueTo="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeAlpha"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="strokeAlpha"
+ android:duration="33"
+ android:startOffset="183"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "
+ android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "
+ android:valueTo="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "
+ android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "
+ android:valueTo="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="33"
+ android:startOffset="150"
+ android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueTo="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="fillAlpha"
+ android:duration="200"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="fillAlpha"
+ android:duration="17"
+ android:startOffset="200"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX"
+ android:duration="67"
+ android:startOffset="183"
+ android:valueFrom="1"
+ android:valueTo="1.4186600000000003"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY"
+ android:duration="67"
+ android:startOffset="183"
+ android:valueFrom="1"
+ android:valueTo="1.4186600000000003"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="433"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 28c6166..87a9825 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -51,7 +51,7 @@
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/keyguard_status_area"
+ android:layout_below="@id/keyguard_slice_view"
android:visibility="gone">
<com.android.keyguard.AnimatableClockView
android:id="@+id/animatable_clock_view_large"
@@ -68,19 +68,28 @@
lockScreenWeight="400"
/>
</FrameLayout>
- <include layout="@layout/keyguard_status_area"
+
+ <!-- Not quite optimal but needed to translate these items as a group. The
+ NotificationIconContainer has its own logic for translation. -->
+ <LinearLayout
android:id="@+id/keyguard_status_area"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:layout_below="@id/lockscreen_clock_view" />
+ android:layout_below="@id/lockscreen_clock_view">
- <com.android.systemui.statusbar.phone.NotificationIconContainer
- android:id="@+id/left_aligned_notification_icon_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_shelf_height"
- android:layout_below="@id/keyguard_status_area"
- android:paddingStart="@dimen/below_clock_padding_start_icons"
- android:visibility="invisible"
- />
+ <include layout="@layout/keyguard_slice_view"
+ android:id="@+id/keyguard_slice_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <com.android.systemui.statusbar.phone.NotificationIconContainer
+ android:id="@+id/left_aligned_notification_icon_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_shelf_height"
+ android:paddingStart="@dimen/below_clock_padding_start_icons"
+ android:visibility="invisible"
+ />
+ </LinearLayout>
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
similarity index 89%
rename from packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
rename to packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
index 95eb5c1..1863d11 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
@@ -22,11 +22,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
+ android:layout_gravity="start"
android:clipToPadding="false"
android:orientation="vertical"
- android:paddingStart="@dimen/below_clock_padding_start"
- android:layout_centerHorizontal="true">
+ android:paddingStart="@dimen/below_clock_padding_start">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -42,6 +41,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center"
+ android:gravity="start"
/>
</com.android.keyguard.KeyguardSliceView>
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
index 3761a40..c415ecd 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
@@ -21,7 +21,4 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 12 12 C 12.8284271247 12 13.5 12.6715728753 13.5 13.5 C 13.5 14.3284271247 12.8284271247 15 12 15 C 11.1715728753 15 10.5 14.3284271247 10.5 13.5 C 10.5 12.6715728753 11.1715728753 12 12 12 Z" />
</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index c1d7308b..79ac737 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -323,6 +323,46 @@
</FrameLayout>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/wifi_scan_notify_layout"
+ style="@style/InternetDialog.Network"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:paddingBottom="4dp"
+ android:clickable="false"
+ android:focusable="false">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:src="@drawable/ic_info_outline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="?android:attr/textColorTertiary"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/wifi_scan_notify_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:clickable="true"/>
+ </LinearLayout>
+ </LinearLayout>
+
<FrameLayout
android:id="@+id/done_layout"
android:layout_width="67dp"
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 321fe68..543b7d7 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -16,74 +16,78 @@
~ limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="24dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/qs_dialog_bg"
->
- <TextView
- android:id="@+id/title"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:textAlignment="center"
- android:text="@string/qs_user_switch_dialog_title"
- android:textAppearance="@style/TextAppearance.QSDialog.Title"
- android:layout_marginBottom="32dp"
- sysui:layout_constraintTop_toTopOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/grid"
+ android:padding="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAlignment="center"
+ android:text="@string/qs_user_switch_dialog_title"
+ android:textAppearance="@style/TextAppearance.QSDialog.Title"
+ android:layout_marginBottom="32dp"
+ sysui:layout_constraintTop_toTopOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/grid"
+ />
+
+ <com.android.systemui.qs.PseudoGridView
+ android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="28dp"
+ sysui:verticalSpacing="4dp"
+ sysui:horizontalSpacing="4dp"
+ sysui:fixedChildWidth="80dp"
+ sysui:layout_constraintTop_toBottomOf="@id/title"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/barrier"
/>
- <com.android.systemui.qs.PseudoGridView
- android:id="@+id/grid"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="28dp"
- sysui:verticalSpacing="4dp"
- sysui:horizontalSpacing="4dp"
- sysui:fixedChildWidth="80dp"
- sysui:layout_constraintTop_toBottomOf="@id/title"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/barrier"
- />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/barrier"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ sysui:barrierDirection="top"
+ sysui:constraint_referenced_ids="settings,done"
+ />
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/barrier"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- sysui:barrierDirection="top"
- sysui:constraint_referenced_ids="settings,done"
- />
+ <Button
+ android:id="@+id/settings"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_more_user_settings"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toStartOf="@id/done"
+ sysui:layout_constraintHorizontal_chainStyle="spread_inside"
+ style="@style/Widget.QSDialog.Button.BorderButton"
+ />
- <Button
- android:id="@+id/settings"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_more_user_settings"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toStartOf="@id/done"
- sysui:layout_constraintHorizontal_chainStyle="spread_inside"
- style="@style/Widget.QSDialog.Button.BorderButton"
- />
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_done"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toEndOf="@id/settings"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ style="@style/Widget.QSDialog.Button"
+ />
- <Button
- android:id="@+id/done"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_done"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toEndOf="@id/settings"
- sysui:layout_constraintEnd_toEndOf="parent"
- style="@style/Widget.QSDialog.Button"
- />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index 720e47b..f91ab6f 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -31,8 +31,7 @@
android:id="@+id/privacy_dot_left_container"
android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginLeft="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="left|bottom"
android:visibility="invisible" >
<ImageView
@@ -51,12 +50,12 @@
android:tint="#ff000000"
android:layout_gravity="right|bottom"
android:src="@drawable/rounded_corner_bottom"/>
+
<FrameLayout
android:id="@+id/privacy_dot_right_container"
android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginRight="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="right|bottom"
android:visibility="invisible" >
<ImageView
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 6abe406..819a9a4e9 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -29,10 +29,9 @@
<FrameLayout
android:id="@+id/privacy_dot_left_container"
- android:layout_height="@*android:dimen/status_bar_height_portrait"
+ android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginLeft="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="left|top"
android:visibility="invisible" >
<ImageView
@@ -54,10 +53,9 @@
<FrameLayout
android:id="@+id/privacy_dot_right_container"
- android:layout_height="@*android:dimen/status_bar_height_portrait"
+ android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginRight="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="right|top"
android:visibility="invisible" >
<ImageView
@@ -67,8 +65,6 @@
android:layout_gravity="center_vertical|left"
android:src="@drawable/system_animation_ongoing_dot"
android:visibility="visible" />
-
</FrameLayout>
-
</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a58e12f..702a354 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -69,18 +69,6 @@
android:layout_gravity="center"
android:scaleType="centerCrop"/>
- <!-- Fingerprint -->
- <!-- AOD dashed fingerprint icon with moving dashes -->
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/lock_udfps_aod_fp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
- android:layout_gravity="center"
- android:scaleType="centerCrop"
- systemui:lottie_autoPlay="false"
- systemui:lottie_loop="true"
- systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
diff --git a/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
new file mode 100644
index 0000000..f5bfa49
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<com.airbnb.lottie.LottieAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index db63e73..427af68 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Hervat"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Instellings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> speel tans vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Speel"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Maak <xliff:g id="APP_LABEL">%1$s</xliff:g> oop"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 23bc620..a158d7f 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ከቆመበት ቀጥል"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ቅንብሮች"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> እየተጫወተ ነው"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ከ<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"አጫውት"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ክፈት"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 7495a49..ae4125b 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1114,6 +1114,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"استئناف التشغيل"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"الإعدادات"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"يتم تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> من إجمالي <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"تشغيل"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"فتح <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 43f5758..9da6246 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"পুনৰ আৰম্ভ কৰক"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ছেটিং"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ হৈ আছে"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>ৰ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"প্লে’ কৰক"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> খোলক"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 6909e6c..9b4b42b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Davam edin"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudulur"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oxudun"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> tətbiqini açın"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 8ec3417..4317599 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1096,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Podešavanja"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se pušta iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pusti"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 42680ea..535144f 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Узнавіць"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Налады"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"У праграме \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\" прайграецца кампазіцыя \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Прайграць"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Адкрыйце праграму \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 6c1ce79..2f54457 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Възобновяване"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се възпроизвежда от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> от <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Google Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отваряне на <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index cf8c641..2dc23f0 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"আবার চালু করুন"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"সেটিংস"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চলছে"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"চালান"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> অ্যাপ খুলুন"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 1e05870..0fc1405 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1096,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Pjesma <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se reproducira pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pokrenite"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite aplikaciju <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 64c911c..9d8f599 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reprèn"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configuració"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) s\'està reproduint des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodueix"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Obre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 47b5ff0..1393431 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -716,7 +716,7 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Rozšířené ovládací prvky oznámení"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Zapnuto"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Vypnuto"</string>
- <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt z obrazovky uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
+ <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt na obrazovce uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Oznámení"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Tato oznámení již nebudete dostávat"</string>
<string name="notification_channel_minimized" msgid="6892672757877552959">"Tato oznámení budou minimalizována"</string>
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Pokračovat"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavení"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> hrajte z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Přehrát"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otevřít aplikaci <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 59c24ce..1502dea 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Genoptag"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Indstillinger"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspilles via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspil"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åbn <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 0bfe97e..d4ebeda 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Fortsetzen"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Einstellungen"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wird gerade über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergegeben"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Wiedergeben"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> öffnen"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 4b4371b..576a806 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Συνέχιση"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ρυθμίσεις"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Γίνεται αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> από <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Άνοιγμα της εφαρμογής <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2eebb52..3898159 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 5ce2897..5a31735 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2eebb52..3898159 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2eebb52..3898159 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index a667418..1ec39d4 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index b07038c..e8f8b36cf 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 7a97f1ea..433536a 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -663,8 +663,8 @@
<string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string>
<string name="overview" msgid="3522318590458536816">"Aplicaciones recientes"</string>
<string name="demo_mode" msgid="263484519766901593">"Modo de demostración de UI del sistema"</string>
- <string name="enable_demo_mode" msgid="3180345364745966431">"Habilitar modo de demostración"</string>
- <string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo de demostración"</string>
+ <string name="enable_demo_mode" msgid="3180345364745966431">"Habilitar modo demo"</string>
+ <string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo demo"</string>
<string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string>
<string name="status_bar_alarm" msgid="87160847643623352">"Alarma"</string>
<string name="wallet_title" msgid="5369767670735827105">"Wallet"</string>
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ajustes"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index ab33374..e23f50d 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Jätka"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Seaded"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> esitatakse rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Esitamine"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Rakenduse <xliff:g id="APP_LABEL">%1$s</xliff:g> avamine"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 6731ed1..de23069 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Berrekin"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ezarpenak"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) ari da erreproduzitzen <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Erreproduzitu"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ireki <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 69631d4..dd778af 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ازسرگیری"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"تنظیمات"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش میشود"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"پخش"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"باز کردن <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index eed2a6c..2ea8dd7 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Jatka"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Asetukset"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> soittaa nyt tätä: <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Toista"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Avaa <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 2187f77..db02e05c 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecteur à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Faire jouer"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvrez <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index d927847..cd4cfbd 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -895,7 +895,7 @@
<string name="right_icon" msgid="1103955040645237425">"Icône droite"</string>
<string name="drag_to_add_tiles" msgid="8933270127508303672">"Faites glisser les blocs pour les ajouter"</string>
<string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Faites glisser les blocs pour les réorganiser"</string>
- <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Faites glisser les icônes ici pour les supprimer."</string>
+ <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Faites glisser les blocs ici pour les supprimer"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"Au minimum <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> tuiles sont nécessaires"</string>
<string name="qs_edit" msgid="5583565172803472437">"Modifier"</string>
<string name="tuner_time" msgid="2450785840990529997">"Heure"</string>
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecture depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Lire"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index a7c1f50..b10090c 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Estase reproducindo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index f63e583..d440222 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ફરી શરૂ કરો"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"સેટિંગ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચાલી રહ્યું છે"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ચલાવો"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ખોલો"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 1fc0246..0b945f0 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"फिर से शुरू करें"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चल रहा है"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> में से <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"चलाएं"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोलें"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index f586447..731d00a 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1096,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> reproducira se putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodukcija"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvori <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 202ab05..2ebd1458 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Folytatás"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Beállítások"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című száma hallható itt: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Játék"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> megnyitása"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index d359409..735eedd 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Շարունակել"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Կարգավորումներ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Այժմ նվագարկվում է <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>՝ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ից"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Նվագարկել"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Բացեք <xliff:g id="APP_LABEL">%1$s</xliff:g> հավելվածը"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 947e8882..cc514c0 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Lanjutkan"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Setelan"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sedang diputar dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> dari <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Putar"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index a643b24..0491d8c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Halda áfram"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Stillingar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> er í spilun á <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spila"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Opna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index bb77f0c..8aaf77d 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Riprendi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Impostazioni"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> è in riproduzione da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> di <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Riproduci"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Apri <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 90a2217..9ec4103 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"המשך"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"הגדרות"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מופעל מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> מתוך <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"הפעלה"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"פתיחה של <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 97bf7fb..520b048 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"再開"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)が <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生中"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"再生"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> を開く"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index e1c5b8d..de028b0 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"გაგრძელება"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"პარამეტრები"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, უკრავს <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-დან <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"დაკვრა"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 8462d00..b1dad23 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Жалғастыру"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әні ойнатылуда."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнату"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> қолданбасын ашу"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index f882906..828c864 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"បន្ត"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ការកំណត់"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> កំពុងចាក់ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> នៃ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ចាក់"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"បើក <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 47fa557..67d6488 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ಪುನರಾರಂಭಿಸಿ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ಪ್ಲೇ ಮಾಡಿ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index d1d804a..0f59303 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"다시 시작"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"설정"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생 중"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"재생"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> 열기"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index d5bbb41..6e4c97f 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ыры (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотулуп жатат"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ичинен <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнотуу"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> колдонмосун ачуу"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index cc6c51d..e61e847 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ສືບຕໍ່"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ການຕັ້ງຄ່າ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ກຳລັງຫຼິ້ນຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ຈາກ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ຫຼິ້ນ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"ເປີດ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 91711e0..fc77acb 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Tęsti"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nustatymai"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ leidžiama iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> iš <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Leisti"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atidaryti „<xliff:g id="APP_LABEL">%1$s</xliff:g>“"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index f8611ce..bfcb01d 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1096,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Atsākt"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Iestatījumi"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tiek atskaņots fails “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> no <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Atskaņot"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atveriet lietotni <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index f20b74e..a55fe97 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Продолжи"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Поставки"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> е пуштено на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пушти"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворете <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5ac1696..8242d91 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"പുനരാരംഭിക്കുക"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ക്രമീകരണം"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുന്നു"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ൽ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"പ്ലേ ചെയ്യുക"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> തുറക്കുക"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 5d07534..6772aff 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Үргэлжлүүлэх"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Тохиргоо"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулж буй <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-н <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Тоглуулах"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>-г нээх"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 485ed67..2cba8a0 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"पुन्हा सुरू करा"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग्ज"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले होत आहे"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> पैकी <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले करणे"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> उघडा"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b9e583d..461456b 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Sambung semula"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Tetapan"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dimainkan daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> daripada <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Main"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 7f6eccc..35e53a6 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ဆက်လုပ်ရန်"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ဆက်တင်များ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ထားသည်"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> အနက် <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ဖွင့်ခြင်း"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ကို ဖွင့်ပါ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index fb3c9de..f99aaee 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Gjenoppta"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Innstillinger"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spilles av fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spill av"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åpne <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index be7a404c..f061507 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"सुचारु गर्नुहोस्"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिङ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बज्दै छ"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> मध्ये <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले गर्नुहोस्"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोल्नुहोस्"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index ffcc3a8..07e28b6 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,7 +16,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index ed2c45a..756141e 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Hervatten"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Instellingen"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wordt afgespeeld via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspelen"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> openen"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 69650b2..e336c38 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ସେଟିଂସ୍"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚାଲୁଛି"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ଚଲାନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 29975a7..cc20ab7 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ਸੈਟਿੰਗਾਂ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ਵਿੱਚੋਂ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ਚਲਾਓ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 987def8..b4fb410 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Wznów"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ustawienia"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Aplikacja <xliff:g id="APP_LABEL">%3$s</xliff:g> odtwarza utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Odtwórz"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otwórz aplikację <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 199dbcc..f03525d 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 845de5f..f49627f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Definições"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> em reprodução a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproduzir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 199dbcc..f03525d 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index d6fc9c0..978c222 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1096,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reia"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se redă în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> din <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redați"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschideți <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 74283cc..35366df 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Возобновить"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Воспроизводится медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\"."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> из <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Воспроизведение"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Открыть приложение \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 33abc73..f6bb208 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"නැවත පටන් ගන්න"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"සැකසීම්"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> ගීතය <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් ධාවනය වෙමින් පවතී"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>කින් <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"වාදනය කරන්න"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> විවෘත කරන්න"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 3c1ddae..c349ef1 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Pokračovať"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavenia"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sa prehráva z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Prehrať"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvoriť <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 7d60ade..d41459a 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nadaljuj"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavitve"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se predvaja iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Predvajaj"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Odpri aplikacijo <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index cc3fdbb..e4ec008 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Vazhdo"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Cilësimet"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> po luhet nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> nga <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Luaj"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Hap <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 5398dd7..3dbda9b 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1096,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Настави"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Подешавања"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се пушта из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пусти"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворите <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 250f59b..eccf9ee 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Återuppta"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Inställningar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spelas upp från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> av <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spela upp"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Öppna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index f4a999c..31f74b2 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Endelea"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Mipangilio"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> unacheza katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> kati ya <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Cheza"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Fungua <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 6738b8a..f2d2254 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"தொடர்க"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"அமைப்புகள்"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடல் <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேயாகிறது"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"இயக்குதல்"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ஆப்ஸைத் திறங்கள்"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index cce52dd..be277b1 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1090,6 +1090,8 @@
<string name="controls_media_resume" msgid="1933520684481586053">"కొనసాగించండి"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"సెట్టింగ్లు"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి ప్లే అవుతోంది"</string>
+ <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+ <skip />
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ప్లే చేయండి"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>ను తెరవండి"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 88ee383..f7811fb 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"เล่นต่อ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"การตั้งค่า"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"กำลังเปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> จาก <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"เล่น"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"เปิด <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 0338702..822a3fd 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Ituloy"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Mga Setting"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Nagpe-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> sa <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"I-play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buksan ang <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 6017d9d..3849727 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Devam ettir"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısı çalıyor"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oynat"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> uygulamasını aç"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 82bf387..51cc6dd 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1102,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Відновити"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Налаштування"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Пісня \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, грає в додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Відтворення"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Відкрити додаток <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index f41b8aa..0690b5f 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"دوبارہ شروع کریں"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ترتیبات"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چل رہا ہے"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> از <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"چلائیں"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> کھولیں"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index b5e9b4f..acda73d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Davom etish"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Sozlamalar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ijro"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ilovasini ochish"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 4c73df2..2679b00 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Tiếp tục"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Cài đặt"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Đang phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Phát"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Mở <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 376be92..8ca914d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"继续播放"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"设置"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"打开<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 11e6544..e1c3b35 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在透過 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟 <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index a82d751..66434e4 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"系統正透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>,共 <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟「<xliff:g id="APP_LABEL">%1$s</xliff:g>」"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 03448d3..6505503 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1090,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Qalisa kabusha"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Izilungiselelo"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"I-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> idlala kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ku-<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Dlala"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Vula i-<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 08f72cb..2a70645 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,46 +18,18 @@
<resources>
<bool name="are_flags_overrideable">false</bool>
- <bool name="flag_notification_pipeline2">true</bool>
- <bool name="flag_notification_pipeline2_rendering">false</bool>
- <bool name="flag_notif_updates">true</bool>
-
<bool name="flag_monet">true</bool>
- <!-- AOD/Lockscreen alternate layout -->
- <bool name="flag_keyguard_layout">true</bool>
-
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
- <!-- The new animations to/from lockscreen and AOD! -->
- <bool name="flag_lockscreen_animations">true</bool>
-
- <!-- The new swipe to unlock animation, which shows the app/launcher behind the keyguard during
- the swipe. -->
- <bool name="flag_new_unlock_swipe_animation">true</bool>
-
<!-- Whether the multi-user icon on the lockscreen opens the QS user detail. If false, clicking
the multi-user icon will display a list of users directly on the lockscreen. The multi-user
icon is only shown if config_keyguardUserSwitcher=false in the frameworks config. -->
<bool name="flag_lockscreen_qs_user_detail_shortcut">false</bool>
- <!-- The shared-element transition between lockscreen smartspace and launcher smartspace. -->
- <bool name="flag_smartspace_shared_element_transition">false</bool>
-
- <bool name="flag_pm_lite">true</bool>
-
<bool name="flag_charging_ripple">false</bool>
- <bool name="flag_ongoing_call_status_bar_chip">true</bool>
-
- <bool name="flag_ongoing_call_in_immersive">false</bool>
-
<bool name="flag_smartspace">false</bool>
- <bool name="flag_smartspace_deduping">true</bool>
-
- <bool name="flag_combined_status_bar_signal_icons">false</bool>
-
- <bool name="flag_new_user_switcher">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1d0660e..2de8b49 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3028,6 +3028,8 @@
<string name="see_all_networks">See all</string>
<!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] -->
<string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
+ <!-- Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi scanning is on. [CHAR LIMIT=NONE] -->
+ <string name="wifi_scan_notify_message">To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. You can change this in Wi\u2011Fi scanning settings. <annotation id="link">Change</annotation></string>
<!-- Text for TileService request dialog. This is shown to the user that an app is requesting
user approval to add the shown tile to Quick Settings [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 761e3db..b6ef258 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -417,7 +417,9 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -943,26 +945,14 @@
<item name="actionDividerHeight">32dp</item>
</style>
- <style name="Theme.SystemUI.Dialog.QSDialog">
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowIsFloating">true</item>
- <item name="android:backgroundDimEnabled">true</item>
- <item name="android:windowCloseOnTouchOutside">true</item>
- <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
- <item name="android:dialogCornerRadius">28dp</item>
- <item name="android:buttonCornerRadius">28dp</item>
- <item name="android:colorBackground">@color/prv_color_surface</item>
- </style>
-
- <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog.QSDialog">
+ <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:lineHeight">32sp</item>
</style>
- <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog.QSDialog">
+ <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
<item name="android:textColor">@color/prv_text_color_on_accent</item>
<item name="android:textSize">14sp</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 11557ad..5b7e500 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -143,5 +143,12 @@
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
- // Next id = 48
+ /**
+ * Notifies sysui when taskbar requests autoHide to stop auto-hiding
+ * If called to suspend, caller is also responsible for calling this method to un-suspend
+ * @param suspend should be true to stop auto-hide, false to resume normal behavior
+ */
+ oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
+
+ // Next id = 49
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 196e6f3..d447b48 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -118,6 +118,8 @@
public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
// The home feature is disabled (either by SUW/SysUI/device policy)
public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
+ // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
+ public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -142,7 +144,8 @@
SYSUI_STATE_MAGNIFICATION_OVERLAP,
SYSUI_STATE_IME_SWITCHER_SHOWING,
SYSUI_STATE_DEVICE_DOZING,
- SYSUI_STATE_BACK_DISABLED
+ SYSUI_STATE_BACK_DISABLED,
+ SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
})
public @interface SystemUiStateFlags {}
@@ -174,6 +177,8 @@
str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : "");
str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : "");
str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
+ str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
+ ? "bubbles_mange_menu_expanded" : "");
return str.toString();
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 10e6c2b..90f5998 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.progress
import android.os.Handler
+import android.util.Log
import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -92,7 +93,14 @@
}
}
FOLD_UPDATE_FINISH_FULL_OPEN -> {
- cancelTransition(endValue = 1f, animate = true)
+ // Do not cancel if we haven't started the transition yet.
+ // This could happen when we fully unfolded the device before the screen
+ // became available. In this case we start and immediately cancel the animation
+ // in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to
+ // cancel it here.
+ if (isTransitionRunning) {
+ cancelTransition(endValue = 1f, animate = true)
+ }
}
FOLD_UPDATE_FINISH_CLOSED -> {
cancelTransition(endValue = 0f, animate = false)
@@ -101,6 +109,10 @@
startTransition(startValue = 1f)
}
}
+
+ if (DEBUG) {
+ Log.d(TAG, "onFoldUpdate = $update")
+ }
}
private fun cancelTransition(endValue: Float, animate: Boolean) {
@@ -118,6 +130,10 @@
listeners.forEach {
it.onTransitionFinished()
}
+
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionFinished")
+ }
}
}
@@ -137,6 +153,10 @@
it.onTransitionStarted()
}
isTransitionRunning = true
+
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionStarted")
+ }
}
private fun startTransition(startValue: Float) {
@@ -189,6 +209,9 @@
}
}
+private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
+private const val DEBUG = true
+
private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index c37ab06..35e2b30 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -25,7 +25,7 @@
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import java.util.concurrent.Executor
-internal class DeviceFoldStateProvider(
+class DeviceFoldStateProvider(
context: Context,
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
@@ -43,6 +43,7 @@
private val foldStateListener = FoldStateListener(context)
private var isFolded = false
+ private var isUnfoldHandled = true
override fun start() {
deviceStateManager.registerCallback(
@@ -104,6 +105,7 @@
lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
hingeAngleProvider.stop()
+ isUnfoldHandled = false
} else {
lastFoldUpdate = FOLD_UPDATE_START_OPENING
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
@@ -115,8 +117,15 @@
ScreenStatusProvider.ScreenListener {
override fun onScreenTurnedOn() {
- if (!isFolded) {
+ // Trigger this event only if we are unfolded and this is the first screen
+ // turned on event since unfold started. This prevents running the animation when
+ // turning on the internal display using the power button.
+ // Initially isUnfoldHandled is true so it will be reset to false *only* when we
+ // receive 'folded' event. If SystemUI started when device is already folded it will
+ // still receive 'folded' event on startup.
+ if (!isFolded && !isUnfoldHandled) {
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }
+ isUnfoldHandled = true
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 11984b93..643ece3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -24,7 +24,7 @@
* Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
* start folding/unfolding, screen availability
*/
-internal interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
+interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
fun start()
fun stop()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
index 2520d35..6f52456 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
@@ -9,7 +9,7 @@
* For foldable devices usually 0 corresponds to fully closed (folded) state and
* 180 degrees corresponds to fully open (flat) state
*/
-internal interface HingeAngleProvider : CallbackController<Consumer<Float>> {
+interface HingeAngleProvider : CallbackController<Consumer<Float>> {
fun start()
fun stop()
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
new file mode 100644
index 0000000..e072d41
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.unfold.util
+
+import android.content.Context
+import android.os.RemoteException
+import android.util.Log
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import android.view.Surface
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/**
+ * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has
+ * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180).
+ * It could be helpful to run the animation only when the display's rotation is perpendicular
+ * to the fold.
+ */
+class NaturalRotationUnfoldProgressProvider(
+ private val context: Context,
+ private val windowManagerInterface: IWindowManager,
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
+) : UnfoldTransitionProgressProvider {
+
+ private val scopedUnfoldTransitionProgressProvider =
+ ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+ private val rotationWatcher = RotationWatcher()
+
+ private var isNaturalRotation: Boolean = false
+
+ fun init() {
+ try {
+ windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId)
+ } catch (e: RemoteException) {
+ throw e.rethrowFromSystemServer()
+ }
+
+ onRotationChanged(context.display.rotation)
+ }
+
+ private fun onRotationChanged(rotation: Int) {
+ val isNewRotationNatural = rotation == Surface.ROTATION_0 ||
+ rotation == Surface.ROTATION_180
+
+ if (isNaturalRotation != isNewRotationNatural) {
+ isNaturalRotation = isNewRotationNatural
+ scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(isNewRotationNatural)
+ }
+ }
+
+ override fun destroy() {
+ try {
+ windowManagerInterface.removeRotationWatcher(rotationWatcher)
+ } catch (e: RemoteException) {
+ e.rethrowFromSystemServer()
+ }
+
+ scopedUnfoldTransitionProgressProvider.destroy()
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ scopedUnfoldTransitionProgressProvider.addCallback(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ scopedUnfoldTransitionProgressProvider.removeCallback(listener)
+ }
+
+ private inner class RotationWatcher : IRotationWatcher.Stub() {
+ override fun onRotationChanged(rotation: Int) {
+ this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation)
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
new file mode 100644
index 0000000..543232d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 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.unfold.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+public final class ScopedUnfoldTransitionProgressProvider implements
+ UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private static final float PROGRESS_UNSET = -1f;
+
+ @Nullable
+ private UnfoldTransitionProgressProvider mSource;
+
+ private final List<TransitionProgressListener> mListeners = new ArrayList<>();
+
+ private boolean mIsReadyToHandleTransition;
+ private boolean mIsTransitionRunning;
+ private float mLastTransitionProgress = PROGRESS_UNSET;
+
+ public ScopedUnfoldTransitionProgressProvider() {
+ this(null);
+ }
+
+ public ScopedUnfoldTransitionProgressProvider(
+ @Nullable UnfoldTransitionProgressProvider source) {
+ setSourceProvider(source);
+ }
+
+ /**
+ * Sets the source for the unfold transition progress updates,
+ * it replaces current provider if it is already set
+ * @param provider transition provider that emits transition progress updates
+ */
+ public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
+ if (mSource != null) {
+ mSource.removeCallback(this);
+ }
+
+ if (provider != null) {
+ mSource = provider;
+ mSource.addCallback(this);
+ } else {
+ mSource = null;
+ }
+ }
+
+ /**
+ * Allows to notify this provide whether the listeners can play the transition or not.
+ * Call this method with readyToHandleTransition = true when all listeners
+ * are ready to consume the transition progress events.
+ * Call it with readyToHandleTransition = false when listeners can't process the events.
+ */
+ public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
+ if (mIsTransitionRunning) {
+ if (isReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+
+ if (mLastTransitionProgress != PROGRESS_UNSET) {
+ mListeners.forEach(listener ->
+ listener.onTransitionProgress(mLastTransitionProgress));
+ }
+ } else {
+ mIsTransitionRunning = false;
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+ }
+
+ mIsReadyToHandleTransition = isReadyToHandleTransition;
+ }
+
+ @Override
+ public void addCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void destroy() {
+ mSource.removeCallback(this);
+ }
+
+ @Override
+ public void onTransitionStarted() {
+ this.mIsTransitionRunning = true;
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+ }
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(listener -> listener.onTransitionProgress(progress));
+ }
+
+ mLastTransitionProgress = progress;
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+
+ mIsTransitionRunning = false;
+ mLastTransitionProgress = PROGRESS_UNSET;
+ }
+}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
index b3a6699..5b6845f 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -16,37 +16,174 @@
package com.android.systemui.flags;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
import com.android.systemui.dagger.SysUISingleton;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
import javax.inject.Inject;
/**
* Concrete implementation of the a Flag manager that returns default values for debug builds
+ *
+ * Flags can be set (or unset) via the following adb command:
+ *
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ *
+ * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagManager implements FlagReader, FlagWriter {
+ private static final String TAG = "SysUIFlags";
+
+ private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
+ private static final String FIELD_TYPE = "type";
+ private static final String FIELD_ID = "id";
+ private static final String FIELD_VALUE = "value";
+ private static final String TYPE_BOOLEAN = "boolean";
+ private static final String ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG";
+ private static final String FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS";
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
+
+ private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
+
@Inject
- public FeatureFlagManager() {}
+ public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) {
+ mSystemPropertiesHelper = systemPropertiesHelper;
- public boolean isEnabled(int key, boolean defaultValue) {
- return isEnabled(Integer.toString(key), defaultValue);
+ IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
+ context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
}
- public boolean isEnabled(String key, boolean defaultValue) {
- // TODO
- return false;
+ /** Return a {@link BooleanFlag}'s value. */
+ public boolean isEnabled(int id, boolean defaultValue) {
+ if (!mBooleanFlagCache.containsKey(id)) {
+ Boolean result = isEnabledInternal(id);
+ mBooleanFlagCache.put(id, result == null ? defaultValue : result);
+ }
+
+ return mBooleanFlagCache.get(id);
}
- public void setEnabled(int key, boolean value) {
- setEnabled(Integer.toString(key), value);
+ /** Returns the stored value or null if not set. */
+ private Boolean isEnabledInternal(int id) {
+ String data = mSystemPropertiesHelper.get(keyToSysPropKey(id));
+ if (data.isEmpty()) {
+ return null;
+ }
+ JSONObject json;
+ try {
+ json = new JSONObject(data);
+ if (!assertType(json, TYPE_BOOLEAN)) {
+ return null;
+ }
+
+ return json.getBoolean(FIELD_VALUE);
+ } catch (JSONException e) {
+ eraseInternal(id); // Don't restart SystemUI in this case.
+ }
+ return null;
}
- public void setEnabled(String key, boolean value) {
- // TODO
+ /** Set whether a given {@link BooleanFlag} is enabled or not. */
+ public void setEnabled(int id, boolean value) {
+ Boolean currentValue = isEnabledInternal(id);
+ if (currentValue != null && currentValue == value) {
+ return;
+ }
+
+ JSONObject json = new JSONObject();
+ try {
+ json.put(FIELD_TYPE, TYPE_BOOLEAN);
+ json.put(FIELD_VALUE, value);
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString());
+ Log.i(TAG, "Set id " + id + " to " + value);
+ restartSystemUI();
+ } catch (JSONException e) {
+ // no-op
+ }
+ }
+
+ /** Erase a flag's overridden value if there is one. */
+ public void eraseFlag(int id) {
+ eraseInternal(id);
+ restartSystemUI();
+ }
+
+ /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
+ private void eraseInternal(int id) {
+ // We can't actually "erase" things from sysprops, but we can set them to empty!
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), "");
+ Log.i(TAG, "Erase id " + id);
}
public void addListener(Listener run) {}
public void removeListener(Listener run) {}
+ private void restartSystemUI() {
+ Log.i(TAG, "Restarting SystemUI");
+ // SysUI starts back when up exited. Is there a better way to do this?
+ System.exit(0);
+ }
+
+ private static String keyToSysPropKey(int key) {
+ return SYSPROP_PREFIX + key;
+ }
+
+ private static boolean assertType(JSONObject json, String type) {
+ try {
+ return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN);
+ } catch (JSONException e) {
+ return false;
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ if (ACTION_SET_FLAG.equals(action)) {
+ handleSetFlag(intent.getExtras());
+ }
+ }
+
+ private void handleSetFlag(Bundle extras) {
+ int id = extras.getInt(FIELD_ID);
+ if (id <= 0) {
+ Log.w(TAG, "ID not set or less than or equal to 0: " + id);
+ return;
+ }
+
+ Map<Integer, Flag<?>> flagMap = Flags.collectFlags();
+ if (!flagMap.containsKey(id)) {
+ Log.w(TAG, "Tried to set unknown id: " + id);
+ return;
+ }
+ Flag<?> flag = flagMap.get(id);
+
+ if (!extras.containsKey(FIELD_VALUE)) {
+ eraseFlag(id);
+ return;
+ }
+
+ if (flag instanceof BooleanFlag) {
+ setEnabled(id, extras.getBoolean(FIELD_VALUE));
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index f81f0b9..3d4e896 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -41,7 +41,7 @@
private static final long CLOCK_OUT_MILLIS = 150;
private static final long CLOCK_IN_MILLIS = 200;
- private static final long SMARTSPACE_MOVE_MILLIS = 350;
+ private static final long STATUS_AREA_MOVE_MILLIS = 350;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -63,13 +63,7 @@
private AnimatableClockView mClockView;
private AnimatableClockView mLargeClockView;
- /**
- * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to
- * show it below the alternate clock.
- */
- private View mKeyguardStatusArea;
- /** Mutually exclusive with mKeyguardStatusArea */
- private View mSmartspaceView;
+ private View mStatusArea;
private int mSmartspaceTopOffset;
/**
@@ -85,7 +79,7 @@
@VisibleForTesting AnimatorSet mClockInAnim = null;
@VisibleForTesting AnimatorSet mClockOutAnim = null;
- private ObjectAnimator mSmartspaceAnim = null;
+ private ObjectAnimator mStatusAreaAnim = null;
/**
* If the Keyguard Slice has a header (big center-aligned text.)
@@ -95,6 +89,7 @@
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
+ private OnPreDrawListener mPreDrawListener;
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -131,7 +126,7 @@
mClockView = findViewById(R.id.animatable_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mLargeClockView = findViewById(R.id.animatable_clock_view_large);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
+ mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
@@ -200,22 +195,22 @@
private void animateClockChange(boolean useLargeClock) {
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
- if (mSmartspaceAnim != null) mSmartspaceAnim.cancel();
+ if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
View in, out;
int direction = 1;
- float smartspaceYTranslation;
+ float statusAreaYTranslation;
if (useLargeClock) {
out = mClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- smartspaceYTranslation = mSmartspaceView == null ? 0
- : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset;
+ statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ + mSmartspaceTopOffset;
} else {
in = mClockFrame;
out = mLargeClockFrame;
- smartspaceYTranslation = 0f;
+ statusAreaYTranslation = 0f;
// Must remove in order for notifications to appear in the proper place
removeView(out);
@@ -251,18 +246,16 @@
mClockInAnim.start();
mClockOutAnim.start();
- if (mSmartspaceView != null) {
- mSmartspaceAnim = ObjectAnimator.ofFloat(mSmartspaceView, View.TRANSLATION_Y,
- smartspaceYTranslation);
- mSmartspaceAnim.setDuration(SMARTSPACE_MOVE_MILLIS);
- mSmartspaceAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mSmartspaceAnim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- mSmartspaceAnim = null;
- }
- });
- mSmartspaceAnim.start();
- }
+ mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
+ statusAreaYTranslation);
+ mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
+ mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ mStatusAreaAnim = null;
+ }
+ });
+ mStatusAreaAnim.start();
}
/**
@@ -292,15 +285,14 @@
if (mChildrenAreLaidOut) {
animateClockChange(clockSize == LARGE);
mDisplayedClockSize = clockSize;
- } else {
- getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- switchToClock(clockSize);
- getViewTreeObserver().removeOnPreDrawListener(this);
- return true;
- }
- });
+ } else if (mPreDrawListener == null) {
+ mPreDrawListener = () -> {
+ switchToClock(clockSize);
+ getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
+ mPreDrawListener = null;
+ return true;
+ };
+ getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
}
return true;
}
@@ -311,6 +303,13 @@
mChildrenAreLaidOut = true;
}
+ void onViewDetached() {
+ if (mPreDrawListener != null) {
+ getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
+ mPreDrawListener = null;
+ }
+ }
+
public Paint getPaint() {
return mClockView.getPaint();
}
@@ -352,10 +351,6 @@
}
}
- void setSmartspaceView(View smartspaceView) {
- mSmartspaceView = smartspaceView;
- }
-
void updateColors(ColorExtractor.GradientColors colors) {
mSupportsDarkText = colors.supportsDarkText();
mColorPalette = colors.getColorPalette();
@@ -369,8 +364,7 @@
pw.println(" mClockPlugin: " + mClockPlugin);
pw.println(" mClockFrame: " + mClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
- pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea);
- pw.println(" mSmartspaceView: " + mSmartspaceView);
+ pw.println(" mStatusArea: " + mStatusArea);
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mSupportsDarkText: " + mSupportsDarkText);
pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4111020..1931c0a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -25,7 +25,9 @@
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.android.internal.colorextraction.ColorExtractor;
@@ -99,7 +101,8 @@
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
- // If set, will replace keyguard_status_area
+ private ViewGroup mStatusArea;
+ // If set will replace keyguard_slice_view
private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -191,8 +194,8 @@
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- ksa.setVisibility(View.GONE);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ ksv.setVisibility(View.GONE);
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
@@ -201,19 +204,18 @@
}
updateAodIcons();
+ mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+
if (mSmartspaceController.isEnabled()) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ int ksvIndex = mStatusArea.indexOfChild(ksv);
+ ksv.setVisibility(View.GONE);
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- int ksaIndex = mView.indexOfChild(ksa);
- ksa.setVisibility(View.GONE);
-
- // Place smartspace view below normal clock...
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
- mView.addView(mSmartspaceView, ksaIndex, lp);
+ mStatusArea.addView(mSmartspaceView, ksvIndex, lp);
int startPadding = getContext().getResources()
.getDimensionPixelSize(R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources()
@@ -221,14 +223,6 @@
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
updateClockLayout();
-
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
- lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
- nic.setLayoutParams(lp);
-
- mView.setSmartspaceView(mSmartspaceView);
mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
}
}
@@ -244,8 +238,7 @@
}
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
-
- mSmartspaceController.disconnect();
+ mView.onViewDetached();
}
/**
@@ -328,8 +321,8 @@
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate);
- if (mSmartspaceView != null) {
- PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+ if (mStatusArea != null) {
+ PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
// If we're unlocking with the SmartSpace shared element transition, let the controller
@@ -340,7 +333,6 @@
}
mKeyguardSliceViewController.updatePosition(x, props, animate);
- mNotificationIconAreaController.updatePosition(x, props, animate);
}
/** Sets an alpha value on every child view except for the smartspace. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 9d649e7..d4d3d5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -209,7 +209,7 @@
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mSecurityViewFlipperController.reloadColors();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 428006e..9b76bab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -33,12 +33,10 @@
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.slice.SliceItem;
@@ -85,8 +83,6 @@
private boolean mHasHeader;
private View.OnClickListener mOnClickListener;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
-
public KeyguardSliceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -136,35 +132,6 @@
}
}
- /**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- mTitle.setPaddingRelative(0, 0, 0, 0);
- mTitle.setGravity(Gravity.START);
- setGravity(Gravity.START);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.removeRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- } else {
- final int horizontalPaddingDpValue = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- 44,
- getResources().getDisplayMetrics()
- );
- mTitle.setPaddingRelative(horizontalPaddingDpValue, 0, horizontalPaddingDpValue, 0);
- mTitle.setGravity(Gravity.CENTER_HORIZONTAL);
- setGravity(Gravity.CENTER_HORIZONTAL);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- }
- mRow.setLockscreenMode(mode);
- requestLayout();
- }
-
Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
Trace.beginSection("KeyguardSliceView#showSlice");
mHasHeader = header != null;
@@ -189,8 +156,7 @@
final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams();
- layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL
- ? Gravity.START : Gravity.CENTER;
+ layoutParams.gravity = Gravity.START;
mRow.setLayoutParams(layoutParams);
for (int i = startIndex; i < subItemsCount; i++) {
@@ -224,8 +190,7 @@
final int iconSize = mHasHeader ? mIconSizeWithHeader : mIconSize;
iconDrawable = icon.getIcon().loadDrawable(mContext);
if (iconDrawable != null) {
- if ((iconDrawable instanceof InsetDrawable)
- && mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ if (iconDrawable instanceof InsetDrawable) {
// System icons (DnD) use insets which are fine for centered slice content
// but will cause a slight indent for left/right-aligned slice views
iconDrawable = ((InsetDrawable) iconDrawable).getDrawable();
@@ -321,7 +286,6 @@
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mHasHeader: " + mHasHeader);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
@Override
@@ -332,7 +296,6 @@
public static class Row extends LinearLayout {
private Set<KeyguardSliceTextView> mKeyguardSliceTextViewSet = new HashSet();
- private int mLockScreenModeRow = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
/**
* This view is visible in AOD, which means that the device will sleep if we
@@ -407,11 +370,7 @@
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child instanceof KeyguardSliceTextView) {
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
- } else {
- ((KeyguardSliceTextView) child).setMaxWidth(width / 3);
- }
+ ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
}
}
@@ -443,7 +402,6 @@
super.addView(view, index);
if (view instanceof KeyguardSliceTextView) {
- ((KeyguardSliceTextView) view).setLockScreenMode(mLockScreenModeRow);
mKeyguardSliceTextViewSet.add((KeyguardSliceTextView) view);
}
}
@@ -455,24 +413,6 @@
mKeyguardSliceTextViewSet.remove((KeyguardSliceTextView) view);
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockscreenMode(int mode) {
- mLockScreenModeRow = mode;
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setOrientation(LinearLayout.VERTICAL);
- setGravity(Gravity.START);
- } else {
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
- }
-
- for (KeyguardSliceTextView textView : mKeyguardSliceTextViewSet) {
- textView.setLockScreenMode(mLockScreenModeRow);
- }
- }
}
/**
@@ -480,7 +420,6 @@
*/
@VisibleForTesting
static class KeyguardSliceTextView extends TextView {
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary;
@@ -509,13 +448,8 @@
boolean hasText = !TextUtils.isEmpty(getText());
int padding = (int) getContext().getResources()
.getDimension(R.dimen.widget_horizontal_padding) / 2;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- // orientation is vertical, so add padding to top & bottom
- setPadding(0, padding, 0, hasText ? padding : 0);
- } else {
- // orientation is horizontal, so add padding to left & right
- setPadding(padding, 0, padding * (hasText ? 1 : -1), 0);
- }
+ // orientation is vertical, so add padding to top & bottom
+ setPadding(0, padding, 0, hasText ? padding : 0);
setCompoundDrawablePadding((int) mContext.getResources()
.getDimension(R.dimen.widget_icon_padding));
@@ -543,18 +477,5 @@
}
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- } else {
- setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
- }
- updatePadding();
- }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 8038ce4..d05cc4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -73,7 +73,6 @@
private Uri mKeyguardSliceUri;
private Slice mSlice;
private Map<View, PendingIntent> mClickActions;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
@@ -84,7 +83,7 @@
mView.onDensityOrFontScaleChanged();
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mView.onOverlayChanged();
}
};
@@ -137,7 +136,6 @@
TAG + "@" + Integer.toHexString(
KeyguardSliceViewController.this.hashCode()),
KeyguardSliceViewController.this);
- mView.updateLockScreenMode(mLockScreenMode);
}
@Override
@@ -160,14 +158,6 @@
}
/**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- mView.updateLockScreenMode(mLockScreenMode);
- }
-
- /**
* Sets the slice provider Uri.
*/
public void setupUri(String uriString) {
@@ -249,6 +239,5 @@
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(" mSlice: " + mSlice);
pw.println(" mClickActions: " + mClickActions);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2362a1a..a72a050e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -88,7 +88,7 @@
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
}
- mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
mTextColor = mClockView.getCurrentTextColor();
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 682b217..60af66ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -252,11 +252,6 @@
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onLockScreenModeChanged(int mode) {
- mKeyguardSliceViewController.updateLockScreenMode(mode);
- }
-
- @Override
public void onTimeChanged() {
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5969e92..a41a497 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,7 +71,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -86,11 +85,11 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -98,7 +97,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -191,9 +189,6 @@
private static final int MSG_TIME_FORMAT_UPDATE = 344;
private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
- public static final int LOCK_SCREEN_MODE_NORMAL = 0;
- public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
-
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -277,7 +272,6 @@
private boolean mBouncer; // true if bouncerIsOrWillBeShowing
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
- private boolean mHasLockscreenWallpaper;
private boolean mAssistantVisible;
private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
@@ -286,9 +280,6 @@
@VisibleForTesting
protected boolean mTelephonyCapable;
- private final boolean mAcquiredHapticEnabled = false;
- @Nullable private final Vibrator mVibrator;
-
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -323,6 +314,7 @@
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private final LatencyTracker mLatencyTracker;
private boolean mLogoutEnabled;
// cached value to avoid IPCs
private boolean mIsUdfpsEnrolled;
@@ -1407,7 +1399,6 @@
@VisibleForTesting
final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
- private boolean mPlayedAcquiredHaptic;
@Override
public void onAuthenticationFailed() {
@@ -1419,11 +1410,6 @@
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
Trace.endSection();
-
- // on auth success, we sometimes never received an acquired haptic
- if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) {
- playAcquiredHaptic();
- }
}
@Override
@@ -1439,17 +1425,11 @@
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFingerprintAcquired(acquireInfo);
- if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD
- && isUdfpsEnrolled()) {
- mPlayedAcquiredHaptic = true;
- playAcquiredHaptic();
- }
}
@Override
public void onUdfpsPointerDown(int sensorId) {
Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
- mPlayedAcquiredHaptic = false;
}
@Override
@@ -1458,17 +1438,6 @@
}
};
- /**
- * Play haptic to signal udfps fingeprrint acquired.
- */
- @VisibleForTesting
- public void playAcquiredHaptic() {
- if (mAcquiredHapticEnabled && mVibrator != null) {
- mVibrator.vibrate(UdfpsController.EFFECT_CLICK,
- UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
- }
- }
-
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
// Trigger the face success path so the bouncer can be shown
@@ -1773,7 +1742,7 @@
TelephonyListenerManager telephonyListenerManager,
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
- @Nullable Vibrator vibrator) {
+ LatencyTracker latencyTracker) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -1782,6 +1751,7 @@
mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
+ mLatencyTracker = latencyTracker;
mRingerModeTracker = ringerModeTracker;
mStatusBarStateController = statusBarStateController;
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
@@ -1789,7 +1759,6 @@
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
- mVibrator = vibrator;
mHandler = new Handler(mainLooper) {
@Override
@@ -1898,9 +1867,6 @@
case MSG_KEYGUARD_GOING_AWAY:
handleKeyguardGoingAway((boolean) msg.obj);
break;
- case MSG_LOCK_SCREEN_MODE:
- handleLockScreenMode();
- break;
case MSG_TIME_FORMAT_UPDATE:
handleTimeFormatUpdate((String) msg.obj);
break;
@@ -2042,8 +2008,6 @@
}
}
- updateLockScreenMode(featureFlags.isKeyguardLayoutEnabled());
-
mTimeFormatChangeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -2059,14 +2023,6 @@
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
}
- private void updateLockScreenMode(boolean isEnabled) {
- final int newMode = isEnabled ? LOCK_SCREEN_MODE_LAYOUT_1 : LOCK_SCREEN_MODE_NORMAL;
- if (newMode != mLockScreenMode) {
- mLockScreenMode = newMode;
- mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
- }
- }
-
private void updateUdfpsEnrolled(int userId) {
mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
}
@@ -2579,31 +2535,6 @@
}
/**
- * Update the state whether Keyguard currently has a lockscreen wallpaper.
- *
- * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
- */
- public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
- Assert.isMainThread();
- if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
- }
- }
-
- /**
- * @return Whether Keyguard has a lockscreen wallpaper.
- */
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
- /**
* Handle {@link #MSG_DPM_STATE_CHANGED}
*/
private void handleDevicePolicyManagerStateChanged(int userId) {
@@ -2651,6 +2582,7 @@
}
}
mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH);
+ mLatencyTracker.onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
}
/**
@@ -2722,20 +2654,6 @@
}
/**
- * Handle {@link #MSG_LOCK_SCREEN_MODE}
- */
- private void handleLockScreenMode() {
- Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onLockScreenModeChanged(mLockScreenMode);
- }
- }
- }
-
- /**
* Handle (@line #MSG_TIMEZONE_UPDATE}
*/
private void handleTimeZoneUpdate(String timeZone) {
@@ -3113,7 +3031,6 @@
callback.onKeyguardOccludedChanged(mKeyguardOccluded);
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
- callback.onLockScreenModeChanged(mLockScreenMode);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 6aa7aaa..12431984 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,11 +292,6 @@
public void onStrongAuthStateChanged(int userId) { }
/**
- * Called when the state whether we have a lockscreen wallpaper has changed.
- */
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
-
- /**
* Called when the dream's window state is changed.
* @param dreaming true if the dream's window has been created and is visible
*/
@@ -330,11 +325,6 @@
public void onSecondaryLockscreenRequirementChanged(int userId) { }
/**
- * Called to switch lock screen layout/clock layouts
- */
- public void onLockScreenModeChanged(int mode) { }
-
- /**
* Called when notifying user to unlock in order to use NFC.
*/
public void onRequireUnlockForNfc() { }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index ef4353b..68132f4 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -26,6 +26,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -40,6 +41,17 @@
* A view positioned under the notification shade.
*/
public class LockIconView extends FrameLayout implements Dumpable {
+ @IntDef({ICON_NONE, ICON_LOCK, ICON_FINGERPRINT, ICON_UNLOCK})
+ public @interface IconType {}
+
+ public static final int ICON_NONE = -1;
+ public static final int ICON_LOCK = 0;
+ public static final int ICON_FINGERPRINT = 1;
+ public static final int ICON_UNLOCK = 2;
+
+ private @IconType int mIconType;
+ private boolean mAod;
+
@NonNull private final RectF mSensorRect;
@NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
private int mRadius;
@@ -49,6 +61,7 @@
private int mLockIconColor;
private boolean mUseBackground = false;
+ private float mDozeAmount = 0f;
public LockIconView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -62,11 +75,17 @@
mBgView = findViewById(R.id.lock_icon_bg);
}
+ void setDozeAmount(float dozeAmount) {
+ mDozeAmount = dozeAmount;
+ updateColorAndBackgroundVisibility();
+ }
+
void updateColorAndBackgroundVisibility() {
if (mUseBackground && mLockIcon.getDrawable() != null) {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+ mBgView.setAlpha(1f - mDozeAmount);
mBgView.setVisibility(View.VISIBLE);
} else {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
@@ -129,10 +148,75 @@
return mLockIconCenter.y - mRadius;
}
+ /**
+ * Updates the icon its default state where no visual is shown.
+ */
+ public void clearIcon() {
+ updateIcon(ICON_NONE, false);
+ }
+
+ /**
+ * Transition the current icon to a new state
+ * @param icon type (ie: lock icon, unlock icon, fingerprint icon)
+ * @param aod whether to use the aod icon variant (some icons don't have aod variants and will
+ * therefore show no icon)
+ */
+ public void updateIcon(@IconType int icon, boolean aod) {
+ mIconType = icon;
+ mAod = aod;
+
+ mLockIcon.setImageState(getLockIconState(mIconType, mAod), true);
+ }
+
+ private static int[] getLockIconState(@IconType int icon, boolean aod) {
+ if (icon == ICON_NONE) {
+ return new int[0];
+ }
+
+ int[] lockIconState = new int[2];
+ switch (icon) {
+ case ICON_LOCK:
+ lockIconState[0] = android.R.attr.state_first;
+ break;
+ case ICON_FINGERPRINT:
+ lockIconState[0] = android.R.attr.state_middle;
+ break;
+ case ICON_UNLOCK:
+ lockIconState[0] = android.R.attr.state_last;
+ break;
+ }
+
+ if (aod) {
+ lockIconState[1] = android.R.attr.state_single;
+ } else {
+ lockIconState[1] = -android.R.attr.state_single;
+ }
+
+ return lockIconState;
+ }
+
+ private String typeToString(@IconType int type) {
+ switch (type) {
+ case ICON_NONE:
+ return "none";
+ case ICON_LOCK:
+ return "lock";
+ case ICON_FINGERPRINT:
+ return "fingerprint";
+ case ICON_UNLOCK:
+ return "unlock";
+ }
+
+ return "invalid";
+ }
+
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
pw.println("Radius in pixels: " + mRadius);
pw.println("topLeft= (" + getX() + ", " + getY() + ")");
+ pw.println("topLeft= (" + getX() + ", " + getY() + ")");
+ pw.println("mIconType=" + typeToString(mIconType));
+ pw.println("mAod=" + mAod);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 9aa03a9..94b1728 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -18,16 +18,18 @@
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
+import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
-import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
+import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -38,6 +40,7 @@
import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -98,14 +101,12 @@
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
+ @NonNull private final LayoutInflater mLayoutInflater;
private boolean mUdfpsEnrolled;
- @NonNull private LottieAnimationView mAodFp;
+ @Nullable private LottieAnimationView mAodFp;
+ @NonNull private final AnimatedStateListDrawable mIcon;
- @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
- @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
- @NonNull private final Drawable mLockIcon;
- @NonNull private final Drawable mUnlockIcon;
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
@Nullable private final Vibrator mVibrator;
@@ -131,13 +132,13 @@
private boolean mShowLockIcon;
// for udfps when strong auth is required or unlocked on AOD
+ private boolean mShowAodLockIcon;
private boolean mShowAODFpIcon;
private final int mMaxBurnInOffsetX;
private final int mMaxBurnInOffsetY;
private float mInterpolatedDarkAmount;
private boolean mDownDetected;
- private boolean mDetectedLongPress;
private final Rect mSensorTouchLocation = new Rect();
@Inject
@@ -154,7 +155,9 @@
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
@Nullable Vibrator vibrator,
- @Nullable AuthRippleController authRippleController
+ @Nullable AuthRippleController authRippleController,
+ @NonNull @Main Resources resources,
+ @NonNull LayoutInflater inflater
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -168,27 +171,16 @@
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mLayoutInflater = inflater;
- final Context context = view.getContext();
- mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
- mMaxBurnInOffsetX = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
- mMaxBurnInOffsetY = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+ mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
- mUnlockIcon = mView.getContext().getResources().getDrawable(
- R.drawable.ic_unlock,
- mView.getContext().getTheme());
- mLockIcon = mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mFpToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.fp_to_unlock, mView.getContext().getTheme());
- mLockToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
- mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
+ mIcon = (AnimatedStateListDrawable)
+ resources.getDrawable(R.drawable.super_lock_icon, mView.getContext().getTheme());
+ mView.setImageDrawable(mIcon);
+ mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
+ mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this);
}
@@ -260,47 +252,52 @@
return;
}
+ boolean wasShowingUnlock = mShowUnlockIcon;
boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon;
- boolean wasShowingLockIcon = mShowLockIcon;
- boolean wasShowingUnlockIcon = mShowUnlockIcon;
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
- && (!mUdfpsEnrolled || !mRunningFPS);
+ && (!mUdfpsEnrolled || !mRunningFPS);
mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
- mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
+ mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
+ mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
- mView.setImageDrawable(mLockIcon);
- mView.setVisibility(View.VISIBLE);
+ mView.updateIcon(ICON_LOCK, false);
mView.setContentDescription(mLockedLabel);
- } else if (mShowUnlockIcon) {
- if (!wasShowingUnlockIcon) {
- if (wasShowingFpIcon) {
- mView.setImageDrawable(mFpToUnlockIcon);
- mFpToUnlockIcon.forceAnimationOnUI();
- mFpToUnlockIcon.start();
- } else if (wasShowingLockIcon) {
- mView.setImageDrawable(mLockToUnlockIcon);
- mLockToUnlockIcon.forceAnimationOnUI();
- mLockToUnlockIcon.start();
- } else {
- mView.setImageDrawable(mUnlockIcon);
- }
- }
mView.setVisibility(View.VISIBLE);
+ } else if (mShowUnlockIcon) {
+ if (wasShowingFpIcon) {
+ // fp icon was shown by UdfpsView, and now we still want to animate the transition
+ // in this drawable
+ mView.updateIcon(ICON_FINGERPRINT, false);
+ }
+ mView.updateIcon(ICON_UNLOCK, false);
mView.setContentDescription(mUnlockedLabel);
+ mView.setVisibility(View.VISIBLE);
} else if (mShowAODFpIcon) {
- mView.setImageDrawable(null);
+ // AOD fp icon is special cased as a lottie view (it updates for each burn-in offset),
+ // this state shows a transparent view
mView.setContentDescription(null);
mAodFp.setVisibility(View.VISIBLE);
mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
+
+ mView.updateIcon(ICON_FINGERPRINT, true); // this shows no icon
+ mView.setVisibility(View.VISIBLE);
+ } else if (mShowAodLockIcon) {
+ if (wasShowingUnlock) {
+ // transition to the unlock icon first
+ mView.updateIcon(ICON_LOCK, false);
+ }
+ mView.updateIcon(ICON_LOCK, true);
+ mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
} else {
+ mView.clearIcon();
mView.setVisibility(View.INVISIBLE);
mView.setContentDescription(null);
}
- if (!mShowAODFpIcon) {
+ if (!mShowAODFpIcon && mAodFp != null) {
mAodFp.setVisibility(View.INVISIBLE);
mAodFp.setContentDescription(null);
}
@@ -385,6 +382,11 @@
pw.println("mUdfpsSupported: " + mUdfpsSupported);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
+ pw.println(" mIcon: ");
+ for (int state : mIcon.getState()) {
+ pw.print(" " + state);
+ }
+ pw.println();
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
@@ -416,10 +418,17 @@
- mMaxBurnInOffsetY, mInterpolatedDarkAmount);
float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
- mAodFp.setTranslationX(offsetX);
- mAodFp.setTranslationY(offsetY);
- mAodFp.setProgress(progress);
- mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ if (mAodFp != null) {
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
+
+ if (mShowAodLockIcon) {
+ mView.setTranslationX(offsetX);
+ mView.setTranslationY(offsetY);
+ }
}
private void updateIsUdfpsEnrolled() {
@@ -430,6 +439,10 @@
mView.setUseBackground(mUdfpsSupported);
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) {
+ mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView);
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ }
if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
updateVisibility();
}
@@ -440,6 +453,7 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
mInterpolatedDarkAmount = eased;
+ mView.setDozeAmount(eased);
updateBurnInOffsets();
}
@@ -551,11 +565,6 @@
}
@Override
- public void onOverlayChanged() {
- updateColors();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
updateConfiguration();
updateColors();
@@ -565,7 +574,6 @@
private final GestureDetector mGestureDetector =
new GestureDetector(new SimpleOnGestureListener() {
public boolean onDown(MotionEvent e) {
- mDetectedLongPress = false;
if (!isClickable()) {
mDownDetected = false;
return false;
@@ -590,7 +598,6 @@
if (!wasClickableOnDownEvent()) {
return;
}
- mDetectedLongPress = true;
if (onAffordanceClick() && mVibrator != null) {
// only vibrate if the click went through and wasn't intercepted by falsing
@@ -656,7 +663,7 @@
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& (mView.getVisibility() == View.VISIBLE
- || mAodFp.getVisibility() == View.VISIBLE)) {
+ || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE))) {
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 3775628..013cdac 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -41,7 +41,6 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.settings.CurrentUserObservable;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.util.InjectionInflationController;
import java.util.ArrayList;
import java.util.Collection;
@@ -125,16 +124,16 @@
private final int mHeight;
@Inject
- public ClockManager(Context context, InjectionInflationController injectionInflater,
+ public ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
@Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
- this(context, injectionInflater, pluginManager, colorExtractor,
+ this(context, layoutInflater, pluginManager, colorExtractor,
context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
new SettingsWrapper(context.getContentResolver()), dockManager);
}
@VisibleForTesting
- ClockManager(Context context, InjectionInflationController injectionInflater,
+ ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
SettingsWrapper settingsWrapper, DockManager dockManager) {
@@ -147,7 +146,6 @@
mPreviewClocks = new AvailableClocks();
Resources res = context.getResources();
- LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context));
addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
index 1d51e59..b8841ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
@@ -34,6 +34,6 @@
@Provides
static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) {
- return keyguardClockSwitch.findViewById(R.id.keyguard_status_area);
+ return keyguardClockSwitch.findViewById(R.id.keyguard_slice_view);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index 5ed9eaa..12dd8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -86,7 +86,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 9023d3a..2f5a18e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -89,8 +89,12 @@
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -102,6 +106,7 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -360,6 +365,11 @@
@Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
@Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
@Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
+ @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
+ @Inject Lazy<UnlockedScreenOffAnimationController> mUnlockedScreenOffAnimationControllerLazy;
+ @Inject Lazy<AmbientState> mAmbientStateLazy;
+ @Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy;
+ @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
@Inject
public Dependency() {
@@ -574,6 +584,12 @@
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
+ mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get);
+ mProviders.put(UnlockedScreenOffAnimationController.class,
+ mUnlockedScreenOffAnimationControllerLazy::get);
+ mProviders.put(AmbientState.class, mAmbientStateLazy::get);
+ mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get);
+ mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index cffc048..d1739aa 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -127,7 +127,12 @@
setFixedSizeAllowed(true);
updateSurfaceSize();
- mRenderer.setOnBitmapChanged(this::updateMiniBitmap);
+ mRenderer.setOnBitmapChanged(b -> {
+ mLocalColorsToAdd.addAll(mColorAreas);
+ if (mLocalColorsToAdd.size() > 0) {
+ updateMiniBitmapAndNotify(b);
+ }
+ });
getDisplayContext().getSystemService(DisplayManager.class)
.registerDisplayListener(this, mWorker.getThreadHandler());
Trace.endSection();
@@ -171,7 +176,7 @@
computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
}
- private void updateMiniBitmap(Bitmap b) {
+ private void updateMiniBitmapAndNotify(Bitmap b) {
if (b == null) return;
int size = Math.min(b.getWidth(), b.getHeight());
float scale = 1.0f;
@@ -233,6 +238,7 @@
Bitmap bitmap = mMiniBitmap;
if (bitmap == null) {
mLocalColorsToAdd.addAll(regions);
+ if (mRenderer != null) mRenderer.use(this::updateMiniBitmapAndNotify);
} else {
computeAndNotifyLocalColors(regions, bitmap);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 9c10d74..c4147e7 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -161,8 +161,6 @@
private boolean mPendingRotationChange;
private boolean mIsRoundedCornerMultipleRadius;
private boolean mIsPrivacyDotEnabled;
- private int mStatusBarHeightPortrait;
- private int mStatusBarHeightLandscape;
private Drawable mRoundedCornerDrawable;
private Drawable mRoundedCornerDrawableTop;
private Drawable mRoundedCornerDrawableBottom;
@@ -315,7 +313,6 @@
private void setupDecorations() {
if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) {
- updateStatusBarHeight();
final DisplayCutout cutout = getCutout();
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
@@ -326,7 +323,8 @@
}
}
- if (mIsPrivacyDotEnabled) {
+ if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null
+ && mBottomRightDot != null) {
// Overlays have been created, send the dots to the controller
//TODO: need a better way to do this
mDotViewController.initialize(
@@ -430,7 +428,7 @@
if (mOverlays[pos] != null) {
return;
}
- mOverlays[pos] = overlayForPosition(pos);
+ mOverlays[pos] = overlayForPosition(pos, cutout);
mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
@@ -462,10 +460,46 @@
/**
* Allow overrides for top/bottom positions
*/
- private View overlayForPosition(@BoundsPosition int pos) {
+ private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP)
? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom;
- return LayoutInflater.from(mContext).inflate(layoutId, null);
+ final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null);
+ initPrivacyDotView(vg, pos, cutout);
+ return vg;
+ }
+
+ private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos,
+ @Nullable DisplayCutout cutout) {
+ final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
+ final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
+ if (!shouldShowPrivacyDot(pos, cutout)) {
+ viewGroup.removeView(left);
+ viewGroup.removeView(right);
+ return;
+ }
+
+ switch (pos) {
+ case BOUNDS_POSITION_LEFT: {
+ mTopLeftDot = left;
+ mBottomLeftDot = right;
+ break;
+ }
+ case BOUNDS_POSITION_TOP: {
+ mTopLeftDot = left;
+ mTopRightDot = right;
+ break;
+ }
+ case BOUNDS_POSITION_RIGHT: {
+ mTopRightDot = left;
+ mBottomRightDot = right;
+ break;
+ }
+ case BOUNDS_POSITION_BOTTOM: {
+ mBottomLeftDot = left;
+ mBottomRightDot = right;
+ break;
+ }
+ }
}
private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
@@ -483,8 +517,6 @@
if (mCutoutViews != null && mCutoutViews[pos] != null) {
mCutoutViews[pos].setRotation(mRotation);
}
-
- updatePrivacyDotView(pos, cutout);
}
@VisibleForTesting
@@ -671,14 +703,6 @@
}
}
- private void updateStatusBarHeight() {
- mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height_landscape);
- mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height_portrait);
- mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape);
- }
-
private void updateRoundedCornerRadii() {
// We should eventually move to just using the intrinsic size of the drawables since
// they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
@@ -813,26 +837,6 @@
}
}
- private void updatePrivacyDotView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
- final ViewGroup viewGroup = (ViewGroup) mOverlays[pos];
-
- final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
- final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
- if (shouldShowPrivacyDot(pos, cutout)) {
- // TODO (b/201481944) Privacy Dots pos and var are wrong with long side cutout enable
- if (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP) {
- mTopLeftDot = left;
- mTopRightDot = right;
- } else {
- mBottomLeftDot = left;
- mBottomRightDot = right;
- }
- } else {
- viewGroup.removeView(left);
- viewGroup.removeView(right);
- }
- }
-
private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) {
final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
switch (rotatedPos) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index d31301a..3096b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -118,6 +118,7 @@
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
+ .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -133,6 +134,7 @@
.setAppPairs(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(Transitions.createEmptyForTesting())
+ .setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
.setTaskSurfaceHelper(Optional.ofNullable(null));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java
index d8e80fe..0d7551f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java
@@ -30,7 +30,7 @@
/**
* A span that turns the text wrapped by annotation tag into the clickable link text.
*/
-class AnnotationLinkSpan extends ClickableSpan {
+public class AnnotationLinkSpan extends ClickableSpan {
private final Optional<View.OnClickListener> mClickListener;
private AnnotationLinkSpan(View.OnClickListener listener) {
@@ -50,7 +50,7 @@
* @param linkInfos used to attach the click action into the corresponding span
* @return the text attached with the span
*/
- static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) {
+ public static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) {
final SpannableString msg = new SpannableString(text);
final Annotation[] spans =
msg.getSpans(/* queryStart= */ 0, msg.length(), Annotation.class);
@@ -78,12 +78,12 @@
/**
* Data class to store the annotation and the click action.
*/
- static class LinkInfo {
- static final String DEFAULT_ANNOTATION = "link";
+ public static class LinkInfo {
+ public static final String DEFAULT_ANNOTATION = "link";
private final Optional<String> mAnnotation;
private final Optional<View.OnClickListener> mListener;
- LinkInfo(@NonNull String annotation, View.OnClickListener listener) {
+ public LinkInfo(@NonNull String annotation, View.OnClickListener listener) {
mAnnotation = Optional.of(annotation);
mListener = Optional.ofNullable(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 0932a8c..8b04bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -272,9 +272,6 @@
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
}
private val udfpsControllerCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5018e57c..f7462b6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -256,6 +256,13 @@
@Override
public void hideUdfpsOverlay(int sensorId) {
mFgExecutor.execute(() -> {
+ if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
+ // to be updated shortly afterwards
+ Log.d(TAG, "hiding udfps overlay when "
+ + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true");
+ }
+
mServerRequest = null;
updateOverlay();
});
@@ -882,6 +889,7 @@
}
if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ mKeyguardViewManager.showBouncer(true);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index ea2bbfa..e231310 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -58,9 +58,6 @@
"start" -> {
udfpsController?.playStartHaptic()
}
- "acquired" -> {
- keyguardUpdateMonitor.playAcquiredHaptic()
- }
"success" -> {
// needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
vibrator?.vibrate(
@@ -82,7 +79,6 @@
pw.println("Usage: adb shell cmd statusbar udfps-haptic <haptic>")
pw.println("Available commands:")
pw.println(" start")
- pw.println(" acquired")
pw.println(" success, always plays CLICK haptic")
pw.println(" error, always plays DOUBLE_CLICK haptic")
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index db93b26..7a28c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -398,11 +398,6 @@
}
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 37a6cfa..0a93298 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -330,7 +330,7 @@
|| mTestHarness
|| mDataProvider.isJustUnlockedWithFace()
|| mDataProvider.isDocked()
- || mAccessibilityManager.isEnabled();
+ || mAccessibilityManager.isTouchExplorationEnabled();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java
index c72f542..7be8ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java
@@ -17,6 +17,9 @@
package com.android.systemui.communal;
import android.annotation.NonNull;
+import android.app.communal.CommunalManager;
+import android.content.Context;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -33,7 +36,9 @@
@SysUISingleton
public class CommunalStateController implements
CallbackController<CommunalStateController.Callback> {
+ private static final String TAG = CommunalStateController.class.getSimpleName();
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final CommunalManager mCommunalManager;
private boolean mCommunalViewOccluded;
private boolean mCommunalViewShowing;
@@ -56,7 +61,8 @@
@VisibleForTesting
@Inject
- public CommunalStateController() {
+ public CommunalStateController(Context context) {
+ mCommunalManager = context.getSystemService(CommunalManager.class);
}
/**
@@ -70,6 +76,12 @@
mCommunalViewShowing = communalViewShowing;
+ try {
+ mCommunalManager.setCommunalViewShowing(communalViewShowing);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error updating communal manager service state", e);
+ }
+
final ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
for (Callback callback : callbacks) {
callback.onCommunalViewShowingChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index d74df37..7972318 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -374,6 +374,12 @@
@Provides
@Singleton
+ static Optional<TelecomManager> provideOptionalTelecomManager(Context context) {
+ return Optional.ofNullable(context.getSystemService(TelecomManager.class));
+ }
+
+ @Provides
+ @Singleton
static TelephonyManager provideTelephonyManager(Context context) {
return context.getSystemService(TelephonyManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 0fdc4d8..a9a4da8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -25,11 +25,11 @@
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.InjectionInflationController;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -96,6 +96,9 @@
Builder setStartingSurface(Optional<StartingSurface> s);
@BindsInstance
+ Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
+
+ @BindsInstance
Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
SysUIComponent build();
@@ -143,11 +146,6 @@
InitController getInitController();
/**
- * ViewInstanceCreator generates all Views that need injection.
- */
- InjectionInflationController.ViewInstanceCreator.Factory createViewInstanceCreatorFactory();
-
- /**
* Member injection into the supplied argument.
*/
void inject(SystemUIAppComponentFactory factory);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 0923caaf..50d2dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -187,9 +187,13 @@
return new Recents(context, recentsImplementation, commandQueue);
}
- @Binds
- abstract DeviceProvisionedController bindDeviceProvisionedController(
- DeviceProvisionedControllerImpl deviceProvisionedController);
+ @SysUISingleton
+ @Provides
+ static DeviceProvisionedController bindDeviceProvisionedController(
+ DeviceProvisionedControllerImpl deviceProvisionedController) {
+ deviceProvisionedController.init();
+ return deviceProvisionedController;
+ }
@Binds
abstract KeyguardViewController bindKeyguardViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index e57e991..e845e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -76,7 +76,6 @@
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.user.UserModule;
-import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
import com.android.systemui.util.dagger.UtilModule;
import com.android.systemui.util.sensors.SensorModule;
@@ -218,11 +217,9 @@
@Provides
@SysUISingleton
- static StatusBarWindowView providesStatusBarWindowView(Context context,
- InjectionInflationController injectionInflationController) {
+ static StatusBarWindowView providesStatusBarWindowView(LayoutInflater layoutInflater) {
StatusBarWindowView view =
- (StatusBarWindowView) injectionInflationController.injectable(
- LayoutInflater.from(context)).inflate(R.layout.super_status_bar,
+ (StatusBarWindowView) layoutInflater.inflate(R.layout.super_status_bar,
/* root= */ null);
if (view == null) {
throw new IllegalStateException(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 442d351..618c26b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -27,6 +27,7 @@
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -105,5 +106,8 @@
Optional<StartingSurface> getStartingSurface();
@WMSingleton
+ Optional<DisplayAreaHelper> getDisplayAreaHelper();
+
+ @WMSingleton
Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 845d7dc..669965b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -26,6 +26,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -268,6 +269,16 @@
}
/**
+ * Appends doze updates due to a posture change.
+ */
+ public void tracePostureChanged(
+ @DevicePostureController.DevicePostureInt int posture,
+ String partUpdated
+ ) {
+ mLogger.logPostureChanged(posture, partUpdated);
+ }
+
+ /**
* Appends pulse dropped event to logs
*/
public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
@@ -391,8 +402,8 @@
case REASON_SENSOR_DOUBLE_TAP: return "doubletap";
case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
case PULSE_REASON_DOCKING: return "docking";
- case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "reach-wakelockscreen";
- case REASON_SENSOR_WAKE_UP: return "presence-wakeup";
+ case PULSE_REASON_SENSOR_WAKE_REACH: return "reach-wakelockscreen";
+ case REASON_SENSOR_WAKE_UP_PRESENCE: return "presence-wakeup";
case REASON_SENSOR_TAP: return "tap";
case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps";
case REASON_SENSOR_QUICK_PICKUP: return "quickPickup";
@@ -403,8 +414,8 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
- PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP,
- PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP,
+ PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP_PRESENCE,
+ PULSE_REASON_SENSOR_WAKE_REACH, REASON_SENSOR_TAP,
REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP})
public @interface Reason {}
public static final int PULSE_REASON_NONE = -1;
@@ -415,8 +426,8 @@
public static final int REASON_SENSOR_DOUBLE_TAP = 4;
public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5;
public static final int PULSE_REASON_DOCKING = 6;
- public static final int REASON_SENSOR_WAKE_UP = 7;
- public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8;
+ public static final int REASON_SENSOR_WAKE_UP_PRESENCE = 7;
+ public static final int PULSE_REASON_SENSOR_WAKE_REACH = 8;
public static final int REASON_SENSOR_TAP = 9;
public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10;
public static final int REASON_SENSOR_QUICK_PICKUP = 11;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index dc18618..d79bf22 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -24,6 +24,7 @@
import com.android.systemui.log.LogLevel.ERROR
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.DozeLog
+import com.android.systemui.statusbar.policy.DevicePostureController
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -195,6 +196,16 @@
})
}
+ fun logPostureChanged(posture: Int, partUpdated: String) {
+ buffer.log(TAG, INFO, {
+ int1 = posture
+ str1 = partUpdated
+ }, {
+ "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" +
+ " partUpdated=$str1"
+ })
+ }
+
fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
buffer.log(TAG, INFO, {
bool1 = pulsePending
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index da7b389..765c245 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,16 +29,18 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
-import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
+import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -60,14 +62,17 @@
private final DozeHost mDozeHost;
private final Handler mHandler;
private final SensorManager mSensorManager;
- private final Optional<Sensor> mLightSensorOptional;
+ private final Optional<Sensor>[] mLightSensorOptional; // light sensors to use per posture
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeParameters mDozeParameters;
- private final DockManager mDockManager;
+ private final DevicePostureController mDevicePostureController;
+ private final DozeLog mDozeLog;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
private final int mScreenBrightnessDim;
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture;
private boolean mRegistered;
private int mDefaultDozeBrightness;
private boolean mPaused = false;
@@ -83,27 +88,36 @@
private int mDebugBrightnessBucket = -1;
@Inject
- public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
+ public DozeScreenBrightness(
+ Context context,
+ @WrappedService DozeMachine.Service service,
AsyncSensorManager sensorManager,
- @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
+ @BrightnessSensor Optional<Sensor>[] lightSensorOptional,
+ DozeHost host, Handler handler,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
WakefulnessLifecycle wakefulnessLifecycle,
DozeParameters dozeParameters,
- DockManager dockManager) {
+ DevicePostureController devicePostureController,
+ DozeLog dozeLog
+ ) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
mLightSensorOptional = lightSensorOptional;
+ mDevicePostureController = devicePostureController;
+ mDevicePosture = mDevicePostureController.getDevicePosture();
mWakefulnessLifecycle = wakefulnessLifecycle;
mDozeParameters = dozeParameters;
mDozeHost = host;
mHandler = handler;
- mDockManager = dockManager;
+ mDozeLog = dozeLog;
mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
mSensorToBrightness = alwaysOnDisplayPolicy.screenBrightnessArray;
mSensorToScrimOpacity = alwaysOnDisplayPolicy.dimmingScrimArray;
+
+ mDevicePostureController.addCallback(mDevicePostureCallback);
}
@Override
@@ -133,6 +147,7 @@
private void onDestroy() {
setLightSensorEnabled(false);
+ mDevicePostureController.removeCallback(mDevicePostureCallback);
}
@Override
@@ -159,7 +174,7 @@
}
int scrimOpacity = -1;
- if (!mLightSensorOptional.isPresent()) {
+ if (!isLightSensorPresent()) {
// No light sensor, scrims are always transparent.
scrimOpacity = 0;
} else if (brightnessReady) {
@@ -172,6 +187,27 @@
}
}
+ private boolean lightSensorSupportsCurrentPosture() {
+ return mLightSensorOptional != null
+ && mDevicePosture < mLightSensorOptional.length;
+ }
+
+ private boolean isLightSensorPresent() {
+ if (!lightSensorSupportsCurrentPosture()) {
+ return mLightSensorOptional != null && mLightSensorOptional[0].isPresent();
+ }
+
+ return mLightSensorOptional[mDevicePosture].isPresent();
+ }
+
+ private Sensor getLightSensor() {
+ if (!lightSensorSupportsCurrentPosture()) {
+ return null;
+ }
+
+ return mLightSensorOptional[mDevicePosture].get();
+ }
+
private int computeScrimOpacity(int sensorValue) {
if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) {
return -1;
@@ -220,9 +256,9 @@
}
private void setLightSensorEnabled(boolean enabled) {
- if (enabled && !mRegistered && mLightSensorOptional.isPresent()) {
+ if (enabled && !mRegistered && isLightSensorPresent()) {
// Wait until we get an event from the sensor until indicating ready.
- mRegistered = mSensorManager.registerListener(this, mLightSensorOptional.get(),
+ mRegistered = mSensorManager.registerListener(this, getLightSensor(),
SensorManager.SENSOR_DELAY_NORMAL, mHandler);
mLastSensorValue = -1;
} else if (!enabled && mRegistered) {
@@ -255,6 +291,41 @@
/** Dump current state */
public void dump(PrintWriter pw) {
- pw.println("DozeScreenBrightnessSensorRegistered=" + mRegistered);
+ pw.println("DozeScreenBrightness:");
+ IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
+ idpw.increaseIndent();
+ idpw.println("registered=" + mRegistered);
+ idpw.println("posture=" + DevicePostureController.devicePostureToString(mDevicePosture));
}
+
+ private final DevicePostureController.Callback mDevicePostureCallback =
+ new DevicePostureController.Callback() {
+ @Override
+ public void onPostureChanged(int posture) {
+ if (mDevicePosture == posture
+ || mLightSensorOptional.length < 2
+ || posture >= mLightSensorOptional.length) {
+ return;
+ }
+
+ final Sensor oldSensor = mLightSensorOptional[mDevicePosture].get();
+ final Sensor newSensor = mLightSensorOptional[posture].get();
+ if (Objects.equals(oldSensor, newSensor)) {
+ mDevicePosture = posture;
+ // uses the same sensor for the new posture
+ return;
+ }
+
+ // cancel the previous sensor:
+ if (mRegistered) {
+ setLightSensorEnabled(false);
+ mDevicePosture = posture;
+ setLightSensorEnabled(true);
+ } else {
+ mDevicePosture = posture;
+ }
+ mDozeLog.tracePostureChanged(mDevicePosture, "DozeScreenBrightness swap "
+ + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered);
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 3cefce8..9d0591e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -38,6 +38,7 @@
import android.util.Log;
import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.UiEvent;
@@ -54,10 +55,36 @@
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.function.Consumer;
+/**
+ * Tracks and registers/unregisters sensors while the device is dozing based on the config
+ * provided by {@link AmbientDisplayConfiguration} and parameters provided by {@link DozeParameters}
+ *
+ * Sensors registration depends on:
+ * - sensor existence/availability
+ * - user configuration (some can be toggled on/off via settings)
+ * - use of the proximity sensor (sometimes prox cannot be registered in certain display states)
+ * - touch state
+ * - device posture
+ *
+ * Sensors will trigger the provided Callback's {@link Callback#onSensorPulse} method.
+ * These sensors include:
+ * - pickup gesture
+ * - single and double tap gestures
+ * - udfps long-press gesture
+ * - reach and presence gestures
+ * - quick pickup gesture (low-threshold pickup gesture)
+ *
+ * This class also registers a ProximitySensor that reports near/far events and will
+ * trigger callbacks on the provided {@link mProxCallback}.
+ */
public class DozeSensors {
private static final boolean DEBUG = DozeService.DEBUG;
@@ -68,15 +95,21 @@
private final AsyncSensorManager mSensorManager;
private final AmbientDisplayConfiguration mConfig;
private final WakeLock mWakeLock;
- private final Consumer<Boolean> mProxCallback;
+ private final DozeLog mDozeLog;
private final SecureSettings mSecureSettings;
- private final Callback mCallback;
+ private final DevicePostureController mDevicePostureController;
private final boolean mScreenOffUdfpsEnabled;
+
+ // Sensors
@VisibleForTesting
- protected TriggerSensor[] mSensors;
+ protected TriggerSensor[] mTriggerSensors;
+ private final ProximitySensor mProximitySensor;
+
+ // Sensor callbacks
+ private final Callback mSensorCallback; // receives callbacks on registered sensor events
+ private final Consumer<Boolean> mProxCallback; // receives callbacks on near/far updates
private final Handler mHandler = new Handler();
- private final ProximitySensor mProximitySensor;
private long mDebounceFrom;
private boolean mSettingRegistered;
private boolean mListening;
@@ -106,37 +139,47 @@
}
}
- DozeSensors(Context context, AsyncSensorManager sensorManager,
- DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
- ProximitySensor proximitySensor, SecureSettings secureSettings,
+ DozeSensors(
+ Context context,
+ AsyncSensorManager sensorManager,
+ DozeParameters dozeParameters,
+ AmbientDisplayConfiguration config,
+ WakeLock wakeLock,
+ Callback sensorCallback,
+ Consumer<Boolean> proxCallback,
+ DozeLog dozeLog,
+ ProximitySensor proximitySensor,
+ SecureSettings secureSettings,
AuthController authController,
- int devicePosture) {
+ DevicePostureController devicePostureController
+ ) {
mContext = context;
mSensorManager = sensorManager;
mConfig = config;
mWakeLock = wakeLock;
mProxCallback = proxCallback;
mSecureSettings = secureSettings;
- mCallback = callback;
+ mSensorCallback = sensorCallback;
+ mDozeLog = dozeLog;
mProximitySensor = proximitySensor;
mProximitySensor.setTag(TAG);
mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
mListeningProxSensors = !mSelectivelyRegisterProxSensors;
mScreenOffUdfpsEnabled =
config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
- mDevicePosture = devicePosture;
+ mDevicePostureController = devicePostureController;
+ mDevicePosture = mDevicePostureController.getDevicePosture();
boolean udfpsEnrolled =
authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
- mSensors = new TriggerSensor[] {
+ mTriggerSensors = new TriggerSensor[] {
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
null /* setting */,
dozeParameters.getPulseOnSigMotion(),
DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
- false /* touchscreen */, dozeLog),
+ false /* touchscreen */),
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
Settings.Secure.DOZE_PICK_UP_GESTURE,
@@ -145,18 +188,16 @@
DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
false /* touchscreen */,
false /* ignoresSetting */,
- false /* requires prox */,
- dozeLog),
+ false /* requires prox */),
new TriggerSensor(
findSensor(config.doubleTapSensorType()),
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
true /* configured */,
DozeLog.REASON_SENSOR_DOUBLE_TAP,
dozeParameters.doubleTapReportsTouchCoordinates(),
- true /* touchscreen */,
- dozeLog),
+ true /* touchscreen */),
new TriggerSensor(
- findSensor(config.tapSensorType(mDevicePosture)),
+ findSensors(config.tapSensorTypeMapping()),
Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
true /* settingDef */,
true /* configured */,
@@ -165,7 +206,7 @@
true /* touchscreen */,
false /* ignoresSetting */,
dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */,
- dozeLog),
+ mDevicePosture),
new TriggerSensor(
findSensor(config.longPressSensorType()),
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
@@ -175,8 +216,7 @@
true /* reports touch coordinates */,
true /* touchscreen */,
false /* ignoresSetting */,
- dozeParameters.longPressUsesProx() /* requiresProx */,
- dozeLog),
+ dozeParameters.longPressUsesProx() /* requiresProx */),
new TriggerSensor(
findSensor(config.udfpsLongPressSensorType()),
"doze_pulse_on_auth",
@@ -186,25 +226,22 @@
true /* reports touch coordinates */,
true /* touchscreen */,
false /* ignoresSetting */,
- dozeParameters.longPressUsesProx(),
- dozeLog),
+ dozeParameters.longPressUsesProx()),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
mConfig.wakeScreenGestureAvailable() && alwaysOn,
- DozeLog.REASON_SENSOR_WAKE_UP,
+ DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
false /* reports touch coordinates */,
- false /* touchscreen */,
- dozeLog),
+ false /* touchscreen */),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
mConfig.wakeScreenGestureAvailable(),
- DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN,
+ DozeLog.PULSE_REASON_SENSOR_WAKE_REACH,
false /* reports touch coordinates */,
false /* touchscreen */,
- mConfig.getWakeLockScreenDebounce(),
- dozeLog),
+ mConfig.getWakeLockScreenDebounce()),
new TriggerSensor(
findSensor(config.quickPickupSensorType()),
Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
@@ -212,10 +249,11 @@
config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser())
&& udfpsEnrolled,
DozeLog.REASON_SENSOR_QUICK_PICKUP,
- false /* touchCoords */,
- false /* touchscreen */, dozeLog),
+ false /* requiresTouchCoordinates */,
+ false /* requiresTouchscreen */,
+ false /* ignoresSetting */,
+ false /* requiresProx */),
};
-
setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
@@ -223,17 +261,21 @@
mProxCallback.accept(!proximityEvent.getBelow());
}
});
+
+ mDevicePostureController.addCallback(mDevicePostureCallback);
}
/**
- * Unregister any sensors.
+ * Unregister all sensors and callbacks.
*/
public void destroy() {
// Unregisters everything, which is enough to allow gc.
- for (TriggerSensor triggerSensor : mSensors) {
+ for (TriggerSensor triggerSensor : mTriggerSensors) {
triggerSensor.setListening(false);
}
mProximitySensor.pause();
+
+ mDevicePostureController.removeCallback(mDevicePostureCallback);
}
/**
@@ -248,6 +290,24 @@
return findSensor(mSensorManager, type, null);
}
+ @NonNull
+ private Sensor[] findSensors(@NonNull String[] types) {
+ Sensor[] sensorMap = new Sensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+
+ // Map of sensorType => Sensor, so we reuse the same sensor if it's the same between
+ // postures
+ Map<String, Sensor> typeToSensorMap = new HashMap<>();
+ for (int i = 0; i < types.length; i++) {
+ String sensorType = types[i];
+ if (!typeToSensorMap.containsKey(sensorType)) {
+ typeToSensorMap.put(sensorType, findSensor(sensorType));
+ }
+ sensorMap[i] = typeToSensorMap.get(sensorType);
+ }
+
+ return sensorMap;
+ }
+
/**
* Utility method to find a {@link Sensor} for the supplied string type and string name.
*
@@ -306,7 +366,7 @@
*/
private void updateListening() {
boolean anyListening = false;
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
boolean listen = mListening
&& (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
&& (!s.mRequiresProx || mListeningProxSensors);
@@ -319,7 +379,7 @@
if (!anyListening) {
mSecureSettings.unregisterContentObserver(mSettingsObserver);
} else if (!mSettingRegistered) {
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
}
}
@@ -328,7 +388,7 @@
/** Set the listening state of only the sensors that require the touchscreen. */
public void setTouchscreenSensorsListening(boolean listening) {
- for (TriggerSensor sensor : mSensors) {
+ for (TriggerSensor sensor : mTriggerSensors) {
if (sensor.mRequiresTouchscreen) {
sensor.setListening(listening);
}
@@ -336,7 +396,7 @@
}
public void onUserSwitched() {
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
s.updateListening();
}
}
@@ -366,7 +426,7 @@
if (userId != ActivityManager.getCurrentUser()) {
return;
}
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
s.updateListening();
}
}
@@ -374,7 +434,7 @@
/** Ignore the setting value of only the sensors that require the touchscreen. */
public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
- for (TriggerSensor sensor : mSensors) {
+ for (TriggerSensor sensor : mTriggerSensors) {
if (sensor.mRequiresTouchscreen) {
sensor.ignoreSetting(ignore);
}
@@ -392,7 +452,7 @@
pw.println("mScreenOffUdfpsEnabled=" + mScreenOffUdfpsEnabled);
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
idpw.println("Sensor: " + s.toString());
}
idpw.println("ProxSensor: " + mProximitySensor.toString());
@@ -407,7 +467,7 @@
@VisibleForTesting
class TriggerSensor extends TriggerEventListener {
- final Sensor mSensor;
+ @NonNull final Sensor[] mSensors; // index = posture, value = sensor
final boolean mConfigured;
final int mPulseReason;
private final String mSetting;
@@ -420,27 +480,67 @@
protected boolean mRegistered;
protected boolean mDisabled;
protected boolean mIgnoresSetting;
- protected final DozeLog mDozeLog;
+ private @DevicePostureController.DevicePostureInt int mPosture;
- public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
- boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog) {
- this(sensor, setting, true /* settingDef */, configured, pulseReason,
- reportsTouchCoordinates, requiresTouchscreen, dozeLog);
+ TriggerSensor(
+ Sensor sensor,
+ String setting,
+ boolean configured,
+ int pulseReason,
+ boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen
+ ) {
+ this(
+ sensor,
+ setting,
+ true /* settingDef */,
+ configured,
+ pulseReason,
+ false /* ignoresSetting */,
+ false /* requiresProx */,
+ reportsTouchCoordinates,
+ requiresTouchscreen
+ );
}
- public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
- boolean configured, int pulseReason, boolean reportsTouchCoordinates,
- boolean requiresTouchscreen, DozeLog dozeLog) {
- this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, false /* ignoresSetting */,
- false /* requiresProx */, dozeLog);
+ TriggerSensor(
+ Sensor sensor,
+ String setting,
+ boolean settingDef,
+ boolean configured,
+ int pulseReason,
+ boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen,
+ boolean ignoresSetting,
+ boolean requiresProx
+ ) {
+ this(
+ new Sensor[]{ sensor },
+ setting,
+ settingDef,
+ configured,
+ pulseReason,
+ reportsTouchCoordinates,
+ requiresTouchscreen,
+ ignoresSetting,
+ requiresProx,
+ DevicePostureController.DEVICE_POSTURE_UNKNOWN
+ );
}
- private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
- boolean configured, int pulseReason, boolean reportsTouchCoordinates,
- boolean requiresTouchscreen, boolean ignoresSetting, boolean requiresProx,
- DozeLog dozeLog) {
- mSensor = sensor;
+ TriggerSensor(
+ @NonNull Sensor[] sensors,
+ String setting,
+ boolean settingDef,
+ boolean configured,
+ int pulseReason,
+ boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen,
+ boolean ignoresSetting,
+ boolean requiresProx,
+ @DevicePostureController.DevicePostureInt int posture
+ ) {
+ mSensors = sensors;
mSetting = setting;
mSettingDefault = settingDef;
mConfigured = configured;
@@ -449,7 +549,43 @@
mRequiresTouchscreen = requiresTouchscreen;
mIgnoresSetting = ignoresSetting;
mRequiresProx = requiresProx;
- mDozeLog = dozeLog;
+ mPosture = posture;
+ }
+
+ /**
+ * @return true if the sensor changed based for the new posture
+ */
+ public boolean setPosture(@DevicePostureController.DevicePostureInt int posture) {
+ if (mPosture == posture
+ || mSensors.length < 2
+ || posture >= mSensors.length) {
+ return false;
+ }
+
+ Sensor oldSensor = mSensors[mPosture];
+ Sensor newSensor = mSensors[posture];
+ if (Objects.equals(oldSensor, newSensor)) {
+ mPosture = posture;
+ // uses the same sensor for the new posture
+ return false;
+ }
+
+ // cancel the previous sensor:
+ if (mRegistered) {
+ final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor);
+ if (DEBUG) {
+ Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] "
+ + rt);
+ }
+ mRegistered = false;
+ }
+
+ // update the new sensor:
+ mPosture = posture;
+ updateListening();
+ mDozeLog.tracePostureChanged(mPosture, "DozeSensors swap "
+ + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered);
+ return true;
}
public void setListening(boolean listen) {
@@ -471,24 +607,23 @@
}
public void updateListening() {
- if (!mConfigured || mSensor == null) return;
+ final Sensor sensor = mSensors[mPosture];
+ if (!mConfigured || sensor == null) return;
if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
if (!mRegistered) {
- mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+ mRegistered = mSensorManager.requestTriggerSensor(this, sensor);
if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + mSensor
- + "] " + mRegistered);
+ Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered);
}
} else {
if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + mSensor
- + "] already registered");
+ Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered");
}
}
} else if (mRegistered) {
- final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
+ final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor);
if (DEBUG) {
- Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+ Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt);
}
mRegistered = false;
}
@@ -506,21 +641,29 @@
@Override
public String toString() {
- return new StringBuilder("{mRegistered=").append(mRegistered)
+ StringBuilder sb = new StringBuilder();
+ sb.append("{")
+ .append("mRegistered=").append(mRegistered)
.append(", mRequested=").append(mRequested)
.append(", mDisabled=").append(mDisabled)
.append(", mConfigured=").append(mConfigured)
.append(", mIgnoresSetting=").append(mIgnoresSetting)
- .append(", mSensor=").append(mSensor).append("}").toString();
+ .append(", mSensors=").append(Arrays.toString(mSensors));
+ if (mSensors.length > 2) {
+ sb.append(", mPosture=")
+ .append(DevicePostureController.devicePostureToString(mDevicePosture));
+ }
+ return sb.append("}").toString();
}
@Override
@AnyThread
public void onTrigger(TriggerEvent event) {
+ final Sensor sensor = mSensors[mDevicePosture];
mDozeLog.traceSensor(mPulseReason);
mHandler.post(mWakeLock.wrap(() -> {
if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
- if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
+ if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
}
@@ -531,7 +674,7 @@
screenX = event.values[0];
screenY = event.values[1];
}
- mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
+ mSensorCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
if (!mRegistered) {
updateListening(); // reregister, this sensor only fires once
}
@@ -569,17 +712,16 @@
private long mDebounce;
PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
- int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
- DozeLog dozeLog) {
+ int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
this(sensor, setting, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, 0L /* debounce */, dozeLog);
+ requiresTouchscreen, 0L /* debounce */);
}
PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
- long debounce, DozeLog dozeLog) {
+ long debounce) {
super(null, setting, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, dozeLog);
+ requiresTouchscreen);
mPluginSensor = sensor;
mDebounce = debounce;
}
@@ -633,11 +775,22 @@
return;
}
if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
- mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
+ mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
}));
}
}
+ private final DevicePostureController.Callback mDevicePostureCallback = posture -> {
+ if (mDevicePosture == posture) {
+ return;
+ }
+ mDevicePosture = posture;
+
+ for (TriggerSensor triggerSensor : mTriggerSensors) {
+ triggerSensor.setPosture(mDevicePosture);
+ }
+ };
+
public interface Callback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b17f078..20cd5b9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -99,8 +99,6 @@
private final UiEventLogger mUiEventLogger;
private final DevicePostureController mDevicePostureController;
- private @DevicePostureController.DevicePostureInt int mDevicePosture;
-
private long mNotificationPulseTime;
private boolean mPulsePending;
private Runnable mAodInterruptRunnable;
@@ -197,11 +195,12 @@
mSensorManager = sensorManager;
mWakeLock = wakeLock;
mAllowPulseTriggers = true;
+
mDevicePostureController = devicePostureController;
- mDevicePosture = devicePostureController.getDevicePosture();
mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings, authController, mDevicePosture);
+ secureSettings, authController, devicePostureController);
+
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
mProxCheck = proxCheck;
@@ -212,6 +211,10 @@
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
}
+ private final DevicePostureController.Callback mDevicePostureCallback =
+ posture -> {
+
+ };
@Override
public void setDozeMachine(DozeMachine dozeMachine) {
@@ -284,8 +287,8 @@
boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP;
boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP;
boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
- boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP;
- boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN;
+ boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE;
+ boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH;
boolean isUdfpsLongPress = pulseReason == DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
boolean isQuickPickup = pulseReason == DozeLog.REASON_SENSOR_QUICK_PICKUP;
boolean isWakeDisplayEvent = isQuickPickup || ((isWakeOnPresence || isWakeOnReach)
@@ -455,7 +458,7 @@
mWantSensors = true;
mWantTouchScreenSensors = true;
if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
- onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP);
+ onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE);
}
break;
case DOZE_AOD_PAUSED:
@@ -524,7 +527,7 @@
// When already pulsing we're allowed to show the wallpaper directly without
// requesting a new pulse.
if (dozeState == DozeMachine.State.DOZE_PULSING
- && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index fbe06b0..374bed3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -155,7 +155,7 @@
public void onPulseStarted() {
try {
mMachine.requestState(
- reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+ reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
? DozeMachine.State.DOZE_PULSING_BRIGHT
: DozeMachine.State.DOZE_PULSING);
} catch (IllegalStateException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 571b666..32b7658 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -43,6 +43,9 @@
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
import dagger.Module;
@@ -94,16 +97,43 @@
@Provides
@BrightnessSensor
- static Optional<Sensor> providesBrightnessSensor(
+ static Optional<Sensor>[] providesBrightnessSensors(
AsyncSensorManager sensorManager,
Context context,
- DozeParameters dozeParameters,
- DevicePostureController devicePostureController) {
- return Optional.ofNullable(
- DozeSensors.findSensor(
- sensorManager,
- context.getString(R.string.doze_brightness_sensor_type),
- dozeParameters.brightnessName(devicePostureController.getDevicePosture())
- ));
+ DozeParameters dozeParameters) {
+ String[] sensorNames = dozeParameters.brightnessNames();
+ if (sensorNames.length == 0 || sensorNames == null) {
+ // if no brightness names are specified, just use the brightness sensor type
+ return new Optional[]{
+ Optional.ofNullable(DozeSensors.findSensor(
+ sensorManager,
+ context.getString(R.string.doze_brightness_sensor_type),
+ null
+ ))
+ };
+ }
+
+ // length and index of brightnessMap correspond to DevicePostureController.DevicePostureInt:
+ final Optional<Sensor>[] brightnessSensorMap =
+ new Optional[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+ Arrays.fill(brightnessSensorMap, Optional.empty());
+
+ // Map of sensorName => Sensor, so we reuse the same sensor if it's the same between
+ // postures
+ Map<String, Optional<Sensor>> nameToSensorMap = new HashMap<>();
+ for (int i = 0; i < sensorNames.length; i++) {
+ final String sensorName = sensorNames[i];
+ if (!nameToSensorMap.containsKey(sensorName)) {
+ nameToSensorMap.put(sensorName,
+ Optional.ofNullable(
+ DozeSensors.findSensor(
+ sensorManager,
+ context.getString(R.string.doze_brightness_sensor_type),
+ sensorNames[i]
+ )));
+ }
+ brightnessSensorMap[i] = nameToSensorMap.get(sensorName);
+ }
+ return brightnessSensorMap;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 77bd777..5eff0e6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -33,7 +33,7 @@
/**
* Class to manage simple DeviceConfig-based feature flags.
*
- * See {@link FeatureFlagReader} for instructions on defining and flipping flags.
+ * See {@link Flags} for instructions on defining new flags.
*/
@SysUISingleton
public class FeatureFlags {
@@ -87,57 +87,61 @@
}
public boolean isNewNotifPipelineEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
+ return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE);
}
public boolean isNewNotifPipelineRenderingEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering);
- }
-
- public boolean isKeyguardLayoutEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
+ return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING);
}
/** */
public boolean useNewLockscreenAnimations() {
- return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
+ return isEnabled(Flags.LOCKSCREEN_ANIMATIONS);
}
public boolean isPeopleTileEnabled() {
+ // TODO(b/202860494): different resource overlays have different values.
return mFlagReader.isEnabled(R.bool.flag_conversations);
}
public boolean isMonetEnabled() {
+ // TODO(b/202860494): used in wallpaper picker. Always true, maybe delete.
return mFlagReader.isEnabled(R.bool.flag_monet);
}
public boolean isPMLiteEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_pm_lite);
+ return isEnabled(Flags.POWER_MENU_LITE);
}
public boolean isChargingRippleEnabled() {
+ // TODO(b/202860494): different resource overlays have different values.
return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
}
public boolean isOngoingCallStatusBarChipEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip);
+ return isEnabled(Flags.ONGOING_CALL_STATUS_BAR_CHIP);
}
public boolean isOngoingCallInImmersiveEnabled() {
- return isOngoingCallStatusBarChipEnabled()
- && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive);
+ return isOngoingCallStatusBarChipEnabled() && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE);
+ }
+
+ public boolean isOngoingCallInImmersiveChipTapEnabled() {
+ return isOngoingCallInImmersiveEnabled()
+ && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP);
}
public boolean isSmartspaceEnabled() {
+ // TODO(b/202860494): different resource overlays have different values.
return mFlagReader.isEnabled(R.bool.flag_smartspace);
}
public boolean isSmartspaceDedupingEnabled() {
- return isSmartspaceEnabled() && mFlagReader.isEnabled(R.bool.flag_smartspace_deduping);
+ return isSmartspaceEnabled() && isEnabled(Flags.SMARTSPACE_DEDUPING);
}
public boolean isNewKeyguardSwipeAnimationEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_new_unlock_swipe_animation);
+ return isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION);
}
public boolean isKeyguardQsUserDetailsShortcutEnabled() {
@@ -145,12 +149,12 @@
}
public boolean isSmartSpaceSharedElementTransitionEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_smartspace_shared_element_transition);
+ return isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED);
}
/** Whether or not to use the provider model behavior for the status bar icons */
public boolean isCombinedStatusBarSignalIconsEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_combined_status_bar_signal_icons);
+ return isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS);
}
/** System setting for provider model behavior */
@@ -162,7 +166,7 @@
* Use the new version of the user switcher
*/
public boolean useNewUserSwitcher() {
- return mFlagReader.isEnabled(R.bool.flag_new_user_switcher);
+ return isEnabled(Flags.NEW_USER_SWITCHER);
}
/** static method for the system setting */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6561bd5..1dc5a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -26,11 +26,19 @@
*/
@SysUISingleton
open class SystemPropertiesHelper @Inject constructor() {
+ fun get(name: String): String {
+ return SystemProperties.get(name)
+ }
+
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+ fun set(name: String, value: String) {
+ SystemProperties.set(name, value)
+ }
+
fun set(name: String, value: Int) {
- SystemProperties.set(name, value.toString())
+ set(name, value.toString())
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index a51ec54..729730c 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -66,6 +66,13 @@
mOnBitmapUpdated = c;
}
+ /**
+ * @hide
+ */
+ public void use(Consumer<Bitmap> c) {
+ mTexture.use(c);
+ }
+
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt
new file mode 100644
index 0000000..8564497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.idle
+
+import android.annotation.IntDef
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.util.Log
+import com.android.systemui.util.sensors.AsyncSensorManager
+import javax.inject.Inject
+import kotlin.properties.Delegates
+
+/**
+ * Monitors ambient light signals, applies a debouncing algorithm, and produces the current
+ * [AmbientLightMode].
+ *
+ * For debouncer behavior, refer to go/titan-light-sensor-debouncer.
+ *
+ * @property sensorManager the sensor manager used to register sensor event updates.
+ */
+class AmbientLightModeMonitor @Inject constructor(
+ private val sensorManager: AsyncSensorManager
+) {
+ companion object {
+ private const val TAG = "AmbientLightModeMonitor"
+ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+ const val AMBIENT_LIGHT_MODE_LIGHT = 0
+ const val AMBIENT_LIGHT_MODE_DARK = 1
+ const val AMBIENT_LIGHT_MODE_UNDECIDED = 2
+ }
+
+ // Light sensor used to detect ambient lighting conditions.
+ private val lightSensor: Sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
+
+ // Registered callback, which gets triggered when the ambient light mode changes.
+ private var callback: Callback? = null
+
+ // Represents all ambient light modes.
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED)
+ annotation class AmbientLightMode
+
+ // The current ambient light mode.
+ @AmbientLightMode private var mode: Int by Delegates.observable(AMBIENT_LIGHT_MODE_UNDECIDED
+ ) { _, old, new ->
+ if (old != new) {
+ callback?.onChange(new)
+ }
+ }
+
+ /**
+ * Start monitoring the current ambient light mode.
+ *
+ * @param callback callback that gets triggered when the ambient light mode changes. It also
+ * gets triggered immediately to update the current value when this function is called.
+ */
+ fun start(callback: Callback) {
+ if (DEBUG) Log.d(TAG, "start monitoring ambient light mode")
+
+ if (this.callback != null) {
+ if (DEBUG) Log.w(TAG, "already started")
+ return
+ }
+
+ this.callback = callback
+ callback.onChange(mode)
+
+ sensorManager.registerListener(mSensorEventListener, lightSensor,
+ SensorManager.SENSOR_DELAY_NORMAL)
+ }
+
+ /**
+ * Stop monitoring the current ambient light mode.
+ */
+ fun stop() {
+ if (DEBUG) Log.d(TAG, "stop monitoring ambient light mode")
+
+ if (callback == null) {
+ if (DEBUG) Log.w(TAG, "haven't started")
+ return
+ }
+
+ callback = null
+ sensorManager.unregisterListener(mSensorEventListener)
+ }
+
+ private val mSensorEventListener: SensorEventListener = object : SensorEventListener {
+ override fun onSensorChanged(event: SensorEvent) {
+ if (event.values.isEmpty()) {
+ if (DEBUG) Log.w(TAG, "SensorEvent doesn't have any value")
+ return
+ }
+
+ // TODO(b/201657509): add debouncing logic.
+ val shouldBeLowLight = event.values[0] < 10
+ mode = if (shouldBeLowLight) AMBIENT_LIGHT_MODE_DARK else AMBIENT_LIGHT_MODE_LIGHT
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Interface of the ambient light mode callback, which gets triggered when the mode changes.
+ */
+ interface Callback {
+ fun onChange(@AmbientLightMode mode: Int)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java b/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java
index 192c4184a..6b212b7 100644
--- a/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java
@@ -24,10 +24,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -44,7 +40,6 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.sensors.AsyncSensorManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -56,8 +51,7 @@
/**
* {@link IdleHostViewController} processes signals to control the lifecycle of the idle screen.
*/
-public class IdleHostViewController extends ViewController<IdleHostView> implements
- SensorEventListener {
+public class IdleHostViewController extends ViewController<IdleHostView> {
private static final String INPUT_MONITOR_IDENTIFIER = "IdleHostViewController";
private static final String TAG = "IdleHostViewController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -114,11 +108,6 @@
private final PowerManager mPowerManager;
- private final AsyncSensorManager mSensorManager;
-
- // Light sensor used to detect low light condition.
- private final Sensor mSensor;
-
// Runnable for canceling enabling idle.
private Runnable mCancelEnableIdling;
@@ -146,6 +135,9 @@
// Intent filter for receiving dream broadcasts.
private IntentFilter mDreamIntentFilter;
+ // Monitor for the current ambient light mode. Used to trigger / exit low-light mode.
+ private final AmbientLightModeMonitor mAmbientLightModeMonitor;
+
// Delayed callback for starting idling.
private final Runnable mEnableIdlingCallback = () -> {
if (DEBUG) {
@@ -181,6 +173,31 @@
}
};
+ private final AmbientLightModeMonitor.Callback mAmbientLightModeCallback =
+ mode -> {
+ boolean shouldBeLowLight;
+ switch (mode) {
+ case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED:
+ return;
+ case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT:
+ shouldBeLowLight = false;
+ break;
+ case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK:
+ shouldBeLowLight = true;
+ break;
+ default:
+ Log.w(TAG, "invalid ambient light mode");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "ambient light mode changed to " + mode);
+
+ final boolean isLowLight = getState(STATE_LOW_LIGHT);
+ if (shouldBeLowLight != isLowLight) {
+ setState(STATE_LOW_LIGHT, shouldBeLowLight);
+ }
+ };
+
final Provider<View> mIdleViewProvider;
@Inject
@@ -188,7 +205,6 @@
Context context,
BroadcastDispatcher broadcastDispatcher,
PowerManager powerManager,
- AsyncSensorManager sensorManager,
IdleHostView view, InputMonitorFactory factory,
@Main DelayableExecutor delayableExecutor,
@Main Resources resources,
@@ -197,18 +213,19 @@
Choreographer choreographer,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- DreamHelper dreamHelper) {
+ DreamHelper dreamHelper,
+ AmbientLightModeMonitor ambientLightModeMonitor) {
super(view);
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mPowerManager = powerManager;
- mSensorManager = sensorManager;
mIdleViewProvider = idleViewProvider;
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
mLooper = looper;
mChoreographer = choreographer;
mDreamHelper = dreamHelper;
+ mAmbientLightModeMonitor = ambientLightModeMonitor;
mState = STATE_KEYGUARD_SHOWING;
@@ -222,7 +239,6 @@
mIdleTimeout = resources.getInteger(R.integer.config_idleModeTimeout);
mInputMonitorFactory = factory;
mDelayableExecutor = delayableExecutor;
- mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
if (DEBUG) {
Log.d(TAG, "initial state:" + mState + " enabled:" + enabled
@@ -405,11 +421,10 @@
if (mIsMonitoringLowLight) {
if (DEBUG) Log.d(TAG, "enable low light monitoring");
- mSensorManager.registerListener(this /*listener*/, mSensor,
- SensorManager.SENSOR_DELAY_NORMAL);
+ mAmbientLightModeMonitor.start(mAmbientLightModeCallback);
} else {
if (DEBUG) Log.d(TAG, "disable low light monitoring");
- mSensorManager.unregisterListener(this);
+ mAmbientLightModeMonitor.stop();
}
}
@@ -449,28 +464,6 @@
mStatusBarStateController.removeCallback(mStatusBarCallback);
}
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.values.length == 0) {
- if (DEBUG) Log.w(TAG, "SensorEvent doesn't have value");
- return;
- }
-
- final boolean shouldBeLowLight = event.values[0] < 10;
- final boolean isLowLight = getState(STATE_LOW_LIGHT);
-
- if (shouldBeLowLight != isLowLight) {
- setState(STATE_LOW_LIGHT, shouldBeLowLight);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- if (DEBUG) {
- Log.d(TAG, "onAccuracyChanged accuracy=" + accuracy);
- }
- }
-
// Returns whether the device just stopped idling by comparing the previous state with the
// current one.
private boolean stoppedIdling(int oldState) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index a5dd6a1..01a0f27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -102,7 +102,7 @@
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
@@ -358,7 +358,7 @@
if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
mBinder.setOccluded(true /* isOccluded */, true /* animate */);
} else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) {
- mBinder.setOccluded(false /* isOccluded */, true /* animate */);
+ mBinder.setOccluded(false /* isOccluded */, false /* animate */);
}
// TODO(bc-unlock): Implement (un)occlude animation.
finishedCallback.onAnimationFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index e51b602..2cc564b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -62,7 +62,7 @@
* The dismiss amount is the inverse of the notification panel expansion, which decreases as the
* lock screen is swiped away.
*/
-const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f
+const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f
/**
* Dismiss amount at which to complete the keyguard exit animation and hide the keyguard.
@@ -70,7 +70,7 @@
* The dismiss amount is the inverse of the notification panel expansion, which decreases as the
* lock screen is swiped away.
*/
-const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f
+const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f
/**
* Initiates, controls, and ends the keyguard unlock animation.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5265718..1547cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -119,12 +119,15 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
import dagger.Lazy;
@@ -670,13 +673,6 @@
}
}
}
-
- @Override
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- synchronized (KeyguardViewMediator.this) {
- notifyHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
};
ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -815,6 +811,10 @@
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
+ private final UnfoldTransitionConfig mUnfoldTransitionConfig;
+ private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
+ private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
+
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private boolean mWallpaperSupportsAmbientMode;
@@ -837,6 +837,8 @@
NavigationModeController navigationModeController,
KeyguardDisplayManager keyguardDisplayManager,
DozeParameters dozeParameters,
+ UnfoldTransitionConfig unfoldTransitionConfig,
+ Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
SysuiStatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
@@ -870,6 +872,8 @@
mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
}));
mDozeParameters = dozeParameters;
+ mUnfoldTransitionConfig = unfoldTransitionConfig;
+ mUnfoldLightRevealAnimation = unfoldLightRevealOverlayAnimation;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2558,6 +2562,24 @@
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurningOn");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
+
+ if (mUnfoldTransitionConfig.isEnabled()) {
+ mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
+
+ mUnfoldLightRevealAnimation.get()
+ .onScreenTurningOn(() -> {
+ if (mPendingDrawnTasks.decrementAndGet() == 0) {
+ try {
+ callback.onDrawn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception calling onDrawn():", e);
+ }
+ }
+ });
+ } else {
+ mPendingDrawnTasks.set(1); // only keyguard drawn
+ }
+
mKeyguardViewControllerLazy.get().onScreenTurningOn();
if (callback != null) {
if (mWakeAndUnlocking) {
@@ -2588,10 +2610,12 @@
private void notifyDrawn(final IKeyguardDrawnCallback callback) {
Trace.beginSection("KeyguardViewMediator#notifyDrawn");
- try {
- callback.onDrawn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception calling onDrawn():", e);
+ if (mPendingDrawnTasks.decrementAndGet() == 0) {
+ try {
+ callback.onDrawn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception calling onDrawn():", e);
+ }
}
Trace.endSection();
}
@@ -2744,6 +2768,7 @@
pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun);
pw.print(" mPendingReset: "); pw.println(mPendingReset);
pw.print(" mPendingLock: "); pw.println(mPendingLock);
+ pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback);
}
@@ -2873,21 +2898,6 @@
}
}
- private void notifyHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- int size = mKeyguardStateCallbacks.size();
- for (int i = size - 1; i >= 0; i--) {
- try {
- mKeyguardStateCallbacks.get(i).onHasLockscreenWallpaperChanged(
- hasLockscreenWallpaper);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onHasLockscreenWallpaperChanged", e);
- if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(i);
- }
- }
- }
- }
-
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -2897,7 +2907,6 @@
callback.onInputRestrictedStateChanged(mInputRestricted);
callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
KeyguardUpdateMonitor.getCurrentUser()));
- callback.onHasLockscreenWallpaperChanged(mUpdateMonitor.hasLockscreenWallpaper());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 8a383b9..11d4aac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -55,6 +55,8 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.settings.GlobalSettings;
@@ -99,6 +101,8 @@
NavigationModeController navigationModeController,
KeyguardDisplayManager keyguardDisplayManager,
DozeParameters dozeParameters,
+ UnfoldTransitionConfig unfoldTransitionConfig,
+ Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
SysuiStatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
@@ -121,6 +125,8 @@
navigationModeController,
keyguardDisplayManager,
dozeParameters,
+ unfoldTransitionConfig,
+ unfoldLightRevealOverlayAnimation,
statusBarStateController,
keyguardStateController,
keyguardUnlockAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 84c5a57..72601e9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -111,6 +111,17 @@
return factory.create("CollapsedSbFragmentLog", 20);
}
+ /**
+ * Provides a logging buffer for logs related to swiping away the status bar while in immersive
+ * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+ @Provides
+ @SysUISingleton
+ @SwipeStatusBarAwayLog
+ public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) {
+ return factory.create("SwipeStatusBarAwayLog", 30);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
new file mode 100644
index 0000000..dd68375
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface SwipeStatusBarAwayLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 0e70945..e87558e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -148,7 +148,7 @@
inflateSettingsButton()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
recreatePlayers()
inflateSettingsButton()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 8576a28..7809b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -93,7 +93,6 @@
import android.view.Display;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
-import android.view.IWindowManager;
import android.view.InsetsState.InternalInsetsType;
import android.view.InsetsVisibilities;
import android.view.KeyEvent;
@@ -118,20 +117,17 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
-import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
@@ -151,8 +147,6 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;
@@ -162,6 +156,8 @@
import java.util.Optional;
import java.util.function.Consumer;
+import javax.inject.Inject;
+
import dagger.Lazy;
/**
@@ -243,7 +239,13 @@
private boolean mTransientShown;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
+ private final LightBarController mMainLightBarController;
+ private final LightBarController.Factory mLightBarControllerFactory;
private AutoHideController mAutoHideController;
+ private final AutoHideController mMainAutoHideController;
+ private final AutoHideController.Factory mAutoHideControllerFactory;
+ private final Optional<TelecomManager> mTelecomManagerOptional;
+ private final InputMethodManager mInputMethodManager;
@VisibleForTesting
public int mDisplayId;
@@ -267,6 +269,7 @@
private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
private boolean mShowOrientedHandleForImmersiveMode;
+
@com.android.internal.annotations.VisibleForTesting
public enum NavBarActionEvent implements UiEventLogger.UiEventEnum {
@@ -478,11 +481,10 @@
}
};
- public NavigationBar(Context context,
+ private NavigationBar(Context context,
WindowManager windowManager,
Lazy<AssistManager> assistManagerLazy,
AccessibilityManager accessibilityManager,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
@@ -504,7 +506,13 @@
NavigationBarOverlayController navbarOverlayController,
UiEventLogger uiEventLogger,
NavigationBarA11yHelper navigationBarA11yHelper,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ LightBarController mainLightBarController,
+ LightBarController.Factory lightBarControllerFactory,
+ AutoHideController mainAutoHideController,
+ AutoHideController.Factory autoHideControllerFactory,
+ Optional<TelecomManager> telecomManagerOptional,
+ InputMethodManager inputMethodManager) {
mContext = context;
mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
@@ -531,6 +539,12 @@
mNavigationBarA11yHelper = navigationBarA11yHelper;
mUserTracker = userTracker;
mNotificationShadeDepthController = notificationShadeDepthController;
+ mMainLightBarController = mainLightBarController;
+ mLightBarControllerFactory = lightBarControllerFactory;
+ mMainAutoHideController = mainAutoHideController;
+ mAutoHideControllerFactory = autoHideControllerFactory;
+ mTelecomManagerOptional = telecomManagerOptional;
+ mInputMethodManager = inputMethodManager;
mNavBarMode = mNavigationModeController.addListener(this);
}
@@ -548,7 +562,7 @@
mNavigationBarView = barView.findViewById(R.id.navigation_bar_view);
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
- mContext.getSystemService(WindowManager.class).addView(mFrame,
+ mWindowManager.addView(mFrame,
getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
.getRotation()));
mDisplayId = mContext.getDisplayId();
@@ -606,8 +620,7 @@
public void destroyView() {
setAutoHideController(/* autoHideController */ null);
mCommandQueue.removeCallback(this);
- mContext.getSystemService(WindowManager.class).removeViewImmediate(
- mNavigationBarView.getRootView());
+ mWindowManager.removeViewImmediate(mNavigationBarView.getRootView());
mNavigationModeController.removeListener(this);
mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
@@ -673,22 +686,16 @@
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
LightBarController lightBarController = mIsOnDefaultDisplay
- ? Dependency.get(LightBarController.class)
- : new LightBarController(mContext,
- Dependency.get(DarkIconDispatcher.class),
- Dependency.get(BatteryController.class),
- Dependency.get(NavigationModeController.class),
- Dependency.get(DumpManager.class));
+ ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
// per-display, while others may be global. I think it's time to
// add a new class maybe named DisplayDependency to solve
// per-display Dependency problem.
+ // Alternative: this is a good case for a Dagger subcomponent. Same with LightBarController.
AutoHideController autoHideController = mIsOnDefaultDisplay
- ? Dependency.get(AutoHideController.class)
- : new AutoHideController(mContext, mHandler,
- Dependency.get(IWindowManager.class));
+ ? mMainAutoHideController : mAutoHideControllerFactory.create(mContext);
setAutoHideController(autoHideController);
restoreAppearanceAndTransientState();
}
@@ -1183,9 +1190,8 @@
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mHomeBlockedThisTouch = false;
- TelecomManager telecomManager =
- mContext.getSystemService(TelecomManager.class);
- if (telecomManager != null && telecomManager.isRinging()) {
+ if (mTelecomManagerOptional.isPresent()
+ && mTelecomManagerOptional.get().isRinging()) {
if (statusBarOptional.map(StatusBar::isKeyguardShowing).orElse(false)) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
"No heads up");
@@ -1267,7 +1273,7 @@
}
private void onImeSwitcherClick(View v) {
- mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+ mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
};
@@ -1702,4 +1708,121 @@
int getNavigationIconHints() {
return mNavigationIconHints;
}
-}
+
+ /**
+ * Injectable factory for construction a {@link NavigationBar}.
+ */
+ public static class Factory {
+ private final Lazy<AssistManager> mAssistManagerLazy;
+ private final AccessibilityManager mAccessibilityManager;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final MetricsLogger mMetricsLogger;
+ private final OverviewProxyService mOverviewProxyService;
+ private final NavigationModeController mNavigationModeController;
+ private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+ private final StatusBarStateController mStatusBarStateController;
+ private final SysUiState mSysUiFlagsContainer;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final CommandQueue mCommandQueue;
+ private final Optional<Pip> mPipOptional;
+ private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<Recents> mRecentsOptional;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
+ private final ShadeController mShadeController;
+ private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+ private final NotificationShadeDepthController mNotificationShadeDepthController;
+ private final SystemActions mSystemActions;
+ private final Handler mMainHandler;
+ private final NavigationBarOverlayController mNavbarOverlayController;
+ private final UiEventLogger mUiEventLogger;
+ private final NavigationBarA11yHelper mNavigationBarA11yHelper;
+ private final UserTracker mUserTracker;
+ private final LightBarController mMainLightBarController;
+ private final LightBarController.Factory mLightBarControllerFactory;
+ private final AutoHideController mMainAutoHideController;
+ private final AutoHideController.Factory mAutoHideControllerFactory;
+ private final Optional<TelecomManager> mTelecomManagerOptional;
+ private final InputMethodManager mInputMethodManager;
+
+ @Inject
+ public Factory(
+ Lazy<AssistManager> assistManagerLazy,
+ AccessibilityManager accessibilityManager,
+ DeviceProvisionedController deviceProvisionedController,
+ MetricsLogger metricsLogger,
+ OverviewProxyService overviewProxyService,
+ NavigationModeController navigationModeController,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver,
+ StatusBarStateController statusBarStateController,
+ SysUiState sysUiFlagsContainer,
+ BroadcastDispatcher broadcastDispatcher,
+ CommandQueue commandQueue,
+ Optional<Pip> pipOptional,
+ Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<Recents> recentsOptional,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
+ ShadeController shadeController,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationShadeDepthController notificationShadeDepthController,
+ SystemActions systemActions,
+ @Main Handler mainHandler,
+ NavigationBarOverlayController navbarOverlayController,
+ UiEventLogger uiEventLogger,
+ NavigationBarA11yHelper navigationBarA11yHelper,
+ UserTracker userTracker,
+ LightBarController mainLightBarController,
+ LightBarController.Factory lightBarControllerFactory,
+ AutoHideController mainAutoHideController,
+ AutoHideController.Factory autoHideControllerFactory,
+ Optional<TelecomManager> telecomManagerOptional,
+ InputMethodManager inputMethodManager) {
+ mAssistManagerLazy = assistManagerLazy;
+ mAccessibilityManager = accessibilityManager;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mMetricsLogger = metricsLogger;
+ mOverviewProxyService = overviewProxyService;
+ mNavigationModeController = navigationModeController;
+ mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
+ mStatusBarStateController = statusBarStateController;
+ mSysUiFlagsContainer = sysUiFlagsContainer;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mCommandQueue = commandQueue;
+ mPipOptional = pipOptional;
+ mSplitScreenOptional = splitScreenOptional;
+ mRecentsOptional = recentsOptional;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
+ mShadeController = shadeController;
+ mNotificationRemoteInputManager = notificationRemoteInputManager;
+ mNotificationShadeDepthController = notificationShadeDepthController;
+ mSystemActions = systemActions;
+ mMainHandler = mainHandler;
+ mNavbarOverlayController = navbarOverlayController;
+ mUiEventLogger = uiEventLogger;
+ mNavigationBarA11yHelper = navigationBarA11yHelper;
+ mUserTracker = userTracker;
+ mMainLightBarController = mainLightBarController;
+ mLightBarControllerFactory = lightBarControllerFactory;
+ mMainAutoHideController = mainAutoHideController;
+ mAutoHideControllerFactory = autoHideControllerFactory;
+ mTelecomManagerOptional = telecomManagerOptional;
+ mInputMethodManager = inputMethodManager;
+ }
+
+ /** Construct a {@link NavigationBar} */
+ public NavigationBar create(Context context) {
+ final WindowManager wm = context.getSystemService(WindowManager.class);
+ return new NavigationBar(context, wm, mAssistManagerLazy,
+ mAccessibilityManager, mDeviceProvisionedController, mMetricsLogger,
+ mOverviewProxyService, mNavigationModeController,
+ mAccessibilityButtonModeObserver, mStatusBarStateController,
+ mSysUiFlagsContainer, mBroadcastDispatcher, mCommandQueue, mPipOptional,
+ mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy,
+ mShadeController, mNotificationRemoteInputManager,
+ mNotificationShadeDepthController, mSystemActions, mMainHandler,
+ mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper,
+ mUserTracker, mMainLightBarController, mLightBarControllerFactory,
+ mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional,
+ mInputMethodManager);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 7622d66..97bcb00 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -32,52 +32,31 @@
import android.view.Display;
import android.view.IWindowManager;
import android.view.View;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dumpable;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.pip.Pip;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.Optional;
import javax.inject.Inject;
-import dagger.Lazy;
-
/** A controller to handle navigation bars. */
@SysUISingleton
@@ -90,36 +69,12 @@
private static final String TAG = NavigationBarController.class.getSimpleName();
private final Context mContext;
- private final WindowManager mWindowManager;
- private final Lazy<AssistManager> mAssistManagerLazy;
- private final AccessibilityManager mAccessibilityManager;
- private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
- private final DeviceProvisionedController mDeviceProvisionedController;
- private final MetricsLogger mMetricsLogger;
- private final OverviewProxyService mOverviewProxyService;
- private final NavigationModeController mNavigationModeController;
- private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
- private final StatusBarStateController mStatusBarStateController;
- private final SysUiState mSysUiFlagsContainer;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final CommandQueue mCommandQueue;
- private final Optional<Pip> mPipOptional;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
- private final Optional<Recents> mRecentsOptional;
- private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
- private final ShadeController mShadeController;
- private final NotificationRemoteInputManager mNotificationRemoteInputManager;
- private final SystemActions mSystemActions;
- private final UiEventLogger mUiEventLogger;
private final Handler mHandler;
- private final NavigationBarA11yHelper mNavigationBarA11yHelper;
+ private final NavigationBar.Factory mNavigationBarFactory;
private final DisplayManager mDisplayManager;
- private final NavigationBarOverlayController mNavBarOverlayController;
private final TaskbarDelegate mTaskbarDelegate;
- private final NotificationShadeDepthController mNotificationShadeDepthController;
private int mNavMode;
@VisibleForTesting boolean mIsTablet;
- private final UserTracker mUserTracker;
/** A displayId - nav bar maps. */
@VisibleForTesting
@@ -132,74 +87,30 @@
@Inject
public NavigationBarController(Context context,
- WindowManager windowManager,
- Lazy<AssistManager> assistManagerLazy,
- AccessibilityManager accessibilityManager,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- DeviceProvisionedController deviceProvisionedController,
- MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
NavigationModeController navigationModeController,
- AccessibilityButtonModeObserver accessibilityButtonModeObserver,
- StatusBarStateController statusBarStateController,
SysUiState sysUiFlagsContainer,
- BroadcastDispatcher broadcastDispatcher,
CommandQueue commandQueue,
- Optional<Pip> pipOptional,
- Optional<LegacySplitScreen> splitScreenOptional,
- Optional<Recents> recentsOptional,
- Lazy<Optional<StatusBar>> statusBarOptionalLazy,
- ShadeController shadeController,
- NotificationRemoteInputManager notificationRemoteInputManager,
- NotificationShadeDepthController notificationShadeDepthController,
- SystemActions systemActions,
@Main Handler mainHandler,
- UiEventLogger uiEventLogger,
- NavigationBarOverlayController navBarOverlayController,
ConfigurationController configurationController,
NavigationBarA11yHelper navigationBarA11yHelper,
TaskbarDelegate taskbarDelegate,
- UserTracker userTracker,
- DumpManager dumpManager) {
+ NavigationBar.Factory navigationBarFactory,
+ DumpManager dumpManager,
+ AutoHideController autoHideController) {
mContext = context;
- mWindowManager = windowManager;
- mAssistManagerLazy = assistManagerLazy;
- mAccessibilityManager = accessibilityManager;
- mAccessibilityManagerWrapper = accessibilityManagerWrapper;
- mDeviceProvisionedController = deviceProvisionedController;
- mMetricsLogger = metricsLogger;
- mOverviewProxyService = overviewProxyService;
- mNavigationModeController = navigationModeController;
- mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
- mStatusBarStateController = statusBarStateController;
- mSysUiFlagsContainer = sysUiFlagsContainer;
- mBroadcastDispatcher = broadcastDispatcher;
- mCommandQueue = commandQueue;
- mPipOptional = pipOptional;
- mSplitScreenOptional = splitScreenOptional;
- mRecentsOptional = recentsOptional;
- mStatusBarOptionalLazy = statusBarOptionalLazy;
- mShadeController = shadeController;
- mNotificationRemoteInputManager = notificationRemoteInputManager;
- mNotificationShadeDepthController = notificationShadeDepthController;
- mSystemActions = systemActions;
- mUiEventLogger = uiEventLogger;
mHandler = mainHandler;
- mNavigationBarA11yHelper = navigationBarA11yHelper;
+ mNavigationBarFactory = navigationBarFactory;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
commandQueue.addCallback(this);
configurationController.addCallback(this);
mConfigChanges.applyNewConfig(mContext.getResources());
- mNavBarOverlayController = navBarOverlayController;
- mNavMode = mNavigationModeController.addListener(this);
- mNavigationModeController.addListener(this);
+ mNavMode = navigationModeController.addListener(this);
mTaskbarDelegate = taskbarDelegate;
- mTaskbarDelegate.setOverviewProxyService(commandQueue, overviewProxyService,
+ mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer,
- dumpManager);
+ dumpManager, autoHideController);
mIsTablet = isTablet(mContext);
- mUserTracker = userTracker;
-
dumpManager.registerDumpable(this);
}
@@ -355,33 +266,8 @@
final Context context = isOnDefaultDisplay
? mContext
: mContext.createDisplayContext(display);
- NavigationBar navBar = new NavigationBar(context,
- mWindowManager,
- mAssistManagerLazy,
- mAccessibilityManager,
- mAccessibilityManagerWrapper,
- mDeviceProvisionedController,
- mMetricsLogger,
- mOverviewProxyService,
- mNavigationModeController,
- mAccessibilityButtonModeObserver,
- mStatusBarStateController,
- mSysUiFlagsContainer,
- mBroadcastDispatcher,
- mCommandQueue,
- mPipOptional,
- mSplitScreenOptional,
- mRecentsOptional,
- mStatusBarOptionalLazy,
- mShadeController,
- mNotificationRemoteInputManager,
- mNotificationShadeDepthController,
- mSystemActions,
- mHandler,
- mNavBarOverlayController,
- mUiEventLogger,
- mNavigationBarA11yHelper,
- mUserTracker);
+ NavigationBar navBar = mNavigationBarFactory.create(context);
+
mNavigationBars.put(displayId, navBar);
View navigationBarView = navBar.createView(savedState);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 0603bb7..73a0c54 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -118,7 +118,7 @@
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
if (DEBUG) {
Log.d(TAG, "onOverlayChanged");
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 4d29612..d707dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -19,6 +19,8 @@
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -57,7 +59,9 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.AutoHideController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -77,6 +81,7 @@
private NavigationBarA11yHelper mNavigationBarA11yHelper;
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
+ private AutoHideController mAutoHideController;
private int mDisplayId;
private int mNavigationIconHints;
private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener =
@@ -87,6 +92,28 @@
private final Context mContext;
private final DisplayManager mDisplayManager;
private Context mWindowContext;
+ /**
+ * Tracks the system calls for when taskbar should transiently show or hide so we can return
+ * this value in {@link AutoHideUiElement#isVisible()} below.
+ *
+ * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient
+ * taskbar if launcher has requested to suspend auto-hide behavior.
+ */
+ private boolean mTaskbarTransientShowing;
+ private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mTaskbarTransientShowing;
+ }
+
+ @Override
+ public void hide() {
+ }
+ };
@Inject
public TaskbarDelegate(Context context) {
@@ -96,11 +123,12 @@
mDisplayManager = mContext.getSystemService(DisplayManager.class);
}
- public void setOverviewProxyService(CommandQueue commandQueue,
+ public void setDependencies(CommandQueue commandQueue,
OverviewProxyService overviewProxyService,
NavigationBarA11yHelper navigationBarA11yHelper,
NavigationModeController navigationModeController,
- SysUiState sysUiState, DumpManager dumpManager) {
+ SysUiState sysUiState, DumpManager dumpManager,
+ AutoHideController autoHideController) {
// TODO: adding this in the ctor results in a dagger dependency cycle :(
mCommandQueue = commandQueue;
mOverviewProxyService = overviewProxyService;
@@ -108,18 +136,7 @@
mNavigationModeController = navigationModeController;
mSysUiState = sysUiState;
dumpManager.registerDumpable(this);
- }
-
- public void destroy() {
- mCommandQueue.removeCallback(this);
- mOverviewProxyService.removeCallback(this);
- mNavigationModeController.removeListener(this);
- mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
- mEdgeBackGestureHandler.onNavBarDetached();
- if (mWindowContext != null) {
- mWindowContext.unregisterComponentCallbacks(this);
- mWindowContext = null;
- }
+ mAutoHideController = autoHideController;
}
public void init(int displayId) {
@@ -136,6 +153,20 @@
mWindowContext.registerComponentCallbacks(this);
// Set initial state for any listeners
updateSysuiFlags();
+ mAutoHideController.setNavigationBar(mAutoHideUiElement);
+ }
+
+ public void destroy() {
+ mCommandQueue.removeCallback(this);
+ mOverviewProxyService.removeCallback(this);
+ mNavigationModeController.removeListener(this);
+ mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarDetached();
+ if (mWindowContext != null) {
+ mWindowContext.unregisterComponentCallbacks(this);
+ mWindowContext = null;
+ }
+ mAutoHideController.setNavigationBar(null);
}
private void updateSysuiFlags() {
@@ -209,6 +240,38 @@
}
@Override
+ public void showTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = true;
+ }
+
+ @Override
+ public void abortTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = false;
+ }
+
+ @Override
+ public void onTaskbarAutohideSuspend(boolean suspend) {
+ mTaskbarTransientShowing = suspend;
+ if (suspend) {
+ mAutoHideController.suspendAutoHide();
+ } else {
+ mAutoHideController.resumeSuspendedAutoHide();
+ }
+ }
+
+ @Override
public void onNavigationModeChanged(int mode) {
mEdgeBackGestureHandler.onNavigationModeChanged(mode);
}
@@ -236,6 +299,7 @@
pw.println(" mDisabledFlags=" + mDisabledFlags);
pw.println(" mTaskBarWindowState=" + mTaskBarWindowState);
pw.println(" mBehavior=" + mBehavior);
+ pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing);
mEdgeBackGestureHandler.dump(pw);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 17c2fd0..89bbcf5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -37,9 +37,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
@@ -54,7 +54,6 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.LifecycleFragment;
import com.android.systemui.util.Utils;
@@ -95,7 +94,6 @@
private ImageView mQsDragHandler;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
- private final InjectionInflationController mInjectionInflater;
private final CommandQueue mCommandQueue;
private final QSDetailDisplayer mQsDetailDisplayer;
private final MediaHost mQsMediaHost;
@@ -145,7 +143,7 @@
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
- InjectionInflationController injectionInflater, QSTileHost qsTileHost,
+ QSTileHost qsTileHost,
StatusBarStateController statusBarStateController, CommandQueue commandQueue,
QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
@@ -153,7 +151,6 @@
QSFragmentComponent.Factory qsComponentFactory,
FalsingManager falsingManager, DumpManager dumpManager) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
- mInjectionInflater = injectionInflater;
mCommandQueue = commandQueue;
mQsDetailDisplayer = qsDetailDisplayer;
mQsMediaHost = qsMediaHost;
@@ -170,9 +167,8 @@
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
- inflater = mInjectionInflater.injectable(
- inflater.cloneInContext(new ContextThemeWrapper(getContext(),
- R.style.Theme_SystemUI_QuickSettings)));
+ inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+ R.style.Theme_SystemUI_QuickSettings));
return inflater.inflate(R.layout.qs_panel, container, false);
}
@@ -574,7 +570,7 @@
} else if (progress > 0 && view.getVisibility() != View.VISIBLE) {
view.setVisibility((View.VISIBLE));
}
- float alpha = Interpolators.getNotificationScrimAlpha(progress, true /* uiContent */);
+ float alpha = ShadeInterpolation.getContentAlpha(progress);
view.setAlpha(alpha);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 11430d9..15b78e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -36,6 +36,7 @@
import android.telephony.TelephonyManager;
import android.text.Html;
import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -63,12 +64,15 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
@@ -82,6 +86,7 @@
static final long PROGRESS_DELAY_MS = 2000L;
private final Handler mHandler;
+ private final Executor mBackgroundExecutor;
private final LinearLayoutManager mLayoutManager;
@VisibleForTesting
@@ -110,6 +115,8 @@
private LinearLayout mTurnWifiOnLayout;
private LinearLayout mEthernetLayout;
private TextView mWifiToggleTitleText;
+ private LinearLayout mWifiScanNotifyLayout;
+ private TextView mWifiScanNotifyText;
private LinearLayout mSeeAllLayout;
private RecyclerView mWifiRecyclerView;
private ImageView mConnectedWifiIcon;
@@ -154,13 +161,14 @@
public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
InternetDialogController internetDialogController, boolean canConfigMobileData,
boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
- @Main Handler handler) {
+ @Main Handler handler, @Background Executor executor) {
super(context, R.style.Theme_SystemUI_Dialog_Internet);
if (DEBUG) {
Log.d(TAG, "Init InternetDialog");
}
mContext = context;
mHandler = handler;
+ mBackgroundExecutor = executor;
mInternetDialogFactory = internetDialogFactory;
mInternetDialogController = internetDialogController;
mSubscriptionManager = mInternetDialogController.getSubscriptionManager();
@@ -220,6 +228,8 @@
mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
+ mWifiScanNotifyLayout = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
+ mWifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text);
mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title);
@@ -293,7 +303,13 @@
dismiss();
}
- void updateDialog() {
+ /**
+ * Update the internet dialog when receiving the callback.
+ *
+ * @param shouldUpdateMobileNetwork {@code true} for update the mobile network layout,
+ * otherwise {@code false}.
+ */
+ void updateDialog(boolean shouldUpdateMobileNetwork) {
if (DEBUG) {
Log.d(TAG, "updateDialog");
}
@@ -303,8 +319,10 @@
mInternetDialogSubTitle.setText(getSubtitleText());
}
updateEthernet();
- setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
- || mInternetDialogController.isCarrierNetworkActive());
+ if (shouldUpdateMobileNetwork) {
+ setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
+ || mInternetDialogController.isCarrierNetworkActive());
+ }
if (!mCanConfigWifi) {
return;
@@ -313,8 +331,10 @@
showProgressBar();
final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+ final boolean isWifiScanEnabled = mWifiManager.isScanAlwaysAvailable();
updateWifiToggle(isWifiEnabled, isDeviceLocked);
updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+ updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked);
final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0)
? View.GONE : View.VISIBLE;
@@ -371,7 +391,13 @@
} else {
mMobileSummaryText.setVisibility(View.GONE);
}
- mSignalIcon.setImageDrawable(getSignalStrengthDrawable());
+
+ mBackgroundExecutor.execute(() -> {
+ Drawable drawable = getSignalStrengthDrawable();
+ mHandler.post(() -> {
+ mSignalIcon.setImageDrawable(drawable);
+ });
+ });
mMobileTitleText.setTextAppearance(isCarrierNetworkConnected
? R.style.TextAppearance_InternetDialog_Active
: R.style.TextAppearance_InternetDialog);
@@ -411,6 +437,24 @@
mContext.getColor(R.color.connected_network_primary_color));
}
+ @MainThread
+ private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled,
+ boolean isDeviceLocked) {
+ if (isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) {
+ mWifiScanNotifyLayout.setVisibility(View.GONE);
+ return;
+ }
+ if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) {
+ final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo(
+ AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION,
+ v -> mInternetDialogController.launchWifiScanningSetting());
+ mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify(
+ getContext().getText(R.string.wifi_scan_notify_message), linkInfo));
+ mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ mWifiScanNotifyLayout.setVisibility(View.VISIBLE);
+ }
+
void onClickConnectedWifi() {
if (mConnectedWifiEntry == null) {
return;
@@ -508,52 +552,57 @@
@Override
public void onRefreshCarrierInfo() {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
public void onSimStateChanged() {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
@WorkerThread
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
@WorkerThread
public void onLost(Network network) {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
public void onSubscriptionsChanged(int defaultDataSubId) {
mDefaultDataSubId = defaultDataSubId;
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+ }
+
+ @Override
+ public void onUserMobileDataStateChanged(boolean enabled) {
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
public void onServiceStateChanged(ServiceState serviceState) {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
@WorkerThread
public void onDataConnectionStateChanged(int state, int networkType) {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
- mHandler.post(() -> updateDialog());
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
}
@Override
@@ -565,7 +614,7 @@
mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
mHandler.post(() -> {
mAdapter.notifyDataSetChanged();
- updateDialog();
+ updateDialog(false /* shouldUpdateMobileNetwork */);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index b9cd08e..1ade5ce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -103,6 +103,8 @@
private static final String TAG = "InternetDialogController";
private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
"android.settings.NETWORK_PROVIDER_SETTINGS";
+ private static final String ACTION_WIFI_SCANNING_SETTINGS =
+ "android.settings.WIFI_SCANNING_SETTINGS";
private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
public static final int NO_CELL_DATA_TYPE_ICON = 0;
@@ -147,6 +149,7 @@
private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
private WindowManager mWindowManager;
private ToastFactory mToastFactory;
+ private SignalDrawable mSignalDrawable;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -223,6 +226,7 @@
mConnectivityManagerNetworkCallback = new DataConnectivityListener();
mWindowManager = windowManager;
mToastFactory = toastFactory;
+ mSignalDrawable = new SignalDrawable(mContext);
}
void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -429,10 +433,7 @@
Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
int iconType, boolean cutOut) {
- Log.d(TAG, "getSignalStrengthIcon");
- final SignalDrawable signalDrawable = new SignalDrawable(context);
- signalDrawable.setLevel(
- SignalDrawable.getState(level, numLevels, cutOut));
+ mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
// Make the network type drawable
final Drawable networkDrawable =
@@ -441,7 +442,7 @@
: context.getResources().getDrawable(iconType, context.getTheme());
// Overlay the two drawables
- final Drawable[] layers = {networkDrawable, signalDrawable};
+ final Drawable[] layers = {networkDrawable, mSignalDrawable};
final int iconSize =
context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
@@ -603,6 +604,13 @@
}
}
+ void launchWifiScanningSetting() {
+ mCallback.dismissDialog();
+ final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+
void connectCarrierNetwork() {
final MergedCarrierEntry mergedCarrierEntry =
mAccessPointController.getMergedCarrierEntry();
@@ -883,7 +891,8 @@
TelephonyCallback.DataConnectionStateListener,
TelephonyCallback.DisplayInfoListener,
TelephonyCallback.ServiceStateListener,
- TelephonyCallback.SignalStrengthsListener {
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.UserMobileDataStateListener {
@Override
public void onServiceStateChanged(@NonNull ServiceState serviceState) {
@@ -905,6 +914,11 @@
mTelephonyDisplayInfo = telephonyDisplayInfo;
mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
}
+
+ @Override
+ public void onUserMobileDataStateChanged(boolean enabled) {
+ mCallback.onUserMobileDataStateChanged(enabled);
+ }
}
private class InternetOnSubscriptionChangedListener
@@ -1009,6 +1023,8 @@
void onSignalStrengthsChanged(SignalStrength signalStrength);
+ void onUserMobileDataStateChanged(boolean enabled);
+
void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
void dismissDialog();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index 11c6980..ea5df17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -20,7 +20,9 @@
import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
import javax.inject.Inject
private const val TAG = "InternetDialogFactory"
@@ -32,6 +34,7 @@
@SysUISingleton
class InternetDialogFactory @Inject constructor(
@Main private val handler: Handler,
+ @Background private val executor: Executor,
private val internetDialogController: InternetDialogController,
private val context: Context,
private val uiEventLogger: UiEventLogger
@@ -49,7 +52,8 @@
return
} else {
internetDialog = InternetDialog(context, this, internetDialogController,
- canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler)
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
+ executor)
internetDialog?.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
index 2ad06c1..01afa56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -32,7 +32,7 @@
*/
class UserDialog(
context: Context
-) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog_QSDialog) {
+) : SystemUIDialog(context) {
// create() is no-op after creation
private lateinit var _doneButton: View
@@ -72,7 +72,7 @@
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
attributes.receiveInsetsIgnoringZOrder = true
setLayout(
- context.resources.getDimensionPixelSize(R.dimen.qs_panel_width),
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_width),
ViewGroup.LayoutParams.WRAP_CONTENT
)
setGravity(Gravity.CENTER)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index a5e4ba1..bae7996 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -21,6 +21,7 @@
import android.provider.Settings
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -36,6 +37,7 @@
private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
private val dialogFactory: (Context) -> UserDialog
) {
@@ -43,11 +45,13 @@
constructor(
userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
activityStarter: ActivityStarter,
- falsingManager: FalsingManager
+ falsingManager: FalsingManager,
+ dialogLaunchAnimator: DialogLaunchAnimator
) : this(
userDetailViewAdapterProvider,
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ UserDialog(it) }
)
@@ -69,7 +73,11 @@
settingsButton.setOnClickListener {
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- activityStarter.postStartActivityDismissingKeyguard(USER_SETTINGS_INTENT, 0)
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0
+ )
}
dismiss()
}
@@ -81,7 +89,7 @@
}
adapter.linkToViewGroup(grid)
- show()
+ dialogLaunchAnimator.showFromView(this, view)
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index c76f01b..721a6af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -248,6 +248,12 @@
onTaskbarStatusUpdated(visible, stashed));
}
+ @Override
+ public void notifyTaskbarAutohideSuspend(boolean suspend) {
+ verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarAutohideSuspend", () ->
+ onTaskbarAutohideSuspend(suspend));
+ }
+
private boolean sendEvent(int action, int code) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
@@ -818,6 +824,12 @@
}
}
+ private void onTaskbarAutohideSuspend(boolean suspend) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onTaskbarAutohideSuspend(suspend);
+ }
+ }
+
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
@@ -1000,6 +1012,7 @@
default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
+ default void onTaskbarAutohideSuspend(boolean suspend) {}
default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 4f932a3..74ebfe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -147,7 +147,6 @@
private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private String mMessageToShowOnScreenOn;
- protected int mLockScreenMode;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -600,10 +599,6 @@
mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
- if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
- // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
- mWakeLock.setAcquired(true);
- }
hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
updateIndication(false);
@@ -623,10 +618,6 @@
}
protected final void updateIndication(boolean animate) {
- if (TextUtils.isEmpty(mTransientIndication)) {
- mWakeLock.setAcquired(false);
- }
-
if (!mVisible) {
return;
}
@@ -644,24 +635,31 @@
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
if (!TextUtils.isEmpty(mTransientIndication)) {
- mTopIndicationView.switchIndication(mTransientIndication, null);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(mTransientIndication, null,
+ true, () -> mWakeLock.setAcquired(false));
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTopIndicationView.switchIndication(mAlignmentIndication, null);
+ mTopIndicationView.switchIndication(mAlignmentIndication, null,
+ false /* animate */, null /* onAnimationEndCallback */);
mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
String indication = computePowerIndication();
if (animate) {
- animateText(mTopIndicationView, indication);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(indication, null, true /* animate */,
+ () -> mWakeLock.setAcquired(false));
} else {
- mTopIndicationView.switchIndication(indication, null);
+ mTopIndicationView.switchIndication(indication, null, false /* animate */,
+ null /* onAnimationEndCallback */);
}
} else {
String percentage = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
- mTopIndicationView.switchIndication(percentage, null);
+ mTopIndicationView.switchIndication(percentage, null /* indication */,
+ false /* animate */, null /* onAnimationEnd*/);
}
return;
}
@@ -863,11 +861,6 @@
public static final int HIDE_DELAY_MS = 5000;
@Override
- public void onLockScreenModeChanged(int mode) {
- mLockScreenMode = mode;
- }
-
- @Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d4f54e1..5648741e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,6 +32,7 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -184,12 +185,12 @@
val animationRadius = MathUtils.constrain(shadeAnimation.radius,
blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
val expansionRadius = blurUtils.blurRadiusOfRatio(
- Interpolators.getNotificationScrimAlpha(
- if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
+ ShadeInterpolation.getNotificationScrimAlpha(
+ if (shouldApplyShadeBlur()) shadeExpansion else 0f))
var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
animationRadius * ANIMATION_BLUR_FRACTION)
- val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion,
- false /* notification */) * shadeExpansion
+ val qsExpandedRatio = ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) *
+ shadeExpansion
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 3bd7dd3..0b93fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -31,7 +31,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -168,8 +168,8 @@
viewState.clipTopAmount = 0;
if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) {
- viewState.alpha = Interpolators.getNotificationScrimAlpha(
- ambientState.getExpansionFraction(), true /* notification */);
+ float expansion = ambientState.getExpansionFraction();
+ viewState.alpha = ShadeInterpolation.getContentAlpha(expansion);
} else {
viewState.alpha = 1f - ambientState.getHideAmount();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index cbb3aba..6da981b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -28,6 +28,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.SystemProperties;
+import android.os.Trace;
import android.text.format.DateFormat;
import android.util.FloatProperty;
import android.util.Log;
@@ -181,6 +182,7 @@
}
synchronized (mListeners) {
+ Trace.beginSection(TAG + "#setState(" + StatusBarState.toShortString(state) + ")");
String tag = getClass().getSimpleName() + "#setState(" + state + ")";
DejankUtils.startDetectingBlockingIpcs(tag);
for (RankedListener rl : new ArrayList<>(mListeners)) {
@@ -198,6 +200,7 @@
rl.mListener.onStatePostChange();
}
DejankUtils.stopDetectingBlockingIpcs(tag);
+ Trace.endSection();
}
return true;
@@ -262,12 +265,14 @@
mIsDozing = isDozing;
synchronized (mListeners) {
+ Trace.beginSection(TAG + "#setDozing(" + isDozing + ")");
String tag = getClass().getSimpleName() + "#setIsDozing";
DejankUtils.startDetectingBlockingIpcs(tag);
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onDozingChanged(isDozing);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
+ Trace.endSection();
}
return true;
@@ -333,12 +338,14 @@
mDozeAmount = dozeAmount;
float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount);
synchronized (mListeners) {
+ Trace.beginSection(TAG + "#setDozeAmount");
String tag = getClass().getSimpleName() + "#setDozeAmount";
DejankUtils.startDetectingBlockingIpcs(tag);
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
+ Trace.endSection();
}
}
@@ -469,11 +476,13 @@
public void setPulsing(boolean pulsing) {
if (mPulsing != pulsing) {
mPulsing = pulsing;
+ Trace.beginSection(TAG + "#setPulsing(" + pulsing + ")");
synchronized (mListeners) {
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onPulsingChanged(pulsing);
}
}
+ Trace.endSection();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index d74297e..04c60fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -114,9 +114,6 @@
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
override fun onConfigChanged(newConfig: Configuration?) {
normalizedPortPosX = context.resources.getFloat(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 0bd841f..23ad4ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -332,8 +332,7 @@
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
- setUserSetupComplete(deviceProvisionedController.isUserSetup(
- deviceProvisionedController.getCurrentUser()));
+ setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 452e737..d297d95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -69,7 +70,6 @@
import com.android.systemui.statusbar.phone.SystemUIHostDialogProvider;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
-import com.android.systemui.statusbar.phone.ongoingcall.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -257,7 +257,8 @@
OngoingCallLogger logger,
DumpManager dumpManager,
StatusBarWindowController statusBarWindowController,
- SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler) {
+ SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler,
+ StatusBarStateController statusBarStateController) {
Optional<StatusBarWindowController> windowController =
featureFlags.isOngoingCallInImmersiveEnabled()
? Optional.of(statusBarWindowController)
@@ -277,8 +278,8 @@
logger,
dumpManager,
windowController,
- gestureHandler
- );
+ gestureHandler,
+ statusBarStateController);
ongoingCallController.init();
return ongoingCallController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 1000788..6f8a5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -24,7 +24,6 @@
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
-
import com.android.internal.annotations.GuardedBy
import com.android.systemui.animation.Interpolators
import com.android.systemui.R
@@ -44,7 +43,6 @@
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
-import java.lang.IllegalStateException
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -71,9 +69,6 @@
private val contentInsetsProvider: StatusBarContentInsetsProvider,
private val animationScheduler: SystemStatusAnimationScheduler
) {
- private var sbHeightPortrait = 0
- private var sbHeightLandscape = 0
-
private lateinit var tl: View
private lateinit var tr: View
private lateinit var bl: View
@@ -157,16 +152,12 @@
val newCorner = selectDesignatedCorner(rot, isRtl)
val index = newCorner.cornerIndex()
+ val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot)
- val h = when (rot) {
- 0, 2 -> sbHeightPortrait
- 1, 3 -> sbHeightLandscape
- else -> 0
- }
synchronized(lock) {
nextViewState = nextViewState.copy(
rotation = rot,
- height = h,
+ paddingTop = paddingTop,
designatedCorner = newCorner,
cornerIndex = index)
}
@@ -204,26 +195,17 @@
}
}
- @UiThread
- private fun updateHeights(rot: Int) {
- val height = when (rot) {
- 0, 2 -> sbHeightPortrait
- 1, 3 -> sbHeightLandscape
- else -> 0
- }
-
- views.forEach { it.layoutParams.height = height }
- }
-
// Update the gravity and margins of the privacy views
@UiThread
- private fun updateRotations(rotation: Int) {
+ private fun updateRotations(rotation: Int, paddingTop: Int) {
// To keep a view in the corner, its gravity is always the description of its current corner
// Therefore, just figure out which view is in which corner. This turns out to be something
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
// rotating the device counter-clockwise increments rotation by 1
views.forEach { corner ->
+ corner.setPadding(0, paddingTop, 0, 0)
+
val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
(corner.layoutParams as FrameLayout.LayoutParams).apply {
gravity = rotatedCorner.toGravity()
@@ -266,6 +248,7 @@
var rot = activeRotationForCorner(tl, rtl)
var contentInsets = state.contentRectForRotation(rot)
+ tl.setPadding(0, state.paddingTop, 0, 0)
(tl.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -277,6 +260,7 @@
rot = activeRotationForCorner(tr, rtl)
contentInsets = state.contentRectForRotation(rot)
+ tr.setPadding(0, state.paddingTop, 0, 0)
(tr.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -288,6 +272,7 @@
rot = activeRotationForCorner(br, rtl)
contentInsets = state.contentRectForRotation(rot)
+ br.setPadding(0, state.paddingTop, 0, 0)
(br.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -299,6 +284,7 @@
rot = activeRotationForCorner(bl, rtl)
contentInsets = state.contentRectForRotation(rot)
+ bl.setPadding(0, state.paddingTop, 0, 0)
(bl.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -413,6 +399,7 @@
val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE)
val bottom = contentInsetsProvider
.getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN)
+ val paddingTop = contentInsetsProvider.getStatusBarPaddingTop()
synchronized(lock) {
nextViewState = nextViewState.copy(
@@ -423,20 +410,12 @@
portraitRect = top,
landscapeRect = right,
upsideDownRect = bottom,
+ paddingTop = paddingTop,
layoutRtl = rtl
)
}
}
- /**
- * Set the status bar height in portrait and landscape, in pixels. If they are the same you can
- * pass the same value twice
- */
- fun setStatusBarHeights(portrait: Int, landscape: Int) {
- sbHeightPortrait = portrait
- sbHeightLandscape = landscape
- }
-
private fun updateStatusBarState() {
synchronized(lock) {
nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs())
@@ -489,7 +468,7 @@
if (state.rotation != currentViewState.rotation) {
// A rotation has started, hide the views to avoid flicker
- updateRotations(state.rotation)
+ updateRotations(state.rotation, state.paddingTop)
}
if (state.needsLayout(currentViewState)) {
@@ -628,7 +607,7 @@
val layoutRtl: Boolean = false,
val rotation: Int = 0,
- val height: Int = 0,
+ val paddingTop: Int = 0,
val cornerIndex: Int = -1,
val designatedCorner: View? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/SwipeStatusBarAwayGestureHandler.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
index c97cf14..80577ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/SwipeStatusBarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.gesture
import android.content.Context
import android.os.Looper
-import android.util.Log
import android.view.Choreographer
import android.view.Display
import android.view.InputEvent
@@ -38,6 +37,7 @@
open class SwipeStatusBarAwayGestureHandler @Inject constructor(
context: Context,
private val statusBarWindowController: StatusBarWindowController,
+ private val logger: SwipeStatusBarAwayGestureLogger
) {
/**
@@ -53,7 +53,6 @@
private var inputMonitor: InputMonitorCompat? = null
private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
- // TODO(b/195839150): Update this threshold when the config changes?
private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
com.android.internal.R.dimen.system_gestures_start_threshold
)
@@ -84,12 +83,10 @@
ACTION_DOWN -> {
if (
// Gesture starts just below the status bar
- // TODO(b/195839150): Is [statusBarHeight] the correct dimension to use for
- // determining which down touches are valid?
ev.y >= statusBarWindowController.statusBarHeight
&& ev.y <= 3 * statusBarWindowController.statusBarHeight
) {
- Log.d(TAG, "Beginning gesture detection, y=${ev.y}")
+ logger.logGestureDetectionStarted(ev.y.toInt())
startY = ev.y
startTime = ev.eventTime
monitoringCurrentTouch = true
@@ -109,12 +106,15 @@
// Gesture completed quickly enough
&& (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
) {
- Log.i(TAG, "Gesture detected; notifying callbacks")
- callbacks.values.forEach { it.invoke() }
monitoringCurrentTouch = false
+ logger.logGestureDetected(ev.y.toInt())
+ callbacks.values.forEach { it.invoke() }
}
}
ACTION_CANCEL, ACTION_UP -> {
+ if (monitoringCurrentTouch) {
+ logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt())
+ }
monitoringCurrentTouch = false
}
}
@@ -124,7 +124,7 @@
private fun startGestureListening() {
stopGestureListening()
- if (DEBUG) { Log.d(TAG, "Input listening started") }
+ logger.logInputListeningStarted()
inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also {
inputReceiver = it.getInputReceiver(
Looper.getMainLooper(),
@@ -137,7 +137,7 @@
/** Stop listening for the swipe gesture. */
private fun stopGestureListening() {
inputMonitor?.let {
- if (DEBUG) { Log.d(TAG, "Input listening stopped") }
+ logger.logInputListeningStopped()
inputMonitor = null
it.dispose()
}
@@ -150,4 +150,3 @@
private const val SWIPE_TIMEOUT_MS: Long = 500
private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName
-private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
new file mode 100644
index 0000000..17feaa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.gesture
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import javax.inject.Inject
+
+/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
+class SwipeStatusBarAwayGestureLogger @Inject constructor(
+ @SwipeStatusBarAwayLog private val buffer: LogBuffer
+) {
+ fun logGestureDetectionStarted(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Beginning gesture detection. y=$int1" }
+ )
+ }
+
+ fun logGestureDetectionEndedWithoutTriggering(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Gesture finished; no swipe up gesture detected. Final y=$int1" }
+ )
+ }
+
+ fun logGestureDetected(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = y },
+ { "Gesture detected; notifying callbacks. y=$int1" }
+ )
+ }
+
+ fun logInputListeningStarted() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "})
+ }
+
+ fun logInputListeningStopped() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "})
+ }
+}
+
+private const val TAG = "SwipeStatusBarAwayGestureHandler"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 83c98d5..bacb85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -24,12 +24,12 @@
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
-import android.content.pm.UserInfo
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.android.settingslib.Utils
@@ -74,6 +74,10 @@
@Main private val handler: Handler,
optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
+ companion object {
+ private const val TAG = "LockscreenSmartspaceController"
+ }
+
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -198,7 +202,7 @@
}
private fun connectSession() {
- if (plugin == null || session != null) {
+ if (plugin == null || session != null || smartspaceViews.isEmpty()) {
return
}
@@ -211,6 +215,7 @@
val newSession = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "lockscreen").build())
+ Log.d(TAG, "Starting smartspace session for lockscreen")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
@@ -232,6 +237,8 @@
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
*/
fun disconnect() {
+ if (!smartspaceViews.isEmpty()) return
+
execution.assertIsMainThread()
if (session == null) {
@@ -249,6 +256,7 @@
session = null
plugin?.onTargetsAvailable(emptyList())
+ Log.d(TAG, "Ending smartspace session for lockscreen")
}
fun addListener(listener: SmartspaceTargetListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index acb0e82..2eb2065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
-import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.MotionEvent;
@@ -111,7 +110,6 @@
private Interpolator mCurrentAppearInterpolator;
NotificationBackgroundView mBackgroundNormal;
- private RectF mAppearAnimationRect = new RectF();
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
private ValueAnimator mAppearAnimator;
@@ -123,13 +121,6 @@
private long mLastActionUpTime;
private float mNormalBackgroundVisibilityAmount;
- private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha());
- }
- };
private FakeShadowView mFakeShadow;
private int mCurrentBackgroundTint;
private int mTargetTint;
@@ -138,11 +129,8 @@
private float mOverrideAmount;
private boolean mShadowHidden;
private boolean mIsHeadsUpAnimation;
- private int mHeadsUpAddStartLocation;
- private float mHeadsUpLocation;
/* In order to track headsup longpress coorindate. */
protected Point mTargetPoint;
- private boolean mIsAppearing;
private boolean mDismissed;
private boolean mRefocusOnDismiss;
private AccessibilityManager mAccessibilityManager;
@@ -154,7 +142,6 @@
setClipChildren(false);
setClipToPadding(false);
updateColors();
- initDimens();
}
private void updateColors() {
@@ -166,17 +153,6 @@
R.color.notification_ripple_untinted_color);
}
- private void initDimens() {
- mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_start);
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- super.onDensityOrFontScaleChanged();
- initDimens();
- }
-
/**
* Reload background colors from resources and invalidate views.
*/
@@ -438,7 +414,6 @@
Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
- mHeadsUpLocation = endLocation;
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
delay, duration, onFinishedRunnable, animationListener);
@@ -452,7 +427,6 @@
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAppear;
- mHeadsUpLocation = mHeadsUpAddStartLocation;
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
duration, null, null);
@@ -474,7 +448,6 @@
mAppearAnimationTranslation = 0;
}
}
- mIsAppearing = isAppearing;
float targetValue;
if (isAppearing) {
@@ -782,8 +755,4 @@
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
}
-
- interface OnDimmedListener {
- void onSetDimmed(boolean dimmed);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b7d721e..3a37fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -41,8 +41,11 @@
import android.widget.FrameLayout.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -305,7 +308,7 @@
final int showDismissSetting = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1);
final boolean newFlowHideShelf = showDismissSetting == -1
- ? mContext.getResources().getBoolean(R.bool.flag_notif_updates)
+ ? Dependency.get(FeatureFlags.class).isEnabled(Flags.NOTIFICATION_UPDATES)
: showDismissSetting == 1;
if (newFlowHideShelf) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 45fd5ef..a9cc3237 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -72,7 +72,6 @@
private boolean mPanelFullWidth;
private boolean mPulsing;
private boolean mUnlockHintRunning;
- private int mIntrinsicPadding;
private float mHideAmount;
private boolean mAppearing;
private float mPulseHeight = MAX_PULSE_HEIGHT;
@@ -82,6 +81,7 @@
private float mAppearFraction;
private boolean mIsShadeOpening;
private float mOverExpansion;
+ private int mStackTopMargin;
/** Distance of top of notifications panel from top of screen. */
private float mStackY = 0;
@@ -94,7 +94,6 @@
/** Height of the notifications panel without top padding when expansion completes. */
private float mStackEndHeight;
- private float mTransitionToFullShadeAmount;
/**
* @return Height of the notifications panel without top padding when expansion completes.
@@ -493,14 +492,6 @@
return mUnlockHintRunning;
}
- public void setIntrinsicPadding(int intrinsicPadding) {
- mIntrinsicPadding = intrinsicPadding;
- }
-
- public int getIntrinsicPadding() {
- return mIntrinsicPadding;
- }
-
/**
* @return whether a view is dozing and not pulsing right now
*/
@@ -577,30 +568,11 @@
mOnPulseHeightChangedListener = onPulseHeightChangedListener;
}
- public Runnable getOnPulseHeightChangedListener() {
- return mOnPulseHeightChangedListener;
- }
-
public void setTrackedHeadsUpRow(ExpandableNotificationRow row) {
mTrackedHeadsUpRow = row;
}
/**
- * Set the amount of pixels we have currently dragged down if we're transitioning to the full
- * shade. 0.0f means we're not transitioning yet.
- */
- public void setTransitionToFullShadeAmount(float transitionToFullShadeAmount) {
- mTransitionToFullShadeAmount = transitionToFullShadeAmount;
- }
-
- /**
- * get
- */
- public float getTransitionToFullShadeAmount() {
- return mTransitionToFullShadeAmount;
- }
-
- /**
* Returns the currently tracked heads up row, if there is one and it is currently above the
* shelf (still appearing).
*/
@@ -622,4 +594,12 @@
public void setHasAlertEntries(boolean hasAlertEntries) {
mHasAlertEntries = hasAlertEntries;
}
+
+ public void setStackTopMargin(int stackTopMargin) {
+ mStackTopMargin = stackTopMargin;
+ }
+
+ public int getStackTopMargin() {
+ return mStackTopMargin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 135b7df..1cb5e62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -21,6 +21,8 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -39,6 +41,7 @@
private final ExpandableView[] mLastInSectionViews;
private final ExpandableView[] mTmpFirstInSectionViews;
private final ExpandableView[] mTmpLastInSectionViews;
+ private final FeatureFlags mFeatureFlags;
private boolean mExpanded;
private HashSet<ExpandableView> mAnimatedChildren;
private Runnable mRoundingChangedCallback;
@@ -52,7 +55,10 @@
private ExpandableView mViewAfterSwipedView = null;
@Inject
- NotificationRoundnessManager(NotificationSectionsFeatureManager sectionsFeatureManager) {
+ NotificationRoundnessManager(
+ NotificationSectionsFeatureManager sectionsFeatureManager,
+ FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
mFirstInSectionViews = new ExpandableView[numberOfSections];
mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -118,9 +124,8 @@
void setViewsAffectedBySwipe(
ExpandableView viewBefore,
ExpandableView viewSwiped,
- ExpandableView viewAfter,
- boolean cornerAnimationsEnabled) {
- if (!cornerAnimationsEnabled) {
+ ExpandableView viewAfter) {
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_UPDATES)) {
return;
}
final boolean animate = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 562d7ea..df91fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -19,7 +19,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -74,6 +73,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
@@ -120,9 +120,6 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
-import javax.inject.Inject;
-import javax.inject.Named;
-
/**
* A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
*/
@@ -565,24 +562,17 @@
@Nullable
private OnClickListener mManageButtonClickListener;
- @Inject
- public NotificationStackScrollLayout(
- @Named(VIEW_CONTEXT) Context context,
- AttributeSet attrs,
- NotificationSectionsManager notificationSectionsManager,
- GroupMembershipManager groupMembershipManager,
- GroupExpansionManager groupExpansionManager,
- AmbientState ambientState,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+ public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0, 0);
Resources res = getResources();
- mSectionsManager = notificationSectionsManager;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mSectionsManager = Dependency.get(NotificationSectionsManager.class);
+ mUnlockedScreenOffAnimationController =
+ Dependency.get(UnlockedScreenOffAnimationController.class);
updateSplitNotificationShade();
mSectionsManager.initialize(this, LayoutInflater.from(context));
mSections = mSectionsManager.createSectionsForBuckets();
- mAmbientState = ambientState;
+ mAmbientState = Dependency.get(AmbientState.class);
mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
.getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
@@ -608,8 +598,8 @@
mDebugPaint.setTextSize(25f);
}
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
- mGroupMembershipManager = groupMembershipManager;
- mGroupExpansionManager = groupExpansionManager;
+ mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
+ mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@@ -4274,7 +4264,6 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
void setIntrinsicPadding(int intrinsicPadding) {
mIntrinsicPadding = intrinsicPadding;
- mAmbientState.setIntrinsicPadding(intrinsicPadding);
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -5266,10 +5255,6 @@
mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
}
- public NotificationStackScrollLayoutController getController() {
- return mController;
- }
-
void addSwipedOutView(View v) {
mSwipedOutViews.add(v);
}
@@ -5299,10 +5284,10 @@
}
}
mController.getNoticationRoundessManager()
- .setViewsAffectedBySwipe((ExpandableView) viewBefore,
+ .setViewsAffectedBySwipe(
+ (ExpandableView) viewBefore,
(ExpandableView) viewSwiped,
- (ExpandableView) viewAfter,
- getResources().getBoolean(R.bool.flag_notif_updates));
+ (ExpandableView) viewAfter);
updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true);
@@ -5314,8 +5299,7 @@
void onSwipeEnd() {
updateFirstAndLastBackgroundViews();
mController.getNoticationRoundessManager()
- .setViewsAffectedBySwipe(null, null, null,
- getResources().getBoolean(R.bool.flag_notif_updates));
+ .setViewsAffectedBySwipe(null, null, null);
// Round bottom corners for notification right before shelf.
mShelf.updateAppearance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 216115e..09ab90e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -272,15 +272,6 @@
}
@Override
- public void onOverlayChanged() {
- updateShowEmptyShadeView();
- mView.updateCornerRadius();
- mView.updateBgColor();
- mView.updateDecorViews();
- mView.reinflateViews();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateBgColor();
mView.updateDecorViews();
@@ -288,6 +279,11 @@
@Override
public void onThemeChanged() {
+ updateShowEmptyShadeView();
+ mView.updateCornerRadius();
+ mView.updateBgColor();
+ mView.updateDecorViews();
+ mView.reinflateViews();
updateFooter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8be5de7..0accce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -24,8 +24,10 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -54,8 +56,7 @@
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
private boolean mClipNotificationScrollToTop;
- private int mStatusBarHeight;
- private float mHeadsUpInset;
+ @VisibleForTesting float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
@@ -75,9 +76,9 @@
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
- mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
- mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
+ int statusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
+ mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
@@ -407,8 +408,8 @@
viewState.alpha = 1f - ambientState.getHideAmount();
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
- viewState.alpha = Interpolators.getNotificationScrimAlpha(
- ambientState.getExpansionFraction(), true /* notification */);
+ float expansion = ambientState.getExpansionFraction();
+ viewState.alpha = ShadeInterpolation.getContentAlpha(expansion);
}
if (ambientState.isShadeExpanded() && view.mustStayOnScreen()
@@ -562,13 +563,14 @@
// Move the tracked heads up into position during the appear animation, by interpolating
// between the HUN inset (where it will appear as a HUN) and the end position in the shade
+ float headsUpTranslation = mHeadsUpInset - ambientState.getStackTopMargin();
ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
if (trackedHeadsUpRow != null) {
ExpandableViewState childState = trackedHeadsUpRow.getViewState();
if (childState != null) {
float endPosition = childState.yTranslation - ambientState.getStackTranslation();
childState.yTranslation = MathUtils.lerp(
- mHeadsUpInset, endPosition, ambientState.getAppearFraction());
+ headsUpTranslation, endPosition, ambientState.getAppearFraction());
}
}
@@ -602,7 +604,7 @@
}
}
if (row.isPinned()) {
- childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset);
+ childState.yTranslation = Math.max(childState.yTranslation, headsUpTranslation);
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index aeb2efd..111cbbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -37,6 +37,7 @@
private final Handler mHandler;
private AutoHideUiElement mStatusBar;
+ /** For tablets, this will represent the Taskbar */
private AutoHideUiElement mNavigationBar;
private int mDisplayId;
@@ -89,7 +90,7 @@
}
}
- void resumeSuspendedAutoHide() {
+ public void resumeSuspendedAutoHide() {
if (mAutoHideSuspended) {
scheduleAutoHide();
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
@@ -99,7 +100,7 @@
}
}
- void suspendAutoHide() {
+ public void suspendAutoHide() {
mHandler.removeCallbacks(mAutoHide);
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
if (checkBarModesRunnable != null) {
@@ -171,4 +172,23 @@
return false;
}
+
+ /**
+ * Injectable factory for creating a {@link AutoHideController}.
+ */
+ public static class Factory {
+ private final Handler mHandler;
+ private final IWindowManager mIWindowManager;
+
+ @Inject
+ public Factory(@Main Handler handler, IWindowManager iWindowManager) {
+ mHandler = handler;
+ mIWindowManager = iWindowManager;
+ }
+
+ /** Create an {@link AutoHideController} */
+ public AutoHideController create(Context context) {
+ return new AutoHideController(context, mHandler, mIWindowManager);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 12ae3f1..96fa8a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -123,7 +123,7 @@
if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
listeners.filterForEach({ this.listeners.contains(it) }) {
- it.onOverlayChanged()
+ it.onThemeChanged()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5bf982b..49e3fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -283,14 +283,12 @@
}
/**
- * Sensor to use for brightness changes.
+ * Gets the brightness string array per posture. Brightness names along with
+ * doze_brightness_sensor_type is used to determine the brightness sensor to use for
+ * the current posture.
*/
- public String brightnessName(@DevicePostureController.DevicePostureInt int posture) {
- return AmbientDisplayConfiguration.getSensorFromPostureMapping(
- mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping),
- null /* defaultValue */,
- posture
- );
+ public String[] brightnessNames() {
+ return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index f289b9f..57b9c03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -226,11 +226,11 @@
return;
}
- if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
mScrimController.setWakeLockScreenSensorActive(true);
}
- boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+ boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
&& mWakeLockScreenPerformsAuth;
// Set the state to pulsing, so ScrimController will know what to do once we ask it to
// execute the transition. The pulse callback will then be invoked when the scrims
@@ -329,7 +329,7 @@
@Override
public void extendPulse(int reason) {
- if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
mScrimController.setWakeLockScreenSensorActive(true);
}
if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 878fbbf..927b4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -163,7 +163,6 @@
mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
mWakeUpCoordinator.removeListener(this);
mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
- mNotificationPanelViewController.setVerticalTranslationListener(null);
mNotificationPanelViewController.setHeadsUpAppearanceController(null);
mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.removeDarkReceiver(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4b545eb..5f402d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -121,7 +121,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
updateResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index a090ac3..7abe9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -44,6 +44,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ListenerSet;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -83,11 +84,11 @@
private final Runnable mRemoveViewRunnable = this::removeView;
private final KeyguardBypassController mKeyguardBypassController;
private KeyguardHostViewController mKeyguardViewController;
- private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>();
+ private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>();
private final Runnable mResetRunnable = ()-> {
if (mKeyguardViewController != null) {
mKeyguardViewController.resetSecurityContainer();
- for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) {
+ for (KeyguardResetCallback callback : mResetCallbacks) {
callback.onKeyguardReset();
}
}
@@ -602,7 +603,7 @@
}
public void addKeyguardResetCallback(KeyguardResetCallback callback) {
- mResetCallbacks.add(callback);
+ mResetCallbacks.addIfAbsent(callback);
}
public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index a5b5f1c..3a68b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -95,44 +95,85 @@
}
/**
- * Changes the text with an animation and makes sure a single indication is shown long enough.
+ * Changes the text with an animation. Makes sure a single indication is shown long enough.
+ */
+ public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ switchIndication(text, indication, true, null);
+ }
+
+ /**
+ * Changes the text with an optional animation. For animating text, makes sure a single
+ * indication is shown long enough.
*
* @param text The text to show.
* @param indication optional display information for the text
+ * @param animate whether to animate this indication in - we may not want this on AOD
+ * @param onAnimationEndCallback runnable called after this indication is animated in
*/
- public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ public void switchIndication(CharSequence text, KeyguardIndication indication,
+ boolean animate, Runnable onAnimationEndCallback) {
if (text == null) text = "";
CharSequence lastPendingMessage = mMessages.peekLast();
if (TextUtils.equals(lastPendingMessage, text)
|| (lastPendingMessage == null && TextUtils.equals(text, getText()))) {
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
return;
}
mMessages.add(text);
mKeyguardIndicationInfo.add(indication);
- final boolean hasIcon = indication != null && indication.getIcon() != null;
- final AnimatorSet animSet = new AnimatorSet();
- final AnimatorSet.Builder animSetBuilder = animSet.play(getOutAnimator());
+ if (animate) {
+ final boolean hasIcon = indication != null && indication.getIcon() != null;
+ final AnimatorSet animator = new AnimatorSet();
+ // Make sure each animation is visible for a minimum amount of time, while not worrying
+ // about fading in blank text
+ long timeInMillis = System.currentTimeMillis();
+ long delay = Math.max(0, mNextAnimationTime - timeInMillis);
+ setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
+ final long minDurationMillis =
+ (indication != null && indication.getMinVisibilityMillis() != null)
+ ? indication.getMinVisibilityMillis()
+ : MSG_MIN_DURATION_MILLIS_DEFAULT;
+ if (!text.equals("") || hasIcon) {
+ setNextAnimationTime(mNextAnimationTime + minDurationMillis);
+ Animator inAnimator = getInAnimator();
+ inAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.playSequentially(getOutAnimator(), inAnimator);
+ } else {
+ Animator outAnimator = getOutAnimator();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.play(outAnimator);
+ }
- // Make sure each animation is visible for a minimum amount of time, while not worrying
- // about fading in blank text
- long timeInMillis = System.currentTimeMillis();
- long delay = Math.max(0, mNextAnimationTime - timeInMillis);
- setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
-
- final long minDurationMillis =
- (indication != null && indication.getMinVisibilityMillis() != null)
- ? indication.getMinVisibilityMillis()
- : MSG_MIN_DURATION_MILLIS_DEFAULT;
-
- if (!text.equals("") || hasIcon) {
- setNextAnimationTime(mNextAnimationTime + minDurationMillis);
- animSetBuilder.before(getInAnimator());
+ animator.setStartDelay(delay);
+ animator.start();
+ } else {
+ setAlpha(1f);
+ setTranslationY(0f);
+ setNextIndication();
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
}
-
- animSet.setStartDelay(delay);
- animSet.start();
}
private AnimatorSet getOutAnimator() {
@@ -143,29 +184,8 @@
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
- KeyguardIndication info = mKeyguardIndicationInfo.poll();
- if (info != null) {
- // First, update the style.
- // If a background is set on the text, we don't want shadow on the text
- if (info.getBackground() != null) {
- setTextAppearance(sButtonStyleId);
- } else {
- setTextAppearance(sStyleId);
- }
- setBackground(info.getBackground());
- setTextColor(info.getTextColor());
- setOnClickListener(info.getClickListener());
- setClickable(info.getClickListener() != null);
- final Drawable icon = info.getIcon();
- if (icon != null) {
- icon.setTint(getCurrentTextColor());
- if (icon instanceof AnimatedVectorDrawable) {
- ((AnimatedVectorDrawable) icon).start();
- }
- }
- setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
- }
- setText(mMessages.poll());
+ super.onAnimationEnd(animator);
+ setNextIndication();
}
});
@@ -177,6 +197,32 @@
return animatorSet;
}
+ private void setNextIndication() {
+ KeyguardIndication info = mKeyguardIndicationInfo.poll();
+ if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
+ setTextColor(info.getTextColor());
+ setOnClickListener(info.getClickListener());
+ setClickable(info.getClickListener() != null);
+ final Drawable icon = info.getIcon();
+ if (icon != null) {
+ icon.setTint(getCurrentTextColor());
+ if (icon instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) icon).start();
+ }
+ }
+ setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
+ }
+ setText(mMessages.poll());
+ }
+
private AnimatorSet getInAnimator() {
AnimatorSet animatorSet = new AnimatorSet();
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f);
@@ -190,6 +236,7 @@
yTranslate.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
setTranslationY(0);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 5feb405..9055081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -100,13 +100,8 @@
}
@Override
- public void onOverlayChanged() {
- mView.onOverlayChanged();
- KeyguardStatusBarViewController.this.onThemeChanged();
- }
-
- @Override
public void onThemeChanged() {
+ mView.onOverlayChanged();
KeyguardStatusBarViewController.this.onThemeChanged();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index abee7a5..570b0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -101,7 +102,9 @@
mNavigationMode = mode;
});
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
+ if (ctx.getDisplayId() == DEFAULT_DISPLAY) {
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+ }
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -298,4 +301,33 @@
pw.println();
}
}
+
+ /**
+ * Injectable factory for creating a {@link LightBarController}.
+ */
+ public static class Factory {
+ private final DarkIconDispatcher mDarkIconDispatcher;
+ private final BatteryController mBatteryController;
+ private final NavigationModeController mNavModeController;
+ private final DumpManager mDumpManager;
+
+ @Inject
+ public Factory(
+ DarkIconDispatcher darkIconDispatcher,
+ BatteryController batteryController,
+ NavigationModeController navModeController,
+ DumpManager dumpManager) {
+
+ mDarkIconDispatcher = darkIconDispatcher;
+ mBatteryController = batteryController;
+ mNavModeController = navModeController;
+ mDumpManager = dumpManager;
+ }
+
+ /** Create an {@link LightBarController} */
+ public LightBarController create(Context context) {
+ return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
+ mNavModeController, mDumpManager);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 78fcd82..2a13e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -119,7 +119,6 @@
LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
if (result.success) {
mCached = true;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mCache = result.bitmap;
}
return mCache;
@@ -235,7 +234,6 @@
if (result.success) {
mCached = true;
mCache = result.bitmap;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mMediaManager.updateMediaMetaData(
true /* metaDataChanged */, true /* allowEnterAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d09a89e..2e5cdec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -473,7 +473,6 @@
private ArrayList<Consumer<ExpandableNotificationRow>>
mTrackingHeadsUpListeners =
new ArrayList<>();
- private Runnable mVerticalTranslationListener;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private int mPanelAlpha;
@@ -1120,6 +1119,7 @@
constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
constraintSet.applyTo(mNotificationContainerParent);
+ mAmbientState.setStackTopMargin(topMargin);
mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
updateKeyguardStatusViewAlignment(/* animate= */false);
@@ -1360,6 +1360,7 @@
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
}
+ mSplitShadeHeaderController.setShadeExpandedFraction(getExpandedFraction());
mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
@@ -1667,6 +1668,24 @@
mNotificationStackScrollLayoutController.resetScrollPosition();
}
+ /** Collapses the panel. */
+ public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
+ boolean waiting = false;
+ if (animate && !isFullyCollapsed()) {
+ collapse(delayed, speedUpFactor);
+ waiting = true;
+ } else {
+ resetViews(false /* animate */);
+ setExpandedFraction(0); // just in case
+ }
+ if (!waiting) {
+ // it's possible that nothing animated, so we replicate the termination
+ // conditions of panelExpansionChanged here
+ // TODO(b/200063118): This can likely go away in a future refactor CL.
+ mBar.updateState(STATE_CLOSED);
+ }
+ }
+
@Override
public void collapse(boolean delayed, float speedUpFactor) {
if (!canPanelBeCollapsed()) {
@@ -3117,7 +3136,7 @@
@Override
protected void onExpandingFinished() {
- super.onExpandingFinished();
+ mScrimController.onExpandingFinished();
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
@@ -3192,6 +3211,7 @@
protected void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
super.onTrackingStarted();
+ mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
mQsExpandImmediate = true;
if (!mShouldUseSplitNotificationShade) {
@@ -3202,6 +3222,7 @@
mAffordanceHelper.animateHideLeftRightIcon();
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
+ cancelPendingPanelCollapse();
}
@Override
@@ -3391,8 +3412,7 @@
@Override
protected void onClosingFinished() {
- super.onClosingFinished();
- resetHorizontalPanelPosition();
+ mStatusBar.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
}
@@ -3402,47 +3422,6 @@
mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
}
- /**
- * Updates the horizontal position of the panel so it is positioned closer to the touch
- * responsible for opening the panel.
- *
- * @param x the x-coordinate the touch event
- */
- protected void updateHorizontalPanelPosition(float x) {
- if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()
- || mShouldUseSplitNotificationShade) {
- resetHorizontalPanelPosition();
- return;
- }
- float leftMost = mPositionMinSideMargin
- + mNotificationStackScrollLayoutController.getWidth() / 2;
- float
- rightMost =
- mView.getWidth() - mPositionMinSideMargin
- - mNotificationStackScrollLayoutController.getWidth() / 2;
- if (Math.abs(x - mView.getWidth() / 2)
- < mNotificationStackScrollLayoutController.getWidth() / 4) {
- x = mView.getWidth() / 2;
- }
- x = Math.min(rightMost, Math.max(leftMost, x));
- float
- center = mNotificationStackScrollLayoutController.getLeft()
- + mNotificationStackScrollLayoutController.getWidth() / 2;
- setHorizontalPanelTranslation(x - center);
- }
-
- private void resetHorizontalPanelPosition() {
- setHorizontalPanelTranslation(0f);
- }
-
- protected void setHorizontalPanelTranslation(float translation) {
- mNotificationStackScrollLayoutController.setTranslationX(translation);
- mQsFrame.setTranslationX(translation);
- if (mVerticalTranslationListener != null) {
- mVerticalTranslationListener.run();
- }
- }
-
protected void updateExpandedHeight(float expandedHeight) {
if (mTracking) {
mNotificationStackScrollLayoutController
@@ -3755,10 +3734,6 @@
mTrackingHeadsUpListeners.remove(listener);
}
- public void setVerticalTranslationListener(Runnable verticalTranslationListener) {
- mVerticalTranslationListener = verticalTranslationListener;
- }
-
public void setHeadsUpAppearanceController(
HeadsUpAppearanceController headsUpAppearanceController) {
mHeadsUpAppearanceController = headsUpAppearanceController;
@@ -3868,13 +3843,27 @@
mNotificationStackScrollLayoutController.setScrollingEnabled(b);
}
+ private Runnable mHideExpandedRunnable;
+ private final Runnable mMaybeHideExpandedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (getExpansionFraction() == 0.0f) {
+ mView.post(mHideExpandedRunnable);
+ }
+ }
+ };
+
/**
* Initialize objects instead of injecting to avoid circular dependencies.
+ *
+ * @param hideExpandedRunnable a runnable to run when we need to hide the expanded panel.
*/
public void initDependencies(
StatusBar statusBar,
+ Runnable hideExpandedRunnable,
NotificationShelfController notificationShelfController) {
setStatusBar(statusBar);
+ mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
mNotificationShelfController = notificationShelfController;
mLockscreenShadeTransitionController.bindController(notificationShelfController);
@@ -3931,6 +3920,45 @@
private long mLastTouchDownTime = -1L;
@Override
+ public boolean onTouchForwardedFromStatusBar(MotionEvent event) {
+ // TODO(b/202981994): Move the touch debugging in this method to a central location.
+ // (Right now, it's split between StatusBar and here.)
+
+ // If panels aren't enabled, ignore the gesture and don't pass it down to the
+ // panel view.
+ if (!mCommandQueue.panelsEnabled()) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ Log.v(
+ TAG,
+ String.format(
+ "onTouchForwardedFromStatusBar: "
+ + "panel disabled, ignoring touch at (%d,%d)",
+ (int) event.getX(),
+ (int) event.getY()
+ )
+ );
+ }
+ return false;
+ }
+
+ // If the view that would receive the touch is disabled, just have status bar eat
+ // the gesture.
+ if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
+ Log.v(TAG,
+ String.format(
+ "onTouchForwardedFromStatusBar: "
+ + "panel view disabled, eating touch at (%d,%d)",
+ (int) event.getX(),
+ (int) event.getY()
+ )
+ );
+ return true;
+ }
+
+ return mView.dispatchTouchEvent(event);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches || mQs.disallowPanelTouches()) {
return false;
@@ -3941,7 +3969,7 @@
if (mStatusBar.isBouncerShowing()) {
return true;
}
- if (mBar.panelEnabled()
+ if (mCommandQueue.panelsEnabled()
&& !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
@@ -4027,7 +4055,6 @@
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- updateHorizontalPanelPosition(event.getX());
handled = true;
}
@@ -4411,12 +4438,7 @@
@Override
public void onThemeChanged() {
if (DEBUG) Log.d(TAG, "onThemeChanged");
- final int themeResId = mView.getContext().getThemeResId();
- if (mThemeResId == themeResId) {
- return;
- }
- mThemeResId = themeResId;
-
+ mThemeResId = mView.getContext().getThemeResId();
reInflateViews();
}
@@ -4430,12 +4452,6 @@
}
@Override
- public void onOverlayChanged() {
- if (DEBUG) Log.d(TAG, "onOverlayChanged");
- reInflateViews();
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
reInflateViews();
@@ -4525,9 +4541,16 @@
}
}
} else {
- mKeyguardStatusBarViewController.updateViewState(
- /* alpha= */ 1f,
- keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+ final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
+ && statusBarState == KEYGUARD
+ && mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
+ if (!animatingUnlockedShadeToKeyguard) {
+ // Only make the status bar visible if we're not animating the screen off, since
+ // we only want to be showing the clock/notifications during the animation.
+ mKeyguardStatusBarViewController.updateViewState(
+ /* alpha= */ 1f,
+ keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+ }
if (keyguardShowing && oldState != mBarState) {
if (mQs != null) {
mQs.hideImmediately();
@@ -4543,7 +4566,6 @@
// The update needs to happen after the headerSlide in above, otherwise the translation
// would reset
maybeAnimateBottomAreaAlpha();
- resetHorizontalPanelPosition();
updateQsState();
mSplitShadeHeaderController.setShadeExpanded(
mBarState == SHADE || mBarState == SHADE_LOCKED);
@@ -4794,9 +4816,6 @@
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAffordanceHelper.onConfigurationChanged();
- if (newConfig.orientation != mLastOrientation) {
- resetHorizontalPanelPosition();
- }
mLastOrientation = newConfig.orientation;
}
}
@@ -4815,6 +4834,11 @@
}
}
+ /** Removes any pending runnables that would collapse the panel. */
+ public void cancelPendingPanelCollapse() {
+ mView.removeCallbacks(mMaybeHideExpandedRunnable);
+ }
+
private final PanelBar.PanelStateChangeListener mPanelStateChangeListener =
new PanelBar.PanelStateChangeListener() {
@@ -4829,6 +4853,14 @@
if (state == STATE_OPEN && mCurrentState != state) {
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
+ if (state == STATE_OPENING) {
+ mStatusBar.makeExpandedVisible(false);
+ }
+ if (state == STATE_CLOSED) {
+ // Close the status bar in the next frame so we can show the end of the
+ // animation.
+ mView.post(mMaybeHideExpandedRunnable);
+ }
mCurrentState = state;
}
};
@@ -4836,4 +4868,10 @@
public PanelBar.PanelStateChangeListener getPanelStateChangeListener() {
return mPanelStateChangeListener;
}
+
+
+ /** Returns the handler that the status bar should forward touches to. */
+ public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
+ return getTouchHandler()::onTouchForwardedFromStatusBar;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 9d06d2b..36bd31b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -56,7 +56,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -68,7 +67,6 @@
*/
public class NotificationShadeWindowViewController {
private static final String TAG = "NotifShadeWindowVC";
- private final InjectionInflationController mInjectionInflationController;
private final NotificationWakeUpCoordinator mCoordinator;
private final PulseExpansionHandler mPulseExpansionHandler;
private final DynamicPrivacyController mDynamicPrivacyController;
@@ -116,7 +114,6 @@
@Inject
public NotificationShadeWindowViewController(
- InjectionInflationController injectionInflationController,
NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
@@ -141,7 +138,6 @@
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
LockIconViewController lockIconViewController) {
- mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
mDynamicPrivacyController = dynamicPrivacyController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 310fe73..e90258d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -25,7 +25,6 @@
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -53,11 +52,18 @@
public static final int STATE_OPENING = 1;
public static final int STATE_OPEN = 2;
- private PanelViewController mPanel;
@Nullable private PanelStateChangeListener mPanelStateChangeListener;
private int mState = STATE_CLOSED;
private boolean mTracking;
+ /** Updates the panel state if necessary. */
+ public void updateState(@PanelState int state) {
+ if (DEBUG) LOG("update state: %d -> %d", mState, state);
+ if (mState != state) {
+ go(state);
+ }
+ }
+
private void go(@PanelState int state) {
if (DEBUG) LOG("go state: %d -> %d", mState, state);
mState = state;
@@ -97,54 +103,11 @@
super.onFinishInflate();
}
- /** Set the PanelViewController */
- public void setPanel(PanelViewController pv) {
- mPanel = pv;
- pv.setBar(this);
- }
-
/** Sets the listener that will be notified of panel state changes. */
public void setPanelStateChangeListener(PanelStateChangeListener listener) {
mPanelStateChangeListener = listener;
}
- public boolean panelEnabled() {
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // Allow subclasses to implement enable/disable semantics
- if (!panelEnabled()) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- Log.v(TAG, String.format("onTouch: all panels disabled, ignoring touch at (%d,%d)",
- (int) event.getX(), (int) event.getY()));
- }
- return false;
- }
-
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- final PanelViewController panel = mPanel;
- if (panel == null) {
- // panel is not there, so we'll eat the gesture
- Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
- (int) event.getX(), (int) event.getY()));
- return true;
- }
- boolean enabled = panel.isEnabled();
- if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel,
- (enabled ? "" : " (disabled)"));
- if (!enabled) {
- // panel is disabled, so we'll eat the gesture
- Log.v(TAG, String.format(
- "onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)",
- panel, (int) event.getX(), (int) event.getY()));
- return true;
- }
- }
- return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
- }
-
/**
* @param frac the fraction from the expansion in [0, 1]
* @param expanded whether the panel is currently expanded; this is independent from the
@@ -162,7 +125,6 @@
if (expanded) {
if (mState == STATE_CLOSED) {
go(STATE_OPENING);
- onPanelPeeked();
}
fullyClosed = false;
fullyOpened = frac >= 1f;
@@ -171,44 +133,16 @@
go(STATE_OPEN);
} else if (fullyClosed && !mTracking && mState != STATE_CLOSED) {
go(STATE_CLOSED);
- onPanelCollapsed();
}
if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState,
fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":"");
}
- public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
- boolean waiting = false;
- PanelViewController pv = mPanel;
- if (animate && !pv.isFullyCollapsed()) {
- pv.collapse(delayed, speedUpFactor);
- waiting = true;
- } else {
- pv.resetViews(false /* animate */);
- pv.setExpandedFraction(0); // just in case
- }
- if (DEBUG) LOG("collapsePanel: animate=%s waiting=%s", animate, waiting);
- if (!waiting && mState != STATE_CLOSED) {
- // it's possible that nothing animated, so we replicate the termination
- // conditions of panelExpansionChanged here
- go(STATE_CLOSED);
- onPanelCollapsed();
- }
- }
-
- public void onPanelPeeked() {
- if (DEBUG) LOG("onPanelPeeked");
- }
-
public boolean isClosed() {
return mState == STATE_CLOSED;
}
- public void onPanelCollapsed() {
- if (DEBUG) LOG("onPanelCollapsed");
- }
-
public void onTrackingStarted() {
mTracking = true;
}
@@ -217,14 +151,6 @@
mTracking = false;
}
- public void onExpandingFinished() {
- if (DEBUG) LOG("onExpandingFinished");
- }
-
- public void onClosingFinished() {
-
- }
-
/** An interface that will be notified of panel state changes. */
public interface PanelStateChangeListener {
/** Called when the state changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index c23577c..e5296af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -185,10 +185,9 @@
protected final SysuiStatusBarStateController mStatusBarStateController;
protected final AmbientState mAmbientState;
protected final LockscreenGestureLogger mLockscreenGestureLogger;
+ private final TouchHandler mTouchHandler;
- protected void onExpandingFinished() {
- mBar.onExpandingFinished();
- }
+ protected abstract void onExpandingFinished();
protected void onExpandingStarted() {
}
@@ -226,6 +225,7 @@
mView = view;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
+ mTouchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -238,7 +238,7 @@
});
mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(createTouchHandler());
+ mView.setOnTouchListener(mTouchHandler);
mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
mResources = mView.getResources();
@@ -289,6 +289,10 @@
: mTouchSlop;
}
+ protected TouchHandler getTouchHandler() {
+ return mTouchHandler;
+ }
+
private void addMovement(MotionEvent event) {
// Add movement to velocity tracker using raw screen X and Y coordinates instead
// of window coordinates because the window frame may be moving at the same time.
@@ -392,6 +396,12 @@
expand = false;
} else if (onKeyguard) {
expand = true;
+ } else if (mKeyguardStateController.isKeyguardFadingAway()) {
+ // If we're in the middle of dismissing the keyguard, don't expand due to the
+ // cancelled gesture. Gesture cancellation during an unlock is expected in some
+ // situations, such keeping your finger down while swiping to unlock to an app
+ // that is locked in landscape (the rotation will cancel the touch event).
+ expand = false;
} else {
// If we get a cancel, put the shade back to the state it was in when the
// gesture started
@@ -448,6 +458,7 @@
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mBar.onTrackingStopped(expand);
+ mStatusBar.onTrackingStopped(expand);
updatePanelExpansionAndVisibility();
}
@@ -455,6 +466,7 @@
endClosing();
mTracking = true;
mBar.onTrackingStarted();
+ mStatusBar.onTrackingStarted();
notifyExpandingStarted();
updatePanelExpansionAndVisibility();
}
@@ -927,10 +939,7 @@
mView.removeCallbacks(mFlingCollapseRunnable);
}
- protected void onClosingFinished() {
- mBar.onClosingFinished();
- }
-
+ protected abstract void onClosingFinished();
protected void startUnlockHintAnimation() {
@@ -1153,23 +1162,28 @@
return mView;
}
- public boolean isEnabled() {
- return mView.isEnabled();
- }
-
public OnLayoutChangeListener createLayoutChangeListener() {
return new OnLayoutChangeListener();
}
- protected TouchHandler createTouchHandler() {
- return new TouchHandler();
- }
+ protected abstract TouchHandler createTouchHandler();
protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
- public class TouchHandler implements View.OnTouchListener {
+ public abstract class TouchHandler implements View.OnTouchListener {
+ /**
+ * Method called when a touch has occurred on {@link PhoneStatusBarView}.
+ *
+ * Touches that occur on the status bar view may have ramifications for the notification
+ * panel (e.g. a touch that pulls down the shade could start on the status bar), so we need
+ * to notify the panel controller when these touches occur.
+ *
+ * Returns true if the event was handled and false otherwise.
+ */
+ public abstract boolean onTouchForwardedFromStatusBar(MotionEvent event);
+
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
&& event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
@@ -1435,4 +1449,8 @@
private void cancelJankMonitoring(int cuj) {
InteractionJankMonitor.getInstance().cancel(cuj);
}
+
+ protected float getExpansionFraction() {
+ return mExpandedFraction;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 7b110a0..d19ed28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -604,8 +604,7 @@
@Override
public void onUserSetupChanged() {
- boolean userSetup = mProvisionedController.isUserSetup(
- mProvisionedController.getCurrentUser());
+ boolean userSetup = mProvisionedController.isCurrentUserSetup();
if (mCurrentUserSetup == userSetup) return;
mCurrentUserSetup = userSetup;
updateAlarm();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 150d9c8..06a31c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -24,7 +24,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -37,7 +36,6 @@
import android.widget.LinearLayout;
import com.android.systemui.Dependency;
-import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
@@ -55,14 +53,6 @@
StatusBar mBar;
private ScrimController mScrimController;
- private Runnable mHideExpandedRunnable = new Runnable() {
- @Override
- public void run() {
- if (mPanelFraction == 0.0f) {
- mBar.makeExpandedInvisible();
- }
- }
- };
private DarkReceiver mBattery;
private DarkReceiver mClock;
private int mRotationOrientation = -1;
@@ -76,15 +66,12 @@
@Nullable
private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
@Nullable
- private PanelExpansionStateChangedListener mPanelExpansionStateChangedListener;
-
- private PanelEnabledProvider mPanelEnabledProvider;
+ private TouchEventHandler mTouchEventHandler;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
*/
private int mCutoutSideNudge = 0;
- private boolean mHeadsUpVisible;
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -100,8 +87,8 @@
mExpansionChangedListeners = listeners;
}
- void setPanelExpansionStateChangedListener(PanelExpansionStateChangedListener listener) {
- mPanelExpansionStateChangedListener = listener;
+ void setTouchEventHandler(TouchEventHandler handler) {
+ mTouchEventHandler = handler;
}
public void setScrimController(ScrimController scrimController) {
@@ -178,15 +165,6 @@
}
@Override
- public boolean panelEnabled() {
- if (mPanelEnabledProvider == null) {
- Log.e(TAG, "panelEnabledProvider is null; defaulting to super class.");
- return super.panelEnabled();
- }
- return mPanelEnabledProvider.panelEnabled();
- }
-
- @Override
public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// The status bar is very small so augment the view that the user is touching
@@ -202,79 +180,31 @@
}
@Override
- public void onPanelPeeked() {
- super.onPanelPeeked();
- mBar.makeExpandedVisible(false);
- }
-
- @Override
- public void onPanelCollapsed() {
- super.onPanelCollapsed();
- // Close the status bar in the next frame so we can show the end of the animation.
- post(mHideExpandedRunnable);
- }
-
- public void removePendingHideExpandedRunnables() {
- removeCallbacks(mHideExpandedRunnable);
- }
-
- @Override
public boolean onTouchEvent(MotionEvent event) {
- boolean barConsumedEvent = mBar.interceptTouchEvent(event);
-
- if (DEBUG_GESTURES) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH,
- event.getActionMasked(), (int) event.getX(), (int) event.getY(),
- barConsumedEvent ? 1 : 0);
- }
+ mBar.onTouchEvent(event);
+ if (mTouchEventHandler == null) {
+ Log.w(
+ TAG,
+ String.format(
+ "onTouch: No touch handler provided; eating gesture at (%d,%d)",
+ (int) event.getX(),
+ (int) event.getY()
+ )
+ );
+ return true;
}
-
- return barConsumedEvent || super.onTouchEvent(event);
- }
-
- @Override
- public void onTrackingStarted() {
- super.onTrackingStarted();
- mBar.onTrackingStarted();
- mScrimController.onTrackingStarted();
- removePendingHideExpandedRunnables();
- }
-
- @Override
- public void onClosingFinished() {
- super.onClosingFinished();
- mBar.onClosingFinished();
- }
-
- @Override
- public void onTrackingStopped(boolean expand) {
- super.onTrackingStopped(expand);
- mBar.onTrackingStopped(expand);
- }
-
- @Override
- public void onExpandingFinished() {
- super.onExpandingFinished();
- mScrimController.onExpandingFinished();
+ return mTouchEventHandler.handleTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event);
+ mBar.onTouchEvent(event);
+ return super.onInterceptTouchEvent(event);
}
@Override
public void panelExpansionChanged(float frac, boolean expanded) {
super.panelExpansionChanged(frac, expanded);
- if ((frac == 0 || frac == 1)) {
- if (mPanelExpansionStateChangedListener != null) {
- mPanelExpansionStateChangedListener.onPanelExpansionStateChanged();
- } else {
- Log.w(TAG, "No PanelExpansionStateChangedListener provided.");
- }
- }
-
if (mExpansionChangedListeners != null) {
for (StatusBar.ExpansionChangedListener listener : mExpansionChangedListeners) {
listener.onExpansionChanged(frac, expanded);
@@ -282,11 +212,6 @@
}
}
- /** Set the {@link PanelEnabledProvider} to use. */
- public void setPanelEnabledProvider(PanelEnabledProvider panelEnabledProvider) {
- mPanelEnabledProvider = panelEnabledProvider;
- }
-
public void updateResources() {
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
@@ -366,15 +291,14 @@
getPaddingBottom());
}
- /** An interface that will provide whether panel is enabled. */
- interface PanelEnabledProvider {
- /** Returns true if the panel is enabled and false otherwise. */
- boolean panelEnabled();
- }
-
- /** A listener that will be notified when a panel's expansion state may have changed. */
- public interface PanelExpansionStateChangedListener {
- /** Called when a panel's expansion state may have changed. */
- void onPanelExpansionStateChanged();
+ /**
+ * A handler repsonsible for all touch event handling on the status bar.
+ *
+ * The handler will be notified each time {@link this#onTouchEvent} is called, and the return
+ * value from the handler will be returned from {@link this#onTouchEvent}.
+ **/
+ public interface TouchEventHandler {
+ /** Called each time {@link this#onTouchEvent} is called. */
+ boolean handleTouchEvent(MotionEvent event);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 4c0332a..de21e73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -18,29 +18,28 @@
import android.graphics.Point
import android.view.View
import android.view.ViewGroup
+import android.view.ViewTreeObserver
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.unfold.UNFOLD_STATUS_BAR
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.util.ViewController
+import javax.inject.Inject
+import javax.inject.Named
+import dagger.Lazy
/** Controller for [PhoneStatusBarView]. */
-class PhoneStatusBarViewController(
+class PhoneStatusBarViewController private constructor(
view: PhoneStatusBarView,
- commandQueue: CommandQueue,
- statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
- panelExpansionStateChangedListener: PhoneStatusBarView.PanelExpansionStateChangedListener,
+ @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
+ private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
+ touchEventHandler: PhoneStatusBarView.TouchEventHandler,
) : ViewController<PhoneStatusBarView>(view) {
- override fun onViewAttached() {}
- override fun onViewDetached() {}
-
- init {
- mView.setPanelEnabledProvider {
- commandQueue.panelsEnabled()
- }
- mView.setPanelExpansionStateChangedListener(panelExpansionStateChangedListener)
-
- statusBarMoveFromCenterAnimationController?.let { animationController ->
+ override fun onViewAttached() {
+ moveFromCenterAnimationController?.let { animationController ->
val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
@@ -50,15 +49,33 @@
systemIconArea
)
- animationController.init(viewsToAnimate, viewCenterProvider)
+ mView.viewTreeObserver.addOnPreDrawListener(object :
+ ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ animationController.onViewsReady(viewsToAnimate, viewCenterProvider)
+ mView.viewTreeObserver.removeOnPreDrawListener(this)
+ return true
+ }
+ })
mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
val widthChanged = right - left != oldRight - oldLeft
if (widthChanged) {
- statusBarMoveFromCenterAnimationController.onStatusBarWidthChanged()
+ moveFromCenterAnimationController.onStatusBarWidthChanged()
}
}
}
+
+ progressProvider?.setReadyToHandleTransition(true)
+ }
+
+ override fun onViewDetached() {
+ progressProvider?.setReadyToHandleTransition(false)
+ moveFromCenterAnimationController?.onViewDetached()
+ }
+
+ init {
+ mView.setTouchEventHandler(touchEventHandler)
}
fun setImportantForAccessibility(mode: Int) {
@@ -96,4 +113,23 @@
outPoint.y = viewY + view.height / 2
}
}
+
+ class Factory @Inject constructor(
+ @Named(UNFOLD_STATUS_BAR)
+ private val progressProvider: Lazy<ScopedUnfoldTransitionProgressProvider>,
+ private val moveFromCenterController: Lazy<StatusBarMoveFromCenterAnimationController>,
+ private val unfoldConfig: UnfoldTransitionConfig,
+ ) {
+ fun create(
+ view: PhoneStatusBarView,
+ touchEventHandler: PhoneStatusBarView.TouchEventHandler
+ ): PhoneStatusBarViewController {
+ return PhoneStatusBarViewController(
+ view,
+ if (unfoldConfig.isEnabled) progressProvider.get() else null,
+ if (unfoldConfig.isEnabled) moveFromCenterController.get() else null,
+ touchEventHandler
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 371ec7a..1921357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -47,7 +47,7 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
@@ -265,11 +265,6 @@
}
@Override
- public void onOverlayChanged() {
- ScrimController.this.onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
ScrimController.this.onThemeChanged();
}
@@ -584,8 +579,7 @@
if (isNaN(expansionFraction)) {
return;
}
- expansionFraction = Interpolators
- .getNotificationScrimAlpha(expansionFraction, false /* notification */);
+ expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction);
boolean qsBottomVisible = qsPanelBottomY > 0;
if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
mQsExpansion = expansionFraction;
@@ -680,6 +674,12 @@
}
mInFrontAlpha = 0;
}
+ } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+
+ mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+ mNotificationsAlpha = mBehindAlpha;
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING) {
Pair<Integer, Float> result = calculateBackStateForState(mState);
@@ -920,8 +920,7 @@
}
private float getInterpolatedFraction() {
- return Interpolators.getNotificationScrimAlpha(
- mPanelExpansionFraction, false /* notification */);
+ return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction);
}
private void setScrimAlpha(ScrimView scrim, float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 850b986..9246c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -87,6 +87,17 @@
}
},
+ AUTH_SCRIMMED_SHADE {
+ @Override
+ public void prepare(ScrimState previousState) {
+ // notif & behind scrim alpha values are determined by ScrimController#applyState
+ // based on the shade expansion
+
+ mFrontTint = Color.BLACK;
+ mFrontAlpha = .66f;
+ }
+ },
+
AUTH_SCRIMMED {
@Override
public void prepare(ScrimState previousState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 768222d..a54251a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -128,7 +128,8 @@
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
- getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
+ getNotificationPanelViewController()
+ .collapsePanel(true /* animate */, delayed, speedUpFactor);
} else if (mBubblesOptional.isPresent()) {
mBubblesOptional.get().collapseStack();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
index 4b7fe4e..a7ecd06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -18,6 +18,7 @@
import android.view.View
import com.android.systemui.R
+import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.flags.FeatureFlags
@@ -53,6 +54,14 @@
updateVisibility()
}
+ var shadeExpandedFraction = -1f
+ set(value) {
+ if (visible && field != value) {
+ statusBar.alpha = ShadeInterpolation.getContentAlpha(value)
+ field = value
+ }
+ }
+
init {
batteryMeterViewController.init()
val batteryIcon: BatteryMeterView = statusBar.findViewById(R.id.batteryRemainingIcon)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1f0785e..0e75a45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -233,6 +233,7 @@
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
@@ -525,6 +526,7 @@
private QSPanelController mQSPanelController;
private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
+ private final PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory;
KeyguardIndicationController mKeyguardIndicationController;
private View mReportRejectedTouch;
@@ -543,8 +545,8 @@
private final FeatureFlags mFeatureFlags;
private final UnfoldTransitionConfig mUnfoldTransitionConfig;
private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
+ private final Lazy<NaturalRotationUnfoldProgressProvider> mNaturalUnfoldProgressProvider;
private final Lazy<UnfoldTransitionWallpaperController> mUnfoldWallpaperController;
- private final Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimation;
private final WallpaperController mWallpaperController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final MessageRouter mMessageRouter;
@@ -772,6 +774,7 @@
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
+ PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
DemoModeController demoModeController,
@@ -782,7 +785,7 @@
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
- Lazy<StatusBarMoveFromCenterAnimationController> statusBarUnfoldAnimationController,
+ Lazy<NaturalRotationUnfoldProgressProvider> naturalRotationUnfoldProgressProvider,
WallpaperController wallpaperController,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
@@ -812,6 +815,7 @@
mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManagerPhone;
mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
+ mPhoneStatusBarViewControllerFactory = phoneStatusBarViewControllerFactory;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -879,9 +883,9 @@
mBrightnessSliderFactory = brightnessSliderFactory;
mUnfoldTransitionConfig = unfoldTransitionConfig;
mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation;
+ mNaturalUnfoldProgressProvider = naturalRotationUnfoldProgressProvider;
mUnfoldWallpaperController = unfoldTransitionWallpaperController;
mWallpaperController = wallpaperController;
- mMoveFromCenterAnimation = statusBarUnfoldAnimationController;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mStatusBarLocationPublisher = locationPublisher;
@@ -902,6 +906,7 @@
mExpansionChangedListeners = new ArrayList<>();
addExpansionChangedListener(
(expansion, expanded) -> mScrimController.setRawPanelExpansionFraction(expansion));
+ addExpansionChangedListener(this::onPanelExpansionChanged);
mBubbleExpandListener =
(isExpanding, key) -> mContext.getMainExecutor().execute(() -> {
@@ -1076,6 +1081,7 @@
if (mUnfoldTransitionConfig.isEnabled()) {
mUnfoldLightRevealOverlayAnimation.get().init();
mUnfoldWallpaperController.get().init();
+ mNaturalUnfoldProgressProvider.get().init();
}
mPluginManager.addPluginListener(
@@ -1167,7 +1173,6 @@
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
mStatusBarView.setBar(this);
- mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setPanelStateChangeListener(
mNotificationPanelViewController.getPanelStateChangeListener());
mStatusBarView.setScrimController(mScrimController);
@@ -1176,16 +1181,11 @@
sendInitialExpansionAmount(listener);
}
- StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null;
- if (mUnfoldTransitionConfig.isEnabled()) {
- moveFromCenterAnimation = mMoveFromCenterAnimation.get();
- }
- mPhoneStatusBarViewController =
- new PhoneStatusBarViewController(
- mStatusBarView,
- mCommandQueue,
- moveFromCenterAnimation,
- this::onPanelExpansionStateChanged);
+ mNotificationPanelViewController.setBar(mStatusBarView);
+
+ mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory
+ .create(mStatusBarView, mNotificationPanelViewController
+ .getStatusBarTouchEventHandler());
mPhoneStatusBarViewController.init();
mBatteryMeterViewController = new BatteryMeterViewController(
@@ -1216,7 +1216,7 @@
// TODO (b/136993073) Separate notification shade and status bar
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager,
- mStackScroller.getController(),
+ mStackScrollerController,
mStatusBarStateController, mKeyguardBypassController,
mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
mNotificationPanelViewController, mStatusBarView);
@@ -1316,6 +1316,7 @@
mNotificationPanelViewController.initDependencies(
this,
+ this::makeExpandedInvisible,
mNotificationShelfController);
BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1453,12 +1454,14 @@
}
}
- private void onPanelExpansionStateChanged() {
- if (getNavigationBarView() != null) {
- getNavigationBarView().onStatusBarPanelStateChanged();
- }
- if (getNotificationPanelViewController() != null) {
- getNotificationPanelViewController().updateSystemUiStateFlags();
+ private void onPanelExpansionChanged(float frac, boolean expanded) {
+ if (frac == 0 || frac == 1) {
+ if (getNavigationBarView() != null) {
+ getNavigationBarView().onStatusBarPanelStateChanged();
+ }
+ if (getNotificationPanelViewController() != null) {
+ getNotificationPanelViewController().updateSystemUiStateFlags();
+ }
}
}
@@ -2157,7 +2160,8 @@
public void animateCollapseQuickSettings() {
if (mState == StatusBarState.SHADE) {
- mStatusBarView.collapsePanel(true, false /* delayed */, 1.0f /* speedUpFactor */);
+ mNotificationPanelViewController.collapsePanel(
+ true, false /* delayed */, 1.0f /* speedUpFactor */);
}
}
@@ -2170,7 +2174,7 @@
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
+ mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
1.0f /* speedUpFactor */);
mNotificationPanelViewController.closeQs();
@@ -2204,7 +2208,10 @@
}
}
- public boolean interceptTouchEvent(MotionEvent event) {
+ /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
+ public void onTouchEvent(MotionEvent event) {
+ // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's
+ // split between NotificationPanelViewController and here.)
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
@@ -2236,7 +2243,6 @@
event.getAction() == MotionEvent.ACTION_CANCEL;
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
}
- return false;
}
boolean isSameStatusBarState(int state) {
@@ -3287,16 +3293,16 @@
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
-
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
// to set our default theme.
final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
: R.style.Theme_SystemUI;
- if (mContext.getThemeResId() != themeResId) {
- mContext.setTheme(themeResId);
- mConfigurationController.notifyThemeChanged();
+ if (mContext.getThemeResId() == themeResId) {
+ return;
}
+ mContext.setTheme(themeResId);
+ mConfigurationController.notifyThemeChanged();
}
private void updateDozingState() {
@@ -3837,7 +3843,11 @@
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED) {
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ } else {
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ }
} else if (mBouncerShowing) {
// Bouncer needs the front scrim when it's on top of an activity,
// tapping on a notification, editing QS or being dismissed by
@@ -4328,10 +4338,9 @@
private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
- final boolean userSetup = mDeviceProvisionedController.isUserSetup(
- mDeviceProvisionedController.getCurrentUser());
- Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for user "
- + mDeviceProvisionedController.getCurrentUser());
+ final boolean userSetup = mDeviceProvisionedController.isCurrentUserSetup();
+ Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for "
+ + "current user");
if (MULTIUSER_DEBUG) {
Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
userSetup, mUserSetup));
@@ -4409,6 +4418,13 @@
@Override
public void onThemeChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ // We need the new R.id.keyguard_indication_area before recreating
+ // mKeyguardIndicationController
+ mNotificationPanelViewController.onThemeChanged();
+
if (mStatusBarKeyguardViewManager != null) {
mStatusBarKeyguardViewManager.onThemeChanged();
}
@@ -4419,17 +4435,6 @@
}
@Override
- public void onOverlayChanged() {
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
- // We need the new R.id.keyguard_indication_area before recreating
- // mKeyguardIndicationController
- mNotificationPanelViewController.onThemeChanged();
- onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onUiModeChanged();
@@ -4464,7 +4469,7 @@
mNavigationBarController.touchAutoDim(mDisplayId);
Trace.beginSection("StatusBar#updateKeyguardState");
if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
- mStatusBarView.removePendingHideExpandedRunnables();
+ mNotificationPanelViewController.cancelPendingPanelCollapse();
}
updateDozingState();
checkBarModes();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index 5301b25..bb1daa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -536,7 +536,7 @@
}
if (mStatusBar.getStatusBarView() != null) {
if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) {
- mStatusBar.getStatusBarView().collapsePanel(
+ mNotificationPanelViewController.collapsePanel(
false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 61552f0..5bfb236 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -91,7 +91,7 @@
clearCachedInsets()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
clearCachedInsets()
}
@@ -179,6 +179,11 @@
minRight)
}
+ fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int {
+ val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources
+ return res.getDimensionPixelSize(R.dimen.status_bar_padding_top)
+ }
+
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
insetsCache.snapshot().forEach { (key, rect) ->
pw.println("$key -> $rect")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 8af03aa..2707fa3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -20,43 +20,48 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UNFOLD_STATUS_BAR
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import javax.inject.Inject
+import javax.inject.Named
@SysUISingleton
class StatusBarMoveFromCenterAnimationController @Inject constructor(
- private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
- private val windowManager: WindowManager
+ @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider,
+ private val windowManager: WindowManager,
) {
- private lateinit var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator
+ private val transitionListener = TransitionListener()
+ private var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator? = null
- fun init(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
+ fun onViewsReady(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
viewCenterProvider = viewCenterProvider)
- unfoldTransitionProgressProvider.addCallback(object : TransitionProgressListener {
- override fun onTransitionStarted() {
- moveFromCenterAnimator.updateDisplayProperties()
+ moveFromCenterAnimator?.updateDisplayProperties()
- viewsToAnimate.forEach {
- moveFromCenterAnimator.registerViewForAnimation(it)
- }
- }
+ viewsToAnimate.forEach {
+ moveFromCenterAnimator?.registerViewForAnimation(it)
+ }
- override fun onTransitionFinished() {
- moveFromCenterAnimator.onTransitionFinished()
- moveFromCenterAnimator.clearRegisteredViews()
- }
+ progressProvider.addCallback(transitionListener)
+ }
- override fun onTransitionProgress(progress: Float) {
- moveFromCenterAnimator.onTransitionProgress(progress)
- }
- })
+ fun onViewDetached() {
+ progressProvider.removeCallback(transitionListener)
+ moveFromCenterAnimator?.clearRegisteredViews()
+ moveFromCenterAnimator = null
}
fun onStatusBarWidthChanged() {
- moveFromCenterAnimator.updateViewPositions()
+ moveFromCenterAnimator?.updateDisplayProperties()
+ moveFromCenterAnimator?.updateViewPositions()
+ }
+
+ private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionProgress(progress: Float) {
+ moveFromCenterAnimator?.onTransitionProgress(progress)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 832f317..c655964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -25,7 +25,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -248,7 +247,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d3d9063..eb405e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -83,7 +83,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
initResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
index 0c5502b..26ba31c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
@@ -43,11 +43,6 @@
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index f3f3325..fdc0ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -226,6 +226,12 @@
return false
}
+ // If animations are disabled system-wide, don't play this one either.
+ if (Settings.Global.getString(
+ context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") {
+ return false
+ }
+
// We only play the unlocked screen off animation if we are... unlocked.
if (statusBarStateControllerImpl.state != StatusBarState.SHADE) {
return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 5689707..c452a48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -88,6 +88,7 @@
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -111,6 +112,7 @@
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
@@ -211,6 +213,7 @@
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
+ PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
DemoModeController demoModeController,
@@ -220,6 +223,7 @@
BrightnessSlider.Factory brightnessSliderFactory,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<NaturalRotationUnfoldProgressProvider> naturalRotationUnfoldProgressProvider,
Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation,
WallpaperController wallpaperController,
@@ -311,6 +315,7 @@
extensionController,
userInfoControllerImpl,
operatorNameViewControllerFactory,
+ phoneStatusBarViewControllerFactory,
phoneStatusBarPolicy,
keyguardIndicationController,
demoModeController,
@@ -321,7 +326,7 @@
unfoldTransitionConfig,
unfoldLightRevealOverlayAnimation,
unfoldTransitionWallpaperController,
- statusBarMoveFromCenterAnimation,
+ naturalRotationUnfoldProgressProvider,
wallpaperController,
ongoingCallController,
animationScheduler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 2791678..9de0c46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone.dagger;
import android.annotation.Nullable;
-import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,7 +32,6 @@
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
import com.android.systemui.statusbar.phone.TapAgainView;
-import com.android.systemui.util.InjectionInflationController;
import javax.inject.Named;
@@ -49,12 +47,9 @@
@Provides
@StatusBarComponent.StatusBarScope
public static NotificationShadeWindowView providesNotificationShadeWindowView(
- InjectionInflationController injectionInflationController,
- Context context) {
+ LayoutInflater layoutInflater) {
NotificationShadeWindowView notificationShadeWindowView = (NotificationShadeWindowView)
- injectionInflationController.injectable(
- LayoutInflater.from(context)).inflate(R.layout.super_notification_shade,
- /* root= */ null);
+ layoutInflater.inflate(R.layout.super_notification_shade, /* root= */ null);
if (notificationShadeWindowView == null) {
throw new IllegalStateException(
"R.layout.super_notification_shade could not be properly inflated");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index c3c935e..3806d9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -34,6 +34,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -61,8 +63,10 @@
private val dumpManager: DumpManager,
private val statusBarWindowController: Optional<StatusBarWindowController>,
private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>,
+ private val statusBarStateController: StatusBarStateController,
) : CallbackController<OngoingCallListener>, Dumpable {
+ private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
private var callNotificationInfo: CallNotificationInfo? = null
/** True if the application managing the call is visible to the user. */
@@ -124,6 +128,7 @@
dumpManager.registerDumpable(this)
if (featureFlags.isOngoingCallStatusBarChipEnabled) {
notifCollection.addCollectionListener(notifListener)
+ statusBarStateController.addCallback(statusBarStateListener)
}
}
@@ -177,10 +182,8 @@
val currentChipView = chipView
val timeView = currentChipView?.getTimeView()
- val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
- if (currentChipView != null && timeView != null && backgroundView != null) {
+ if (currentChipView != null && timeView != null) {
if (currentCallNotificationInfo.hasValidStartTime()) {
timeView.setShouldHideText(false)
timeView.base = currentCallNotificationInfo.callStartTime -
@@ -191,29 +194,15 @@
timeView.setShouldHideText(true)
timeView.stop()
}
+ updateChipClickListener()
- currentCallNotificationInfo.intent?.let { intent ->
- currentChipView.setOnClickListener {
- logger.logChipClicked()
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- ActivityLaunchAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
- )
- }
- }
setUpUidObserver(currentCallNotificationInfo)
if (!currentCallNotificationInfo.statusBarSwipedAway) {
statusBarWindowController.ifPresent {
it.setOngoingProcessRequiresStatusBarVisible(true)
}
- // TODO(b/195839150): Only listen for the gesture when in immersive mode.
- swipeStatusBarAwayGestureHandler.ifPresent {
- it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
- }
}
+ updateGestureListening()
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
} else {
// If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
@@ -227,6 +216,30 @@
}
}
+ private fun updateChipClickListener() {
+ if (callNotificationInfo == null) { return }
+ if (isFullscreen && !featureFlags.isOngoingCallInImmersiveChipTapEnabled) {
+ chipView?.setOnClickListener(null)
+ } else {
+ val currentChipView = chipView
+ val backgroundView =
+ currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ val intent = callNotificationInfo?.intent
+ if (currentChipView != null && backgroundView != null && intent != null) {
+ currentChipView.setOnClickListener {
+ logger.logChipClicked()
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ ActivityLaunchAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ )
+ }
+ }
+ }
+ }
+
/**
* Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
*/
@@ -277,6 +290,18 @@
return procState <= ActivityManager.PROCESS_STATE_TOP
}
+ private fun updateGestureListening() {
+ if (callNotificationInfo == null
+ || callNotificationInfo?.statusBarSwipedAway == true
+ || !isFullscreen) {
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
+ } else {
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
+ }
+ }
+ }
+
private fun removeChip() {
callNotificationInfo = null
tearDownChipView()
@@ -304,14 +329,22 @@
* This method updates the status bar window appropriately when the swipe away gesture is
* detected.
*/
- private fun onSwipeAwayGestureDetected() {
- if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
- callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
- statusBarWindowController.ifPresent {
- it.setOngoingProcessRequiresStatusBarVisible(false)
- }
- swipeStatusBarAwayGestureHandler.ifPresent {
- it.removeOnGestureDetectedCallback(TAG)
+ private fun onSwipeAwayGestureDetected() {
+ if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+ callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(false)
+ }
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onFullscreenStateChanged(isFullscreen: Boolean) {
+ this@OngoingCallController.isFullscreen = isFullscreen
+ updateChipClickListener()
+ updateGestureListening()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index 856853d..2dd4128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -14,7 +14,6 @@
package com.android.systemui.statusbar.policy;
-import android.content.Context;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
@@ -35,8 +34,8 @@
private final AccessibilityManager mAccessibilityManager;
@Inject
- public AccessibilityManagerWrapper(Context context) {
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ public AccessibilityManagerWrapper(AccessibilityManager accessibilityManager) {
+ mAccessibilityManager = accessibilityManager;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index e679c4c..6b80a9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -38,7 +38,6 @@
default void onDensityOrFontScaleChanged() {}
default void onSmallestScreenWidthChanged() {}
default void onMaxBoundsChanged() {}
- default void onOverlayChanged() {}
default void onUiModeChanged() {}
default void onThemeChanged() {}
default void onLocaleListChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index 7b4c35a..3944c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -14,23 +14,60 @@
package com.android.systemui.statusbar.policy;
+import android.provider.Settings;
+
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+/**
+ * Controller to cache in process the state of the device provisioning.
+ * <p>
+ * This controller keeps track of the values of device provisioning and user setup complete
+ */
public interface DeviceProvisionedController extends CallbackController<DeviceProvisionedListener> {
+ /**
+ * @return whether the device is provisioned
+ * @see Settings.Global#DEVICE_PROVISIONED
+ */
boolean isDeviceProvisioned();
- boolean isUserSetup(int currentUser);
+
+ /**
+ * @deprecated use {@link com.android.systemui.settings.UserTracker}
+ */
+ @Deprecated
int getCurrentUser();
- default boolean isCurrentUserSetup() {
- return isUserSetup(getCurrentUser());
- }
+ /**
+ * @param user the user to query
+ * @return whether that user has completed the user setup
+ * @see Settings.Secure#USER_SETUP_COMPLETE
+ */
+ boolean isUserSetup(int user);
+ /**
+ * @see DeviceProvisionedController#isUserSetup
+ */
+ boolean isCurrentUserSetup();
+
+ /**
+ * Interface to provide calls when the values tracked change
+ */
interface DeviceProvisionedListener {
+ /**
+ * Call when the device changes from not provisioned to provisioned
+ */
default void onDeviceProvisionedChanged() { }
+
+ /**
+ * Call on user switched
+ */
default void onUserSwitched() {
onUserSetupChanged();
}
+
+ /**
+ * Call when some user changes from not provisioned to provisioned
+ */
default void onUserSetupChanged() { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
deleted file mode 100644
index 485b1b1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.app.ActivityManager;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-/**
- */
-@SysUISingleton
-public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements
- DeviceProvisionedController {
-
- protected static final String TAG = DeviceProvisionedControllerImpl.class.getSimpleName();
- protected final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
- private final GlobalSettings mGlobalSettings;
- private final SecureSettings mSecureSettings;
- private final Uri mDeviceProvisionedUri;
- private final Uri mUserSetupUri;
- protected final ContentObserver mSettingsObserver;
-
- /**
- */
- @Inject
- public DeviceProvisionedControllerImpl(@Main Handler mainHandler,
- BroadcastDispatcher broadcastDispatcher, GlobalSettings globalSettings,
- SecureSettings secureSettings) {
- super(broadcastDispatcher);
- mGlobalSettings = globalSettings;
- mSecureSettings = secureSettings;
- mDeviceProvisionedUri = mGlobalSettings.getUriFor(Global.DEVICE_PROVISIONED);
- mUserSetupUri = mSecureSettings.getUriFor(Secure.USER_SETUP_COMPLETE);
- mSettingsObserver = new ContentObserver(mainHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri, int flags) {
- Log.d(TAG, "Setting change: " + uri);
- if (mUserSetupUri.equals(uri)) {
- notifySetupChanged();
- } else {
- notifyProvisionedChanged();
- }
- }
- };
- }
-
- @Override
- public boolean isDeviceProvisioned() {
- return mGlobalSettings.getInt(Global.DEVICE_PROVISIONED, 0) != 0;
- }
-
- @Override
- public boolean isUserSetup(int currentUser) {
- return mSecureSettings.getIntForUser(Secure.USER_SETUP_COMPLETE, 0, currentUser) != 0;
- }
-
- @Override
- public int getCurrentUser() {
- return ActivityManager.getCurrentUser();
- }
-
- @Override
- public void addCallback(@NonNull DeviceProvisionedListener listener) {
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- startListening(getCurrentUser());
- }
- listener.onUserSetupChanged();
- listener.onDeviceProvisionedChanged();
- }
-
- @Override
- public void removeCallback(@NonNull DeviceProvisionedListener listener) {
- mListeners.remove(listener);
- if (mListeners.size() == 0) {
- stopListening();
- }
- }
-
- protected void startListening(int user) {
- mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true,
- mSettingsObserver, 0);
- mSecureSettings.registerContentObserverForUser(mUserSetupUri, true,
- mSettingsObserver, user);
- startTracking();
- }
-
- protected void stopListening() {
- stopTracking();
- mGlobalSettings.unregisterContentObserver(mSettingsObserver);
- }
-
- @Override
- public void onUserSwitched(int newUserId) {
- mGlobalSettings.unregisterContentObserver(mSettingsObserver);
- mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true,
- mSettingsObserver, 0);
- mSecureSettings.registerContentObserverForUser(mUserSetupUri, true,
- mSettingsObserver, newUserId);
- notifyUserChanged();
- }
-
- private void notifyUserChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onUserSwitched();
- }
- }
-
- private void notifySetupChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onUserSetupChanged();
- }
- }
-
- private void notifyProvisionedChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onDeviceProvisionedChanged();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
new file mode 100644
index 0000000..acc1214
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import androidx.annotation.GuardedBy
+import androidx.annotation.WorkerThread
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+
+@SysUISingleton
+open class DeviceProvisionedControllerImpl @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val globalSettings: GlobalSettings,
+ private val userTracker: UserTracker,
+ private val dumpManager: DumpManager,
+ @Background private val backgroundHandler: Handler,
+ @Main private val mainExecutor: Executor
+) : DeviceProvisionedController,
+ DeviceProvisionedController.DeviceProvisionedListener,
+ Dumpable {
+
+ companion object {
+ private const val ALL_USERS = -1
+ private const val NO_USERS = -2
+ protected const val TAG = "DeviceProvisionedControllerImpl"
+ }
+
+ private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED)
+ private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
+
+ private val deviceProvisioned = AtomicBoolean(false)
+ @GuardedBy("lock")
+ private val userSetupComplete = SparseBooleanArray()
+ @GuardedBy("lock")
+ private val listeners = ArraySet<DeviceProvisionedController.DeviceProvisionedListener>()
+
+ private val lock = Any()
+
+ private val backgroundExecutor = HandlerExecutor(backgroundHandler)
+
+ private val initted = AtomicBoolean(false)
+
+ private val _currentUser: Int
+ get() = userTracker.userId
+
+ override fun getCurrentUser(): Int {
+ return _currentUser
+ }
+
+ private val observer = object : ContentObserver(backgroundHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: MutableCollection<Uri>,
+ flags: Int,
+ userId: Int
+ ) {
+ val updateDeviceProvisioned = deviceProvisionedUri in uris
+ val updateUser = if (userSetupUri in uris) userId else NO_USERS
+ updateValues(updateDeviceProvisioned, updateUser)
+ if (updateDeviceProvisioned) {
+ onDeviceProvisionedChanged()
+ }
+ if (updateUser != NO_USERS) {
+ onUserSetupChanged()
+ }
+ }
+ }
+
+ private val userChangedCallback = object : UserTracker.Callback {
+ @WorkerThread
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ updateValues(updateDeviceProvisioned = false, updateUser = newUser)
+ onUserSwitched()
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {}
+ }
+
+ init {
+ userSetupComplete.put(currentUser, false)
+ }
+
+ /**
+ * Call to initialize values and register observers
+ */
+ open fun init() {
+ if (!initted.compareAndSet(false, true)) {
+ return
+ }
+ dumpManager.registerDumpable(this)
+ updateValues()
+ userTracker.addCallback(userChangedCallback, backgroundExecutor)
+ globalSettings.registerContentObserver(deviceProvisionedUri, observer)
+ secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL)
+ }
+
+ @WorkerThread
+ private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) {
+ if (updateDeviceProvisioned) {
+ deviceProvisioned
+ .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
+ }
+ synchronized(lock) {
+ if (updateUser == ALL_USERS) {
+ val N = userSetupComplete.size()
+ for (i in 0 until N) {
+ val user = userSetupComplete.keyAt(i)
+ val value = secureSettings
+ .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+ userSetupComplete.put(user, value)
+ }
+ } else if (updateUser != NO_USERS) {
+ val value = secureSettings
+ .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, updateUser) != 0
+ userSetupComplete.put(updateUser, value)
+ }
+ }
+ }
+
+ /**
+ * Adds a listener.
+ *
+ * The listener will not be called when this happens.
+ */
+ override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
+ synchronized(lock) {
+ listeners.add(listener)
+ }
+ }
+
+ override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
+ synchronized(lock) {
+ listeners.remove(listener)
+ }
+ }
+
+ override fun isDeviceProvisioned(): Boolean {
+ return deviceProvisioned.get()
+ }
+
+ override fun isUserSetup(user: Int): Boolean {
+ val index = synchronized(lock) {
+ userSetupComplete.indexOfKey(user)
+ }
+ return if (index < 0) {
+ val value = secureSettings
+ .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+ synchronized(lock) {
+ userSetupComplete.put(user, value)
+ }
+ value
+ } else {
+ synchronized(lock) {
+ userSetupComplete.get(user, false)
+ }
+ }
+ }
+
+ override fun isCurrentUserSetup(): Boolean {
+ return isUserSetup(currentUser)
+ }
+
+ override fun onDeviceProvisionedChanged() {
+ dispatchChange(
+ DeviceProvisionedController.DeviceProvisionedListener::onDeviceProvisionedChanged
+ )
+ }
+
+ override fun onUserSetupChanged() {
+ dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSetupChanged)
+ }
+
+ override fun onUserSwitched() {
+ dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSwitched)
+ }
+
+ protected fun dispatchChange(
+ callback: DeviceProvisionedController.DeviceProvisionedListener.() -> Unit
+ ) {
+ val listenersCopy = synchronized(lock) {
+ ArrayList(listeners)
+ }
+ mainExecutor.execute {
+ listenersCopy.forEach(callback)
+ }
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("Device provisioned: ${deviceProvisioned.get()}")
+ synchronized(lock) {
+ pw.println("User setup complete: $userSetupComplete")
+ pw.println("Listeners: $listeners")
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a8097c4..e0b0dd36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -38,11 +38,10 @@
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.util.ListenerSet;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
/**
* A manager which handles heads up notifications which is a special mode where
@@ -52,7 +51,7 @@
private static final String TAG = "HeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
- protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
protected final Context mContext;
@@ -118,7 +117,7 @@
* Adds an OnHeadUpChangedListener to observe events.
*/
public void addListener(@NonNull OnHeadsUpChangedListener listener) {
- mListeners.add(listener);
+ mListeners.addIfAbsent(listener);
}
/**
@@ -158,7 +157,7 @@
NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
}
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
if (isPinned) {
listener.onHeadsUpPinned(entry);
} else {
@@ -178,7 +177,7 @@
entry.setHeadsUp(true);
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
}
}
@@ -189,7 +188,7 @@
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
}
@@ -207,7 +206,7 @@
if (mHasPinnedNotification) {
MetricsLogger.count(mContext, "note_peek", 1);
}
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index dadc016..b630689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -59,6 +59,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -128,6 +129,7 @@
private final TelephonyListenerManager mTelephonyListenerManager;
private final IActivityTaskManager mActivityTaskManager;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private final LatencyTracker mLatencyTracker;
private ArrayList<UserRecord> mUsers = new ArrayList<>();
@VisibleForTesting
@@ -174,6 +176,7 @@
SecureSettings secureSettings,
@Background Executor bgExecutor,
InteractionJankMonitor interactionJankMonitor,
+ LatencyTracker latencyTracker,
DumpManager dumpManager) {
mContext = context;
mActivityManager = activityManager;
@@ -184,6 +187,7 @@
mUiEventLogger = uiEventLogger;
mFalsingManager = falsingManager;
mInteractionJankMonitor = interactionJankMonitor;
+ mLatencyTracker = latencyTracker;
mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
this, mUserTracker, mUiEventLogger, secureSettings);
mUserDetailAdapter = userDetailAdapter;
@@ -499,6 +503,7 @@
mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
.withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView)
.setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
pauseRefreshUsers();
mActivityManager.switchUser(id);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 8912448..923aff1 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -180,9 +180,13 @@
return new Recents(context, recentsImplementation, commandQueue);
}
- @Binds
- abstract DeviceProvisionedController bindDeviceProvisionedController(
- DeviceProvisionedControllerImpl deviceProvisionedController);
+ @SysUISingleton
+ @Provides
+ static DeviceProvisionedController providesDeviceProvisionedController(
+ DeviceProvisionedControllerImpl deviceProvisionedController) {
+ deviceProvisionedController.init();
+ return deviceProvisionedController;
+ }
@Binds
abstract KeyguardViewController bindKeyguardViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 3c3cc64..f0760d4 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -19,13 +19,27 @@
import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.os.Trace
+import android.view.Choreographer
+import android.view.Display
+import android.view.DisplayInfo
import android.view.Surface
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.SurfaceSession
import android.view.WindowManager
+import android.view.WindowlessWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper
+import java.util.Optional
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
@@ -34,53 +48,148 @@
class UnfoldLightRevealOverlayAnimation @Inject constructor(
private val context: Context,
private val deviceStateManager: DeviceStateManager,
+ private val displayManager: DisplayManager,
private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
- private val windowManager: WindowManager
+ @Main private val handler: Handler,
+ @UiBackground private val backgroundExecutor: Executor
) {
private val transitionListener = TransitionListener()
+ private val displayListener = DisplayChangeListener()
+
+ private lateinit var wwm: WindowlessWindowManager
+ private lateinit var unfoldedDisplayInfo: DisplayInfo
+ private lateinit var overlayContainer: SurfaceControl
+
+ private var root: SurfaceControlViewHost? = null
private var scrimView: LightRevealScrim? = null
+ private var isFolded: Boolean = false
+ private var isUnfoldHandled: Boolean = true
+
+ private var currentRotation: Int = context.display!!.rotation
fun init() {
deviceStateManager.registerCallback(executor, FoldListener())
unfoldTransitionProgressProvider.addCallback(transitionListener)
- }
- private inner class TransitionListener : TransitionProgressListener {
+ val containerBuilder = SurfaceControl.Builder(SurfaceSession())
+ .setContainerLayer()
+ .setName("unfold-overlay-container")
- override fun onTransitionProgress(progress: Float) {
- scrimView?.revealAmount = progress
- }
+ displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY,
+ containerBuilder) { builder ->
+ executor.execute {
+ overlayContainer = builder.build()
- override fun onTransitionFinished() {
- removeOverlayView()
- }
+ SurfaceControl.Transaction()
+ .setLayer(overlayContainer, Integer.MAX_VALUE)
+ .show(overlayContainer)
+ .apply()
- override fun onTransitionStarted() {
- // When unfolding the view is added earlier, add view for folding case
- if (scrimView == null) {
- addOverlayView()
+ wwm = WindowlessWindowManager(context.resources.configuration,
+ overlayContainer, null)
}
}
+
+ displayManager.registerDisplayListener(displayListener, handler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+
+ // Get unfolded display size immediately as 'current display info' might be
+ // not up-to-date during unfolding
+ unfoldedDisplayInfo = getUnfoldedDisplayInfo()
}
- private inner class FoldListener : FoldStateListener(context, Consumer { isFolded ->
- if (isFolded) {
- removeOverlayView()
- } else {
- // Add overlay view before starting the transition as soon as we unfolded the device
- addOverlayView()
+ /**
+ * Called when screen starts turning on, the contents of the screen might not be visible yet.
+ * This method reports back that the overlay is ready in [onOverlayReady] callback.
+ *
+ * @param onOverlayReady callback when the overlay is drawn and visible on the screen
+ * @see [com.android.systemui.keyguard.KeyguardViewMediator]
+ */
+ fun onScreenTurningOn(onOverlayReady: Runnable) {
+ Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
+ try {
+ // Add the view only if we are unfolding and this is the first screen on
+ if (!isFolded && !isUnfoldHandled) {
+ addView(onOverlayReady)
+ isUnfoldHandled = true
+ } else {
+ // No unfold transition, immediately report that overlay is ready
+ ensureOverlayRemoved()
+ onOverlayReady.run()
+ }
+ } finally {
+ Trace.endSection()
}
- })
+ }
- private fun addOverlayView() {
+ private fun addView(onOverlayReady: Runnable? = null) {
+ if (!::wwm.isInitialized) {
+ // Surface overlay is not created yet on the first SysUI launch
+ onOverlayReady?.run()
+ return
+ }
+
+ ensureOverlayRemoved()
+
+ val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
+ val newView = LightRevealScrim(context, null)
+ .apply {
+ revealEffect = createLightRevealEffect()
+ isScrimOpaqueChangedListener = Consumer {}
+ revealAmount = 0f
+ }
+
+ val params = getLayoutParams()
+ newRoot.setView(newView, params)
+
+ onOverlayReady?.let { callback ->
+ Trace.beginAsyncSection(
+ "UnfoldLightRevealOverlayAnimation#relayout", 0)
+
+ newRoot.relayout(params) { transaction ->
+ val vsyncId = Choreographer.getSfInstance().vsyncId
+
+ backgroundExecutor.execute {
+ // Apply the transaction that contains the first frame of the overlay
+ // synchronously and apply another empty transaction with
+ // 'vsyncId + 1' to make sure that it is actually displayed on
+ // the screen. The second transaction is necessary to remove the screen blocker
+ // (turn on the brightness) only when the content is actually visible as it
+ // might be presented only in the next frame.
+ // See b/197538198
+ transaction.setFrameTimelineVsync(vsyncId)
+ .apply(/* sync */true)
+
+ transaction
+ .setFrameTimelineVsync(vsyncId + 1)
+ .apply(/* sync */ true)
+
+ Trace.endAsyncSection(
+ "UnfoldLightRevealOverlayAnimation#relayout", 0)
+ callback.run()
+ }
+ }
+ }
+
+ scrimView = newView
+ root = newRoot
+ }
+
+ private fun getLayoutParams(): WindowManager.LayoutParams {
val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
- params.height = WindowManager.LayoutParams.MATCH_PARENT
- params.width = WindowManager.LayoutParams.MATCH_PARENT
- params.format = PixelFormat.TRANSLUCENT
- // TODO(b/193801466): create a separate type for this overlay
+ val rotation = context.display!!.rotation
+ val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+ params.height = if (isNatural)
+ unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
+ params.width = if (isNatural)
+ unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
+
+ params.format = PixelFormat.TRANSLUCENT
params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
params.title = "Unfold Light Reveal Animation"
params.layoutInDisplayCutoutMode =
@@ -90,41 +199,72 @@
or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
params.setTrustedOverlay()
- val rotation = windowManager.defaultDisplay.rotation
- val isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
- val newScrimView = LightRevealScrim(context, null)
- .apply {
- revealEffect = LinearLightRevealEffect(isVerticalFold)
- isScrimOpaqueChangedListener = Consumer {}
- revealAmount = 0f
- }
-
- val packageName: String = newScrimView.context.opPackageName
+ val packageName: String = context.opPackageName
params.packageName = packageName
- params.hideTimeoutMilliseconds = OVERLAY_HIDE_TIMEOUT_MILLIS
- if (scrimView?.parent != null) {
- windowManager.removeView(scrimView)
- }
-
- this.scrimView = newScrimView
-
- try {
- windowManager.addView(scrimView, params)
- } catch (e: WindowManager.BadTokenException) {
- e.printStackTrace()
- }
+ return params
}
- private fun removeOverlayView() {
- scrimView?.let {
- if (it.parent != null) {
- windowManager.removeViewImmediate(it)
+ private fun createLightRevealEffect(): LightRevealEffect {
+ val isVerticalFold = currentRotation == Surface.ROTATION_0 ||
+ currentRotation == Surface.ROTATION_180
+ return LinearLightRevealEffect(isVertical = isVerticalFold)
+ }
+
+ private fun ensureOverlayRemoved() {
+ root?.release()
+ root = null
+ scrimView = null
+ }
+
+ private fun getUnfoldedDisplayInfo(): DisplayInfo =
+ displayManager.displays
+ .asSequence()
+ .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
+ .filter { it.type == Display.TYPE_INTERNAL }
+ .maxByOrNull { it.naturalWidth }!!
+
+ private inner class TransitionListener : TransitionProgressListener {
+
+ override fun onTransitionProgress(progress: Float) {
+ scrimView?.revealAmount = progress
+ }
+
+ override fun onTransitionFinished() {
+ ensureOverlayRemoved()
+ }
+
+ override fun onTransitionStarted() {
+ // Add view for folding case (when unfolding the view is added earlier)
+ if (scrimView == null) {
+ addView()
}
- scrimView = null
}
}
-}
-private const val OVERLAY_HIDE_TIMEOUT_MILLIS = 10_000L
+ private inner class DisplayChangeListener : DisplayManager.DisplayListener {
+
+ override fun onDisplayChanged(displayId: Int) {
+ val newRotation: Int = context.display!!.rotation
+ if (currentRotation != newRotation) {
+ currentRotation = newRotation
+ scrimView?.revealEffect = createLightRevealEffect()
+ root?.relayout(getLayoutParams())
+ }
+ }
+
+ override fun onDisplayAdded(displayId: Int) {
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {
+ }
+ }
+
+ private inner class FoldListener : FoldStateListener(context, Consumer { isFolded ->
+ if (isFolded) {
+ ensureOverlayRemoved()
+ isUnfoldHandled = false
+ }
+ this.isFolded = isFolded
+ })
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index b23aa20..7e4ec67 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -20,15 +20,19 @@
import android.hardware.SensorManager
import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
+import android.view.IWindowManager
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
import dagger.Lazy
import dagger.Module
import dagger.Provides
import java.util.Optional
import java.util.concurrent.Executor
+import javax.inject.Named
import javax.inject.Singleton
@Module
@@ -62,6 +66,27 @@
@Provides
@Singleton
+ fun provideNaturalRotationProgressProvider(
+ context: Context,
+ windowManager: IWindowManager,
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
+ ): NaturalRotationUnfoldProgressProvider =
+ NaturalRotationUnfoldProgressProvider(
+ context,
+ windowManager,
+ unfoldTransitionProgressProvider
+ )
+
+ @Provides
+ @Named(UNFOLD_STATUS_BAR)
+ @Singleton
+ fun provideStatusBarScopedTransitionProvider(
+ source: NaturalRotationUnfoldProgressProvider
+ ): ScopedUnfoldTransitionProgressProvider =
+ ScopedUnfoldTransitionProgressProvider(source)
+
+ @Provides
+ @Singleton
fun provideShellProgressProvider(
config: UnfoldTransitionConfig,
provider: Lazy<UnfoldTransitionProgressProvider>
@@ -72,3 +97,5 @@
Optional.empty()
}
}
+
+const val UNFOLD_STATUS_BAR = "unfold_status_bar"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
index 8dd3d6b..4f45aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
@@ -33,7 +33,14 @@
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- wallpaperController.setUnfoldTransitionZoom(progress)
+ // Fully zoomed in when fully unfolded
+ wallpaperController.setUnfoldTransitionZoom(1 - progress)
+ }
+
+ override fun onTransitionFinished() {
+ // Resets wallpaper zoom-out to 0f when fully folded
+ // When fully unfolded it is set to 0f by onTransitionProgress
+ wallpaperController.setUnfoldTransitionZoom(0f)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index da4ccd0..c420a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -112,6 +112,7 @@
mOtherProfileIntent.putParcelableArrayListExtra(EXTRA_RESOLVE_INFOS,
rListOtherProfile);
} else {
+ mOtherProfileIntent = new Intent();
mOtherProfileIntent.setComponent(ComponentName.unflattenFromString(
this.getResources().getString(
com.android.internal.R.string.config_usbConfirmActivity)));
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
deleted file mode 100644
index cdc5d87..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 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.util;
-
-import android.content.Context;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Manages inflation that requires dagger injection.
- * See docs/dagger.md for details.
- */
-@SysUISingleton
-public class InjectionInflationController {
-
- public static final String VIEW_CONTEXT = "view_context";
- private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>();
- private final LayoutInflater.Factory2 mFactory = new InjectionFactory();
- private final ViewInstanceCreator.Factory mViewInstanceCreatorFactory;
-
- @Inject
- public InjectionInflationController(ViewInstanceCreator.Factory viewInstanceCreatorFactory) {
- mViewInstanceCreatorFactory = viewInstanceCreatorFactory;
- initInjectionMap();
- }
-
- /**
- * Wraps a {@link LayoutInflater} to support creating dagger injected views.
- * See docs/dagger.md for details.
- */
- public LayoutInflater injectable(LayoutInflater inflater) {
- LayoutInflater ret = inflater.cloneInContext(inflater.getContext());
- ret.setPrivateFactory(mFactory);
- return ret;
- }
-
- private void initInjectionMap() {
- for (Method method : ViewInstanceCreator.class.getDeclaredMethods()) {
- if (View.class.isAssignableFrom(method.getReturnType())
- && (method.getModifiers() & Modifier.PUBLIC) != 0) {
- mInjectionMap.put(method.getReturnType().getName(), method);
- }
- }
- }
-
- /**
- * Subcomponent that actually creates injected views.
- */
- @Subcomponent
- public interface ViewInstanceCreator {
-
- /** Factory for creating a ViewInstanceCreator. */
- @Subcomponent.Factory
- interface Factory {
- ViewInstanceCreator build(
- @BindsInstance @Named(VIEW_CONTEXT) Context context,
- @BindsInstance AttributeSet attributeSet);
- }
-
- /**
- * Creates the NotificationStackScrollLayout.
- */
- NotificationStackScrollLayout createNotificationStackScrollLayout();
- }
-
-
- private class InjectionFactory implements LayoutInflater.Factory2 {
-
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
- Method creationMethod = mInjectionMap.get(name);
- if (creationMethod != null) {
- try {
- return (View) creationMethod.invoke(
- mViewInstanceCreatorFactory.build(context, attrs));
- } catch (IllegalAccessException e) {
- throw new InflateException("Could not inflate " + name, e);
- } catch (InvocationTargetException e) {
- throw new InflateException("Could not inflate " + name, e);
- }
- }
- return null;
- }
-
- @Override
- public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- return onCreateView(name, context, attrs);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
new file mode 100644
index 0000000..0f4193e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well.
+ */
+class ListenerSet<E> : Iterable<E> {
+ private val listeners: CopyOnWriteArrayList<E> = CopyOnWriteArrayList()
+
+ /**
+ * A thread-safe, reentrant-safe method to add a listener.
+ * Does nothing if the listener is already in the set.
+ */
+ fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
+
+ /**
+ * A thread-safe, reentrant-safe method to remove a listener.
+ */
+ fun remove(element: E): Boolean = listeners.remove(element)
+
+ /**
+ * Returns an iterator over the listeners currently in the set. Note that to ensure
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes
+ * made to the set after the iterator is constructed.
+ */
+ override fun iterator(): Iterator<E> = listeners.iterator()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
index 0be6068c..96980b8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -177,9 +178,7 @@
// length and index of sensorMap correspond to DevicePostureController.DevicePostureInt:
final ThresholdSensor[] sensorMap =
new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
- for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) {
- sensorMap[i] = noProxSensor;
- }
+ Arrays.fill(sensorMap, noProxSensor);
if (!hasPostureSupport(sensorTypes)) {
Log.e("SensorModule", "config doesn't support postures,"
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index d3581a9..291c64d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -377,10 +377,24 @@
sysuiMainExecutor.execute(() -> {
sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
.commitUpdate(mContext.getDisplayId());
+ if (!shouldExpand) {
+ sysUiState.setFlag(
+ QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ false).commitUpdate(mContext.getDisplayId());
+ }
});
}
@Override
+ public void onManageMenuExpandChanged(boolean menuExpanded) {
+ sysuiMainExecutor.execute(() -> {
+ sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ menuExpanded).commitUpdate(mContext.getDisplayId());
+ });
+ }
+
+
+ @Override
public void onUnbubbleConversation(String key) {
sysuiMainExecutor.execute(() -> {
final NotificationEntry entry =
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index db965db..74611cc 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,6 +20,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
@@ -101,6 +102,7 @@
| SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_BUBBLES_EXPANDED
+ | SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
// Shell interfaces
@@ -212,7 +214,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
pip.onOverlayChanged();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ff1929c..fd9783a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -29,6 +29,7 @@
import com.android.launcher3.icons.IconProvider;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellCommandHandlerImpl;
@@ -55,6 +56,8 @@
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
@@ -349,13 +352,21 @@
return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
}
- @WMSingleton
@Provides
static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
}
+ @WMSingleton
+ @Provides
+ static Optional<DisplayAreaHelper> provideDisplayAreaHelper(
+ @ShellMainThread ShellExecutor mainExecutor,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ return Optional.ofNullable(new DisplayAreaHelperController(mainExecutor,
+ rootDisplayAreaOrganizer));
+ }
+
//
// Pip (optional feature)
//
@@ -431,6 +442,13 @@
@WMSingleton
@Provides
+ static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new RootDisplayAreaOrganizer(mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<SplitScreen> provideSplitScreen(
Optional<SplitScreenController> splitScreenController) {
return splitScreenController.map((controller) -> controller.asSplitScreen());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 5c7885f..5e0f427 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,6 +29,7 @@
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
@@ -108,7 +108,7 @@
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
- private View mStatusArea;
+ private View mSliceView;
@Before
public void setup() {
@@ -149,8 +149,10 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
- mStatusArea = new View(getContext());
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+ mSliceView = new View(getContext());
+ when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
+ new LinearLayout(getContext()));
}
@Test
@@ -192,7 +194,7 @@
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
-
+ verify(mView).onViewDetached();
verify(mColorExtractor).removeOnColorsChangedListener(
any(ColorExtractor.OnColorsChangedListener.class));
}
@@ -215,7 +217,7 @@
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
- assertEquals(View.GONE, mStatusArea.getVisibility());
+ assertEquals(View.GONE, mSliceView.getVisibility());
}
@Test
@@ -223,22 +225,7 @@
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
- assertEquals(View.VISIBLE, mStatusArea.getVisibility());
- }
-
- @Test
- public void testDetachDisconnectsSmartspace() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
- mController.init();
- verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
-
- ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
-
- listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mSmartspaceController).disconnect();
+ assertEquals(View.VISIBLE, mSliceView.getVisibility());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 1ab08c2..77302ce 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -54,7 +54,7 @@
MockitoAnnotations.initMocks(this);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardSliceView = (KeyguardSliceView) layoutInflater
- .inflate(R.layout.keyguard_status_area, null);
+ .inflate(R.layout.keyguard_slice_view, null);
mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0772b20..e53b450 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -64,7 +64,6 @@
import android.os.IRemoteCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -80,6 +79,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
@@ -174,7 +174,7 @@
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Mock
- private Vibrator mVibrator;
+ private LatencyTracker mLatencyTracker;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
// Direct executor
@@ -242,8 +242,6 @@
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
- when(mFeatureFlags.isKeyguardLayoutEnabled()).thenReturn(false);
-
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class).startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
@@ -746,7 +744,8 @@
public void sendResult(Bundle data) {} // do nothing
};
mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */);
- verify(mInteractionJankMonitor).end(eq(InteractionJankMonitor.CUJ_USER_SWITCH));
+ verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH);
+ verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
}
@Test
@@ -1064,7 +1063,7 @@
mRingerModeTracker, mBackgroundExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager, mFeatureFlags,
- mInteractionJankMonitor, mVibrator);
+ mInteractionJankMonitor, mLatencyTracker);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index 35fe1ba..ff4412e9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,7 +17,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -40,7 +39,6 @@
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.settings.CurrentUserObservable;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.After;
import org.junit.Before;
@@ -69,7 +67,6 @@
private ContentObserver mContentObserver;
private DockManagerFake mFakeDockManager;
private MutableLiveData<Integer> mCurrentUser;
- @Mock InjectionInflationController mMockInjectionInflationController;
@Mock PluginManager mMockPluginManager;
@Mock SysuiColorExtractor mMockColorExtractor;
@Mock ContentResolver mMockContentResolver;
@@ -83,7 +80,6 @@
MockitoAnnotations.initMocks(this);
LayoutInflater inflater = LayoutInflater.from(getContext());
- when(mMockInjectionInflationController.injectable(any())).thenReturn(inflater);
mFakeDockManager = new DockManagerFake();
@@ -91,7 +87,7 @@
mCurrentUser.setValue(MAIN_USER_ID);
when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
- mClockManager = new ClockManager(getContext(), mMockInjectionInflationController,
+ mClockManager = new ClockManager(getContext(), inflater,
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index c4f480d..55ee433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -91,7 +91,7 @@
@Test
public void testA11yDisablesGesture() {
assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
- when(mAccessibilityManager.isEnabled()).thenReturn(true);
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
@@ -99,7 +99,7 @@
@Test
public void testA11yDisablesTap() {
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
- when(mAccessibilityManager.isEnabled()).thenReturn(true);
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
}
@@ -107,7 +107,7 @@
@Test
public void testA11yDisablesDoubleTap() {
assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue();
- when(mAccessibilityManager.isEnabled()).thenReturn(true);
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java
index 7f85c35..42abff0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java
@@ -48,13 +48,13 @@
@Test
public void testDefaultCommunalViewShowingState() {
// The state controller should report the communal view as not showing by default.
- final CommunalStateController stateController = new CommunalStateController();
+ final CommunalStateController stateController = new CommunalStateController(getContext());
assertThat(stateController.getCommunalViewShowing()).isFalse();
}
@Test
public void testNotifyCommunalSurfaceShow() {
- final CommunalStateController stateController = new CommunalStateController();
+ final CommunalStateController stateController = new CommunalStateController(getContext());
stateController.addCallback(mCallback);
// Verify setting communal view to showing propagates to callback.
@@ -72,7 +72,7 @@
@Test
public void testCallbackRegistration() {
- final CommunalStateController stateController = new CommunalStateController();
+ final CommunalStateController stateController = new CommunalStateController(getContext());
stateController.addCallback(mCallback);
// Verify setting communal view to showing propagates to callback.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 866791c..364b5d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -67,7 +67,8 @@
when(config.tapGestureEnabled(anyInt())).thenReturn(true);
when(config.tapSensorAvailable()).thenReturn(true);
- when(config.tapSensorType(anyInt())).thenReturn(FakeSensorManager.TAP_SENSOR_TYPE);
+ when(config.tapSensorTypeMapping()).thenReturn(
+ new String[]{FakeSensorManager.TAP_SENSOR_TYPE});
when(config.dozePickupSensorAvailable()).thenReturn(false);
when(config.wakeScreenGestureAvailable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index e0520b4..886f84e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -60,6 +61,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -76,6 +78,7 @@
private DozeServiceFake mServiceFake;
private FakeSensorManager.FakeGenericSensor mSensor;
+ private FakeSensorManager.FakeGenericSensor mSensorInner;
private AsyncSensorManager mSensorManager;
private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
@Mock
@@ -86,6 +89,10 @@
DozeParameters mDozeParameters;
@Mock
DockManager mDockManager;
+ @Mock
+ DevicePostureController mDevicePostureController;
+ @Mock
+ DozeLog mDozeLog;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
@@ -111,9 +118,19 @@
mAlwaysOnDisplayPolicy.dimBrightness = DIM_BRIGHTNESS;
mAlwaysOnDisplayPolicy.dimmingScrimArray = SENSOR_TO_OPACITY;
mSensor = fakeSensorManager.getFakeLightSensor();
- mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
+ mSensorInner = fakeSensorManager.getFakeLightSensor2();
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{Optional.of(mSensor.getSensor())},
+ mDozeHost,
+ null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
}
@Test
@@ -151,7 +168,7 @@
@Test
public void doze_doesNotUseLightSensor() {
- // GIVEN the device is docked and the display state changes to ON
+ // GIVEN the device is DOZE and the display state changes to ON
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
waitForSensorManager();
@@ -166,7 +183,7 @@
@Test
public void aod_usesLightSensor() {
- // GIVEN the device is docked and the display state changes to ON
+ // GIVEN the device is DOZE_AOD and the display state changes to ON
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
waitForSensorManager();
@@ -209,9 +226,17 @@
@Test
public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
- mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[] {Optional.empty()} /* sensor */,
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
reset(mDozeHost);
@@ -238,9 +263,17 @@
@Test
public void testNullSensor() throws Exception {
- mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{Optional.empty()} /* sensor */,
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -249,6 +282,130 @@
}
@Test
+ public void testSensorsSupportPostures_closed() throws Exception {
+ // GIVEN the device is CLOSED
+ when(mDevicePostureController.getDevicePosture()).thenReturn(
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+ // GIVEN closed and opened postures use different light sensors
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{
+ Optional.empty() /* unknown */,
+ Optional.of(mSensor.getSensor()) /* closed */,
+ Optional.of(mSensorInner.getSensor()) /* half-opened */,
+ Optional.of(mSensorInner.getSensor()) /* opened */,
+ Optional.empty() /* flipped */
+ },
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
+
+ // GIVEN the device is in AOD
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new different events are sent from the inner and outer sensors
+ mSensor.sendSensorEvent(3); // CLOSED sensor
+ mSensorInner.sendSensorEvent(4); // OPENED sensor
+
+ // THEN brightness is updated according to the sensor for CLOSED
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void testSensorsSupportPostures_open() throws Exception {
+ // GIVEN the device is OPENED
+ when(mDevicePostureController.getDevicePosture()).thenReturn(
+ DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // GIVEN closed and opened postures use different light sensors
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{
+ Optional.empty() /* unknown */,
+ Optional.of(mSensor.getSensor()) /* closed */,
+ Optional.of(mSensorInner.getSensor()) /* half-opened */,
+ Optional.of(mSensorInner.getSensor()) /* opened */,
+ Optional.empty() /* flipped */
+ },
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
+
+ // GIVEN device is in AOD
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new different events are sent from the inner and outer sensors
+ mSensorInner.sendSensorEvent(4); // OPENED sensor
+ mSensor.sendSensorEvent(3); // CLOSED sensor
+
+ // THEN brightness is updated according to the sensor for OPENED
+ assertEquals(4, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void testSensorsSupportPostures_swapPostures() throws Exception {
+ ArgumentCaptor<DevicePostureController.Callback> postureCallbackCaptor =
+ ArgumentCaptor.forClass(DevicePostureController.Callback.class);
+ reset(mDevicePostureController);
+
+ // GIVEN the device starts up AOD OPENED
+ when(mDevicePostureController.getDevicePosture()).thenReturn(
+ DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // GIVEN closed and opened postures use different light sensors
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{
+ Optional.empty() /* unknown */,
+ Optional.of(mSensor.getSensor()) /* closed */,
+ Optional.of(mSensorInner.getSensor()) /* half-opened */,
+ Optional.of(mSensorInner.getSensor()) /* opened */,
+ Optional.empty() /* flipped */
+ },
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
+ verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture());
+
+ // GIVEN device is in AOD
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN the posture changes to CLOSED
+ postureCallbackCaptor.getValue().onPostureChanged(
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+ waitForSensorManager();
+
+ // WHEN new different events are sent from the inner and outer sensors
+ mSensor.sendSensorEvent(3); // CLOSED sensor
+ mSensorInner.sendSensorEvent(4); // OPENED sensor
+
+ // THEN brightness is updated according to the sensor for CLOSED
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void testNoBrightnessDeliveredAfterFinish() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 42e34c8..f525fee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -91,9 +91,9 @@
@Mock
private AuthController mAuthController;
@Mock
+ private DevicePostureController mDevicePostureController;
+ @Mock
private ProximitySensor mProximitySensor;
- private @DevicePostureController.DevicePostureInt int mDevicePosture =
- DevicePostureController.DEVICE_POSTURE_UNKNOWN;
private FakeSettings mFakeSettings = new FakeSettings();
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
@@ -104,13 +104,14 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
+ when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
+ .thenReturn(new String[]{"tapSEnsor"});
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mWakeLock).wrap(any(Runnable.class));
- mDevicePosture = DevicePostureController.DEVICE_POSTURE_UNKNOWN;
mDozeSensors = new TestableDozeSensors();
}
@@ -127,14 +128,14 @@
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
- verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
+ verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH),
anyFloat(), anyFloat(), eq(null));
mDozeSensors.requestTemporaryDisable();
reset(mCallback);
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
- verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
+ verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH),
anyFloat(), anyFloat(), eq(null));
}
@@ -269,15 +270,80 @@
}
@Test
- public void testPostureOpen_registersCorrectTapGesture() {
- // GIVEN device posture open
- mDevicePosture = DevicePostureController.DEVICE_POSTURE_OPENED;
+ public void testPostureStartStateClosed_registersCorrectSensor() throws Exception {
+ // GIVEN doze sensor that supports postures
+ Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ new Sensor[] {
+ null /* unknown */,
+ closedSensor,
+ null /* half-opened */,
+ openedSensor},
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
- // WHEN DozeSensors are initialized
- new TestableDozeSensors();
+ // WHEN trigger sensor requests listening
+ triggerSensor.setListening(true);
- // THEN we use the posture to determine which tap sensor to use
- verify(mAmbientDisplayConfiguration).tapSensorType(eq(mDevicePosture));
+ // THEN the correct sensor is registered
+ verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(closedSensor));
+ verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(openedSensor));
+ }
+
+ @Test
+ public void testPostureChange_registersCorrectSensor() throws Exception {
+ // GIVEN doze sensor that supports postures
+ Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ new Sensor[] {
+ null /* unknown */,
+ closedSensor,
+ null /* half-opened */,
+ openedSensor},
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+ // GIVEN sensor is listening
+ when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true);
+ triggerSensor.setListening(true);
+ reset(mSensorManager);
+ assertTrue(triggerSensor.mRegistered);
+
+ // WHEN posture changes
+ boolean sensorChanged =
+ triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // THEN the correct sensor is registered
+ assertTrue(sensorChanged);
+ verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(openedSensor));
+ verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(closedSensor));
+ }
+
+ @Test
+ public void testPostureChange_noSensorChange() throws Exception {
+ // GIVEN doze sensor that supports postures
+ Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ new Sensor[] {
+ null /* unknown */,
+ closedSensor,
+ openedSensor /* half-opened uses the same sensor as opened*/,
+ openedSensor},
+ DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+
+ // GIVEN sensor is listening
+ when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true);
+ triggerSensor.setListening(true);
+ reset(mSensorManager);
+
+ // WHEN posture changes
+ boolean sensorChanged =
+ triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // THEN no change in sensor
+ assertFalse(sensorChanged);
+ verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), any());
}
@Test
@@ -311,13 +377,12 @@
private class TestableDozeSensors extends DozeSensors {
-
TestableDozeSensors() {
super(getContext(), mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePosture);
- for (TriggerSensor sensor : mSensors) {
+ mDevicePostureController);
+ for (TriggerSensor sensor : mTriggerSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
== TYPE_WAKE_LOCK_SCREEN) {
@@ -326,7 +391,7 @@
mSensorTap = sensor;
}
}
- mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
+ mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
}
public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
@@ -337,8 +402,25 @@
/* configured */ true,
/* pulseReason*/ 0,
/* reportsTouchCoordinate*/ false,
- requiresTouchScreen,
- mDozeLog);
+ /* requiresTouchscreen */ false,
+ /* ignoresSetting */ false,
+ requiresTouchScreen);
+ }
+
+ /**
+ * create a doze sensor that supports postures and is enabled
+ */
+ public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) {
+ return new TriggerSensor(/* sensor */ sensors,
+ /* setting name */ "test_setting",
+ /* settingDefault */ true,
+ /* configured */ true,
+ /* pulseReason*/ 0,
+ /* reportsTouchCoordinate*/ false,
+ /* requiresTouchscreen */ false,
+ /* ignoresSetting */ true,
+ /* requiresProx */false,
+ posture);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 31fa3f8..35dca7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -204,7 +204,7 @@
public void testProximitySensorNotAvailablel() {
mProximitySensor.setSensorAvailable(false);
mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
- mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 100, 100,
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH, 100, 100,
new float[]{1});
mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
new file mode 100644
index 0000000..172dcda
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagManagerTest extends SysuiTestCase {
+ FeatureFlagManager mFeatureFlagManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFeatureFlagManager = new FeatureFlagManager();
+ }
+
+ @Test
+ public void testIsEnabled() {
+ mFeatureFlagManager.setEnabled(1, true);
+ // Again, nothing changes.
+ assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt
new file mode 100644
index 0000000..9bd5bc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 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.idle
+
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.sensors.AsyncSensorManager
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AmbientLightModeMonitorTest : SysuiTestCase() {
+ @Mock private lateinit var sensorManager: AsyncSensorManager
+ @Mock private lateinit var sensor: Sensor
+
+ private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(sensor)
+ ambientLightModeMonitor = AmbientLightModeMonitor(sensorManager)
+ }
+
+ @Test
+ fun shouldTriggerCallbackImmediatelyOnStart() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ // Monitor just started, should receive UNDECIDED.
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED)
+
+ // Receives SensorEvent, and now mode is LIGHT.
+ val sensorEventListener = captureSensorEventListener()
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(15f))
+
+ // Stop monitor.
+ ambientLightModeMonitor.stop()
+
+ // Restart monitor.
+ reset(callback)
+ ambientLightModeMonitor.start(callback)
+
+ // Verify receiving current mode (LIGHT) immediately.
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+ }
+
+ @Test
+ fun shouldReportDarkModeWhenSensorValueIsLessThanTen() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ val sensorEventListener = captureSensorEventListener()
+ reset(callback)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(0f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(1f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(5f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(9.9f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ }
+
+ @Test
+ fun shouldReportLightModeWhenSensorValueIsGreaterThanOrEqualToTen() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ val sensorEventListener = captureSensorEventListener()
+ reset(callback)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(10f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(10.1f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(15f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(100f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+ }
+
+ @Test
+ fun shouldOnlyTriggerCallbackWhenValueChanges() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ val sensorEventListener = captureSensorEventListener()
+
+ // Light mode, should trigger callback.
+ reset(callback)
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(20f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+
+ // Light mode again, should NOT trigger callback.
+ reset(callback)
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(25f))
+ verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+
+ // Dark mode, should trigger callback.
+ reset(callback)
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(2f))
+ verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+
+ // Dark mode again, should not trigger callback.
+ reset(callback)
+ sensorEventListener.onSensorChanged(sensorEventWithSingleValue(3f))
+ verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ }
+
+ // Captures [SensorEventListener], assuming it has been registered with [sensorManager].
+ private fun captureSensorEventListener(): SensorEventListener {
+ val captor = ArgumentCaptor.forClass(SensorEventListener::class.java)
+ verify(sensorManager).registerListener(captor.capture(), any(), anyInt())
+ return captor.value
+ }
+
+ // Returns a [SensorEvent] with a single [value].
+ private fun sensorEventWithSingleValue(value: Float): SensorEvent {
+ return SensorEvent(sensor, 1, 1, FloatArray(1) { value })
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java
index a685e20..8ff33a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java
@@ -28,8 +28,6 @@
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
import android.os.Looper;
import android.os.PowerManager;
import android.testing.AndroidTestingRunner;
@@ -46,7 +44,6 @@
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.sensors.AsyncSensorManager;
import org.junit.Before;
import org.junit.Test;
@@ -55,8 +52,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.time.Instant;
-
import javax.inject.Provider;
@SmallTest
@@ -64,7 +59,6 @@
public class IdleHostViewControllerTest extends SysuiTestCase {
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private PowerManager mPowerManager;
- @Mock private AsyncSensorManager mSensorManager;
@Mock private IdleHostView mIdleHostView;
@Mock private InputMonitorFactory mInputMonitorFactory;
@Mock private DelayableExecutor mDelayableExecutor;
@@ -74,12 +68,11 @@
@Mock private Choreographer mChoreographer;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private StatusBarStateController mStatusBarStateController;
- @Mock private Sensor mSensor;
@Mock private DreamHelper mDreamHelper;
@Mock private InputMonitorCompat mInputMonitor;
@Mock private InputChannelCompat.InputEventReceiver mInputEventReceiver;
+ @Mock private AmbientLightModeMonitor mAmbientLightModeMonitor;
- private final long mTimestamp = Instant.now().toEpochMilli();
private KeyguardStateController.Callback mKeyguardStateCallback;
private StatusBarStateController.StateListener mStatusBarStateListener;
private IdleHostViewController mController;
@@ -90,15 +83,15 @@
when(mResources.getBoolean(R.bool.config_enableIdleMode)).thenReturn(true);
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mSensor);
when(mInputMonitorFactory.getInputMonitor("IdleHostViewController"))
.thenReturn(mInputMonitor);
when(mInputMonitor.getInputReceiver(any(), any(), any())).thenReturn(mInputEventReceiver);
mController = new IdleHostViewController(mContext,
- mBroadcastDispatcher, mPowerManager, mSensorManager, mIdleHostView,
+ mBroadcastDispatcher, mPowerManager, mIdleHostView,
mInputMonitorFactory, mDelayableExecutor, mResources, mLooper, mViewProvider,
- mChoreographer, mKeyguardStateController, mStatusBarStateController, mDreamHelper);
+ mChoreographer, mKeyguardStateController, mStatusBarStateController, mDreamHelper,
+ mAmbientLightModeMonitor);
mController.init();
mController.onViewAttached();
@@ -122,9 +115,9 @@
mKeyguardStateCallback.onKeyguardShowingChanged();
// Regular ambient lighting.
- final SensorEvent sensorEvent = new SensorEvent(mSensor, 3, mTimestamp,
- new float[]{90});
- mController.onSensorChanged(sensorEvent);
+ final AmbientLightModeMonitor.Callback lightMonitorCallback =
+ captureAmbientLightModeMonitorCallback();
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
// Times out.
ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
@@ -149,9 +142,9 @@
final BroadcastReceiver dreamBroadcastReceiver = dreamBroadcastReceiverCaptor.getValue();
// Low ambient lighting.
- final SensorEvent sensorEvent = new SensorEvent(mSensor, 3, mTimestamp,
- new float[]{5});
- mController.onSensorChanged(sensorEvent);
+ final AmbientLightModeMonitor.Callback lightMonitorCallback =
+ captureAmbientLightModeMonitorCallback();
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
// Verifies it goes to sleep because of low light.
verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
@@ -179,15 +172,15 @@
@Test
public void testTransitionBetweenIdleAndLowLightMode() {
- // Regular ambient lighting.
- final SensorEvent sensorEventRegularLight = new SensorEvent(mSensor, 3, mTimestamp,
- new float[]{90});
- mController.onSensorChanged(sensorEventRegularLight);
-
// Keyguard showing.
when(mKeyguardStateController.isShowing()).thenReturn(true);
mKeyguardStateCallback.onKeyguardShowingChanged();
+ // Regular ambient lighting.
+ final AmbientLightModeMonitor.Callback lightMonitorCallback =
+ captureAmbientLightModeMonitorCallback();
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
+
// Times out.
ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
verify(mDelayableExecutor).executeDelayed(callbackCapture.capture(), anyLong());
@@ -198,15 +191,13 @@
clearInvocations(mDreamHelper);
// Ambient lighting becomes dim.
- final SensorEvent sensorEventLowLight = new SensorEvent(mSensor, 3, mTimestamp,
- new float[]{5});
- mController.onSensorChanged(sensorEventLowLight);
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
// Verifies in low light mode (dozing).
verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
// Ambient lighting becomes bright again.
- mController.onSensorChanged(sensorEventRegularLight);
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
// Verifies in idle mode (dreaming).
verify(mDreamHelper).startDreaming(any());
@@ -214,22 +205,20 @@
@Test
public void testStartDozingWhenLowLight() {
- // Regular ambient lighting.
- final SensorEvent sensorEventRegularLight = new SensorEvent(mSensor, 3, mTimestamp,
- new float[]{90});
- mController.onSensorChanged(sensorEventRegularLight);
-
// Keyguard showing.
when(mKeyguardStateController.isShowing()).thenReturn(true);
mKeyguardStateCallback.onKeyguardShowingChanged();
+ // Regular ambient lighting.
+ final AmbientLightModeMonitor.Callback lightMonitorCallback =
+ captureAmbientLightModeMonitorCallback();
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
+
// Verifies it doesn't go to sleep yet.
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
// Ambient lighting becomes dim.
- final SensorEvent sensorEventLowLight = new SensorEvent(mSensor, 3, mTimestamp,
- new float[]{5});
- mController.onSensorChanged(sensorEventLowLight);
+ lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
// Verifies it goes to sleep.
verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
@@ -251,4 +240,13 @@
// Should dispose input event receiver.
verify(mInputEventReceiver).dispose();
}
+
+ // Captures [AmbientLightModeMonitor.Callback] assuming that the ambient light mode monitor
+ // has been started.
+ private AmbientLightModeMonitor.Callback captureAmbientLightModeMonitorCallback() {
+ ArgumentCaptor<AmbientLightModeMonitor.Callback> captor =
+ ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class);
+ verify(mAmbientLightModeMonitor).start(captor.capture());
+ return captor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 31d70f5..1bb660e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -34,12 +34,14 @@
import android.app.trust.TrustManager;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -55,6 +57,8 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -63,6 +67,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -85,11 +90,14 @@
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
private @Mock DozeParameters mDozeParameters;
+ private @Mock UnfoldTransitionConfig mUnfoldTransitionConfig;
+ private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -120,6 +128,8 @@
mNavigationModeController,
mKeyguardDisplayManager,
mDozeParameters,
+ mUnfoldTransitionConfig,
+ () -> mUnfoldAnimation,
mStatusBarStateController,
mKeyguardStateController,
() -> mKeyguardUnlockAnimationController,
@@ -148,6 +158,33 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUnfoldTransitionEnabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
+ throws RemoteException {
+ when(mUnfoldTransitionConfig.isEnabled()).thenReturn(true);
+
+ mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
+ TestableLooper.get(this).processAllMessages();
+ onUnfoldOverlayReady();
+
+ // Should be called when both unfold overlay and keyguard drawn ready
+ verify(mKeyguardDrawnCallback).onDrawn();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
+ throws RemoteException {
+ when(mUnfoldTransitionConfig.isEnabled()).thenReturn(false);
+
+ mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
+ TestableLooper.get(this).processAllMessages();
+
+ // Should be called when only keyguard drawn
+ verify(mKeyguardDrawnCallback).onDrawn();
+ }
+
+ @Test
public void testIsAnimatingScreenOff() {
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
@@ -187,4 +224,11 @@
// then make sure it comes back
verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
}
+
+ private void onUnfoldOverlayReady() {
+ ArgumentCaptor<Runnable> overlayReadyCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mUnfoldAnimation).onScreenTurningOn(overlayReadyCaptor.capture());
+ overlayReadyCaptor.getValue().run();
+ TestableLooper.get(this).processAllMessages();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index ddf1d70..c5436ef6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,11 +16,15 @@
package com.android.systemui.keyguard;
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+
import static junit.framework.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -28,7 +32,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
-import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -37,6 +41,7 @@
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.util.Pair;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -79,7 +84,7 @@
private static final String UNLOCKED_LABEL = "unlocked";
private @Mock LockIconView mLockIconView;
- private @Mock AnimatedVectorDrawable mIconDrawable;
+ private @Mock AnimatedStateListDrawable mIconDrawable;
private @Mock Context mContext;
private @Mock Resources mResources;
private @Mock DisplayMetrics mDisplayMetrics;
@@ -96,6 +101,7 @@
private @Mock Vibrator mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private @Mock LottieAnimationView mAodFp;
+ private @Mock LayoutInflater mLayoutInflater;
private LockIconViewController mLockIconViewController;
@@ -104,6 +110,14 @@
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
private View.OnAttachStateChangeListener mAttachListener;
+ @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ private KeyguardStateController.Callback mKeyguardStateCallback;
+
+ @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ private StatusBarStateController.StateListener mStatusBarStateListener;
+
@Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
private AuthController.Callback mAuthControllerCallback;
@@ -120,11 +134,11 @@
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
- when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
- when(mResources.getDrawable(anyInt(), anyObject())).thenReturn(mIconDrawable);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
@@ -144,11 +158,42 @@
mConfigurationController,
mDelayableExecutor,
mVibrator,
- mAuthRippleController
+ mAuthRippleController,
+ mResources,
+ mLayoutInflater
);
}
@Test
+ public void testIgnoreUdfpsWhenNotSupported() {
+ // GIVEN Udpfs sensor is NOT available
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should NOT be inflated
+ verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
+ public void testInflateUdfpsWhenSupported() {
+ // GIVEN Udpfs sensor is available
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should be inflated
+ verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
Pair<Integer, PointF> udfps = setupUdfps();
@@ -237,6 +282,86 @@
verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
}
+ @Test
+ public void testLockIconStartState() {
+ // GIVEN lock icon state
+ setupShowLockIcon();
+
+ // WHEN lock icon controller is initialized
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_updateToUnlock() {
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureKeyguardStateCallback();
+ reset(mLockIconView);
+
+ // WHEN the unlocked state changes to canDismissLockScreen=true
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the unlock should show
+ verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
+ // GIVEN udfps not enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon();
+ }
+
+ @Test
+ public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true);
+ }
+
private Pair<Integer, PointF> setupUdfps() {
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
@@ -256,6 +381,15 @@
return new Pair(radius, udfpsLocation);
}
+ private void setupShowLockIcon() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ }
+
private void captureAuthControllerCallback() {
verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
@@ -266,6 +400,16 @@
mAttachListener = mAttachCaptor.getValue();
}
+ private void captureKeyguardStateCallback() {
+ verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
+ mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
+ }
+
+ private void captureStatusBarStateListener() {
+ verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
+ mStatusBarStateListener = mStatusBarStateCaptor.getValue();
+ }
+
private void captureKeyguardUpdateMonitorCallback() {
verify(mKeyguardUpdateMonitor).registerCallback(
mKeyguardUpdateMonitorCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index c1a9739..4fc329f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -35,42 +35,24 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.pip.Pip;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
-import java.util.Optional;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/** atest NavigationBarControllerTest */
@RunWith(AndroidTestingRunner.class)
@@ -78,47 +60,33 @@
@SmallTest
public class NavigationBarControllerTest extends SysuiTestCase {
+ private static final int SECONDARY_DISPLAY = 1;
+
private NavigationBarController mNavigationBarController;
private NavigationBar mDefaultNavBar;
private NavigationBar mSecondaryNavBar;
- private CommandQueue mCommandQueue = mock(CommandQueue.class);
-
- private static final int SECONDARY_DISPLAY = 1;
+ @Mock
+ private CommandQueue mCommandQueue;
+ @Mock
+ private NavigationBar.Factory mNavigationBarFactory;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mNavigationBarController = spy(
new NavigationBarController(mContext,
- mock(WindowManager.class),
- () -> mock(AssistManager.class),
- mock(AccessibilityManager.class),
- mock(AccessibilityManagerWrapper.class),
- mock(DeviceProvisionedController.class),
- mock(MetricsLogger.class),
mock(OverviewProxyService.class),
mock(NavigationModeController.class),
- mock(AccessibilityButtonModeObserver.class),
- mock(StatusBarStateController.class),
mock(SysUiState.class),
- mock(BroadcastDispatcher.class),
mCommandQueue,
- Optional.of(mock(Pip.class)),
- Optional.of(mock(LegacySplitScreen.class)),
- Optional.of(mock(Recents.class)),
- () -> Optional.of(mock(StatusBar.class)),
- mock(ShadeController.class),
- mock(NotificationRemoteInputManager.class),
- mock(NotificationShadeDepthController.class),
- mock(SystemActions.class),
Dependency.get(Dependency.MAIN_HANDLER),
- mock(UiEventLogger.class),
- mock(NavigationBarOverlayController.class),
mock(ConfigurationController.class),
mock(NavigationBarA11yHelper.class),
mock(TaskbarDelegate.class),
- mock(UserTracker.class),
- mock(DumpManager.class)));
+ mNavigationBarFactory,
+ mock(DumpManager.class),
+ mock(AutoHideController.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index e37f422..223ffbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -19,6 +19,7 @@
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -28,7 +29,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -45,11 +45,11 @@
import android.content.IntentFilter;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
-import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.telecom.TelecomManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -60,12 +60,12 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.InputMethodManager;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -81,9 +81,10 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -110,7 +111,13 @@
private NavigationBar mExternalDisplayNavigationBar;
private SysuiTestableContext mSysuiTestableContextExternal;
+ @Mock
private OverviewProxyService mOverviewProxyService;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private NavigationModeController mNavigationModeController;
+ @Mock
private CommandQueue mCommandQueue;
private SysUiState mMockSysUiState;
@Mock
@@ -125,11 +132,25 @@
EdgeBackGestureHandler mEdgeBackGestureHandler;
@Mock
NavigationBarA11yHelper mNavigationBarA11yHelper;
+ @Mock
+ private LightBarController mLightBarController;
+ @Mock
+ private LightBarController.Factory mLightBarcontrollerFactory;
+ @Mock
+ private AutoHideController mAutoHideController;
+ @Mock
+ private AutoHideController.Factory mAutoHideControllerFactory;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private TelecomManager mTelecomManager;
+ @Mock
+ private InputMethodManager mInputMethodManager;
+ @Mock
+ private AssistManager mAssistManager;
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
- private AccessibilityManagerWrapper mAccessibilityWrapper =
- new AccessibilityManagerWrapper(mContext);
@Before
public void setup() throws Exception {
@@ -137,15 +158,19 @@
when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
.thenReturn(mEdgeBackGestureHandler);
- mCommandQueue = new CommandQueue(mContext);
+ when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+ when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
setupSysuiDependency();
- mDependency.injectMockDependency(AssistManager.class);
+ // This class inflates views that call Dependency.get, thus these injections are still
+ // necessary.
+ mDependency.injectTestDependency(AssistManager.class, mAssistManager);
mDependency.injectMockDependency(KeyguardStateController.class);
- mDependency.injectMockDependency(StatusBarStateController.class);
+ mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectMockDependency(NavigationBarController.class);
- mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
mEdgeBackGestureHandlerFactory);
+ mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
+ mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
TestableLooper.get(this).runWithLooper(() -> {
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -164,25 +189,21 @@
mSysuiTestableContextExternal = (SysuiTestableContext) getContext().createDisplayContext(
display);
- WindowManager windowManager = mock(WindowManager.class);
- Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
- when(windowManager.getDefaultDisplay()).thenReturn(
- defaultDisplay);
- WindowMetrics maximumWindowMetrics = mContext.getSystemService(WindowManager.class)
+ Display defaultDisplay = mContext.getDisplay();
+ when(mWindowManager.getDefaultDisplay()).thenReturn(defaultDisplay);
+ WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
.getMaximumWindowMetrics();
- when(windowManager.getMaximumWindowMetrics()).thenReturn(maximumWindowMetrics);
+ when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics);
WindowMetrics currentWindowMetrics = mContext.getSystemService(WindowManager.class)
.getCurrentWindowMetrics();
- when(windowManager.getCurrentWindowMetrics()).thenReturn(currentWindowMetrics);
- doNothing().when(windowManager).addView(any(), any());
- mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
- mSysuiTestableContextExternal.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
-
- mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
- mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper);
-
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(currentWindowMetrics);
+ doNothing().when(mWindowManager).addView(any(), any());
+ doNothing().when(mWindowManager).removeViewImmediate(any());
mMockSysUiState = mock(SysUiState.class);
when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+
+ mContext.addMockSystemService(WindowManager.class, mWindowManager);
+ mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager);
}
@Test
@@ -239,10 +260,8 @@
defaultNavBar.createView(null);
externalNavBar.createView(null);
- // Set IME window status for default NavBar.
- mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true, false);
- processAllMessages();
+ defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+ BACK_DISPOSITION_DEFAULT, true);
// Verify IME window state will be updated in default NavBar & external NavBar state reset.
assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
@@ -250,11 +269,10 @@
assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- // Set IME window status for external NavBar.
- mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
- IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false);
- processAllMessages();
-
+ externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE,
+ BACK_DISPOSITION_DEFAULT, true);
+ defaultNavBar.setImeWindowStatus(
+ DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false);
// Verify IME window state will be updated in external NavBar & default NavBar state reset.
assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
externalNavBar.getNavigationIconHints());
@@ -280,19 +298,15 @@
DeviceProvisionedController deviceProvisionedController =
mock(DeviceProvisionedController.class);
when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- assertNotNull(mAccessibilityWrapper);
- return spy(new NavigationBar(context,
- mock(WindowManager.class),
- () -> mock(AssistManager.class),
+ NavigationBar.Factory factory = new NavigationBar.Factory(
+ () -> mAssistManager,
mock(AccessibilityManager.class),
- context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
- : mock(AccessibilityManagerWrapper.class),
deviceProvisionedController,
new MetricsLogger(),
mOverviewProxyService,
- mock(NavigationModeController.class),
+ mNavigationModeController,
mock(AccessibilityButtonModeObserver.class),
- mock(StatusBarStateController.class),
+ mStatusBarStateController,
mMockSysUiState,
mBroadcastDispatcher,
mCommandQueue,
@@ -308,7 +322,14 @@
mock(NavigationBarOverlayController.class),
mUiEventLogger,
mNavigationBarA11yHelper,
- mock(UserTracker.class)));
+ mock(UserTracker.class),
+ mLightBarController,
+ mLightBarcontrollerFactory,
+ mAutoHideController,
+ mAutoHideControllerFactory,
+ Optional.of(mTelecomManager),
+ mInputMethodManager);
+ return spy(factory.create(context));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index c3d60d4..0252420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -39,7 +39,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
@@ -64,7 +63,6 @@
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
@@ -186,10 +184,6 @@
return new QSFragment(
new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
commandQueue),
- new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory()),
mock(QSTileHost.class),
mock(StatusBarStateController.class),
commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index c42b64a..6688960 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -19,6 +19,7 @@
import android.testing.TestableLooper;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
@@ -26,6 +27,8 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.wifitrackerlib.WifiEntry;
import org.junit.After;
@@ -64,6 +67,7 @@
@Mock
private InternetDialogController mInternetDialogController;
+ private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock());
private InternetDialog mInternetDialog;
private View mDialogView;
private View mSubTitle;
@@ -73,6 +77,7 @@
private LinearLayout mConnectedWifi;
private RecyclerView mWifiList;
private LinearLayout mSeeAll;
+ private LinearLayout mWifiScanNotify;
@Before
public void setUp() {
@@ -91,7 +96,8 @@
when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager);
mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
- mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler);
+ mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler,
+ mBgExecutor);
mInternetDialog.mAdapter = mInternetAdapter;
mInternetDialog.onAccessPointsChanged(mWifiEntries, mInternetWifiEntry);
mInternetDialog.show();
@@ -104,6 +110,7 @@
mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
+ mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
}
@After
@@ -126,7 +133,7 @@
public void updateDialog_withApmOn_internetDialogSubTitleGone() {
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
}
@@ -135,7 +142,7 @@
public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -145,7 +152,7 @@
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
when(mInternetDialogController.hasEthernet()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -155,7 +162,7 @@
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
when(mInternetDialogController.hasEthernet()).thenReturn(false);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
}
@@ -165,7 +172,7 @@
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
when(mInternetDialogController.hasEthernet()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -175,7 +182,7 @@
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
when(mInternetDialogController.hasEthernet()).thenReturn(false);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
}
@@ -184,7 +191,7 @@
public void updateDialog_withApmOn_mobileDataLayoutGone() {
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(true);
assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
}
@@ -194,7 +201,7 @@
// The preconditions WiFi ON and Internet WiFi are already in setUp()
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -205,7 +212,7 @@
mInternetDialog.onAccessPointsChanged(mWifiEntries, null /* connectedEntry*/);
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
}
@@ -215,7 +222,7 @@
// The precondition WiFi ON is already in setUp()
mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, mInternetWifiEntry);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
@@ -225,7 +232,7 @@
public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
@@ -236,7 +243,7 @@
// The preconditions WiFi ON and Internet WiFi are already in setUp()
when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mWifiToggle.getBackground()).isNotNull();
@@ -247,7 +254,7 @@
// The preconditions WiFi ON and Internet WiFi are already in setUp()
when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
}
@@ -257,13 +264,57 @@
// The preconditions WiFi entries are already in setUp()
when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
- mInternetDialog.updateDialog();
+ mInternetDialog.updateDialog(false);
assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
}
@Test
+ public void updateDialog_wifiOn_hideWifiScanNotify() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() {
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(false);
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOffAndWifiScanOnAndDeviceLocked_hideWifiScanNotify() {
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOffAndWifiScanOnAndDeviceUnlocked_showWifiScanNotify() {
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(false);
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE);
+ TextView wifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text);
+ assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0);
+ assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull();
+ }
+
+ @Test
public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
mSeeAll.performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index a1760a7..7e900c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -22,6 +22,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -68,6 +69,8 @@
private lateinit var launchView: View
@Mock
private lateinit var gridView: PseudoGridView
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Captor
private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
@@ -87,6 +90,7 @@
{ userDetailViewAdapter },
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ dialog }
)
}
@@ -94,7 +98,7 @@
@Test
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
- verify(dialog).show()
+ verify(dialogLaunchAnimator).showFromView(dialog, launchView)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2416132..af624ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -15,6 +15,8 @@
package com.android.systemui.statusbar;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -188,8 +190,13 @@
@Test
public void testShowImeButtonForSecondaryDisplay() {
+ // First show in default display to update the "last updated ime display"
+ testShowImeButton();
+
mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true, false);
waitForIdleSync();
+ verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(IME_INVISIBLE),
+ eq(BACK_DISPOSITION_DEFAULT), eq(false));
verify(mCallbacks).setImeWindowStatus(
eq(SECONDARY_DISPLAY), eq(null), eq(1), eq(2), eq(true));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5cab1d..01f7fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -166,7 +166,7 @@
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
- private KeyguardIndicationTextView mTextView;
+ private KeyguardIndicationTextView mTextView; // AOD text
private KeyguardIndicationController mController;
private WakeLockFake.Builder mWakeLockBuilder;
@@ -412,41 +412,32 @@
@Test
public void transientIndication_holdsWakeLock_whenDozing() {
+ // GIVEN animations are enabled and text is visible
+ mTextView.setAnimationsEnabled(true);
createController();
+ mController.setVisible(true);
+ // WHEN transient text is shown
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- assertTrue(mWakeLock.isHeld());
+ // THEN wake lock is held while the animation is running
+ assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
}
@Test
- public void transientIndication_releasesWakeLock_afterHiding() {
+ public void transientIndication_releasesWakeLock_whenDozing() {
+ // GIVEN animations aren't enabled
+ mTextView.setAnimationsEnabled(false);
createController();
+ mController.setVisible(true);
+ // WHEN we show the transient indication
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- mController.hideTransientIndication();
- assertFalse(mWakeLock.isHeld());
- }
-
- @Test
- public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
- mInstrumentation.runOnMainSync(() -> {
- createController();
-
- mStatusBarStateListener.onDozingChanged(true);
- mController.showTransientIndication("Test");
- mController.hideTransientIndicationDelayed(0);
- });
- mInstrumentation.waitForIdleSync();
-
- Boolean[] held = new Boolean[1];
- mInstrumentation.runOnMainSync(() -> {
- held[0] = mWakeLock.isHeld();
- });
- assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]);
+ // THEN wake lock is RELEASED, not held
+ assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index e5ae65f..dbd5168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -24,7 +24,7 @@
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
@@ -208,7 +208,7 @@
notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(
- eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */)))
+ eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index ac699f7..045e6f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -52,7 +52,7 @@
private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
private boolean mCanBubble = false;
- private boolean mIsVisuallyInterruptive = false;
+ private boolean mIsTextChanged = false;
private boolean mIsConversation = false;
private ShortcutInfo mShortcutInfo = null;
private int mRankingAdjustment = 0;
@@ -81,7 +81,7 @@
mSmartActions = copyList(ranking.getSmartActions());
mSmartReplies = copyList(ranking.getSmartReplies());
mCanBubble = ranking.canBubble();
- mIsVisuallyInterruptive = ranking.visuallyInterruptive();
+ mIsTextChanged = ranking.isTextChanged();
mIsConversation = ranking.isConversation();
mShortcutInfo = ranking.getConversationShortcutInfo();
mRankingAdjustment = ranking.getRankingAdjustment();
@@ -110,7 +110,7 @@
mSmartActions,
mSmartReplies,
mCanBubble,
- mIsVisuallyInterruptive,
+ mIsTextChanged,
mIsConversation,
mShortcutInfo,
mRankingAdjustment,
@@ -189,8 +189,8 @@
return this;
}
- public RankingBuilder setVisuallyInterruptive(boolean interruptive) {
- mIsVisuallyInterruptive = interruptive;
+ public RankingBuilder setTextChanged(boolean textChanged) {
+ mIsTextChanged = textChanged;
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 67cab74..b23d07a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -216,7 +216,7 @@
mCallbackHandler = mock(CallbackHandler.class);
mMockProvisionController = mock(DeviceProvisionedController.class);
- when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(true);
+ when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
doAnswer(invocation -> {
mUserCallback = (DeviceProvisionedListener) invocation.getArguments()[0];
mUserCallback.onUserSetupChanged();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 00dedd9..12f8282 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -21,7 +21,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -211,7 +210,7 @@
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
setConnectivityViaCallbackInNetworkController(
NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
- when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(false);
+ when(mMockProvisionController.isCurrentUserSetup()).thenReturn(false);
mUserCallback.onUserSetupChanged();
TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 5d50485..ff91978 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -246,6 +246,7 @@
clearInvocations(plugin)
// WHEN the session is closed
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN the listener receives an empty list of targets
@@ -417,6 +418,7 @@
connectSession()
// WHEN we are told to cleanup
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN we disconnect from the session and unregister any listeners
@@ -446,6 +448,20 @@
verify(smartspaceView2).registerDataProvider(plugin)
}
+ @Test
+ fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
+ // GIVEN an uninitalized smartspaceView
+ // WHEN the device is provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onDeviceProvisionedChanged()
+
+ // THEN no calls to createSmartspaceSession should occur
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+ // THEN no listeners should be registered
+ verify(configurationController, never()).addCallback(any())
+ }
+
private fun connectSession() {
val view = controller.buildAndConnectView(fakeParent)
smartspaceView = view as SmartspaceView
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index d3c1dc9..3c84c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -32,6 +32,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -43,6 +44,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.HashSet;
@@ -57,6 +59,8 @@
private Runnable mRoundnessCallback = mock(Runnable.class);
private ExpandableNotificationRow mFirst;
private ExpandableNotificationRow mSecond;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private float mSmallRadiusRatio;
@Before
@@ -66,7 +70,8 @@
mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small)
/ resources.getDimension(R.dimen.notification_corner_radius);
mRoundnessManager = new NotificationRoundnessManager(
- new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
+ new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
+ mFeatureFlags);
allowTestableLooperAsMainThread();
NotificationTestHelper testHelper = new NotificationTestHelper(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 9f42fa4d..185d9cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -58,6 +58,8 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -110,9 +112,20 @@
Settings.Secure.putIntForUser(mContext.getContentResolver(), NOTIFICATION_HISTORY_ENABLED,
1, UserHandle.USER_CURRENT);
+
+ // Interact with real instance of AmbientState.
+ mAmbientState = new AmbientState(mContext, mNotificationSectionsManager, mBypassController);
+
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectTestDependency(
+ NotificationSectionsManager.class, mNotificationSectionsManager);
+ mDependency.injectTestDependency(GroupMembershipManager.class, mGroupMembershipManger);
+ mDependency.injectTestDependency(GroupExpansionManager.class, mGroupExpansionManager);
+ mDependency.injectTestDependency(AmbientState.class, mAmbientState);
+ mDependency.injectTestDependency(
+ UnlockedScreenOffAnimationController.class, mUnlockedScreenOffAnimationController);
NotificationShelfController notificationShelfController =
mock(NotificationShelfController.class);
@@ -123,22 +136,12 @@
mNotificationSection
});
- // Interact with real instance of AmbientState.
- mAmbientState = new AmbientState(mContext, mNotificationSectionsManager, mBypassController);
-
// The actual class under test. You may need to work with this class directly when
// testing anonymous class members of mStackScroller, like mMenuEventListener,
// which refer to members of NotificationStackScrollLayout. The spy
// holds a copy of the CUT's instances of these KeyguardBypassController, so they still
// refer to the CUT's member variables, not the spy's member variables.
- mStackScrollerInternal = new NotificationStackScrollLayout(
- getContext(),
- null,
- mNotificationSectionsManager,
- mGroupMembershipManger,
- mGroupExpansionManager,
- mAmbientState,
- mUnlockedScreenOffAnimationController);
+ mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null);
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper);
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelfController(notificationShelfController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
new file mode 100644
index 0000000..5b60c9e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -0,0 +1,58 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class StackScrollAlgorithmTest : SysuiTestCase() {
+
+ private val hostView = FrameLayout(context)
+ private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
+ private val expandableViewState = ExpandableViewState()
+ private val notificationRow = mock(ExpandableNotificationRow::class.java)
+ private val ambientState = AmbientState(
+ context,
+ SectionProvider { _, _ -> false },
+ BypassController { false })
+
+ @Before
+ fun setUp() {
+ whenever(notificationRow.viewState).thenReturn(expandableViewState)
+ hostView.addView(notificationRow)
+ }
+
+ @Test
+ fun testUpTranslationSetToDefaultValue() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ assertThat(expandableViewState.yTranslation).isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ fun testHeadsUpTranslationChangesBasedOnStackMargin() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ val minHeadsUpTranslation = context.resources
+ .getDimensionPixelSize(R.dimen.notification_side_paddings)
+
+ // split shade case with top margin introduced by shade's status bar
+ ambientState.stackTopMargin = 100
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ // top margin presence should decrease heads up translation up to minHeadsUpTranslation
+ assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 8b5ba38..38d7ce7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -164,7 +164,7 @@
@Test
public void testPulseWhileDozing_notifyAuthInterrupt() {
HashSet<Integer> reasonsWantingAuth = new HashSet<>(
- Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+ Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH));
HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
Arrays.asList(DozeLog.PULSE_REASON_INTENT,
DozeLog.PULSE_REASON_NOTIFICATION,
@@ -173,7 +173,7 @@
DozeLog.REASON_SENSOR_DOUBLE_TAP,
DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
DozeLog.PULSE_REASON_DOCKING,
- DozeLog.REASON_SENSOR_WAKE_UP,
+ DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
DozeLog.REASON_SENSOR_QUICK_PICKUP,
DozeLog.REASON_SENSOR_TAP));
HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index bca1227..bafbccd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -198,7 +198,6 @@
mHeadsUpAppearanceController.destroy();
verify(mHeadsUpManager).removeListener(any());
verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
- verify(mPanelView).setVerticalTranslationListener(isNull());
verify(mPanelView).removeTrackingHeadsUpListener(any());
verify(mPanelView).setHeadsUpAppearanceController(isNull());
verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 4b2380a..747acdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -384,8 +384,6 @@
when(mView.findViewById(R.id.notification_stack_scroller))
.thenReturn(mNotificationStackScrollLayout);
when(mView.findViewById(R.id.communal_host)).thenReturn(mCommunalHostView);
- when(mNotificationStackScrollLayout.getController())
- .thenReturn(mNotificationStackScrollLayoutController);
when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
.thenReturn(mHeadsUpCallback);
@@ -517,6 +515,7 @@
mControlsComponent);
mNotificationPanelViewController.initDependencies(
mStatusBar,
+ () -> {},
mNotificationShelfController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
mNotificationPanelViewController.setBar(mPanelBar);
@@ -585,6 +584,51 @@
}
@Test
+ public void onTouchForwardedFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(false);
+
+ boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+
+ assertThat(returnVal).isFalse();
+ verify(mView, never()).dispatchTouchEvent(any());
+ }
+
+ @Test
+ public void onTouchForwardedFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(false);
+
+ boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+
+ assertThat(returnVal).isTrue();
+ verify(mView, never()).dispatchTouchEvent(any());
+ }
+
+ @Test
+ public void onTouchForwardedFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(false);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
+
+ mTouchHandler.onTouchForwardedFromStatusBar(event);
+
+ verify(mView).dispatchTouchEvent(event);
+ }
+
+ @Test
+ public void onTouchForwardedFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(true);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
+
+ mTouchHandler.onTouchForwardedFromStatusBar(event);
+
+ verify(mView).dispatchTouchEvent(event);
+ }
+
+ @Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
@@ -725,18 +769,6 @@
}
@Test
- public void testOnDragDownEvent_horizontalTranslationIsZeroForSplitShade() {
- when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(350f);
- when(mView.getWidth()).thenReturn(800);
- enableSplitShade(/* enabled= */ true);
-
- onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN,
- 200f /* x position */, 0f, 0));
-
- verify(mQsFrame).setTranslationX(0);
- }
-
- @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index c62d73c..6e9bb2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -34,7 +34,6 @@
import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.dock.DockManager;
@@ -55,7 +54,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
@@ -117,10 +115,6 @@
when(mDockManager.isDocked()).thenReturn(false);
mController = new NotificationShadeWindowViewController(
- new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory()),
mCoordinator,
mPulseExpansionHandler,
mDynamicPrivacyController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 52a5e06..310a8ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -17,18 +17,25 @@
package com.android.systemui.statusbar.phone
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -36,11 +43,9 @@
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
- private val stateChangeListener = TestStateChangedListener()
+ private val touchEventHandler = TestTouchEventHandler()
@Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
private lateinit var panelViewController: PanelViewController
@Mock
private lateinit var panelView: ViewGroup
@@ -49,10 +54,14 @@
@Mock
private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
+ @Mock
+ private lateinit var progressProvider: ScopedUnfoldTransitionProgressProvider
private lateinit var view: PhoneStatusBarView
private lateinit var controller: PhoneStatusBarViewController
+ private val unfoldConfig = UnfoldConfig()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -63,58 +72,62 @@
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
- view.setPanel(panelViewController)
view.setScrimController(scrimController)
+ view.setBar(mock(StatusBar::class.java))
}
- controller = PhoneStatusBarViewController(
- view,
- commandQueue,
- null,
- stateChangeListener
- )
+ controller = createController(view)
}
@Test
- fun constructor_setsPanelEnabledProviderOnView() {
- var providerUsed = false
- `when`(commandQueue.panelsEnabled()).then {
- providerUsed = true
- true
- }
+ fun constructor_setsTouchHandlerOnView() {
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- // If the constructor correctly set a [PanelEnabledProvider], then it should be used
- // when [PhoneStatusBarView.panelEnabled] is called.
- view.panelEnabled()
+ view.onTouchEvent(event)
- assertThat(providerUsed).isTrue()
+ assertThat(touchEventHandler.lastEvent).isEqualTo(event)
}
@Test
- fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() {
- controller = PhoneStatusBarViewController(
- view, commandQueue, moveFromCenterAnimation, stateChangeListener
- )
+ fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
+ val view = createViewMock()
+ val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
+ unfoldConfig.isEnabled = true
+ controller = createController(view)
+ controller.init()
- verify(moveFromCenterAnimation).init(any(), any())
+ verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
+ argumentCaptor.value.onPreDraw()
+
+ verify(moveFromCenterAnimation).onViewsReady(any(), any())
}
- @Test
- fun constructor_setsExpansionStateChangedListenerOnView() {
- assertThat(stateChangeListener.stateChangeCalled).isFalse()
-
- // If the constructor correctly set the listener, then it should be used when
- // [PhoneStatusBarView.panelExpansionChanged] is called.
- view.panelExpansionChanged(0f, false)
-
- assertThat(stateChangeListener.stateChangeCalled).isTrue()
+ private fun createViewMock(): PhoneStatusBarView {
+ val view = spy(view)
+ val viewTreeObserver = mock(ViewTreeObserver::class.java)
+ `when`(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ `when`(view.isAttachedToWindow).thenReturn(true)
+ return view
}
- private class TestStateChangedListener : PhoneStatusBarView.PanelExpansionStateChangedListener {
- var stateChangeCalled: Boolean = false
+ private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController {
+ return PhoneStatusBarViewController.Factory(
+ { progressProvider },
+ { moveFromCenterAnimation },
+ unfoldConfig
+ ).create(view, touchEventHandler)
+ }
- override fun onPanelExpansionStateChanged() {
- stateChangeCalled = true
+ private class UnfoldConfig : UnfoldTransitionConfig {
+ override var isEnabled: Boolean = false
+ override var isHingeAngleEnabled: Boolean = false
+ }
+
+ private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
+ var lastEvent: MotionEvent? = null
+ override fun handleTouchEvent(event: MotionEvent?): Boolean {
+ lastEvent = event
+ return false
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index ec7e07f90..fe34903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
+import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -48,63 +49,25 @@
`when`(panelViewController.view).thenReturn(panelView)
view = PhoneStatusBarView(mContext, null)
- view.setPanel(panelViewController)
view.setScrimController(scrimController)
view.setBar(statusBar)
}
@Test
- fun panelEnabled_providerReturnsTrue_returnsTrue() {
- view.setPanelEnabledProvider { true }
+ fun panelExpansionChanged_expansionChangeListenerNotified() {
+ val listener = TestExpansionChangedListener()
+ view.setExpansionChangedListeners(listOf(listener))
+ val fraction = 0.4f
+ val isExpanded = true
- assertThat(view.panelEnabled()).isTrue()
+ view.panelExpansionChanged(fraction, isExpanded)
+
+ assertThat(listener.fraction).isEqualTo(fraction)
+ assertThat(listener.isExpanded).isEqualTo(isExpanded)
}
@Test
- fun panelEnabled_providerReturnsFalse_returnsFalse() {
- view.setPanelEnabledProvider { false }
-
- assertThat(view.panelEnabled()).isFalse()
- }
-
- @Test
- fun panelEnabled_noProvider_noCrash() {
- view.panelEnabled()
- // No assert needed, just testing no crash
- }
-
- @Test
- fun panelExpansionChanged_fracZero_stateChangeListenerNotified() {
- val listener = TestExpansionStateChangedListener()
- view.setPanelExpansionStateChangedListener(listener)
-
- view.panelExpansionChanged(0f, false)
-
- assertThat(listener.stateChangeCalled).isTrue()
- }
-
- @Test
- fun panelExpansionChanged_fracOne_stateChangeListenerNotified() {
- val listener = TestExpansionStateChangedListener()
- view.setPanelExpansionStateChangedListener(listener)
-
- view.panelExpansionChanged(1f, false)
-
- assertThat(listener.stateChangeCalled).isTrue()
- }
-
- @Test
- fun panelExpansionChanged_fracHalf_stateChangeListenerNotNotified() {
- val listener = TestExpansionStateChangedListener()
- view.setPanelExpansionStateChangedListener(listener)
-
- view.panelExpansionChanged(0.5f, false)
-
- assertThat(listener.stateChangeCalled).isFalse()
- }
-
- @Test
- fun panelExpansionChanged_noStateChangeListener_noCrash() {
+ fun panelExpansionChanged_noListeners_noCrash() {
view.panelExpansionChanged(1f, false)
// No assert needed, just testing no crash
}
@@ -144,17 +107,52 @@
}
@Test
- fun panelStateChanged_noListener_noCrash() {
- view.panelExpansionChanged(1f, true)
+ fun onTouchEvent_listenerNotified() {
+ val handler = TestTouchEventHandler()
+ view.setTouchEventHandler(handler)
+
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ view.onTouchEvent(event)
+
+ assertThat(handler.lastEvent).isEqualTo(event)
+ }
+
+ @Test
+ fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
+ val handler = TestTouchEventHandler()
+ view.setTouchEventHandler(handler)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ handler.returnValue = true
+
+ assertThat(view.onTouchEvent(event)).isTrue()
+ }
+
+ @Test
+ fun onTouchEvent_listenerReturnsFalse_viewReturnsFalse() {
+ val handler = TestTouchEventHandler()
+ view.setTouchEventHandler(handler)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ handler.returnValue = false
+
+ assertThat(view.onTouchEvent(event)).isFalse()
+ }
+
+ @Test
+ fun onTouchEvent_noListener_noCrash() {
+ view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
// No assert needed, just testing no crash
}
- private class TestExpansionStateChangedListener
- : PhoneStatusBarView.PanelExpansionStateChangedListener {
- var stateChangeCalled: Boolean = false
+ private class TestExpansionChangedListener
+ : StatusBar.ExpansionChangedListener {
+ var fraction: Float = 0f
+ var isExpanded: Boolean = false
- override fun onPanelExpansionStateChanged() {
- stateChangeCalled = true
+ override fun onExpansionChanged(expansion: Float, expanded: Boolean) {
+ this.fraction = expansion
+ this.isExpanded = expanded
}
}
@@ -164,4 +162,13 @@
this.state = state
}
}
+
+ private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
+ var lastEvent: MotionEvent? = null
+ var returnValue: Boolean = false
+ override fun handleTouchEvent(event: MotionEvent?): Boolean {
+ lastEvent = event
+ return returnValue
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 705112a..6849dab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1063,7 +1063,7 @@
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER,
ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED,
- ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED));
+ ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED, ScrimState.AUTH_SCRIMMED_SHADE));
for (ScrimState state : ScrimState.values()) {
if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
@@ -1087,6 +1087,44 @@
}
@Test
+ public void testAuthScrim_notifScrimOpaque_whenShadeFullyExpanded() {
+ // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
+ // with the camera app occluding the keyguard)
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.setRawPanelExpansionFraction(1);
+ // notifications scrim alpha change require calling setQsPosition
+ mScrimController.setQsPosition(0, 300);
+ finishAnimationsImmediately();
+
+ // WHEN the user triggers the auth bouncer
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ finishAnimationsImmediately();
+
+ assertEquals("Behind scrim should be opaque",
+ mScrimBehind.getViewAlpha(), 1, 0.0);
+ assertEquals("Notifications scrim should be opaque",
+ mNotificationsScrim.getViewAlpha(), 1, 0.0);
+ }
+
+ @Test
+ public void testAuthScrimKeyguard() {
+ // GIVEN device is on the keyguard
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ finishAnimationsImmediately();
+
+ // WHEN the user triggers the auth bouncer
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ finishAnimationsImmediately();
+
+ // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
+ // KEYGUARD scrim state
+ assertScrimAlpha(Map.of(
+ mScrimInFront, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT));
+ }
+
+ @Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.3f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 741d95f..943d3c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -144,6 +144,7 @@
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.MessageRouterImpl;
@@ -259,7 +260,7 @@
@Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
@Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
@Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
- @Mock private Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimationLazy;
+ @Mock private Lazy<NaturalRotationUnfoldProgressProvider> mNaturalRotationProgressProvider;
@Mock private Lazy<UnfoldTransitionWallpaperController> mUnfoldWallpaperController;
@Mock private WallpaperController mWallpaperController;
@Mock private OngoingCallController mOngoingCallController;
@@ -276,6 +277,7 @@
@Mock private StartingSurface mStartingSurface;
@Mock private OperatorNameViewController mOperatorNameViewController;
@Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
+ @Mock private PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory;
@Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
private ShadeController mShadeController;
@@ -314,7 +316,6 @@
mContext.setTheme(R.style.Theme_SystemUI_LightWallpaper);
- when(mStackScroller.getController()).thenReturn(mStackScrollerController);
when(mStackScrollerController.getView()).thenReturn(mStackScroller);
when(mStackScrollerController.getNotificationListContainer()).thenReturn(
mNotificationListContainer);
@@ -430,6 +431,7 @@
mExtensionController,
mUserInfoControllerImpl,
mOperatorNameViewControllerFactory,
+ mPhoneStatusBarViewControllerFactory,
mPhoneStatusBarPolicy,
mKeyguardIndicationController,
mDemoModeController,
@@ -440,7 +442,7 @@
mUnfoldTransitionConfig,
mUnfoldLightRevealOverlayAnimationLazy,
mUnfoldWallpaperController,
- mMoveFromCenterAnimationLazy,
+ mNaturalRotationProgressProvider,
mWallpaperController,
mOngoingCallController,
mAnimationScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index be27876..3d2ff47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -36,6 +36,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -82,11 +84,13 @@
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
+ @Mock private lateinit var mockFeatureFlags: FeatureFlags
@Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
@Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var mockStatusBarStateController: StatusBarStateController
private lateinit var chipView: View
@@ -98,13 +102,12 @@
}
MockitoAnnotations.initMocks(this)
- val featureFlags = mock(FeatureFlags::class.java)
- `when`(featureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
+ `when`(mockFeatureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
val notificationCollection = mock(CommonNotifCollection::class.java)
controller = OngoingCallController(
notificationCollection,
- featureFlags,
+ mockFeatureFlags,
clock,
mockActivityStarter,
mainExecutor,
@@ -113,7 +116,8 @@
DumpManager(),
Optional.of(mockStatusBarWindowController),
Optional.of(mockSwipeStatusBarAwayGestureHandler),
- )
+ mockStatusBarStateController,
+ )
controller.init()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -146,14 +150,6 @@
}
@Test
- fun onEntryUpdated_isOngoingCallNotif_swipeGestureCallbackAdded() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- verify(mockSwipeStatusBarAwayGestureHandler)
- .addOnGestureDetectedCallback(anyString(), any())
- }
-
- @Test
fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
@@ -254,17 +250,6 @@
verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
}
- @Test
- fun onEntryUpdated_callNotifAddedThenRemoved_swipeGestureCallbackRemoved() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
-
- notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
-
- verify(mockSwipeStatusBarAwayGestureHandler)
- .removeOnGestureDetectedCallback(anyString())
- }
-
/** Regression test for b/188491504. */
@Test
fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
@@ -455,6 +440,120 @@
// Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
// [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
+ @Test
+ fun callNotificationAdded_chipIsClickable() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_thenCallNotificationAdded_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun callNotificationAdded_thenFullscreenIsTrue_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun fullscreenChangesToFalse_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ // First, update to true
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ // Then, update to false
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ false)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_butChipClickInImmersiveEnabled_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ // Swipe gesture tests
+
+ @Test
+ fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
+ getStateListener().onFullscreenStateChanged(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
+ getStateListener().onFullscreenStateChanged(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler, never())
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ getStateListener().onFullscreenStateChanged(true)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ getStateListener().onFullscreenStateChanged(false)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ @Test
+ fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ // TODO(b/195839150): Add test
+ // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's
+ // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s
+ // callbacks in test.
+
+ // END swipe gesture tests
+
private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle)
private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle)
@@ -479,6 +578,13 @@
}
private fun createNotCallNotifEntry() = NotificationEntryBuilder().build()
+
+ private fun getStateListener(): StatusBarStateController.StateListener {
+ val statusBarStateListenerCaptor = ArgumentCaptor.forClass(
+ StatusBarStateController.StateListener::class.java)
+ verify(mockStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
+ return statusBarStateListenerCaptor.value!!
+ }
}
private val person = Person.Builder().setName("name").build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
new file mode 100644
index 0000000..5129f85
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.os.Handler
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class DeviceProvisionedControllerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val START_USER = 0
+ }
+
+ private lateinit var controller: DeviceProvisionedControllerImpl
+
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var listener: DeviceProvisionedController.DeviceProvisionedListener
+ @Captor
+ private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
+
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ settings = FakeSettings()
+ `when`(userTracker.userId).thenReturn(START_USER)
+
+ controller = DeviceProvisionedControllerImpl(
+ settings,
+ settings,
+ userTracker,
+ dumpManager,
+ Handler(testableLooper.looper),
+ mainExecutor
+ )
+ }
+
+ @Test
+ fun testNotProvisionedByDefault() {
+ init()
+ assertThat(controller.isDeviceProvisioned).isFalse()
+ }
+
+ @Test
+ fun testNotUserSetupByDefault() {
+ init()
+ assertThat(controller.isUserSetup(START_USER)).isFalse()
+ }
+
+ @Test
+ fun testProvisionedWhenCreated() {
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ init()
+
+ assertThat(controller.isDeviceProvisioned).isTrue()
+ }
+
+ @Test
+ fun testUserSetupWhenCreated() {
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ init()
+
+ assertThat(controller.isUserSetup(START_USER))
+ }
+
+ @Test
+ fun testDeviceProvisionedChange() {
+ init()
+
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isDeviceProvisioned).isTrue()
+ }
+
+ @Test
+ fun testUserSetupChange() {
+ init()
+
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isUserSetup(START_USER)).isTrue()
+ }
+
+ @Test
+ fun testUserSetupChange_otherUser() {
+ init()
+ val otherUser = 10
+
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isUserSetup(START_USER)).isFalse()
+ assertThat(controller.isUserSetup(otherUser)).isTrue()
+ }
+
+ @Test
+ fun testCurrentUserSetup() {
+ val otherUser = 10
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+ init()
+
+ assertThat(controller.isCurrentUserSetup).isFalse()
+ switchUser(otherUser)
+ testableLooper.processAllMessages()
+
+ assertThat(controller.isCurrentUserSetup).isTrue()
+ }
+
+ @Test
+ fun testListenerNotCalledOnAdd() {
+ init()
+ controller.addCallback(listener)
+
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onUserSwitched()
+ }
+
+ @Test
+ fun testListenerCalledOnUserSwitched() {
+ init()
+ controller.addCallback(listener)
+
+ switchUser(10)
+
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener).onUserSwitched()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onDeviceProvisionedChanged()
+ }
+
+ @Test
+ fun testListenerCalledOnUserSetupChanged() {
+ init()
+ controller.addCallback(listener)
+
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onUserSwitched()
+ verify(listener).onUserSetupChanged()
+ verify(listener, never()).onDeviceProvisionedChanged()
+ }
+
+ @Test
+ fun testListenerCalledOnDeviceProvisionedChanged() {
+ init()
+ controller.addCallback(listener)
+
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onUserSwitched()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener).onDeviceProvisionedChanged()
+ }
+
+ @Test
+ fun testRemoveListener() {
+ init()
+ controller.addCallback(listener)
+ controller.removeCallback(listener)
+
+ switchUser(10)
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onUserSwitched()
+ }
+
+ private fun init() {
+ controller.init()
+ verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+ }
+
+ private fun switchUser(toUser: Int) {
+ `when`(userTracker.userId).thenReturn(toUser)
+ userTrackerCallbackCaptor.value.onUserChanged(toUser, mContext)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index a846b06..5e3b7d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -34,6 +34,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.LatencyTracker
import com.android.internal.util.UserIcons
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
@@ -83,6 +84,7 @@
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var latencyTracker: LatencyTracker
private lateinit var testableLooper: TestableLooper
private lateinit var uiBgExecutor: FakeExecutor
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -132,6 +134,7 @@
secureSettings,
uiBgExecutor,
interactionJankMonitor,
+ latencyTracker,
dumpManager)
userSwitcherController.mPauseRefreshUsers = true
@@ -156,6 +159,7 @@
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord)
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
+ verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
verify(activityManager).switchUser(guestInfo.id)
assertEquals(1, uiEventLogger.numLogs())
assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
new file mode 100644
index 0000000..c316402
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.unfold
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private val listeners = arrayListOf<TransitionProgressListener>()
+
+ override fun destroy() {
+ listeners.clear()
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners.remove(listener)
+ }
+
+ override fun onTransitionStarted() {
+ listeners.forEach { it.onTransitionStarted() }
+ }
+
+ override fun onTransitionFinished() {
+ listeners.forEach { it.onTransitionFinished() }
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
new file mode 100644
index 0000000..6ec0251
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
@@ -0,0 +1,51 @@
+package com.android.systemui.unfold
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.WallpaperController
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.AdditionalMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var wallpaperController: WallpaperController
+
+ private val progressProvider = TestUnfoldTransitionProvider()
+
+ @JvmField
+ @Rule
+ val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController
+
+ @Before
+ fun setup() {
+ unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider,
+ wallpaperController)
+ unfoldWallpaperController.init()
+ }
+
+ @Test
+ fun onTransitionProgress_zoomsIn() {
+ progressProvider.onTransitionProgress(0.8f)
+
+ verify(wallpaperController).setUnfoldTransitionZoom(eq(0.2f, 0.001f))
+ }
+
+ @Test
+ fun onTransitionFinished_resetsZoom() {
+ progressProvider.onTransitionFinished()
+
+ verify(wallpaperController).setUnfoldTransitionZoom(eq(0f, 0.001f))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
new file mode 100644
index 0000000..a1d9a7b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 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.unfold.updates
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DeviceFoldStateProviderTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var hingeAngleProvider: HingeAngleProvider
+
+ @Mock
+ private lateinit var screenStatusProvider: ScreenStatusProvider
+
+ @Mock
+ private lateinit var deviceStateManager: DeviceStateManager
+
+ private lateinit var foldStateProvider: FoldStateProvider
+
+ private val foldUpdates: MutableList<Int> = arrayListOf()
+ private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
+
+ private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
+ private var foldedDeviceState: Int = 0
+ private var unfoldedDeviceState: Int = 0
+
+ private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val foldedDeviceStates: IntArray = context.resources.getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates)
+ assumeTrue("Test should be launched on a foldable device",
+ foldedDeviceStates.isNotEmpty())
+
+ foldedDeviceState = foldedDeviceStates.maxOrNull()!!
+ unfoldedDeviceState = foldedDeviceState + 1
+
+ foldStateProvider = DeviceFoldStateProvider(
+ context,
+ hingeAngleProvider,
+ screenStatusProvider,
+ deviceStateManager,
+ context.mainExecutor
+ )
+
+ foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
+ override fun onHingeAngleUpdate(angle: Float) {
+ hingeAngleUpdates.add(angle)
+ }
+
+ override fun onFoldUpdate(update: Int) {
+ foldUpdates.add(update)
+ }
+ })
+ foldStateProvider.start()
+
+ verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+ verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
+ }
+
+ @Test
+ fun testOnFolded_emitsFinishClosedEvent() {
+ setFoldState(folded = true)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
+ }
+
+ @Test
+ fun testOnUnfolded_emitsStartOpeningEvent() {
+ setFoldState(folded = false)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
+ }
+
+ @Test
+ fun testOnFolded_stopsHingeAngleProvider() {
+ setFoldState(folded = true)
+
+ verify(hingeAngleProvider).stop()
+ }
+
+ @Test
+ fun testOnUnfolded_startsHingeAngleProvider() {
+ setFoldState(folded = false)
+
+ verify(hingeAngleProvider).start()
+ }
+
+ @Test
+ fun testFirstScreenOnEventWhenFolded_doesNotEmitEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ // Power button turn on
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ @Test
+ fun testFirstScreenOnEventWhenUnfolded_doesNotEmitEvents() {
+ setFoldState(folded = false)
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ @Test
+ fun testFirstScreenOnEventAfterFoldAndUnfold_emitsUnfoldedScreenAvailableEvent() {
+ setFoldState(folded = false)
+ setFoldState(folded = true)
+ fireScreenOnEvent()
+ setFoldState(folded = false)
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+ }
+
+ @Test
+ fun testSecondScreenOnEventWhenUnfolded_doesNotEmitEvents() {
+ setFoldState(folded = false)
+ fireScreenOnEvent()
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ // No events as this is power button turn on
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ private fun setFoldState(folded: Boolean) {
+ val state = if (folded) foldedDeviceState else unfoldedDeviceState
+ foldStateListenerCaptor.value.onStateChanged(state)
+ }
+
+ private fun fireScreenOnEvent() {
+ screenOnListenerCaptor.value.onScreenTurnedOn()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
new file mode 100644
index 0000000..a3f17aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 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.unfold.util
+
+import android.testing.AndroidTestingRunner
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var windowManager: IWindowManager
+
+ @Mock
+ lateinit var sourceProvider: UnfoldTransitionProgressProvider
+
+ @Mock
+ lateinit var transitionListener: TransitionProgressListener
+
+ lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+
+ private val sourceProviderListenerCaptor =
+ ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+ private val rotationWatcherCaptor =
+ ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ progressProvider = NaturalRotationUnfoldProgressProvider(
+ context,
+ windowManager,
+ sourceProvider
+ )
+
+ progressProvider.init()
+
+ verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
+ verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
+
+ progressProvider.addCallback(transitionListener)
+ }
+
+ @Test
+ fun testNaturalRotation0_sendTransitionStartedEvent_eventReceived() {
+ onRotationChanged(Surface.ROTATION_0)
+
+ source.onTransitionStarted()
+
+ verify(transitionListener).onTransitionStarted()
+ }
+
+ @Test
+ fun testNaturalRotation0_sendTransitionProgressEvent_eventReceived() {
+ onRotationChanged(Surface.ROTATION_0)
+
+ source.onTransitionProgress(0.5f)
+
+ verify(transitionListener).onTransitionProgress(0.5f)
+ }
+
+ @Test
+ fun testNotNaturalRotation90_sendTransitionStartedEvent_eventNotReceived() {
+ onRotationChanged(Surface.ROTATION_90)
+
+ source.onTransitionStarted()
+
+ verify(transitionListener, never()).onTransitionStarted()
+ }
+
+ @Test
+ fun testNaturalRotation90_sendTransitionProgressEvent_eventNotReceived() {
+ onRotationChanged(Surface.ROTATION_90)
+
+ source.onTransitionProgress(0.5f)
+
+ verify(transitionListener, never()).onTransitionProgress(0.5f)
+ }
+
+ @Test
+ fun testRotationBecameUnnaturalDuringTransition_sendsTransitionFinishedEvent() {
+ onRotationChanged(Surface.ROTATION_0)
+ source.onTransitionStarted()
+ clearInvocations(transitionListener)
+
+ onRotationChanged(Surface.ROTATION_90)
+
+ verify(transitionListener).onTransitionFinished()
+ }
+
+ @Test
+ fun testRotationBecameNaturalDuringTransition_sendsTransitionStartedEvent() {
+ onRotationChanged(Surface.ROTATION_90)
+ source.onTransitionStarted()
+ clearInvocations(transitionListener)
+
+ onRotationChanged(Surface.ROTATION_0)
+
+ verify(transitionListener).onTransitionStarted()
+ }
+
+ private fun onRotationChanged(rotation: Int) {
+ rotationWatcherCaptor.value.onRotationChanged(rotation)
+ }
+
+ private val source: TransitionProgressListener
+ get() = sourceProviderListenerCaptor.value
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
new file mode 100644
index 0000000..2662da2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListenerSetTest : SysuiTestCase() {
+
+ var runnableSet: ListenerSet<Runnable> = ListenerSet()
+
+ @Before
+ fun setup() {
+ runnableSet = ListenerSet()
+ }
+
+ @Test
+ fun addIfAbsent_doesNotDoubleAdd() {
+ // setup & preconditions
+ val runnable1 = Runnable { }
+ val runnable2 = Runnable { }
+ assertThat(runnableSet.toList()).isEmpty()
+
+ // Test that an element can be added
+ assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+ assertThat(runnableSet.toList()).containsExactly(runnable1)
+
+ // Test that a second element can be added
+ assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that re-adding the first element does nothing and returns false
+ assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ }
+
+ @Test
+ fun remove_removesListener() {
+ // setup and preconditions
+ val runnable1 = Runnable { }
+ val runnable2 = Runnable { }
+ assertThat(runnableSet.toList()).isEmpty()
+ runnableSet.addIfAbsent(runnable1)
+ runnableSet.addIfAbsent(runnable2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that removing the first runnable only removes that one runnable
+ assertThat(runnableSet.remove(runnable1)).isTrue()
+ assertThat(runnableSet.toList()).containsExactly(runnable2)
+
+ // Test that removing a non-present runnable does not error
+ assertThat(runnableSet.remove(runnable1)).isFalse()
+ assertThat(runnableSet.toList()).containsExactly(runnable2)
+
+ // Test that removing the other runnable succeeds
+ assertThat(runnableSet.remove(runnable2)).isTrue()
+ assertThat(runnableSet.toList()).isEmpty()
+ }
+
+ @Test
+ fun remove_isReentrantSafe() {
+ // Setup and preconditions
+ val runnablesCalled = mutableListOf<Int>()
+ // runnable1 is configured to remove itself
+ val runnable1 = object : Runnable {
+ override fun run() {
+ runnableSet.remove(this)
+ runnablesCalled.add(1)
+ }
+ }
+ val runnable2 = Runnable {
+ runnablesCalled.add(2)
+ }
+ assertThat(runnableSet.toList()).isEmpty()
+ runnableSet.addIfAbsent(runnable1)
+ runnableSet.addIfAbsent(runnable2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that both runnables are called and 1 was removed
+ for (runnable in runnableSet) {
+ runnable.run()
+ }
+ assertThat(runnablesCalled).containsExactly(1, 2)
+ assertThat(runnableSet.toList()).containsExactly(runnable2)
+ }
+
+ @Test
+ fun addIfAbsent_isReentrantSafe() {
+ // Setup and preconditions
+ val runnablesCalled = mutableListOf<Int>()
+ val runnable99 = Runnable {
+ runnablesCalled.add(99)
+ }
+ // runnable1 is configured to add runnable99
+ val runnable1 = Runnable {
+ runnableSet.addIfAbsent(runnable99)
+ runnablesCalled.add(1)
+ }
+ val runnable2 = Runnable {
+ runnablesCalled.add(2)
+ }
+ assertThat(runnableSet.toList()).isEmpty()
+ runnableSet.addIfAbsent(runnable1)
+ runnableSet.addIfAbsent(runnable2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that both original runnables are called and 99 was added but not called
+ for (runnable in runnableSet) {
+ runnable.run()
+ }
+ assertThat(runnablesCalled).containsExactly(1, 2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 6e73827..197873f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -55,6 +55,7 @@
private final FakeProximitySensor mFakeProximitySensor;
private final FakeGenericSensor mFakeLightSensor;
+ private final FakeGenericSensor mFakeLightSensor2;
private final FakeGenericSensor mFakeTapSensor;
private final FakeGenericSensor[] mSensors;
@@ -70,7 +71,8 @@
mSensors = new FakeGenericSensor[]{
mFakeProximitySensor = new FakeProximitySensor(proxSensor),
mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
- mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
+ mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE)),
+ mFakeLightSensor2 = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null))
};
}
@@ -82,6 +84,10 @@
return mFakeLightSensor;
}
+ public FakeGenericSensor getFakeLightSensor2() {
+ return mFakeLightSensor2;
+ }
+
public FakeGenericSensor getFakeTapSensor() {
return mFakeTapSensor;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 7bb2674..e66491e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -123,11 +123,11 @@
Uri uri = getUriFor(name);
for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), userHandle);
+ observer.dispatchChange(false, List.of(uri), 0, userHandle);
}
for (ContentObserver observer :
mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), userHandle);
+ observer.dispatchChange(false, List.of(uri), 0, userHandle);
}
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 34cae58..f65caee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -86,7 +87,8 @@
mFakeSettings.putString("cat", "hat");
- verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+ anyInt());
}
@Test
@@ -96,7 +98,8 @@
mFakeSettings.putString("cat", "hat");
- verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+ anyInt());
}
@Test
@@ -119,6 +122,18 @@
mFakeSettings.putString("cat", "hat");
verify(mContentObserver, never()).dispatchChange(
- anyBoolean(), any(Collection.class), anyInt());
+ anyBoolean(), any(Collection.class), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testContentObserverDispatchCorrectUser() {
+ int user = 10;
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL
+ );
+
+ mFakeSettings.putStringForUser("cat", "hat", user);
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+ eq(user));
}
}
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 9f755f7..1159e09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -179,6 +179,7 @@
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
+ private boolean mSysUiStateBubblesManageMenuExpanded;
@Captor
private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -295,9 +296,13 @@
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
mSysUiState = new SysUiState();
- mSysUiState.addCallback(sysUiFlags ->
- mSysUiStateBubblesExpanded =
- (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
+ mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiStateBubblesManageMenuExpanded =
+ (sysUiFlags
+ & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+ mSysUiStateBubblesExpanded =
+ (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ });
// TODO: Fix
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
@@ -372,8 +377,7 @@
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -381,7 +385,7 @@
assertFalse(mBubbleController.hasBubbles());
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -396,7 +400,7 @@
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -459,7 +463,7 @@
verify(mNotificationEntryManager, never()).performRemoveNotification(
eq(mRow.getSbn()), any(), anyInt());
assertFalse(mBubbleController.hasBubbles());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertTrue(mRow.isBubble());
}
@@ -478,7 +482,7 @@
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -498,8 +502,7 @@
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Make sure the notif is suppressed
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -508,8 +511,7 @@
mBubbleController.collapseStack();
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertStackCollapsed();
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -532,8 +534,7 @@
assertStackExpanded();
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow2.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Last added is the one that is expanded
assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -557,8 +558,7 @@
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -578,8 +578,7 @@
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -604,8 +603,7 @@
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -634,7 +632,7 @@
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -666,7 +664,7 @@
assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -679,7 +677,7 @@
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -694,7 +692,7 @@
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertFalse(mBubbleController.hasBubbles());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -711,8 +709,7 @@
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
mRow.getKey());
assertStackCollapsed();
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -728,8 +725,7 @@
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
mRow.getKey());
assertStackExpanded();
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -746,8 +742,7 @@
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -769,8 +764,7 @@
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -784,7 +778,7 @@
mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -1206,6 +1200,63 @@
assertNotNull(info);
}
+ @Test
+ public void testShowManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testHideManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Hide the menu
+ stackView.showManageMenu(false);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testCollapseBubbleManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Collapse the stack
+ mBubbleData.setExpanded(false);
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
@@ -1303,4 +1354,12 @@
assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
entry.getKey(), entry.getGroupKey()));
}
+
+ /**
+ * Asserts that the system ui states associated to bubbles are in the correct state.
+ */
+ private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
+ assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
+ assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index a3bbb26..05c4822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -68,6 +68,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -160,7 +161,9 @@
@Mock
private AuthController mAuthController;
- private SysUiState mSysUiState = new SysUiState();
+ private SysUiState mSysUiState;
+ private boolean mSysUiStateBubblesExpanded;
+ private boolean mSysUiStateBubblesManageMenuExpanded;
@Captor
private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
@@ -257,6 +260,15 @@
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
+ mSysUiState = new SysUiState();
+ mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiStateBubblesManageMenuExpanded =
+ (sysUiFlags
+ & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+ mSysUiStateBubblesExpanded =
+ (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ });
+
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
@@ -325,6 +337,7 @@
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -332,6 +345,7 @@
assertFalse(mBubbleController.hasBubbles());
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -345,6 +359,8 @@
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -407,6 +423,8 @@
verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -425,6 +443,7 @@
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Make sure the notif is suppressed
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -433,6 +452,7 @@
mBubbleController.collapseStack();
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertStackCollapsed();
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -455,6 +475,7 @@
assertStackExpanded();
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow2.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Last added is the one that is expanded
assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -479,6 +500,7 @@
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -498,6 +520,7 @@
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -522,6 +545,7 @@
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -550,6 +574,8 @@
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -580,6 +606,7 @@
assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
assertTrue(mBubbleController.hasBubbles());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -592,6 +619,7 @@
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -606,6 +634,7 @@
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertFalse(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@@ -623,6 +652,7 @@
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
mRow.getKey());
assertStackCollapsed();
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -638,6 +668,7 @@
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
mRow.getKey());
assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -654,6 +685,7 @@
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -675,6 +707,7 @@
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -980,6 +1013,63 @@
verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
}
+ @Test
+ public void testShowManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testHideManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Hide the menu
+ stackView.showManageMenu(false);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testCollapseBubbleManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Collapse the stack
+ mBubbleData.setExpanded(false);
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
@@ -1034,4 +1124,12 @@
assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
entry.getKey(), entry.getGroupKey()));
}
+
+ /**
+ * Asserts that the system ui states associated to bubbles are in the correct state.
+ */
+ private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
+ assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
+ assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
+ }
}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 46bda06..27d4ea7 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import android.webkit.PacProcessor;
@@ -33,16 +34,44 @@
public class PacService extends Service {
private static final String TAG = "PacService";
- private Object mLock = new Object();
+ private final Object mLock = new Object();
+ // Webkit PacProcessor cannot be instantiated before the user is unlocked, so this field is
+ // initialized lazily.
@GuardedBy("mLock")
- private final PacProcessor mPacProcessor = PacProcessor.getInstance();
+ private PacProcessor mPacProcessor;
+
+ // Stores PAC script when setPacFile is called before mPacProcessor is available. In case the
+ // script was already fed to the PacProcessor, it should be null.
+ @GuardedBy("mLock")
+ private String mPendingScript;
private ProxyServiceStub mStub = new ProxyServiceStub();
@Override
public void onCreate() {
super.onCreate();
+
+ synchronized (mLock) {
+ checkPacProcessorLocked();
+ }
+ }
+
+ /**
+ * Initializes PacProcessor if it hasn't been initialized yet and if the system user is
+ * unlocked, e.g. after the user has entered their PIN after a reboot.
+ * Returns whether PacProcessor is available.
+ */
+ private boolean checkPacProcessorLocked() {
+ if (mPacProcessor != null) {
+ return true;
+ }
+ UserManager um = getSystemService(UserManager.class);
+ if (um.isUserUnlocked()) {
+ mPacProcessor = PacProcessor.getInstance();
+ return true;
+ }
+ return false;
}
@Override
@@ -74,7 +103,20 @@
}
synchronized (mLock) {
- return mPacProcessor.findProxyForUrl(url);
+ if (checkPacProcessorLocked()) {
+ // Apply pending script in case it was set before processor was ready.
+ if (mPendingScript != null) {
+ if (!mPacProcessor.setProxyScript(mPendingScript)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ mPendingScript = null;
+ }
+ return mPacProcessor.findProxyForUrl(url);
+ } else {
+ Log.e(TAG, "PacProcessor isn't ready during early boot,"
+ + " request will be direct");
+ return null;
+ }
}
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL was passed");
@@ -88,8 +130,13 @@
throw new SecurityException();
}
synchronized (mLock) {
- if (!mPacProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
+ if (checkPacProcessorLocked()) {
+ if (!mPacProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ } else {
+ Log.d(TAG, "PAC processor isn't ready, saving script for later.");
+ mPendingScript = script;
}
}
}
diff --git a/proto/src/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto
similarity index 97%
rename from proto/src/critical_event_log.proto
rename to proto/src/criticalevents/critical_event_log.proto
index cb05a71..27787c2 100644
--- a/proto/src/critical_event_log.proto
+++ b/proto/src/criticalevents/critical_event_log.proto
@@ -15,7 +15,7 @@
*/
syntax = "proto2";
-package com.android.server.am;
+package com.android.server.criticalevents;
option java_multiple_files = true;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aff7eb2..8205d35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2575,7 +2575,8 @@
final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
|| userState.isDisplayMagnificationEnabledLocked())
&& (userState.getMagnificationCapabilitiesLocked()
- != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
+ || userHasMagnificationServicesLocked(userState);
getWindowMagnificationMgr().requestConnection(connect);
}
@@ -3564,7 +3565,12 @@
}
}
- ArrayList<Display> getValidDisplayList() {
+ /**
+ * Gets all currently valid logical displays.
+ *
+ * @return An array list containing all valid logical displays.
+ */
+ public ArrayList<Display> getValidDisplayList() {
synchronized (mLock) {
return mDisplaysList;
}
diff --git a/services/companion/OWNERS b/services/companion/OWNERS
new file mode 100644
index 0000000..cb4cc56
--- /dev/null
+++ b/services/companion/OWNERS
@@ -0,0 +1,4 @@
+evanxinchen@google.com
+ewol@google.com
+guojing@google.com
+svetoslavganov@google.com
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 7a38a02..a48172b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,16 +17,16 @@
package com.android.server.companion;
-import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
-import static android.content.Context.BIND_IMPORTANT;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
+import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
-import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.CollectionUtils.add;
import static com.android.internal.util.CollectionUtils.any;
-import static com.android.internal.util.CollectionUtils.emptyIfNull;
import static com.android.internal.util.CollectionUtils.filter;
import static com.android.internal.util.CollectionUtils.find;
import static com.android.internal.util.CollectionUtils.forEach;
@@ -38,6 +38,9 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -46,6 +49,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@@ -61,11 +65,10 @@
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
-import android.companion.CompanionDeviceService;
+import android.companion.DeviceId;
import android.companion.DeviceNotAssociatedException;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceManager;
-import android.companion.ICompanionDeviceService;
import android.companion.IFindDeviceCallback;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -78,7 +81,6 @@
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.net.NetworkPolicyManager;
@@ -94,20 +96,17 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
-import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionControllerManager;
import android.text.BidiFormatter;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.AtomicFile;
import android.util.ExceptionUtils;
-import android.util.Log;
import android.util.PackageUtils;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.Xml;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
@@ -127,45 +126,43 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
+import java.util.function.Predicate;
/** @hide */
@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
+ static final String LOG_TAG = "CompanionDeviceManagerService";
+ static final boolean DEBUG = false;
private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
static {
final Map<String, String> map = new ArrayMap<>();
- map.put(AssociationRequest.DEVICE_PROFILE_WATCH,
- Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
+ map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
+ map.put(DEVICE_PROFILE_APP_STREAMING,
+ Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
- DEVICE_PROFILE_TO_PERMISSION = Collections.unmodifiableMap(map);
+ DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
}
+ /** Range of Association IDs allocated for a user.*/
+ static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
+
private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
".CompanionDeviceDiscoveryService");
@@ -173,10 +170,7 @@
private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000;
private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000;
- private static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000;
-
- private static final boolean DEBUG = false;
- private static final String LOG_TAG = "CompanionDeviceManagerService";
+ static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000;
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
@@ -186,27 +180,16 @@
private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
- private static final String XML_TAG_ASSOCIATIONS = "associations";
- private static final String XML_TAG_ASSOCIATION = "association";
- private static final String XML_ATTR_PACKAGE = "package";
- private static final String XML_ATTR_DEVICE = "device";
- private static final String XML_ATTR_PROFILE = "profile";
- private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
- private static final String XML_ATTR_TIME_APPROVED = "time_approved";
- private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";
-
private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static {
sDateFormat.setTimeZone(TimeZone.getDefault());
}
private final CompanionDeviceManagerImpl mImpl;
- private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
+ // Persistent data store for all Associations.
+ private final PersistentDataStore mPersistentDataStore;
private PowerWhitelistManager mPowerWhitelistManager;
private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
- /** userId -> packageName -> serviceConnector */
- private PerUser<ArrayMap<String, ServiceConnector<ICompanionDeviceService>>>
- mDeviceListenerServiceConnectors;
private IAppOpsService mAppOpsManager;
private RoleManager mRoleManager;
private BluetoothAdapter mBluetoothAdapter;
@@ -231,10 +214,19 @@
private final Object mLock = new Object();
private final Handler mMainHandler = Handler.getMain();
+ private CompanionDevicePresenceController mCompanionDevicePresenceController;
- /** userId -> [association] */
+ /** Maps a {@link UserIdInt} to a set of associations for the user. */
@GuardedBy("mLock")
- private @Nullable SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();
+ private final SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();
+ /**
+ * A structure that consist of two nested maps, and effectively maps (userId + packageName) to
+ * a list of IDs that have been previously assigned to associations for that package.
+ * We maintain this structure so that we never re-use association IDs for the same package
+ * (until it's uninstalled).
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
ActivityTaskManagerInternal mAtmInternal;
ActivityManagerInternal mAmInternal;
@@ -243,6 +235,8 @@
public CompanionDeviceManagerService(Context context) {
super(context);
mImpl = new CompanionDeviceManagerImpl();
+ mPersistentDataStore = new PersistentDataStore();
+
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mRoleManager = context.getSystemService(RoleManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
@@ -253,6 +247,7 @@
mPermissionControllerManager = requireNonNull(
context.getSystemService(PermissionControllerManager.class));
mUserManager = context.getSystemService(UserManager.class);
+ mCompanionDevicePresenceController = new CompanionDevicePresenceController();
Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -265,16 +260,6 @@
}
};
- mDeviceListenerServiceConnectors = new PerUser<ArrayMap<String,
- ServiceConnector<ICompanionDeviceService>>>() {
- @NonNull
- @Override
- protected ArrayMap<String, ServiceConnector<ICompanionDeviceService>> create(
- int userId) {
- return new ArrayMap<>();
- }
- };
-
registerPackageMonitor();
}
@@ -286,11 +271,11 @@
+ ", uid = " + uid + ")");
int userId = getChangingUserId();
updateAssociations(
- as -> CollectionUtils.filter(as,
- a -> !Objects.equals(a.getPackageName(), packageName)),
+ set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)),
userId);
- unbindDevicePresenceListener(packageName, userId);
+ mCompanionDevicePresenceController.unbindDevicePresenceListener(
+ packageName, userId);
}
@Override
@@ -305,15 +290,6 @@
}.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
}
- private void unbindDevicePresenceListener(String packageName, int userId) {
- ServiceConnector<ICompanionDeviceService> deviceListener =
- mDeviceListenerServiceConnectors.forUser(userId)
- .remove(packageName);
- if (deviceListener != null) {
- deviceListener.unbind();
- }
- }
-
@Override
public void onStart() {
publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
@@ -469,9 +445,8 @@
}, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
if (err == null) {
- Association association = new Association(userId, deviceAddress, callingPackage,
- deviceProfile, false, System.currentTimeMillis());
- addAssociation(association, userId);
+ createAssociationInternal(
+ userId, deviceAddress, callingPackage, deviceProfile);
} else {
Slog.e(LOG_TAG, "Failed to discover device(s)", err);
callback.onFailure("No devices found: " + err.getMessage());
@@ -551,6 +526,12 @@
// Device profile can be null.
if (deviceProfile == null) return;
+ if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
+ // TODO: remove, when properly supporting this profile.
+ throw new UnsupportedOperationException(
+ "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
+ }
+
if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
}
@@ -649,7 +630,7 @@
checkCallerIsSystemOr(packageName);
int userId = getCallingUserId();
- Set<Association> deviceAssociations = CollectionUtils.filter(
+ Set<Association> deviceAssociations = filter(
getAllAssociations(userId, packageName),
association -> deviceAddress.equals(association.getDeviceMacAddress()));
@@ -662,16 +643,9 @@
updateAssociations(associations -> map(associations, association -> {
if (Objects.equals(association.getPackageName(), packageName)
&& Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
- return new Association(
- association.getUserId(),
- association.getDeviceMacAddress(),
- association.getPackageName(),
- association.getDeviceProfile(),
- active /* notifyOnDeviceNearby */,
- association.getTimeApprovedMs());
- } else {
- return association;
+ association.setNotifyOnDeviceNearby(active);
}
+ return association;
}), userId);
restartBleScan();
@@ -689,9 +663,7 @@
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");
- addAssociation(new Association(
- userId, macAddress, packageName, null, false,
- System.currentTimeMillis()), userId);
+ createAssociationInternal(userId, macAddress, packageName, null);
}
private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
@@ -722,7 +694,7 @@
@Override
public boolean canPairWithoutPrompt(
String packageName, String deviceMacAddress, int userId) {
- return CollectionUtils.any(
+ return any(
getAllAssociations(userId, packageName, deviceMacAddress),
a -> System.currentTimeMillis() - a.getTimeApprovedMs()
< PAIR_WITHOUT_PROMPT_WINDOW_MS);
@@ -732,7 +704,10 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
- new ShellCmd().exec(this, in, out, err, args, callback, resultReceiver);
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_COMPANION_DEVICES, null);
+ new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
@Override
@@ -775,11 +750,13 @@
}
fout.append("Device Listener Services State:").append('\n');
- for (int i = 0, size = mDeviceListenerServiceConnectors.size(); i < size; i++) {
- int userId = mDeviceListenerServiceConnectors.keyAt(i);
+ for (int i = 0, size = mCompanionDevicePresenceController.mBoundServices.size();
+ i < size; i++) {
+ int userId = mCompanionDevicePresenceController.mBoundServices.keyAt(i);
fout.append(" ")
.append("u").append(Integer.toString(userId)).append(": ")
- .append(Objects.toString(mDeviceListenerServiceConnectors.valueAt(i)))
+ .append(Objects.toString(
+ mCompanionDevicePresenceController.mBoundServices.valueAt(i)))
.append('\n');
}
}
@@ -793,32 +770,91 @@
return Binder.getCallingUid() == Process.SYSTEM_UID;
}
- void addAssociation(Association association, int userId) {
+ void createAssociationInternal(
+ int userId, String deviceMacAddress, String packageName, String deviceProfile) {
+ final Association association = new Association(
+ getNewAssociationIdForPackage(userId, packageName),
+ userId,
+ packageName,
+ Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceMacAddress)),
+ deviceProfile,
+ /* managedByCompanionApp */false,
+ /* notifyOnDeviceNearby */ false ,
+ System.currentTimeMillis());
+
updateSpecialAccessPermissionForAssociatedPackage(association);
recordAssociation(association, userId);
}
- void removeAssociation(int userId, String pkg, String deviceMacAddress) {
- updateAssociations(associations -> CollectionUtils.filter(associations, association -> {
- boolean notMatch = association.getUserId() != userId
- || !Objects.equals(association.getDeviceMacAddress(), deviceMacAddress)
- || !Objects.equals(association.getPackageName(), pkg);
- if (!notMatch) {
- onAssociationPreRemove(association);
+ @GuardedBy("mLock")
+ @NonNull
+ private Set<Integer> getPreviouslyUsedIdsForPackageLocked(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final Set<Integer> previouslyUsedIds = mPreviouslyUsedIds.get(userId).get(packageName);
+ if (previouslyUsedIds != null) return previouslyUsedIds;
+ return emptySet();
+ }
+
+ private int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mLock) {
+ readPersistedStateForUserIfNeededLocked(userId);
+
+ // First: collect all IDs currently in use for this user's Associations.
+ final SparseBooleanArray usedIds = new SparseBooleanArray();
+ for (Association it : getAllAssociations(userId)) {
+ usedIds.put(it.getAssociationId(), true);
}
- return notMatch;
+
+ // Second: collect all IDs that have been previously used for this package (and user).
+ final Set<Integer> previouslyUsedIds =
+ getPreviouslyUsedIdsForPackageLocked(userId, packageName);
+
+ int id = getFirstAssociationIdForUser(userId);
+ final int lastAvailableIdForUser = getLastAssociationIdForUser(userId);
+
+ // Find first ID that isn't used now AND has never been used for the given package.
+ while (usedIds.get(id) || previouslyUsedIds.contains(id)) {
+ // Increment and try again
+ id++;
+ // ... but first check if the ID is valid (within the range allocated to the user).
+ if (id > lastAvailableIdForUser) {
+ throw new RuntimeException("Cannot create a new Association ID for "
+ + packageName + " for user " + userId);
+ }
+ }
+
+ return id;
+ }
+ }
+
+ void removeAssociation(int userId, String packageName, String deviceMacAddress) {
+ updateAssociations(associations -> filterOut(associations, it -> {
+ final boolean match = it.belongsToPackage(userId, packageName)
+ && Objects.equals(it.getDeviceMacAddress(), deviceMacAddress);
+ if (match) {
+ onAssociationPreRemove(it);
+ markIdAsPreviouslyUsedForPackage(it.getAssociationId(), userId, packageName);
+ }
+ return match;
}), userId);
restartBleScan();
}
+ private void markIdAsPreviouslyUsedForPackage(
+ int associationId, @UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mLock) {
+ // Mark as previously used.
+ readPersistedStateForUserIfNeededLocked(userId);
+ mPreviouslyUsedIds.get(userId)
+ .computeIfAbsent(packageName, it -> new HashSet<>())
+ .add(associationId);
+ }
+ }
+
void onAssociationPreRemove(Association association) {
if (association.isNotifyOnDeviceNearby()) {
- ServiceConnector<ICompanionDeviceService> serviceConnector =
- mDeviceListenerServiceConnectors.forUser(association.getUserId())
- .get(association.getPackageName());
- if (serviceConnector != null) {
- serviceConnector.unbind();
- }
+ mCompanionDevicePresenceController.unbindDevicePresenceListener(
+ association.getPackageName(), association.getUserId());
}
String deviceProfile = association.getDeviceProfile();
@@ -1001,22 +1037,31 @@
private void recordAssociation(Association association, int userId) {
Slog.i(LOG_TAG, "recordAssociation(" + association + ")");
- updateAssociations(associations -> CollectionUtils.add(associations, association), userId);
+ updateAssociations(associations -> add(associations, association), userId);
}
private void updateAssociations(Function<Set<Association>, Set<Association>> update,
int userId) {
synchronized (mLock) {
- final Set<Association> old = getAllAssociations(userId);
- Set<Association> associations = new ArraySet<>(old);
- associations = update.apply(associations);
- Slog.i(LOG_TAG, "Updating associations: " + old + " --> " + associations);
- mCachedAssociations.put(userId, Collections.unmodifiableSet(associations));
- BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
- CompanionDeviceManagerService::persistAssociations,
- this, associations, userId));
+ if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set...");
- updateAtm(userId, associations);
+ final Set<Association> prevAssociations = getAllAssociations(userId);
+ if (DEBUG) Slog.d(LOG_TAG, " > Before : " + prevAssociations + "...");
+
+ final Set<Association> updatedAssociations = update.apply(
+ new ArraySet<>(prevAssociations));
+ if (DEBUG) Slog.d(LOG_TAG, " > After: " + updatedAssociations);
+
+ mCachedAssociations.put(userId, unmodifiableSet(updatedAssociations));
+
+ BackgroundThread.getHandler().sendMessage(
+ PooledLambda.obtainMessage(
+ (associations, usedIds) ->
+ mPersistentDataStore
+ .persistStateForUser(userId, associations, usedIds),
+ updatedAssociations, deepCopy(mPreviouslyUsedIds.get(userId))));
+
+ updateAtm(userId, updatedAssociations);
}
}
@@ -1038,64 +1083,34 @@
}
}
- private void persistAssociations(Set<Association> associations, int userId) {
- Slog.i(LOG_TAG, "Writing associations to disk: " + associations);
- final AtomicFile file = getStorageFileForUser(userId);
- synchronized (file) {
- file.write(out -> {
- XmlSerializer xml = Xml.newSerializer();
- try {
- xml.setOutput(out, StandardCharsets.UTF_8.name());
- xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- xml.startDocument(null, true);
- xml.startTag(null, XML_TAG_ASSOCIATIONS);
-
- forEach(associations, association -> {
- XmlSerializer tag = xml.startTag(null, XML_TAG_ASSOCIATION)
- .attribute(null, XML_ATTR_PACKAGE, association.getPackageName())
- .attribute(null, XML_ATTR_DEVICE,
- association.getDeviceMacAddress());
- if (association.getDeviceProfile() != null) {
- tag.attribute(null, XML_ATTR_PROFILE, association.getDeviceProfile());
- tag.attribute(null, XML_ATTR_NOTIFY_DEVICE_NEARBY,
- Boolean.toString(
- association.isNotifyOnDeviceNearby()));
- }
- tag.attribute(null, XML_ATTR_TIME_APPROVED,
- Long.toString(association.getTimeApprovedMs()));
- tag.endTag(null, XML_TAG_ASSOCIATION);
- });
-
- xml.endTag(null, XML_TAG_ASSOCIATIONS);
- xml.endDocument();
- } catch (Exception e) {
- Slog.e(LOG_TAG, "Error while writing associations file", e);
- throw ExceptionUtils.propagate(e);
- }
- });
- }
- }
-
- private AtomicFile getStorageFileForUser(int userId) {
- return mUidToStorage.computeIfAbsent(userId, (u) ->
- new AtomicFile(new File(
- //TODO deprecated method - what's the right replacement?
- Environment.getUserSystemDirectory(u),
- XML_FILE_NAME)));
- }
-
- @Nullable
- private Set<Association> getAllAssociations(int userId) {
+ @NonNull Set<Association> getAllAssociations(int userId) {
synchronized (mLock) {
- if (mCachedAssociations.get(userId) == null) {
- mCachedAssociations.put(userId, Collections.unmodifiableSet(
- emptyIfNull(readAllAssociations(userId))));
- Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations);
- }
+ readPersistedStateForUserIfNeededLocked(userId);
+ // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method
+ // we just called adds an empty set, if there was no previously saved data.
return mCachedAssociations.get(userId);
}
}
+ @GuardedBy("mLock")
+ private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) {
+ if (mCachedAssociations.get(userId) != null) return;
+
+ Slog.i(LOG_TAG, "Reading state for user " + userId + " from the disk");
+
+ final Set<Association> associations = new ArraySet<>();
+ final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
+ mPersistentDataStore.readStateForUser(userId, associations, previouslyUsedIds);
+
+ if (DEBUG) {
+ Slog.d(LOG_TAG, " > associations=" + associations + "\n"
+ + " > previouslyUsedIds=" + previouslyUsedIds);
+ }
+
+ mCachedAssociations.put(userId, unmodifiableSet(associations));
+ mPreviouslyUsedIds.append(userId, previouslyUsedIds);
+ }
+
private List<UserInfo> getAllUsers() {
final long identity = Binder.clearCallingIdentity();
try {
@@ -1106,7 +1121,7 @@
}
private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
- return CollectionUtils.filter(
+ return filter(
getAllAssociations(userId),
// Null filter == get all associations
a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName()));
@@ -1125,10 +1140,9 @@
}
}
-
private Set<Association> getAllAssociations(
int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
- return CollectionUtils.filter(
+ return filter(
getAllAssociations(userId),
// Null filter == get all associations
a -> (packageFilter == null || Objects.equals(packageFilter, a.getPackageName()))
@@ -1136,44 +1150,6 @@
|| Objects.equals(addressFilter, a.getDeviceMacAddress())));
}
- private Set<Association> readAllAssociations(int userId) {
- final AtomicFile file = getStorageFileForUser(userId);
-
- if (!file.getBaseFile().exists()) return null;
-
- ArraySet<Association> result = null;
- final XmlPullParser parser = Xml.newPullParser();
- synchronized (file) {
- try (FileInputStream in = file.openRead()) {
- parser.setInput(in, StandardCharsets.UTF_8.name());
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (type != XmlPullParser.START_TAG
- && !XML_TAG_ASSOCIATIONS.equals(parser.getName())) continue;
-
- final String appPackage = parser.getAttributeValue(null, XML_ATTR_PACKAGE);
- final String deviceAddress = parser.getAttributeValue(null, XML_ATTR_DEVICE);
-
- final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE);
- final boolean persistentGrants = Boolean.valueOf(
- parser.getAttributeValue(null, XML_ATTR_NOTIFY_DEVICE_NEARBY));
- final long timeApproved = parseLongOrDefault(
- parser.getAttributeValue(null, XML_ATTR_TIME_APPROVED), 0L);
-
- if (appPackage == null || deviceAddress == null) continue;
-
- result = ArrayUtils.add(result,
- new Association(userId, deviceAddress, appPackage,
- profile, persistentGrants, timeApproved));
- }
- return result;
- } catch (XmlPullParserException | IOException e) {
- Slog.e(LOG_TAG, "Error while reading associations file", e);
- return null;
- }
- }
- }
-
void onDeviceConnected(String address) {
Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
@@ -1233,55 +1209,6 @@
>= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS;
}
- private ServiceConnector<ICompanionDeviceService> getDeviceListenerServiceConnector(
- Association a) {
- return mDeviceListenerServiceConnectors.forUser(a.getUserId()).computeIfAbsent(
- a.getPackageName(),
- pkg -> createDeviceListenerServiceConnector(a));
- }
-
- private ServiceConnector<ICompanionDeviceService> createDeviceListenerServiceConnector(
- Association a) {
- List<ResolveInfo> resolveInfos = getContext().getPackageManager().queryIntentServicesAsUser(
- new Intent(CompanionDeviceService.SERVICE_INTERFACE), MATCH_ALL, a.getUserId());
- List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
- info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
- if (packageResolveInfos.size() != 1) {
- Slog.w(LOG_TAG, "Device presence listener package must have exactly one "
- + "CompanionDeviceService, but " + a.getPackageName()
- + " has " + packageResolveInfos.size());
- return new ServiceConnector.NoOp<>();
- }
- String servicePermission = packageResolveInfos.get(0).serviceInfo.permission;
- if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) {
- Slog.w(LOG_TAG, "Binding CompanionDeviceService must have "
- + BIND_COMPANION_DEVICE_SERVICE + " permission.");
- return new ServiceConnector.NoOp<>();
- }
- ComponentName componentName = packageResolveInfos.get(0).serviceInfo.getComponentName();
- Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
- return new ServiceConnector.Impl<ICompanionDeviceService>(getContext(),
- new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(componentName),
- BIND_IMPORTANT,
- a.getUserId(),
- ICompanionDeviceService.Stub::asInterface) {
-
- @Override
- protected long getAutoDisconnectTimeoutMs() {
- // Service binding is managed manually based on corresponding device being nearby
- return Long.MAX_VALUE;
- }
-
- @Override
- public void binderDied() {
- super.binderDied();
-
- // Re-connect to the service if process gets killed
- mMainHandler.postDelayed(this::connect, DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
- }
- };
- }
-
private class BleScanCallback extends ScanCallback {
@Override
public void onScanResult(int callbackType, ScanResult result) {
@@ -1345,8 +1272,6 @@
@Override
public void run() {
- Slog.i(LOG_TAG, "UnbindDeviceListenersRunnable.run(); devicesNearby = "
- + mDevicesLastNearby);
int size = mDevicesLastNearby.size();
for (int i = 0; i < size; i++) {
String address = mDevicesLastNearby.keyAt(i);
@@ -1355,7 +1280,8 @@
if (isDeviceDisappeared(lastNearby)) {
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- getDeviceListenerServiceConnector(association).unbind();
+ mCompanionDevicePresenceController.unbindDevicePresenceListener(
+ association.getPackageName(), association.getUserId());
}
}
}
@@ -1425,10 +1351,8 @@
Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- Slog.i(LOG_TAG,
- "Sending onDeviceAppeared to " + association.getPackageName() + ")");
- getDeviceListenerServiceConnector(association).run(
- service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
+ mCompanionDevicePresenceController.onDeviceNotifyAppeared(association,
+ getContext(), mMainHandler);
}
}
}
@@ -1440,10 +1364,8 @@
boolean hasDeviceListeners = false;
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- Slog.i(LOG_TAG,
- "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
- getDeviceListenerServiceConnector(association).run(
- service -> service.onDeviceDisappeared(address));
+ mCompanionDevicePresenceController.onDeviceNotifyDisappeared(
+ association, getContext(), mMainHandler);
hasDeviceListeners = true;
}
}
@@ -1541,86 +1463,15 @@
return result;
}
- private static long parseLongOrDefault(String str, long def) {
- try {
- return Long.parseLong(str);
- } catch (NumberFormatException e) {
- Slog.w(LOG_TAG, "Failed to parse", e);
- return def;
- }
+ static int getFirstAssociationIdForUser(@UserIdInt int userId) {
+ // We want the IDs to start from 1, not 0.
+ return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
}
- private class ShellCmd extends ShellCommand {
- public static final String USAGE = "help\n"
- + "list USER_ID\n"
- + "associate USER_ID PACKAGE MAC_ADDRESS\n"
- + "disassociate USER_ID PACKAGE MAC_ADDRESS";
-
- ShellCmd() {
- getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_COMPANION_DEVICES, "ShellCmd");
- }
-
- @Override
- public int onCommand(String cmd) {
- try {
- switch (cmd) {
- case "list": {
- forEach(
- getAllAssociations(getNextArgInt()),
- a -> getOutPrintWriter()
- .println(a.getPackageName() + " "
- + a.getDeviceMacAddress()));
- }
- break;
-
- case "associate": {
- int userId = getNextArgInt();
- String pkg = getNextArgRequired();
- String address = getNextArgRequired();
- addAssociation(new Association(userId, address, pkg, null, false,
- System.currentTimeMillis()), userId);
- }
- break;
-
- case "disassociate": {
- removeAssociation(getNextArgInt(), getNextArgRequired(),
- getNextArgRequired());
- }
- break;
-
- case "simulate_connect": {
- onDeviceConnected(getNextArgRequired());
- }
- break;
-
- case "simulate_disconnect": {
- onDeviceDisconnected(getNextArgRequired());
- }
- break;
-
- default:
- return handleDefaultCommands(cmd);
- }
- return 0;
- } catch (Throwable t) {
- Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t);
- getErrPrintWriter().println(Log.getStackTraceString(t));
- return 1;
- }
- }
-
- private int getNextArgInt() {
- return Integer.parseInt(getNextArgRequired());
- }
-
- @Override
- public void onHelp() {
- getOutPrintWriter().println(USAGE);
- }
+ static int getLastAssociationIdForUser(@UserIdInt int userId) {
+ return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
}
-
private class BluetoothDeviceConnectedListener
extends BluetoothAdapter.BluetoothConnectionCallback {
@Override
@@ -1635,4 +1486,15 @@
CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
}
}
+
+ private static @NonNull <T> Set<T> filterOut(
+ @NonNull Set<T> set, @NonNull Predicate<? super T> predicate) {
+ return CollectionUtils.filter(set, predicate.negate());
+ }
+
+ private Map<String, Set<Integer>> deepCopy(Map<String, Set<Integer>> orig) {
+ final Map<String, Set<Integer>> copy = new HashMap<>(orig.size(), 1f);
+ forEach(orig, (key, value) -> copy.put(key, new ArraySet<>(value)));
+ return copy;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
new file mode 100644
index 0000000..328a8b3
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE;
+import static android.content.Context.BIND_IMPORTANT;
+
+import static com.android.internal.util.CollectionUtils.filter;
+
+import android.annotation.NonNull;
+import android.companion.Association;
+import android.companion.CompanionDeviceService;
+import android.companion.ICompanionDeviceService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.infra.PerUser;
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and
+ * the companion apps. The controller also will notify the companion apps with device status.
+ */
+public class CompanionDevicePresenceController {
+ private static final String LOG_TAG = "CompanionDevicePresenceController";
+ PerUser<ArrayMap<String, List<BoundService>>> mBoundServices;
+ private static final String META_DATA_KEY_PRIMARY = "primary";
+
+ public CompanionDevicePresenceController() {
+ mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() {
+ @NonNull
+ @Override
+ protected ArrayMap<String, List<BoundService>> create(int userId) {
+ return new ArrayMap<>();
+ }
+ };
+ }
+
+ void onDeviceNotifyAppeared(Association association, Context context, Handler handler) {
+ ServiceConnector<ICompanionDeviceService> primaryConnector =
+ getPrimaryServiceConnector(association, context, handler);
+ if (primaryConnector != null) {
+ Slog.i(LOG_TAG,
+ "Sending onDeviceAppeared to " + association.getPackageName() + ")");
+ primaryConnector.run(
+ service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
+ }
+ }
+
+ void onDeviceNotifyDisappeared(Association association, Context context, Handler handler) {
+ ServiceConnector<ICompanionDeviceService> primaryConnector =
+ getPrimaryServiceConnector(association, context, handler);
+ if (primaryConnector != null) {
+ Slog.i(LOG_TAG,
+ "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
+ primaryConnector.run(
+ service -> service.onDeviceDisappeared(association.getDeviceMacAddress()));
+ }
+ }
+
+ void unbindDevicePresenceListener(String packageName, int userId) {
+ List<BoundService> boundServices = mBoundServices.forUser(userId)
+ .remove(packageName);
+ if (boundServices != null) {
+ for (BoundService boundService: boundServices) {
+ Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName);
+ boundService.mServiceConnector.unbind();
+ }
+ }
+ }
+
+ private ServiceConnector<ICompanionDeviceService> getPrimaryServiceConnector(
+ Association association, Context context, Handler handler) {
+ for (BoundService boundService: getDeviceListenerServiceConnector(association, context,
+ handler)) {
+ if (boundService.mIsPrimary) {
+ return boundService.mServiceConnector;
+ }
+ }
+ return null;
+ }
+
+ private List<BoundService> getDeviceListenerServiceConnector(Association a, Context context,
+ Handler handler) {
+ return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
+ a.getPackageName(),
+ pkg -> createDeviceListenerServiceConnector(a, context, handler));
+ }
+
+ private List<BoundService> createDeviceListenerServiceConnector(Association a, Context context,
+ Handler handler) {
+ List<ResolveInfo> resolveInfos = context
+ .getPackageManager()
+ .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA, a.getUserId());
+ List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
+ info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
+ List<BoundService> serviceConnectors = new ArrayList<>();
+ if (!validatePackageInfo(packageResolveInfos, a)) {
+ return serviceConnectors;
+ }
+ for (ResolveInfo packageResolveInfo : packageResolveInfos) {
+ boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null
+ && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY))
+ || packageResolveInfos.size() == 1;
+ ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName();
+
+ Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
+
+ ServiceConnector<ICompanionDeviceService> serviceConnector =
+ new ServiceConnector.Impl<ICompanionDeviceService>(context,
+ new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(
+ componentName), BIND_IMPORTANT, a.getUserId(),
+ ICompanionDeviceService.Stub::asInterface) {
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Service binding is managed manually based on corresponding device
+ // being nearby
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public void binderDied() {
+ super.binderDied();
+
+ // Re-connect to the service if process gets killed
+ handler.postDelayed(
+ this::connect,
+ CompanionDeviceManagerService
+ .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
+ }
+ };
+
+ serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector));
+ }
+ return serviceConnectors;
+ }
+
+ private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos,
+ Association association) {
+ if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) {
+ Slog.e(LOG_TAG, "Device presence listener package must have at least one and not "
+ + "more than five CompanionDeviceService(s) declared. But "
+ + association.getPackageName()
+ + " has " + packageResolveInfos.size());
+ return false;
+ }
+
+ int primaryCount = 0;
+ for (ResolveInfo packageResolveInfo : packageResolveInfos) {
+ String servicePermission = packageResolveInfo.serviceInfo.permission;
+ if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) {
+ Slog.e(LOG_TAG, "Binding CompanionDeviceService must have "
+ + BIND_COMPANION_DEVICE_SERVICE + " permission.");
+ return false;
+ }
+
+ if (packageResolveInfo.serviceInfo.metaData != null
+ && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) {
+ primaryCount++;
+ if (primaryCount > 1) {
+ Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
+ + "to be bound but "
+ + association.getPackageName() + "has " + primaryCount);
+ return false;
+ }
+ }
+ }
+
+ if (packageResolveInfos.size() == 1 && primaryCount != 0) {
+ Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one"
+ + " CompanionDeviceService " + "but " + association.getPackageName()
+ + " has " + primaryCount);
+ }
+
+ return true;
+ }
+
+ private static class BoundService {
+ private final ComponentName mComponentName;
+ private final boolean mIsPrimary;
+ private final ServiceConnector<ICompanionDeviceService> mServiceConnector;
+
+ BoundService(ComponentName componentName,
+ boolean isPrimary, ServiceConnector<ICompanionDeviceService> serviceConnector) {
+ this.mComponentName = componentName;
+ this.mIsPrimary = isPrimary;
+ this.mServiceConnector = serviceConnector;
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
new file mode 100644
index 0000000..e143f5e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static com.android.internal.util.CollectionUtils.forEach;
+import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+class CompanionDeviceShellCommand extends android.os.ShellCommand {
+ private final CompanionDeviceManagerService mService;
+
+ CompanionDeviceShellCommand(CompanionDeviceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ try {
+ switch (cmd) {
+ case "list": {
+ forEach(
+ mService.getAllAssociations(getNextArgInt()),
+ a -> getOutPrintWriter()
+ .println(a.getPackageName() + " "
+ + a.getDeviceMacAddress()));
+ }
+ break;
+
+ case "associate": {
+ int userId = getNextArgInt();
+ String packageName = getNextArgRequired();
+ String address = getNextArgRequired();
+ mService.createAssociationInternal(userId, address, packageName, null);
+ }
+ break;
+
+ case "disassociate": {
+ mService.removeAssociation(getNextArgInt(), getNextArgRequired(),
+ getNextArgRequired());
+ }
+ break;
+
+ case "simulate_connect": {
+ mService.onDeviceConnected(getNextArgRequired());
+ }
+ break;
+
+ case "simulate_disconnect": {
+ mService.onDeviceDisconnected(getNextArgRequired());
+ }
+ break;
+
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ } catch (Throwable t) {
+ Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t);
+ getErrPrintWriter().println(Log.getStackTraceString(t));
+ return 1;
+ }
+ }
+
+ private int getNextArgInt() {
+ return Integer.parseInt(getNextArgRequired());
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Companion Device Manager (companiondevice) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" list USER_ID");
+ pw.println(" List all Associations for a user.");
+ pw.println(" associate USER_ID PACKAGE MAC_ADDRESS");
+ pw.println(" Create a new Association.");
+ pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
+ pw.println(" Remove an existing Association.");
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
new file mode 100644
index 0000000..73d45ad
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
+
+import static com.android.internal.util.CollectionUtils.forEach;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.Association;
+import android.companion.DeviceId;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * The class responsible for persisting Association records and other related information (such as
+ * previously used IDs) to a disk, and reading the data back from the disk.
+ *
+ * Before Android T the data was stored to `companion_device_manager_associations.xml` file in
+ * {@link Environment#getUserSystemDirectory(int)}
+ * (eg. `/data/system/users/0/companion_device_manager_associations.xml`)
+ * @see #getBaseLegacyStorageFileForUser(int)
+ *
+ * Before Android T the data was stored using the v0 schema.
+ *
+ * @see #readAssociationsV0(TypedXmlPullParser, int, Set)
+ * @see #readAssociationV0(TypedXmlPullParser, int, int, Set)
+ *
+ * The following snippet is a sample of a the file that is using v0 schema.
+ * <pre>{@code
+ * <associations>
+ * <association
+ * package="com.sample.companion.app"
+ * device="AA:BB:CC:DD:EE:00"
+ * time_approved="1634389553216" />
+ * <association
+ * package="com.another.sample.companion.app"
+ * device="AA:BB:CC:DD:EE:01"
+ * profile="android.app.role.COMPANION_DEVICE_WATCH"
+ * notify_device_nearby="false"
+ * time_approved="1634389752662" />
+ * </associations>
+ * }</pre>
+ *
+ *
+ * Since Android T the data is stored to `companion_device_manager.xml` file in
+ * {@link Environment#getDataSystemDeDirectory(int)}.
+ * (eg. `/data/system_de/0/companion_device_manager.xml`)
+ * @see #getBaseStorageFileForUser(int)
+
+ * Since Android T the data is stored using the v1 schema.
+ * In the v1 schema, a list of the previously used IDs is storead along with the association
+ * records.
+ * In the v1 schema, we no longer store MAC addresses, instead each assocition record may have a
+ * number of DeviceIds.
+ *
+ * @see #CURRENT_PERSISTENCE_VERSION
+ * @see #readAssociationsV1(TypedXmlPullParser, int, Set)
+ * @see #readAssociationV1(TypedXmlPullParser, int, Set)
+ * @see #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map)
+ *
+ * The following snippet is a sample of a the file that is using v0 schema.
+ * <pre>{@code
+ * <state persistence-version="1">
+ * <associations>
+ * <association
+ * id="1"
+ * package="com.sample.companion.app"
+ * managed_by_app="false"
+ * notify_device_nearby="false"
+ * time_approved="1634389553216">
+ * <device-id type="mac_address" value="AA:BB:CC:DD:EE:00" />
+ * </association>
+ *
+ * <association
+ * id="3"
+ * profile="android.app.role.COMPANION_DEVICE_WATCH"
+ * package="com.sample.companion.another.app"
+ * managed_by_app="false"
+ * notify_device_nearby="false"
+ * time_approved="1634641160229">
+ * <device-id type="mac_address" value="AA:BB:CC:DD:EE:FF" />
+ * </association>
+ * </associations>
+ *
+ * <previously-used-ids>
+ * <package package_name="com.sample.companion.app">
+ * <id>2</id>
+ * </package>
+ * </previously-used-ids>
+ * </state>
+ * }</pre>
+ */
+final class PersistentDataStore {
+ private static final String LOG_TAG = CompanionDeviceManagerService.LOG_TAG + ".DataStore";
+ private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
+
+ private static final int CURRENT_PERSISTENCE_VERSION = 1;
+
+ private static final String FILE_NAME_LEGACY = "companion_device_manager_associations.xml";
+ private static final String FILE_NAME = "companion_device_manager.xml";
+
+ private static final String XML_TAG_STATE = "state";
+ private static final String XML_TAG_ASSOCIATIONS = "associations";
+ private static final String XML_TAG_ASSOCIATION = "association";
+ private static final String XML_TAG_DEVICE_ID = "device-id";
+ private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids";
+ private static final String XML_TAG_PACKAGE = "package";
+ private static final String XML_TAG_ID = "id";
+
+ private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
+ private static final String XML_ATTR_ID = "id";
+ // Used in <package> elements, nested within <previously-used-ids> elements.
+ private static final String XML_ATTR_PACKAGE_NAME = "package_name";
+ // Used in <association> elements, nested within <associations> elements.
+ private static final String XML_ATTR_PACKAGE = "package";
+ private static final String XML_ATTR_DEVICE = "device";
+ private static final String XML_ATTR_PROFILE = "profile";
+ private static final String XML_ATTR_MANAGED_BY_APP = "managed_by_app";
+ private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
+ private static final String XML_ATTR_TIME_APPROVED = "time_approved";
+ private static final String XML_ATTR_TYPE = "type";
+ private static final String XML_ATTR_VALUE = "value";
+
+ private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Reads previously persisted data for the given user "into" the provided containers.
+ *
+ * @param userId Android UserID
+ * @param associationsOut a container to read the {@link Association}s "into".
+ * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
+ */
+ void readStateForUser(@UserIdInt int userId,
+ @NonNull Set<Association> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+ Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
+ final AtomicFile file = getStorageFileForUser(userId);
+ if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath());
+
+ synchronized (file) {
+ File legacyBaseFile = null;
+ final AtomicFile readFrom;
+ final String rootTag;
+ if (!file.getBaseFile().exists()) {
+ if (DEBUG) Slog.d(LOG_TAG, " > File does not exist -> Try to read legacy file");
+
+ legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
+ if (DEBUG) Slog.d(LOG_TAG, " > Legacy file=" + legacyBaseFile.getPath());
+ if (!legacyBaseFile.exists()) {
+ if (DEBUG) Slog.d(LOG_TAG, " > Legacy file does not exist -> Abort");
+ return;
+ }
+
+ readFrom = new AtomicFile(legacyBaseFile);
+ rootTag = XML_TAG_ASSOCIATIONS;
+ } else {
+ readFrom = file;
+ rootTag = XML_TAG_STATE;
+ }
+
+ if (DEBUG) Slog.d(LOG_TAG, " > Reading associations...");
+ final int version = readStateFromFileLocked(userId, readFrom, rootTag,
+ associationsOut, previouslyUsedIdsPerPackageOut);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, " > Done reading: " + associationsOut);
+ if (version < CURRENT_PERSISTENCE_VERSION) {
+ Slog.d(LOG_TAG, " > File used old format: v." + version + " -> Re-write");
+ }
+ }
+
+ if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
+ // The data is either in the legacy file or in the legacy format, or both.
+ // Save the data to right file in using the current format.
+ if (DEBUG) {
+ Slog.d(LOG_TAG, " > Writing the data to " + file.getBaseFile().getPath());
+ }
+ persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
+
+ if (legacyBaseFile != null) {
+ // We saved the data to the right file, can delete the old file now.
+ if (DEBUG) Slog.d(LOG_TAG, " > Deleting legacy file");
+ legacyBaseFile.delete();
+ }
+ }
+ }
+ }
+
+ /**
+ * Persisted data to the disk.
+ *
+ * @param userId Android UserID
+ * @param associations a set of user's associations.
+ * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
+ */
+ void persistStateForUser(@UserIdInt int userId, @NonNull Set<Association> associations,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
+ Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk");
+ if (DEBUG) Slog.d(LOG_TAG, " > " + associations);
+
+ final AtomicFile file = getStorageFileForUser(userId);
+ if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath());
+ synchronized (file) {
+ persistStateToFileLocked(file, associations, previouslyUsedIdsPerPackage);
+ }
+ }
+
+ private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file,
+ @NonNull String rootTag, @Nullable Set<Association> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+ try (FileInputStream in = file.openRead()) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+ XmlUtils.beginDocument(parser, rootTag);
+ final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
+ switch (version) {
+ case 0:
+ readAssociationsV0(parser, userId, associationsOut);
+ break;
+ case 1:
+ while (true) {
+ parser.nextTag();
+ if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
+ readAssociationsV1(parser, userId, associationsOut);
+ } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
+ readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
+ } else if (isEndOfTag(parser, rootTag)) {
+ break;
+ }
+ }
+ break;
+ }
+ return version;
+ } catch (XmlPullParserException | IOException e) {
+ Slog.e(LOG_TAG, "Error while reading associations file", e);
+ return -1;
+ }
+ }
+
+ private void persistStateToFileLocked(@NonNull AtomicFile file,
+ @Nullable Set<Association> associations,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
+ file.write(out -> {
+ try {
+ final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_STATE);
+ writeIntAttribute(serializer,
+ XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
+
+ writeAssociations(serializer, associations);
+ writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage);
+
+ serializer.endTag(null, XML_TAG_STATE);
+ serializer.endDocument();
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error while writing associations file", e);
+ throw ExceptionUtils.propagate(e);
+ }
+ });
+ }
+
+ private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+ return mUserIdToStorageFile.computeIfAbsent(userId,
+ u -> new AtomicFile(getBaseStorageFileForUser(userId)));
+ }
+
+ private static @NonNull File getBaseStorageFileForUser(@UserIdInt int userId) {
+ return new File(Environment.getDataSystemDeDirectory(userId), FILE_NAME);
+ }
+
+ private static @NonNull File getBaseLegacyStorageFileForUser(@UserIdInt int userId) {
+ return new File(Environment.getUserSystemDirectory(userId), FILE_NAME_LEGACY);
+ }
+
+ private static void readAssociationsV0(@NonNull TypedXmlPullParser parser,
+ @UserIdInt int userId, @NonNull Set<Association> out)
+ throws XmlPullParserException, IOException {
+ requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
+
+ // Before Android T Associations didn't have IDs, so when we are upgrading from S (reading
+ // from V0) we need to generate and assign IDs to the existing Associations.
+ // It's safe to do it here, because CDM cannot create new Associations before it reads
+ // existing ones from the backup files. And the fact that we are reading from a V0 file,
+ // means that CDM hasn't assigned any IDs yet, so we can just start from the first available
+ // id for each user (eg. 1 for user 0; 100 001 - for user 1; 200 001 - for user 2; etc).
+ int associationId = CompanionDeviceManagerService.getFirstAssociationIdForUser(userId);
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
+ if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
+
+ readAssociationV0(parser, userId, associationId++, out);
+ }
+ }
+
+ private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
+ int associationId, @NonNull Set<Association> out) throws XmlPullParserException {
+ requireStartOfTag(parser, XML_TAG_ASSOCIATION);
+
+ final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
+ // In v0, CDM did not have a notion of a DeviceId yet, instead each Association had a MAC
+ // address.
+ final String deviceAddress = readStringAttribute(parser, XML_ATTR_DEVICE);
+
+ if (appPackage == null || deviceAddress == null) return;
+
+ final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
+ final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
+ final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
+
+ // "Convert" MAC address into a DeviceId.
+ final List<DeviceId> deviceIds = Arrays.asList(
+ new DeviceId(TYPE_MAC_ADDRESS, deviceAddress));
+ out.add(new Association(associationId, userId, appPackage, deviceIds, profile,
+ /* managedByCompanionApp */false, notify, timeApproved));
+ }
+
+ private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
+ @UserIdInt int userId, @NonNull Set<Association> out)
+ throws XmlPullParserException, IOException {
+ requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
+
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
+ if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
+
+ readAssociationV1(parser, userId, out);
+ }
+ }
+
+ private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
+ @NonNull Set<Association> out) throws XmlPullParserException, IOException {
+ requireStartOfTag(parser, XML_TAG_ASSOCIATION);
+
+ final int associationId = readIntAttribute(parser, XML_ATTR_ID);
+ final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
+ final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
+ final boolean managedByApp = readBooleanAttribute(parser, XML_ATTR_MANAGED_BY_APP);
+ final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
+ final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
+
+ final List<DeviceId> deviceIds = new ArrayList<>();
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_ASSOCIATION)) break;
+ if (!isStartOfTag(parser, XML_TAG_DEVICE_ID)) continue;
+
+ final String type = readStringAttribute(parser, XML_ATTR_TYPE);
+ final String value = readStringAttribute(parser, XML_ATTR_VALUE);
+ deviceIds.add(new DeviceId(type, value));
+ }
+
+ out.add(new Association(associationId, userId, appPackage, deviceIds, profile, managedByApp,
+ notify, timeApproved));
+ }
+
+ private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
+ @NonNull Map<String, Set<Integer>> out) throws XmlPullParserException, IOException {
+ requireStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS);
+
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) break;
+ if (!isStartOfTag(parser, XML_TAG_PACKAGE)) continue;
+
+ final String packageName = readStringAttribute(parser, XML_ATTR_PACKAGE_NAME);
+ final Set<Integer> usedIds = new HashSet<>();
+
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_PACKAGE)) break;
+ if (!isStartOfTag(parser, XML_TAG_ID)) continue;
+
+ parser.nextToken();
+ final int id = Integer.parseInt(parser.getText());
+ usedIds.add(id);
+ }
+
+ out.put(packageName, usedIds);
+ }
+ }
+
+ private static void writeAssociations(@NonNull XmlSerializer parent,
+ @Nullable Set<Association> associations) throws IOException {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
+ forEach(associations, it -> writeAssociation(serializer, it));
+ serializer.endTag(null, XML_TAG_ASSOCIATIONS);
+ }
+
+ private static void writeAssociation(@NonNull XmlSerializer parent, @NonNull Association a)
+ throws IOException {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATION);
+
+ writeIntAttribute(serializer, XML_ATTR_ID, a.getAssociationId());
+ writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
+ writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
+ writeBooleanAttribute(serializer, XML_ATTR_MANAGED_BY_APP, a.isManagedByCompanionApp());
+ writeBooleanAttribute(
+ serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
+ writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
+
+ final List<DeviceId> deviceIds = a.getDeviceIds();
+ for (int i = 0, size = deviceIds.size(); i < size; i++) {
+ writeDeviceId(serializer, deviceIds.get(i));
+ }
+
+ serializer.endTag(null, XML_TAG_ASSOCIATION);
+ }
+
+ private static void writeDeviceId(@NonNull XmlSerializer parent, @NonNull DeviceId deviceId)
+ throws IOException {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
+
+ writeStringAttribute(serializer, XML_ATTR_TYPE, deviceId.getType());
+ writeStringAttribute(serializer, XML_ATTR_VALUE, deviceId.getValue());
+
+ serializer.endTag(null, XML_TAG_DEVICE_ID);
+ }
+
+ private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) throws IOException {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
+ for (Map.Entry<String, Set<Integer>> entry : previouslyUsedIdsPerPackage.entrySet()) {
+ writePreviouslyUsedIdsForPackage(serializer, entry.getKey(), entry.getValue());
+ }
+ serializer.endTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
+ }
+
+ private static void writePreviouslyUsedIdsForPackage(@NonNull XmlSerializer parent,
+ @NonNull String packageName, @NonNull Set<Integer> previouslyUsedIds)
+ throws IOException {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_PACKAGE);
+ writeStringAttribute(serializer, XML_ATTR_PACKAGE_NAME, packageName);
+ forEach(previouslyUsedIds, id -> serializer.startTag(null, XML_TAG_ID)
+ .text(Integer.toString(id))
+ .endTag(null, XML_TAG_ID));
+ serializer.endTag(null, XML_TAG_PACKAGE);
+ }
+
+ private static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+ throws XmlPullParserException {
+ return parser.getEventType() == START_TAG && tag.equals(parser.getName());
+ }
+
+ private static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+ throws XmlPullParserException {
+ return parser.getEventType() == END_TAG && tag.equals(parser.getName());
+ }
+
+ private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+ throws XmlPullParserException {
+ if (isStartOfTag(parser, tag)) return;
+ throw new XmlPullParserException(
+ "Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag");
+ }
+}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index e7b078a..0fde6fa 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -45,8 +45,8 @@
import com.android.internal.os.ZygoteConnectionConstants;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.ActivityManagerService;
-import com.android.server.am.CriticalEventLog;
import com.android.server.am.TraceErrorLogger;
+import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.wm.SurfaceAnimationThread;
import java.io.BufferedReader;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5330252..f6c1106 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -188,6 +188,7 @@
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
+import android.app.compat.CompatChanges;
import android.app.job.JobParameters;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
@@ -195,7 +196,6 @@
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManagerInternal;
-import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
@@ -382,6 +382,7 @@
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.firewall.IntentFirewall;
import com.android.server.graphics.fonts.FontManagerInternal;
import com.android.server.job.JobSchedulerInternal;
@@ -2394,7 +2395,6 @@
private void start() {
removeAllProcessGroups();
- CriticalEventLog.init();
mBatteryStatsService.publish();
mAppOpsService.publish();
Slog.d("AppOps", "AppOpsService published");
@@ -2404,6 +2404,7 @@
mActivityTaskManager.onActivityManagerInternalAdded();
mPendingIntentController.onActivityManagerInternalAdded();
mAppProfiler.onActivityManagerInternalAdded();
+ CriticalEventLog.init();
}
public void initPowerManagement() {
@@ -12517,11 +12518,6 @@
ProcessRecord callerApp = null;
final boolean visibleToInstantApps
= (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
- // Dynamic receivers are exported by default for versions prior to T
- final boolean exported =
- ((flags & Context.RECEIVER_EXPORTED) != 0
- || (!Compatibility.isChangeEnabled(
- DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED)));
int callingUid;
int callingPid;
@@ -12594,8 +12590,8 @@
// If the caller is registering for a sticky broadcast with a null receiver, we won't
// require a flag
if (!onlyProtectedBroadcasts && receiver != null && (
- Compatibility.isChangeEnabled(
- DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED)
+ CompatChanges.isChangeEnabled(
+ DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
&& (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED))
== 0)) {
Slog.e(TAG,
@@ -12610,6 +12606,12 @@
}
}
+ // Dynamic receivers are exported by default for versions prior to T
+ final boolean exported =
+ ((flags & Context.RECEIVER_EXPORTED) != 0
+ || (!CompatChanges.isChangeEnabled(
+ DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)));
+
ArrayList<Intent> allSticky = null;
if (stickyIntents != null) {
final ContentResolver resolver = mContext.getContentResolver();
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index baf0f39..b7b4870 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -117,6 +117,9 @@
private int mScreenState;
@GuardedBy("this")
+ private int[] mPerDisplayScreenStates = null;
+
+ @GuardedBy("this")
private boolean mUseLatestStates = true;
@GuardedBy("this")
@@ -294,8 +297,8 @@
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(
- int flags, boolean onBattery, boolean onBatteryScreenOff, int screenState) {
+ public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
synchronized (BatteryExternalStatsWorker.this) {
if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
mOnBattery = onBattery;
@@ -304,6 +307,7 @@
}
// always update screen state
mScreenState = screenState;
+ mPerDisplayScreenStates = perDisplayScreenStates;
return scheduleSyncLocked("screen-state", flags);
}
}
@@ -446,6 +450,7 @@
final boolean onBattery;
final boolean onBatteryScreenOff;
final int screenState;
+ final int[] displayScreenStates;
final boolean useLatestStates;
synchronized (BatteryExternalStatsWorker.this) {
updateFlags = mUpdateFlags;
@@ -454,6 +459,7 @@
onBattery = mOnBattery;
onBatteryScreenOff = mOnBatteryScreenOff;
screenState = mScreenState;
+ displayScreenStates = mPerDisplayScreenStates;
useLatestStates = mUseLatestStates;
mUpdateFlags = 0;
mCurrentReason = null;
@@ -475,7 +481,8 @@
}
try {
updateExternalStatsLocked(reason, updateFlags, onBattery,
- onBatteryScreenOff, screenState, useLatestStates);
+ onBatteryScreenOff, screenState, displayScreenStates,
+ useLatestStates);
} finally {
if (DEBUG) {
Slog.d(TAG, "end updateExternalStatsSync");
@@ -520,7 +527,8 @@
@GuardedBy("mWorkerLock")
private void updateExternalStatsLocked(final String reason, int updateFlags, boolean onBattery,
- boolean onBatteryScreenOff, int screenState, boolean useLatestStates) {
+ boolean onBatteryScreenOff, int screenState, int[] displayScreenStates,
+ boolean useLatestStates) {
// We will request data from external processes asynchronously, and wait on a timeout.
SynchronousResultReceiver wifiReceiver = null;
SynchronousResultReceiver bluetoothReceiver = null;
@@ -675,11 +683,10 @@
if (measuredEnergyDeltas != null) {
final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC;
if (displayChargeUC != null && displayChargeUC.length > 0) {
- // TODO (b/194107383): pass all display ordinals to mStats.
- final long primaryDisplayChargeUC = displayChargeUC[0];
- // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
- mStats.updateDisplayMeasuredEnergyStatsLocked(primaryDisplayChargeUC,
- screenState, elapsedRealtime);
+ // If updating, pass in what BatteryExternalStatsWorker thinks
+ // displayScreenStates is.
+ mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC,
+ displayScreenStates, elapsedRealtime);
}
final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 60530a3..03a4d84 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1215,7 +1215,7 @@
mHandler.post(() -> {
if (DBG) Slog.d(TAG, "begin noteScreenState");
synchronized (mStats) {
- mStats.noteScreenStateLocked(state, elapsedRealtime, uptime, currentTime);
+ mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime);
}
if (DBG) Slog.d(TAG, "end noteScreenState");
});
@@ -1230,7 +1230,7 @@
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
- mStats.noteScreenBrightnessLocked(brightness, elapsedRealtime, uptime);
+ mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime);
}
});
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ed70d2b..8638c7d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1588,17 +1588,23 @@
perm = PackageManager.PERMISSION_DENIED;
}
- if (perm == PackageManager.PERMISSION_GRANTED) {
- skip = true;
- break;
- }
-
int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
if (appOp != AppOpsManager.OP_NONE) {
- if (mService.getAppOpsManager().checkOpNoThrow(appOp,
+ // When there is an app op associated with the permission,
+ // skip when both the permission and the app op are
+ // granted.
+ if ((perm == PackageManager.PERMISSION_GRANTED) && (
+ mService.getAppOpsManager().checkOpNoThrow(appOp,
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName)
- == AppOpsManager.MODE_ALLOWED) {
+ == AppOpsManager.MODE_ALLOWED)) {
+ skip = true;
+ break;
+ }
+ } else {
+ // When there is no app op associated with the permission,
+ // skip when permission is granted.
+ if (perm == PackageManager.PERMISSION_GRANTED) {
skip = true;
break;
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 5fac879..b7ea3dc 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -49,6 +49,7 @@
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.MemoryPressureUtil;
+import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.wm.WindowProcessController;
import java.io.File;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index fc02f19..4ef36d6 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -87,6 +87,7 @@
DeviceConfig.NAMESPACE_LMKD_NATIVE,
DeviceConfig.NAMESPACE_MEDIA_NATIVE,
DeviceConfig.NAMESPACE_NETD_NATIVE,
+ DeviceConfig.NAMESPACE_NNAPI_NATIVE,
DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 84a3060..4cb786a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -22,6 +22,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothLeAudio;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -503,6 +504,18 @@
}
}
+ /*package*/ static final class BleVolumeInfo {
+ final int mIndex;
+ final int mMaxIndex;
+ final int mStreamType;
+
+ BleVolumeInfo(int index, int maxIndex, int streamType) {
+ mIndex = index;
+ mMaxIndex = maxIndex;
+ mStreamType = streamType;
+ }
+ };
+
/*package*/ static final class BtDeviceConnectionInfo {
final @NonNull BluetoothDevice mDevice;
final @AudioService.BtProfileConnectionState int mState;
@@ -711,6 +724,11 @@
sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
}
+ /*package*/ void postSetLeAudioVolumeIndex(int index, int maxIndex, int streamType) {
+ BleVolumeInfo info = new BleVolumeInfo(index, maxIndex, streamType);
+ sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
+ }
+
/*package*/ void postSetModeOwnerPid(int pid, int mode) {
sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
}
@@ -851,6 +869,10 @@
return mAudioService.getVssVolumeForDevice(streamType, device);
}
+ /*package*/ int getMaxVssVolumeForStream(int streamType) {
+ return mAudioService.getMaxVssVolumeForStream(streamType);
+ }
+
/*package*/ int getDeviceForStream(int streamType) {
return mAudioService.getDeviceForStream(streamType);
}
@@ -962,6 +984,10 @@
sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
}
+ /*package*/ void postDisconnectLeAudio() {
+ sendMsgNoDelay(MSG_DISCONNECT_BT_LE_AUDIO, SENDMSG_QUEUE);
+ }
+
/*package*/ void postDisconnectHeadset() {
sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
}
@@ -983,6 +1009,11 @@
hearingAidProfile);
}
+ /*package*/ void postBtLeAudioProfileConnected(BluetoothLeAudio leAudioProfile) {
+ sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO, SENDMSG_QUEUE,
+ leAudioProfile);
+ }
+
/*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) {
sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client);
}
@@ -1321,6 +1352,12 @@
mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
}
break;
+ case MSG_II_SET_LE_AUDIO_OUT_VOLUME: {
+ final BleVolumeInfo info = (BleVolumeInfo) msg.obj;
+ synchronized (mDeviceStateLock) {
+ mBtHelper.setLeAudioVolume(info.mIndex, info.mMaxIndex, info.mStreamType);
+ }
+ } break;
case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
synchronized (mDeviceStateLock) {
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
@@ -1384,6 +1421,11 @@
}
}
break;
+ case MSG_DISCONNECT_BT_LE_AUDIO:
+ synchronized(mDeviceStateLock) {
+ mDeviceInventory.disconnectLeAudio();
+ }
+ break;
case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
synchronized (mDeviceStateLock) {
mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
@@ -1399,6 +1441,12 @@
mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
}
break;
+
+ case MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO:
+ synchronized(mDeviceStateLock) {
+ mBtHelper.onLeAudioProfileConnected((BluetoothLeAudio) msg.obj);
+ }
+ break;
case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
@@ -1586,6 +1634,11 @@
private static final int MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE = 43;
private static final int MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT = 44;
private static final int MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT = 45;
+ // process set volume for Le Audio, obj is BleVolumeInfo
+ private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46;
+
+ private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO = 47;
+ private static final int MSG_DISCONNECT_BT_LE_AUDIO = 48;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
@@ -1714,10 +1767,12 @@
MESSAGES_MUTE_MUSIC = new HashSet<>();
MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED);
MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED);
+ MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION);
+ MESSAGES_MUTE_MUSIC.add(MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT);
MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
MESSAGES_MUTE_MUSIC.add(MSG_REPORT_NEW_ROUTES_A2DP);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 64e620e..6c3c736 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -887,6 +887,28 @@
}
}
+ /*package*/ void disconnectLeAudio() {
+ synchronized (mDevicesLock) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_OUT_BLE_HEADSET devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
+ .record();
+ if (toRemove.size() > 0) {
+ final int delay = checkSendBecomingNoisyIntentInt(
+ AudioSystem.DEVICE_OUT_BLE_HEADSET, 0, AudioSystem.DEVICE_NONE);
+ toRemove.stream().forEach(deviceAddress ->
+ makeLeAudioDeviceUnavailable(deviceAddress,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET)
+ );
+ }
+ }
+ }
+
// must be called before removing the device from mConnectedDevices
// musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
// from AudioSystem
@@ -1195,6 +1217,10 @@
return;
}
+ final int leAudioVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
+ mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
}
@@ -1243,6 +1269,7 @@
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
+ BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET);
BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3aa7ab9..34277a2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -97,6 +97,7 @@
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController;
import android.media.MediaMetrics;
@@ -344,6 +345,10 @@
return mStreamStates[stream].getIndex(device);
}
+ /*package*/ int getMaxVssVolumeForStream(int stream) {
+ return mStreamStates[stream].getMaxIndex();
+ }
+
private SettingsObserver mSettingsObserver;
private AtomicInteger mMode = new AtomicInteger(AudioSystem.MODE_NORMAL);
@@ -2990,6 +2995,16 @@
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);
}
+ if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
+ && streamType == getBluetoothContextualVolumeStream()) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+ + newIndex + " stream=" + streamType);
+ }
+ mDeviceBroker.postSetLeAudioVolumeIndex(newIndex,
+ mStreamStates[streamType].getMaxIndex(), streamType);
+ }
+
// Check if volume update should be send to Hearing Aid
if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
// only modify the hearing aid attenuation when the stream to modify matches
@@ -3608,6 +3623,16 @@
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
}
+ if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
+ && streamType == getBluetoothContextualVolumeStream()) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+ + index + " stream=" + streamType);
+ }
+ mDeviceBroker.postSetLeAudioVolumeIndex(index,
+ mStreamStates[streamType].getMaxIndex(), streamType);
+ }
+
if (device == AudioSystem.DEVICE_OUT_HEARING_AID
&& streamType == getBluetoothContextualVolumeStream()) {
Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
@@ -6138,6 +6163,11 @@
if (pkgName == null) {
pkgName = "";
}
+ if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
+ avrcpSupportsAbsoluteVolume(device.getAddress(),
+ deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ return;
+ }
int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
device.getType());
@@ -7816,7 +7846,7 @@
}
}
- public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
+ private void avrcpSupportsAbsoluteVolume(String address, boolean support) {
// address is not used for now, but may be used when multiple a2dp devices are supported
sVolumeLogger.log(new AudioEventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+ address + " support=" + support));
@@ -8509,6 +8539,26 @@
mSpatializerHelper.getEffectParameter(key, value);
}
+ /** @see Spatializer#getOutput */
+ public int getSpatializerOutput() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getOutput();
+ }
+
+ /** @see Spatializer#setOnSpatializerOutputChangedListener */
+ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerSpatializerOutputCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnSpatializerOutputChangedListener */
+ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
+ }
+
/**
* post a message to schedule init/release of head tracking sensors
* @param init initialization if true, release if false
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 0eb5a5d..3137fa5 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -155,6 +155,7 @@
static final int VOL_MODE_CHANGE_HEARING_AID = 7;
static final int VOL_SET_GROUP_VOL = 8;
static final int VOL_MUTE_STREAM_INT = 9;
+ static final int VOL_SET_LE_AUDIO_VOL = 10;
final int mOp;
final int mStream;
@@ -310,6 +311,13 @@
.set(MediaMetrics.Property.INDEX, mVal1)
.record();
return;
+ case VOL_SET_LE_AUDIO_VOL:
+ new MediaMetrics.Item(mMetricsId)
+ .set(MediaMetrics.Property.EVENT, "setLeAudioVolume")
+ .set(MediaMetrics.Property.INDEX, mVal1)
+ .set(MediaMetrics.Property.MAX_INDEX, mVal2)
+ .record();
+ return;
case VOL_SET_AVRCP_VOL:
new MediaMetrics.Item(mMetricsId)
.set(MediaMetrics.Property.EVENT, "setAvrcpVolume")
@@ -382,6 +390,11 @@
.append(" index:").append(mVal1)
.append(" gain dB:").append(mVal2)
.toString();
+ case VOL_SET_LE_AUDIO_VOL:
+ return new StringBuilder("setLeAudioVolume:")
+ .append(" index:").append(mVal1)
+ .append(" gain dB:").append(mVal2)
+ .toString();
case VOL_SET_AVRCP_VOL:
return new StringBuilder("setAvrcpVolume:")
.append(" index:").append(mVal1)
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 52e8edf..c924fde 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -25,6 +25,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.media.AudioDeviceAttributes;
@@ -63,6 +64,8 @@
private @Nullable BluetoothHearingAid mHearingAid;
+ private @Nullable BluetoothLeAudio mLeAudio;
+
// Reference to BluetoothA2dp to query for AbsoluteVolume.
private @Nullable BluetoothA2dp mA2dp;
@@ -106,6 +109,8 @@
private static final int SCO_MODE_MAX = 2;
private static final int BT_HEARING_AID_GAIN_MIN = -128;
+ private static final int BT_LE_AUDIO_MIN_VOL = 0;
+ private static final int BT_LE_AUDIO_MAX_VOL = 255;
/**
* Returns a string representation of the scoAudioMode.
@@ -235,6 +240,8 @@
mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
adapter.getProfileProxy(mDeviceBroker.getContext(),
mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
+ adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO);
}
}
@@ -389,6 +396,26 @@
return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
}
+ /*package*/ synchronized void setLeAudioVolume(int index, int maxIndex, int streamType) {
+ if (mLeAudio == null) {
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setLeAudioVolume: null mLeAudio");
+ }
+ return;
+ }
+ /* leaudio expect volume value in range 0 to 255
+ */
+ int volume = (index * (BT_LE_AUDIO_MAX_VOL - BT_LE_AUDIO_MIN_VOL)) / maxIndex ;
+
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx="
+ + index + " volume=" + volume);
+ }
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
+ mLeAudio.setVolume(volume);
+ }
+
/*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
if (mHearingAid == null) {
if (AudioService.DEBUG_VOL) {
@@ -428,6 +455,7 @@
mDeviceBroker.postDisconnectA2dpSink();
mDeviceBroker.postDisconnectHeadset();
mDeviceBroker.postDisconnectHearingAid();
+ mDeviceBroker.postDisconnectLeAudio();
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@@ -488,6 +516,23 @@
/*eventSource*/ "mBluetoothProfileServiceListener");
}
+ /*package*/ synchronized void onLeAudioProfileConnected(BluetoothLeAudio leAudio) {
+ mLeAudio = leAudio;
+ final List<BluetoothDevice> deviceList = mLeAudio.getConnectedDevices();
+ if (deviceList.isEmpty()) {
+ return;
+ }
+
+ final BluetoothDevice btDevice = deviceList.get(0);
+ final @BluetoothProfile.BtProfileState int state =
+ mLeAudio.getConnectionState(btDevice);
+ mDeviceBroker.postBluetoothLeAudioOutDeviceConnectionState(
+ btDevice, state,
+ /*suppressNoisyIntent*/ false,
+ /*musicDevice android.media.AudioSystem.DEVICE_NONE,*/
+ /*eventSource*/ "mBluetoothProfileServiceListener");
+ }
+
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
@@ -655,6 +700,13 @@
mDeviceBroker.postBtHearingAidProfileConnected(
(BluetoothHearingAid) proxy);
break;
+
+ case BluetoothProfile.LE_AUDIO:
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "BT profile service: connecting LE_AUDIO profile"));
+ mDeviceBroker.postBtLeAudioProfileConnected(
+ (BluetoothLeAudio) proxy);
+ break;
default:
break;
}
@@ -677,6 +729,9 @@
case BluetoothProfile.HEARING_AID:
mDeviceBroker.postDisconnectHearingAid();
break;
+ case BluetoothProfile.LE_AUDIO:
+ mDeviceBroker.postDisconnectLeAudio();
+ break;
default:
break;
@@ -899,6 +954,7 @@
pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
+ pw.println("\n" + prefix + "mLeAudio: " + mLeAudio);
pw.println(prefix + "mA2dp: " + mA2dp);
pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 98452e5..7cd027c 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -31,6 +31,7 @@
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.SpatializationLevel;
import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
@@ -76,6 +77,7 @@
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mSpatOutput = 0;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
@@ -213,6 +215,18 @@
postInitSensors(true);
}
}
+
+ public void onOutputChanged(int output) {
+ logd("SpatializerCallback.onOutputChanged output:" + output);
+ int oldOutput;
+ synchronized (SpatializerHelper.this) {
+ oldOutput = mSpatOutput;
+ mSpatOutput = output;
+ }
+ if (oldOutput != output) {
+ dispatchOutputUpdate(output);
+ }
+ }
};
// spatializer head tracking callback from native
@@ -782,6 +796,60 @@
}
//------------------------------------------------------
+ // output
+
+ /** @see Spatializer#getOutput */
+ synchronized int getOutput() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get output without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getOutput"));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return mSpat.getOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getOutput", e);
+ return 0;
+ }
+ }
+
+ final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks =
+ new RemoteCallbackList<ISpatializerOutputCallback>();
+
+ synchronized void registerSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.register(callback);
+ }
+
+ synchronized void unregisterSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.unregister(callback);
+ }
+
+ private void dispatchOutputUpdate(int output) {
+ final int nbCallbacks = mOutputCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchOutputUpdate", e);
+ }
+ }
+ mOutputCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
// sensors
private void initSensors(boolean init) {
if (mSensorManager == null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 85d6d7f..031f6ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -360,6 +360,43 @@
}
}
+ /**
+ * Only call this method on interfaces where lockout does not come from onError, I.E. the
+ * old HIDL implementation.
+ */
+ protected void onLockoutTimed(long durationMillis) {
+ final ClientMonitorCallbackConverter listener = getListener();
+ final CoexCoordinator coordinator = CoexCoordinator.getInstance();
+ coordinator.onAuthenticationError(this, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
+ new CoexCoordinator.ErrorCallback() {
+ @Override
+ public void sendHapticFeedback() {
+ if (listener != null && mShouldVibrate) {
+ vibrateError();
+ }
+ }
+ });
+ }
+
+ /**
+ * Only call this method on interfaces where lockout does not come from onError, I.E. the
+ * old HIDL implementation.
+ */
+ protected void onLockoutPermanent() {
+ final ClientMonitorCallbackConverter listener = getListener();
+ final CoexCoordinator coordinator = CoexCoordinator.getInstance();
+ coordinator.onAuthenticationError(this,
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
+ new CoexCoordinator.ErrorCallback() {
+ @Override
+ public void sendHapticFeedback() {
+ if (listener != null && mShouldVibrate) {
+ vibrateError();
+ }
+ }
+ });
+ }
+
private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) {
if (listener == null) {
Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null");
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 cbceba6..97d791b 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
@@ -225,6 +225,7 @@
@Override
public void onLockoutTimed(long durationMillis) {
+ super.onLockoutTimed(durationMillis);
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
@@ -239,6 +240,7 @@
@Override
public void onLockoutPermanent() {
+ super.onLockoutPermanent();
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 42b676f..9d2cff9 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,6 +15,7 @@
*/
package com.android.server.camera;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.os.Build.VERSION_CODES.M;
import android.annotation.IntDef;
@@ -39,7 +40,9 @@
import android.hardware.CameraStreamStats;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceProxy;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManager;
@@ -346,13 +349,13 @@
private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
- private final class TaskInfo {
- private int frontTaskId;
- private boolean isResizeable;
- private boolean isFixedOrientationLandscape;
- private boolean isFixedOrientationPortrait;
- private int displayId;
- private int userId;
+ public static final class TaskInfo {
+ public int frontTaskId;
+ public boolean isResizeable;
+ public boolean isFixedOrientationLandscape;
+ public boolean isFixedOrientationPortrait;
+ public int displayId;
+ public int userId;
}
private final class TaskStateHandler extends TaskStackListener {
@@ -367,7 +370,8 @@
synchronized (mMapLock) {
TaskInfo info = new TaskInfo();
info.frontTaskId = taskInfo.taskId;
- info.isResizeable = taskInfo.isResizeable;
+ info.isResizeable =
+ (taskInfo.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE);
info.displayId = taskInfo.displayId;
info.userId = taskInfo.userId;
info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape(
@@ -427,97 +431,108 @@
}
};
- private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
- private boolean isMOrBelow(Context ctx, String packageName) {
- try {
- return ctx.getPackageManager().getPackageInfo(
- packageName, 0).applicationInfo.targetSdkVersion <= M;
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG,"Package name not found!");
- }
- return false;
+ private static boolean isMOrBelow(Context ctx, String packageName) {
+ try {
+ return ctx.getPackageManager().getPackageInfo(
+ packageName, 0).applicationInfo.targetSdkVersion <= M;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG,"Package name not found!");
+ }
+ return false;
+ }
+
+ /**
+ * Estimate the app crop-rotate-scale compensation value.
+ */
+ public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
+ @Nullable TaskInfo taskInfo, int displayRotation, int lensFacing,
+ boolean ignoreResizableAndSdkCheck) {
+ if (taskInfo == null) {
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
- /**
- * Gets whether crop-rotate-scale is needed.
- */
- private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
- @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing,
- boolean ignoreResizableAndSdkCheck) {
- if (taskInfo == null) {
- return false;
- }
+ // External cameras do not need crop-rotate-scale.
+ if (lensFacing != CameraMetadata.LENS_FACING_FRONT
+ && lensFacing != CameraMetadata.LENS_FACING_BACK) {
+ Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
- // External cameras do not need crop-rotate-scale.
- if (lensFacing != CameraMetadata.LENS_FACING_FRONT
- && lensFacing != CameraMetadata.LENS_FACING_BACK) {
- Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
- return false;
- }
-
- // In case the activity behavior is not explicitly overridden, enable the
- // crop-rotate-scale workaround if the app targets M (or below) or is not
- // resizeable.
- if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) &&
- taskInfo.isResizeable) {
- Slog.v(TAG,
- "The activity is N or above and claims to support resizeable-activity. "
- + "Crop-rotate-scale is disabled.");
- return false;
- }
-
- DisplayManager displayManager = ctx.getSystemService(DisplayManager.class);
- int rotationDegree = 0;
- if (displayManager != null) {
- Display display = displayManager.getDisplay(taskInfo.displayId);
- if (display == null) {
- Slog.e(TAG, "Invalid display id: " + taskInfo.displayId);
- return false;
- }
-
- int rotation = display.getRotation();
- switch (rotation) {
- case Surface.ROTATION_0:
- rotationDegree = 0;
- break;
- case Surface.ROTATION_90:
- rotationDegree = 90;
- break;
- case Surface.ROTATION_180:
- rotationDegree = 180;
- break;
- case Surface.ROTATION_270:
- rotationDegree = 270;
- break;
- }
- } else {
- Slog.e(TAG, "Failed to query display manager!");
- return false;
- }
-
- // Here we only need to know whether the camera is landscape or portrait. Therefore we
- // don't need to consider whether it is a front or back camera. The formula works for
- // both.
- boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0);
+ // In case the activity behavior is not explicitly overridden, enable the
+ // crop-rotate-scale workaround if the app targets M (or below) or is not
+ // resizeable.
+ if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) &&
+ taskInfo.isResizeable) {
Slog.v(TAG,
- "Display.getRotation()=" + rotationDegree
- + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation
- + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
- + " isFixedOrientationLandscape=" +
- taskInfo.isFixedOrientationLandscape);
- // We need to do crop-rotate-scale when camera is landscape and activity is portrait or
- // vice versa.
- return (taskInfo.isFixedOrientationPortrait && landscapeCamera)
- || (taskInfo.isFixedOrientationLandscape && !landscapeCamera);
+ "The activity is N or above and claims to support resizeable-activity. "
+ + "Crop-rotate-scale is disabled.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
+ if (!taskInfo.isFixedOrientationPortrait && !taskInfo.isFixedOrientationLandscape) {
+ Log.v(TAG, "Non-fixed orientation activity. Crop-rotate-scale is disabled.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ int rotationDegree;
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ rotationDegree = 0;
+ break;
+ case Surface.ROTATION_90:
+ rotationDegree = 90;
+ break;
+ case Surface.ROTATION_180:
+ rotationDegree = 180;
+ break;
+ case Surface.ROTATION_270:
+ rotationDegree = 270;
+ break;
+ default:
+ Log.e(TAG, "Unsupported display rotation: " + displayRotation);
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ Slog.v(TAG,
+ "Display.getRotation()=" + rotationDegree
+ + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
+ + " isFixedOrientationLandscape=" +
+ taskInfo.isFixedOrientationLandscape);
+ // We are trying to estimate the necessary rotation compensation for clients that
+ // don't handle various display orientations.
+ // The logic that is missing on client side is similar to the reference code
+ // in {@link android.hardware.Camera#setDisplayOrientation} where "info.orientation"
+ // is already applied in "CameraUtils::getRotationTransform".
+ // Care should be taken to reverse the rotation direction depending on the camera
+ // lens facing.
+ if (rotationDegree == 0) {
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+ if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
+ // Switch direction for front facing cameras
+ rotationDegree = 360 - rotationDegree;
+ }
+
+ switch (rotationDegree) {
+ case 90:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_90;
+ case 270:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_270;
+ case 180:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_180;
+ case 0:
+ default:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+ }
+
+ private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
@Override
- public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation,
- int lensFacing) {
+ public int getRotateAndCropOverride(String packageName, int lensFacing) {
if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
" camera service UID!");
- return false;
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
// TODO: Modify the sensor orientation in camera characteristics along with any 3A
@@ -531,10 +546,10 @@
if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName,
UserHandle.getUserHandleForUid(taskInfo.userId))) {
Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!");
- return true;
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
} else {
Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!");
- return false;
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
}
boolean ignoreResizableAndSdkCheck = false;
@@ -544,7 +559,23 @@
Slog.v(TAG, "OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK enabled!");
ignoreResizableAndSdkCheck = true;
}
- return getNeedCropRotateScale(mContext, packageName, taskInfo, sensorOrientation,
+
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ int displayRotation;
+ if (displayManager != null) {
+ Display display = displayManager.getDisplay(taskInfo.displayId);
+ if (display == null) {
+ Slog.e(TAG, "Invalid display id: " + taskInfo.displayId);
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ displayRotation = display.getRotation();
+ } else {
+ Slog.e(TAG, "Failed to query display manager!");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ return getCropRotateScale(mContext, packageName, taskInfo, displayRotation,
lensFacing, ignoreResizableAndSdkCheck);
}
diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java
new file mode 100644
index 0000000..d77e3b5
--- /dev/null
+++ b/services/core/java/com/android/server/communal/CommunalManagerService.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.communal;
+
+import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
+
+import static com.android.server.wm.ActivityInterceptorCallback.COMMUNAL_MODE_ORDERED_ID;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.communal.ICommunalManager;
+import android.content.Context;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.LaunchAfterAuthenticationActivity;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityInterceptorCallback;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * System service for handling Communal Mode state.
+ */
+public final class CommunalManagerService extends SystemService {
+ private static final String DELIMITER = ",";
+ private final Context mContext;
+ private final ActivityTaskManagerInternal mAtmInternal;
+ private final KeyguardManager mKeyguardManager;
+ private final AtomicBoolean mCommunalViewIsShowing = new AtomicBoolean(false);
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Set<String> mEnabledApps = new HashSet<>();
+ private final SettingsObserver mSettingsObserver;
+
+ private final ActivityInterceptorCallback mActivityInterceptorCallback =
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ if (isActivityAllowed(info.aInfo)) {
+ return null;
+ }
+
+ final IIntentSender target = mAtmInternal.getIntentSender(
+ INTENT_SENDER_ACTIVITY,
+ info.callingPackage,
+ info.callingFeatureId,
+ info.callingUid,
+ info.userId,
+ /* token= */null,
+ /* resultWho= */ null,
+ /* requestCode= */ 0,
+ new Intent[]{info.intent},
+ new String[]{info.resolvedType},
+ PendingIntent.FLAG_IMMUTABLE,
+ /* bOptions= */ null);
+
+ return LaunchAfterAuthenticationActivity.createLaunchAfterAuthenticationIntent(
+ new IntentSender(target));
+
+ }
+ };
+
+ public CommunalManagerService(Context context) {
+ super(context);
+ mContext = context;
+ mSettingsObserver = new SettingsObserver();
+ mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.COMMUNAL_MANAGER_SERVICE, new BinderService());
+ mAtmInternal.registerActivityStartInterceptor(COMMUNAL_MODE_ORDERED_ID,
+ mActivityInterceptorCallback);
+
+
+ updateSelectedApps();
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.COMMUNAL_MODE_PACKAGES), false, mSettingsObserver,
+ UserHandle.USER_SYSTEM);
+ }
+
+ @VisibleForTesting
+ void updateSelectedApps() {
+ final String encodedApps = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.COMMUNAL_MODE_PACKAGES,
+ UserHandle.USER_SYSTEM);
+
+ mEnabledApps.clear();
+
+ if (!TextUtils.isEmpty(encodedApps)) {
+ mEnabledApps.addAll(Arrays.asList(encodedApps.split(DELIMITER)));
+ }
+ }
+
+ private boolean isActivityAllowed(ActivityInfo activityInfo) {
+ return true;
+ // TODO(b/191994709): Uncomment the lines below once Dreams and Assistant have been fixed.
+// if (!mCommunalViewIsShowing.get() || !mKeyguardManager.isKeyguardLocked()) return true;
+//
+// // If the activity doesn't have showWhenLocked enabled, disallow the activity.
+// final boolean showWhenLocked =
+// (activityInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0;
+// if (!showWhenLocked) {
+// return false;
+// }
+//
+// // Check the cached user preferences to see if the user has allowed this app.
+// return mEnabledApps.contains(activityInfo.applicationInfo.packageName);
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver() {
+ super(mHandler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mContext.getMainExecutor().execute(CommunalManagerService.this::updateSelectedApps);
+ }
+ }
+
+ private final class BinderService extends ICommunalManager.Stub {
+ /**
+ * Sets whether or not we are in communal mode.
+ */
+ @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE)
+ @Override
+ public void setCommunalViewShowing(boolean isShowing) {
+ mContext.enforceCallingPermission(Manifest.permission.WRITE_COMMUNAL_STATE,
+ Manifest.permission.WRITE_COMMUNAL_STATE
+ + "permission required to modify communal state.");
+ mCommunalViewIsShowing.set(isShowing);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/communal/TEST_MAPPING b/services/core/java/com/android/server/communal/TEST_MAPPING
new file mode 100644
index 0000000..026e9bb
--- /dev/null
+++ b/services/core/java/com/android/server/communal/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.communal"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 565c9ae..243a336b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1270,6 +1270,9 @@
capsBuilder.addCapability(NET_CAPABILITY_NOT_METERED);
}
+ capsBuilder.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
+ ? Arrays.asList(mConfig.underlyingNetworks) : null);
+
mNetworkCapabilities = capsBuilder.build();
mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
@@ -1290,8 +1293,6 @@
} finally {
Binder.restoreCallingIdentity(token);
}
- mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
- ? Arrays.asList(mConfig.underlyingNetworks) : null);
updateState(DetailedState.CONNECTED, "agentConnect");
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index cfd0a2d..f4aa88f 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1625,7 +1625,7 @@
Slog.v(TAG, "scheduling sync operation " + syncOperation.toString());
}
- int priority = syncOperation.findPriority();
+ int bias = syncOperation.getJobBias();
final int networkType = syncOperation.isNotAllowedOnMetered() ?
JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY;
@@ -1641,7 +1641,7 @@
.setRequiredNetworkType(networkType)
.setRequiresStorageNotLow(true)
.setPersisted(true)
- .setPriority(priority)
+ .setBias(bias)
.setFlags(jobFlags);
if (syncOperation.isPeriodic) {
@@ -3228,7 +3228,7 @@
if (asc.mSyncOperation.isConflict(op)) {
// If the provided SyncOperation conflicts with a running one, the lower
// priority sync is pre-empted.
- if (asc.mSyncOperation.findPriority() >= op.findPriority()) {
+ if (asc.mSyncOperation.getJobBias() >= op.getJobBias()) {
if (isLoggable) {
Slog.v(TAG, "Rescheduling sync due to conflict " + op.toString());
}
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index f6fad25..64b17e5 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -358,13 +358,13 @@
return sourcePeriodicId != NO_JOB_ID;
}
- int findPriority() {
+ int getJobBias() {
if (isInitialization()) {
- return JobInfo.PRIORITY_SYNC_INITIALIZATION;
+ return JobInfo.BIAS_SYNC_INITIALIZATION;
} else if (isExpedited()) {
- return JobInfo.PRIORITY_SYNC_EXPEDITED;
+ return JobInfo.BIAS_SYNC_EXPEDITED;
}
- return JobInfo.PRIORITY_DEFAULT;
+ return JobInfo.BIAS_DEFAULT;
}
private String toKey() {
diff --git a/services/core/java/com/android/server/am/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
similarity index 96%
rename from services/core/java/com/android/server/am/CriticalEventLog.java
rename to services/core/java/com/android/server/criticalevents/CriticalEventLog.java
index 6b69559..d5fe9c9 100644
--- a/services/core/java/com/android/server/am/CriticalEventLog.java
+++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.am;
+package com.android.server.criticalevents;
import android.os.Handler;
import android.os.HandlerThread;
@@ -23,11 +23,11 @@
import com.android.framework.protobuf.nano.MessageNanoPrinter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.RingBuffer;
-import com.android.server.am.nano.CriticalEventLogProto;
-import com.android.server.am.nano.CriticalEventLogStorageProto;
-import com.android.server.am.nano.CriticalEventProto;
-import com.android.server.am.nano.CriticalEventProto.HalfWatchdog;
-import com.android.server.am.nano.CriticalEventProto.Watchdog;
+import com.android.server.criticalevents.nano.CriticalEventLogProto;
+import com.android.server.criticalevents.nano.CriticalEventLogStorageProto;
+import com.android.server.criticalevents.nano.CriticalEventProto;
+import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.criticalevents.nano.CriticalEventProto.Watchdog;
import java.io.File;
import java.io.FileOutputStream;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 63d32c8..768587a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1052,11 +1052,6 @@
}
assert(state != Display.STATE_UNKNOWN);
- // Initialize things the first time the power state is changed.
- if (mustInitialize) {
- initialize(state);
- }
-
// Apply the proximity sensor.
if (mProximitySensor != null) {
if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
@@ -1107,6 +1102,11 @@
state = Display.STATE_OFF;
}
+ // Initialize things the first time the power state is changed.
+ if (mustInitialize) {
+ initialize(state);
+ }
+
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index cf8cc38..65ec1c0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -22,17 +22,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
-import android.database.ContentObserver;
import android.hardware.hdmi.HdmiControlManager;
-import android.net.Uri;
import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.ArrayMap;
@@ -89,8 +83,6 @@
private final ArrayMap<Setting, ArrayMap<SettingChangeListener, Executor>>
mSettingChangeListeners = new ArrayMap<>();
- private SettingsObserver mSettingsObserver;
-
private LinkedHashMap<String, Setting> mSettings = new LinkedHashMap<>();
/**
@@ -186,18 +178,6 @@
}
}
- private class SettingsObserver extends ContentObserver {
- SettingsObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- String setting = uri.getLastPathSegment();
- HdmiCecConfig.this.notifyGlobalSettingChanged(setting);
- }
- }
-
private class Value {
private final String mStringValue;
private final Integer mIntValue;
@@ -428,11 +408,11 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
R.bool.config_cecRcProfileSourceRootMenu_userConfigurable);
rcProfileSourceRootMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED,
R.bool.config_cecRcProfileSourceRootMenuHandled_allowed,
R.bool.config_cecRcProfileSourceRootMenuHandled_default);
rcProfileSourceRootMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_NOT_HANDLED,
R.bool.config_cecRcProfileSourceRootMenuNotHandled_allowed,
R.bool.config_cecRcProfileSourceRootMenuNotHandled_default);
@@ -440,11 +420,11 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
R.bool.config_cecRcProfileSourceSetupMenu_userConfigurable);
rcProfileSourceSetupMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED,
R.bool.config_cecRcProfileSourceSetupMenuHandled_allowed,
R.bool.config_cecRcProfileSourceSetupMenuHandled_default);
rcProfileSourceSetupMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_NOT_HANDLED,
R.bool.config_cecRcProfileSourceSetupMenuNotHandled_allowed,
R.bool.config_cecRcProfileSourceSetupMenuNotHandled_default);
@@ -452,11 +432,11 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
R.bool.config_cecRcProfileSourceContentsMenu_userConfigurable);
rcProfileSourceContentsMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED,
R.bool.config_cecRcProfileSourceContentsMenuHandled_allowed,
R.bool.config_cecRcProfileSourceContentsMenuHandled_default);
rcProfileSourceContentsMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_NOT_HANDLED,
R.bool.config_cecRcProfileSourceContentsMenuNotHandled_allowed,
R.bool.config_cecRcProfileSourceContentsMenuNotHandled_default);
@@ -464,11 +444,11 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
R.bool.config_cecRcProfileSourceTopMenu_userConfigurable);
rcProfileSourceTopMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED,
R.bool.config_cecRcProfileSourceTopMenuHandled_allowed,
R.bool.config_cecRcProfileSourceTopMenuHandled_default);
rcProfileSourceTopMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_NOT_HANDLED,
R.bool.config_cecRcProfileSourceTopMenuNotHandled_allowed,
R.bool.config_cecRcProfileSourceTopMenuNotHandled_default);
@@ -477,14 +457,194 @@
.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
R.bool.config_cecRcProfileSourceMediaContextSensitiveMenu_userConfigurable);
rcProfileSourceMediaContextSensitiveMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED,
R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_allowed,
R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_default);
rcProfileSourceMediaContextSensitiveMenu.registerValue(
- HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_NOT_HANDLED,
R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed,
R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default);
+ Setting querySadLpcm = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ R.bool.config_cecQuerySadLpcm_userConfigurable);
+ querySadLpcm.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadLpcmEnabled_allowed,
+ R.bool.config_cecQuerySadLpcmEnabled_default);
+ querySadLpcm.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadLpcmDisabled_allowed,
+ R.bool.config_cecQuerySadLpcmDisabled_default);
+
+ Setting querySadDd = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD,
+ R.bool.config_cecQuerySadDd_userConfigurable);
+ querySadDd.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadDdEnabled_allowed,
+ R.bool.config_cecQuerySadDdEnabled_default);
+ querySadDd.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadDdDisabled_allowed,
+ R.bool.config_cecQuerySadDdDisabled_default);
+
+ Setting querySadMpeg1 = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ R.bool.config_cecQuerySadMpeg1_userConfigurable);
+ querySadMpeg1.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadMpeg1Enabled_allowed,
+ R.bool.config_cecQuerySadMpeg1Enabled_default);
+ querySadMpeg1.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadMpeg1Disabled_allowed,
+ R.bool.config_cecQuerySadMpeg1Disabled_default);
+
+ Setting querySadMp3 = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3,
+ R.bool.config_cecQuerySadMp3_userConfigurable);
+ querySadMp3.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadMp3Enabled_allowed,
+ R.bool.config_cecQuerySadMp3Enabled_default);
+ querySadMp3.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadMp3Disabled_allowed,
+ R.bool.config_cecQuerySadMp3Disabled_default);
+
+ Setting querySadMpeg2 = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ R.bool.config_cecQuerySadMpeg2_userConfigurable);
+ querySadMpeg2.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadMpeg2Enabled_allowed,
+ R.bool.config_cecQuerySadMpeg2Enabled_default);
+ querySadMpeg2.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadMpeg2Disabled_allowed,
+ R.bool.config_cecQuerySadMpeg2Disabled_default);
+
+ Setting querySadAac = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+ R.bool.config_cecQuerySadAac_userConfigurable);
+ querySadAac.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadAacEnabled_allowed,
+ R.bool.config_cecQuerySadAacEnabled_default);
+ querySadAac.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadAacDisabled_allowed,
+ R.bool.config_cecQuerySadAacDisabled_default);
+
+ Setting querySadDts = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+ R.bool.config_cecQuerySadDts_userConfigurable);
+ querySadDts.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadDtsEnabled_allowed,
+ R.bool.config_cecQuerySadDtsEnabled_default);
+ querySadDts.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadDtsDisabled_allowed,
+ R.bool.config_cecQuerySadDtsDisabled_default);
+
+ Setting querySadAtrac = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ R.bool.config_cecQuerySadAtrac_userConfigurable);
+ querySadAtrac.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadAtracEnabled_allowed,
+ R.bool.config_cecQuerySadAtracEnabled_default);
+ querySadAtrac.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadAtracDisabled_allowed,
+ R.bool.config_cecQuerySadAtracDisabled_default);
+
+ Setting querySadOnebitaudio = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ R.bool.config_cecQuerySadOnebitaudio_userConfigurable);
+ querySadOnebitaudio.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadOnebitaudioEnabled_allowed,
+ R.bool.config_cecQuerySadOnebitaudioEnabled_default);
+ querySadOnebitaudio.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadOnebitaudioDisabled_allowed,
+ R.bool.config_cecQuerySadOnebitaudioDisabled_default);
+
+ Setting querySadDdp = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+ R.bool.config_cecQuerySadDdp_userConfigurable);
+ querySadDdp.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadDdpEnabled_allowed,
+ R.bool.config_cecQuerySadDdpEnabled_default);
+ querySadDdp.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadDdpDisabled_allowed,
+ R.bool.config_cecQuerySadDdpDisabled_default);
+
+ Setting querySadDtshd = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ R.bool.config_cecQuerySadDtshd_userConfigurable);
+ querySadDtshd.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadDtshdEnabled_allowed,
+ R.bool.config_cecQuerySadDtshdEnabled_default);
+ querySadDtshd.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadDtshdDisabled_allowed,
+ R.bool.config_cecQuerySadDtshdDisabled_default);
+
+ Setting querySadTruehd = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ R.bool.config_cecQuerySadTruehd_userConfigurable);
+ querySadTruehd.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadTruehdEnabled_allowed,
+ R.bool.config_cecQuerySadTruehdEnabled_default);
+ querySadTruehd.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadTruehdDisabled_allowed,
+ R.bool.config_cecQuerySadTruehdDisabled_default);
+
+ Setting querySadDst = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+ R.bool.config_cecQuerySadDst_userConfigurable);
+ querySadDst.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadDstEnabled_allowed,
+ R.bool.config_cecQuerySadDstEnabled_default);
+ querySadDst.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadDstDisabled_allowed,
+ R.bool.config_cecQuerySadDstDisabled_default);
+
+ Setting querySadWmapro = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ R.bool.config_cecQuerySadWmapro_userConfigurable);
+ querySadWmapro.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadWmaproEnabled_allowed,
+ R.bool.config_cecQuerySadWmaproEnabled_default);
+ querySadWmapro.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadWmaproDisabled_allowed,
+ R.bool.config_cecQuerySadWmaproDisabled_default);
+
+ Setting querySadMax = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX,
+ R.bool.config_cecQuerySadMax_userConfigurable);
+ querySadMax.registerValue(
+ HdmiControlManager.QUERY_SAD_ENABLED,
+ R.bool.config_cecQuerySadMaxEnabled_allowed,
+ R.bool.config_cecQuerySadMaxEnabled_default);
+ querySadMax.registerValue(
+ HdmiControlManager.QUERY_SAD_DISABLED,
+ R.bool.config_cecQuerySadMaxDisabled_allowed,
+ R.bool.config_cecQuerySadMaxDisabled_default);
+
verifySettings();
}
@@ -518,7 +678,7 @@
private int getStorage(@NonNull Setting setting) {
switch (setting.getName()) {
case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED:
- return STORAGE_GLOBAL_SETTINGS;
+ return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
@@ -536,7 +696,7 @@
case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
- return STORAGE_GLOBAL_SETTINGS;
+ return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -550,6 +710,36 @@
case HdmiControlManager
.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU:
return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO:
+ return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX:
+ return STORAGE_SHARED_PREFS;
default:
throw new VerificationException("Invalid CEC setting '" + setting.getName()
+ "' storage.");
@@ -559,7 +749,7 @@
private String getStorageKey(@NonNull Setting setting) {
switch (setting.getName()) {
case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED:
- return Global.HDMI_CONTROL_ENABLED;
+ return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
@@ -577,7 +767,7 @@
case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
- return Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED;
+ return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -591,6 +781,36 @@
case HdmiControlManager
.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU:
return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO:
+ return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX:
+ return setting.getName();
default:
throw new VerificationException("Invalid CEC setting '" + setting.getName()
+ "' storage key.");
@@ -629,17 +849,6 @@
}
}
- private void notifyGlobalSettingChanged(String setting) {
- switch (setting) {
- case Global.HDMI_CONTROL_ENABLED:
- notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
- break;
- case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
- notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
- break;
- }
- }
-
private void notifySettingChanged(@NonNull @CecSettingName String name) {
Setting setting = getSetting(name);
if (setting == null) {
@@ -669,32 +878,6 @@
}
/**
- * This method registers Global Setting change observer.
- * Needs to be called once after initialization of HdmiCecConfig.
- */
- public void registerGlobalSettingsObserver(Looper looper) {
- Handler handler = new Handler(looper);
- mSettingsObserver = new SettingsObserver(handler);
- ContentResolver resolver = mContext.getContentResolver();
- String[] settings = new String[] {
- Global.HDMI_CONTROL_ENABLED,
- Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
- };
- for (String setting: settings) {
- resolver.registerContentObserver(Global.getUriFor(setting), false,
- mSettingsObserver, UserHandle.USER_ALL);
- }
- }
-
- /**
- * This method unregisters Global Setting change observer.
- */
- public void unregisterGlobalSettingsObserver() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.unregisterContentObserver(mSettingsObserver);
- }
-
- /**
* Register change listener for a given setting name using DirectExecutor.
*/
public void registerChangeListener(@NonNull @CecSettingName String name,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index ab8217a..f356c36 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -151,6 +151,7 @@
private int mActiveRoutingPath;
protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
+ @VisibleForTesting
protected final Object mLock;
// A collection of FeatureAction.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index d4fa1df..4f55249 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -332,27 +332,27 @@
HdmiCecConfig hdmiCecConfig = mService.getHdmiCecConfig();
if (hdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU)
- == HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_HANDLED) {
+ == HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED) {
features.add(Constants.RC_PROFILE_SOURCE_HANDLES_ROOT_MENU);
}
if (hdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU)
- == HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_HANDLED) {
+ == HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED) {
features.add(Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU);
}
if (hdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU)
- == HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED) {
+ == HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED) {
features.add(Constants.RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU);
}
if (hdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU)
- == HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_HANDLED) {
+ == HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED) {
features.add(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU);
}
if (hdmiCecConfig.getIntValue(HdmiControlManager
.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU)
- == HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED) {
+ == HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED) {
features.add(Constants.RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU);
}
return features;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 362db16..b5bb8bd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -601,7 +601,6 @@
if (mMessageValidator == null) {
mMessageValidator = new HdmiCecMessageValidator(this);
}
- mHdmiCecConfig.registerGlobalSettingsObserver(mHandler.getLooper());
mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
new HdmiCecConfig.SettingChangeListener() {
@Override
diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java
index 4d36078..702c000 100644
--- a/services/core/java/com/android/server/hdmi/RequestSadAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java
@@ -16,10 +16,10 @@
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
import android.util.Slog;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -35,27 +35,11 @@
// State in which the action is waiting for <Report Short Audio Descriptor>.
private static final int STATE_WAITING_FOR_REPORT_SAD = 1;
-
- private static final List<Integer> ALL_CEC_CODECS = new ArrayList<Integer>(Arrays.asList(
- Constants.AUDIO_CODEC_LPCM,
- Constants.AUDIO_CODEC_DD,
- Constants.AUDIO_CODEC_MPEG1,
- Constants.AUDIO_CODEC_MP3,
- Constants.AUDIO_CODEC_MPEG2,
- Constants.AUDIO_CODEC_AAC,
- Constants.AUDIO_CODEC_DTS,
- Constants.AUDIO_CODEC_ATRAC,
- Constants.AUDIO_CODEC_ONEBITAUDIO,
- Constants.AUDIO_CODEC_DDP,
- Constants.AUDIO_CODEC_DTSHD,
- Constants.AUDIO_CODEC_TRUEHD,
- Constants.AUDIO_CODEC_DST,
- Constants.AUDIO_CODEC_WMAPRO,
- Constants.AUDIO_CODEC_MAX));
private static final int MAX_SAD_PER_REQUEST = 4;
private static final int RETRY_COUNTER_MAX = 1;
private final int mTargetAddress;
private final RequestSadCallback mCallback;
+ private final List<Integer> mCecCodecsToQuery = new ArrayList<>();
// List of all valid SADs reported by the target device. Not parsed nor deduplicated.
private final List<byte[]> mSupportedSads = new ArrayList<>();
private int mQueriedSadCount = 0; // Number of SADs queries that has already been completed
@@ -71,9 +55,84 @@
super(source);
mTargetAddress = targetAddress;
mCallback = Objects.requireNonNull(callback);
+ HdmiCecConfig hdmiCecConfig = localDevice().mService.getHdmiCecConfig();
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_LPCM);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DD);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MPEG1);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MP3);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MPEG2);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_AAC);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DTS);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_ATRAC);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_ONEBITAUDIO);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DDP);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DTSHD);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_TRUEHD);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DST);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_WMAPRO);
+ }
+ if (hdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX)
+ == HdmiControlManager.QUERY_SAD_ENABLED) {
+ mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MAX);
+ }
}
-
@Override
boolean start() {
querySad();
@@ -81,13 +140,13 @@
}
private void querySad() {
- if (mQueriedSadCount >= ALL_CEC_CODECS.size()) {
+ if (mQueriedSadCount >= mCecCodecsToQuery.size()) {
wrapUpAndFinish();
return;
}
- int[] codecsToQuery = ALL_CEC_CODECS.subList(mQueriedSadCount,
- Math.min(ALL_CEC_CODECS.size(), mQueriedSadCount + MAX_SAD_PER_REQUEST))
- .stream().mapToInt(i -> i).toArray();
+ int[] codecsToQuery = mCecCodecsToQuery.subList(mQueriedSadCount,
+ Math.min(mCecCodecsToQuery.size(), mQueriedSadCount + MAX_SAD_PER_REQUEST))
+ .stream().mapToInt(i -> i).toArray();
sendCommand(HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(getSourceAddress(),
mTargetAddress, codecsToQuery));
mState = STATE_WAITING_FOR_REPORT_SAD;
@@ -111,9 +170,10 @@
byte[] sad = new byte[]{cmd.getParams()[i], cmd.getParams()[i + 1],
cmd.getParams()[i + 2]};
updateResult(sad);
+ } else {
+ // Don't include invalid codecs in the result. Don't query again.
+ Slog.w(TAG, "Dropped invalid codec " + cmd.getParams()[i] + ".");
}
- // Don't include invalid codecs in the result. Don't query again.
- Slog.w(TAG, "Received invalid codec " + cmd.getParams()[i] + ".");
}
mQueriedSadCount += MAX_SAD_PER_REQUEST;
mTimeoutRetry = 0;
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 0045499..ffeaad1 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -18,11 +18,14 @@
import static java.util.Objects.requireNonNull;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.ILocaleManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.LocaleList;
@@ -142,7 +145,6 @@
}
}
-
private void setApplicationLocalesUnchecked(@NonNull String appPackageName,
@UserIdInt int userId, @NonNull LocaleList locales) {
if (DEBUG) {
@@ -152,9 +154,76 @@
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
- updater.setLocales(locales).commit();
+ boolean isSuccess = updater.setLocales(locales).commit();
+
+ //We want to send the broadcasts only if config was actually updated on commit.
+ if (isSuccess) {
+ notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
+ notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
+ notifyRegisteredReceivers(appPackageName, userId, locales);
+ }
}
+ /**
+ * Sends an implicit broadcast with action
+ * {@link android.content.Intent#ACTION_APPLICATION_LOCALE_CHANGED}
+ * to receivers with {@link android.Manifest.permission#READ_APP_SPECIFIC_LOCALES}.
+ */
+ private void notifyRegisteredReceivers(String appPackageName, int userId,
+ LocaleList locales) {
+ Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
+ appPackageName, locales);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId),
+ Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+ }
+
+ /**
+ * Sends an explicit broadcast with action
+ * {@link android.content.Intent#ACTION_APPLICATION_LOCALE_CHANGED} to
+ * the installer (as per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName})
+ * of app whose locale has changed.
+ *
+ * <p><b>Note:</b> This is can be used by installers to deal with cases such as
+ * language-based APK Splits.
+ */
+ private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
+ LocaleList locales) {
+ try {
+ String installingPackageName = mContext.getPackageManager()
+ .getInstallSourceInfo(appPackageName).getInstallingPackageName();
+ if (installingPackageName != null) {
+ Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
+ appPackageName, locales);
+ //Set package name to ensure that only installer of the app receives this intent.
+ intent.setPackage(installingPackageName);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package not found " + appPackageName);
+ }
+ }
+
+ /**
+ * Sends an explicit broadcast with action {@link android.content.Intent#ACTION_LOCALE_CHANGED}
+ * to the app whose locale has changed.
+ */
+ private void notifyAppWhoseLocaleChanged(String appPackageName, int userId,
+ LocaleList locales) {
+ Intent intent = createBaseIntent(Intent.ACTION_LOCALE_CHANGED, appPackageName, locales);
+ //Set package name to ensure that only the app whose locale changed receives this intent.
+ intent.setPackage(appPackageName);
+ intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+
+ private static Intent createBaseIntent(String intentAction, String appPackageName,
+ LocaleList locales) {
+ return new Intent(intentAction)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, appPackageName)
+ .putExtra(Intent.EXTRA_LOCALE_LIST, locales)
+ .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ }
/**
* Checks if the package is owned by the calling app or not for the given user id.
@@ -175,7 +244,7 @@
}
private void enforceChangeConfigurationPermission() {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales");
}
@@ -231,7 +300,7 @@
}
private void enforceReadAppSpecificLocalesPermission() {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_APP_SPECIFIC_LOCALES,
"getApplicationLocales");
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 7afa81a..73de0f8 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -26,6 +26,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -56,6 +57,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
+ private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
@SuppressWarnings("WeakerAccess") /* synthetic access */
// Maps hardware address to BluetoothRouteInfo
@@ -66,6 +68,8 @@
BluetoothA2dp mA2dpProfile;
@SuppressWarnings("WeakerAccess") /* synthetic access */
BluetoothHearingAid mHearingAidProfile;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothLeAudio mLeAudioProfile;
// Route type -> volume map
private final SparseIntArray mVolumeMap = new SparseIntArray();
@@ -108,6 +112,7 @@
public void start(UserHandle user) {
mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
// Bluetooth on/off broadcasts
addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
@@ -119,6 +124,10 @@
deviceStateChangedReceiver);
addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
deviceStateChangedReceiver);
+ addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED,
+ deviceStateChangedReceiver);
+ addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED,
+ deviceStateChangedReceiver);
mContext.registerReceiverAsUser(mBroadcastReceiver, user,
mIntentFilter, null, null);
@@ -240,6 +249,8 @@
| AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
| AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
+ routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
} else {
return false;
}
@@ -288,6 +299,12 @@
routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device);
type = MediaRoute2Info.TYPE_HEARING_AID;
}
+ if (mLeAudioProfile != null
+ && mLeAudioProfile.getConnectedDevices().contains(device)) {
+ newBtRoute.connectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
+ routeId = LE_AUDIO_ROUTE_ID_PREFIX + mLeAudioProfile.getGroupId(device);
+ type = MediaRoute2Info.TYPE_BLE_HEADSET;
+ }
// Current volume will be set when connected.
newBtRoute.route = new MediaRoute2Info.Builder(routeId, deviceName)
@@ -358,11 +375,7 @@
}
}
- private void addActiveHearingAidDevices(BluetoothDevice device) {
- if (DEBUG) {
- Log.d(TAG, "Setting active hearing aid devices. device=" + device);
- }
-
+ private void addActiveDevices(BluetoothDevice device) {
// Let the given device be the first active device
BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress());
addActiveRoute(activeBtRoute);
@@ -376,6 +389,21 @@
}
}
}
+ private void addActiveHearingAidDevices(BluetoothDevice device) {
+ if (DEBUG) {
+ Log.d(TAG, "Setting active hearing aid devices. device=" + device);
+ }
+
+ addActiveDevices(device);
+ }
+
+ private void addActiveLeAudioDevices(BluetoothDevice device) {
+ if (DEBUG) {
+ Log.d(TAG, "Setting active le audio devices. device=" + device);
+ }
+
+ addActiveDevices(device);
+ }
interface BluetoothRoutesUpdatedListener {
void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
@@ -392,6 +420,11 @@
if (connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
return MediaRoute2Info.TYPE_HEARING_AID;
}
+
+ if (connectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ }
+
return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
}
}
@@ -410,6 +443,10 @@
mHearingAidProfile = (BluetoothHearingAid) proxy;
activeDevices = mHearingAidProfile.getActiveDevices();
break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudioProfile = (BluetoothLeAudio) proxy;
+ activeDevices = mLeAudioProfile.getActiveDevices();
+ break;
default:
return;
}
@@ -434,6 +471,9 @@
case BluetoothProfile.HEARING_AID:
mHearingAidProfile = null;
break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudioProfile = null;
+ break;
default:
return;
}
@@ -490,12 +530,22 @@
}
notifyBluetoothRoutesUpdated();
break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET);
+ if (device != null) {
+ addActiveLeAudioDevices(device);
+ }
+ notifyBluetoothRoutesUpdated();
+ break;
case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
break;
case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device);
break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.LE_AUDIO, intent, device);
+ break;
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 21f61ca..c6f8975 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -301,8 +301,8 @@
if (DEBUG) {
Slog.d(TAG, this + ": Service binding died");
}
+ unbind();
if (shouldBind()) {
- unbind();
bind();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index eb0b2bb..14f5214 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -344,6 +344,25 @@
// Binder call
@Override
+ public void setBluetoothA2dpOn(IMediaRouterClient client, boolean on) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mAudioService.setBluetoothA2dpOn(on);
+ }
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
public void setDiscoveryRequest(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
if (client == null) {
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index 8e7c4ff..fd141bd7 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -306,6 +306,7 @@
return LOGGING_LEVEL_EVERYTHING;
}
if (mMode == MEDIA_METRICS_MODE_OFF) {
+ Slog.v(TAG, "Logging level blocked: MEDIA_METRICS_MODE_OFF");
return LOGGING_LEVEL_BLOCKED;
}
@@ -326,6 +327,8 @@
mBlockList = getListLocked(PLAYER_METRICS_APP_BLOCKLIST);
if (mBlockList == null) {
// failed to get the blocklist. Block it.
+ Slog.v(TAG, "Logging level blocked: Failed to get "
+ + "PLAYER_METRICS_APP_BLOCKLIST.");
return LOGGING_LEVEL_BLOCKED;
}
}
@@ -339,6 +342,8 @@
getListLocked(PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST);
if (mNoUidBlocklist == null) {
// failed to get the blocklist. Block it.
+ Slog.v(TAG, "Logging level blocked: Failed to get "
+ + "PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST.");
return LOGGING_LEVEL_BLOCKED;
}
}
@@ -358,6 +363,8 @@
getListLocked(PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST);
if (mNoUidAllowlist == null) {
// failed to get the allowlist. Block it.
+ Slog.v(TAG, "Logging level blocked: Failed to get "
+ + "PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST.");
return LOGGING_LEVEL_BLOCKED;
}
}
@@ -372,6 +379,8 @@
mAllowlist = getListLocked(PLAYER_METRICS_APP_ALLOWLIST);
if (mAllowlist == null) {
// failed to get the allowlist. Block it.
+ Slog.v(TAG, "Logging level blocked: Failed to get "
+ + "PLAYER_METRICS_APP_ALLOWLIST.");
return LOGGING_LEVEL_BLOCKED;
}
}
@@ -381,10 +390,12 @@
return level;
}
// Not detected in any allowlist. Block.
+ Slog.v(TAG, "Logging level blocked: Not detected in any allowlist.");
return LOGGING_LEVEL_BLOCKED;
}
}
// Blocked by default.
+ Slog.v(TAG, "Logging level blocked: Blocked by default.");
return LOGGING_LEVEL_BLOCKED;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 654b17f..b45d87f 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -39,6 +39,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.am.ProcessList;
+import com.android.server.net.NetworkPolicyManagerService.UidBlockedState;
import java.text.SimpleDateFormat;
import java.util.Arrays;
@@ -72,16 +73,6 @@
private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
private static final int EVENT_APP_IDLE_WL_CHANGED = 14;
- static final int NTWK_BLOCKED_POWER = 0;
- static final int NTWK_ALLOWED_NON_METERED = 1;
- static final int NTWK_BLOCKED_DENYLIST = 2;
- static final int NTWK_ALLOWED_ALLOWLIST = 3;
- static final int NTWK_ALLOWED_TMP_ALLOWLIST = 4;
- static final int NTWK_BLOCKED_BG_RESTRICT = 5;
- static final int NTWK_ALLOWED_DEFAULT = 6;
- static final int NTWK_ALLOWED_SYSTEM = 7;
- static final int NTWK_BLOCKED_RESTRICTED_MODE = 8;
-
private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -90,12 +81,13 @@
private final Object mLock = new Object();
- void networkBlocked(int uid, int reason) {
+ void networkBlocked(int uid, UidBlockedState uidBlockedState) {
synchronized (mLock) {
if (LOGD || uid == mDebugUid) {
- Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+ Slog.d(TAG, "Blocked state of uid: " + uidBlockedState.toString());
}
- mNetworkBlockedBuffer.networkBlocked(uid, reason);
+ mNetworkBlockedBuffer.networkBlocked(uid, uidBlockedState.blockedReasons,
+ uidBlockedState.allowedReasons, uidBlockedState.effectiveBlockedReasons);
}
}
@@ -269,29 +261,6 @@
}
}
- private static String getBlockedReason(int reason) {
- switch (reason) {
- case NTWK_BLOCKED_POWER:
- return "blocked by power restrictions";
- case NTWK_ALLOWED_NON_METERED:
- return "allowed on unmetered network";
- case NTWK_BLOCKED_DENYLIST:
- return "denylisted on metered network";
- case NTWK_ALLOWED_ALLOWLIST:
- return "allowlisted on metered network";
- case NTWK_ALLOWED_TMP_ALLOWLIST:
- return "temporary allowlisted on metered network";
- case NTWK_BLOCKED_BG_RESTRICT:
- return "blocked when background is restricted";
- case NTWK_ALLOWED_DEFAULT:
- return "allowed by default";
- case NTWK_BLOCKED_RESTRICTED_MODE:
- return "blocked by restricted networking mode";
- default:
- return String.valueOf(reason);
- }
- }
-
private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
return "Policy for " + uid + " changed from "
+ NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
@@ -402,14 +371,17 @@
data.timeStamp = System.currentTimeMillis();
}
- public void networkBlocked(int uid, int reason) {
+ public void networkBlocked(int uid, int blockedReasons, int allowedReasons,
+ int effectiveBlockedReasons) {
final Data data = getNextSlot();
if (data == null) return;
data.reset();
data.type = EVENT_NETWORK_BLOCKED;
data.ifield1 = uid;
- data.ifield2 = reason;
+ data.ifield2 = blockedReasons;
+ data.ifield3 = allowedReasons;
+ data.ifield4 = effectiveBlockedReasons;
data.timeStamp = System.currentTimeMillis();
}
@@ -554,7 +526,8 @@
case EVENT_TYPE_GENERIC:
return data.sfield1;
case EVENT_NETWORK_BLOCKED:
- return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+ return data.ifield1 + "-" + UidBlockedState.toString(
+ data.ifield2, data.ifield3, data.ifield4);
case EVENT_UID_STATE_CHANGED:
return data.ifield1 + ":" + ProcessList.makeProcStateString(data.ifield2)
+ ":" + ActivityManager.getCapabilitiesSummary(data.ifield3)
@@ -593,17 +566,18 @@
}
}
- public final static class Data {
- int type;
- long timeStamp;
+ private static final class Data {
+ public int type;
+ public long timeStamp;
- int ifield1;
- int ifield2;
- int ifield3;
- long lfield1;
- boolean bfield1;
- boolean bfield2;
- String sfield1;
+ public int ifield1;
+ public int ifield2;
+ public int ifield3;
+ public int ifield4;
+ public long lfield1;
+ public boolean bfield1;
+ public boolean bfield2;
+ public String sfield1;
public void reset(){
sfield1 = null;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8f81c0a5..64f72c5 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -79,14 +79,10 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
-import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_RESTRICTED_MODE_NETWORKS;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -135,15 +131,6 @@
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_ALLOWLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_SYSTEM;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_RESTRICTED_MODE;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -516,8 +503,6 @@
/** Defined UID policies. */
@GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
- /** Currently derived rules for each UID. */
- @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidRules = new SparseIntArray();
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
@@ -596,6 +581,10 @@
@GuardedBy("mUidRulesFirstLock")
private final SparseArray<UidBlockedState> mUidBlockedState = new SparseArray<>();
+ /** Objects used temporarily while computing the new blocked state for each uid. */
+ @GuardedBy("mUidRulesFirstLock")
+ private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
+
/** Map from network ID to last observed meteredness state */
@GuardedBy("mNetworkPoliciesSecondLock")
private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -3805,7 +3794,7 @@
final SparseBooleanArray knownUids = new SparseBooleanArray();
collectKeys(mUidState, knownUids);
- collectKeys(mUidRules, knownUids);
+ collectKeys(mUidBlockedState, knownUids);
fout.println("Status for all known UIDs:");
fout.increaseIndent();
@@ -3823,23 +3812,13 @@
fout.print(uidState.toString());
}
- final int uidRules = mUidRules.get(uid, RULE_NONE);
- fout.print(" rules=");
- fout.print(uidRulesToString(uidRules));
- fout.println();
- }
- fout.decreaseIndent();
-
- fout.println("Status for just UIDs with rules:");
- fout.increaseIndent();
- size = mUidRules.size();
- for (int i = 0; i < size; i++) {
- final int uid = mUidRules.keyAt(i);
- fout.print("UID=");
- fout.print(uid);
- final int uidRules = mUidRules.get(uid, RULE_NONE);
- fout.print(" rules=");
- fout.print(uidRulesToString(uidRules));
+ final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ if (uidBlockedState == null) {
+ fout.print(" blocked_state={null}");
+ } else {
+ fout.print(" blocked_state=");
+ fout.print(uidBlockedState.toString());
+ }
fout.println();
}
fout.decreaseIndent();
@@ -3990,22 +3969,17 @@
void updateRestrictedModeAllowlistUL() {
mUidFirewallRestrictedModeRules.clear();
forEachUid("updateRestrictedModeAllowlist", uid -> {
- final int oldUidRule = mUidRules.get(uid);
- final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
- final boolean hasUidRuleChanged = oldUidRule != newUidRule;
- final int newFirewallRule = getRestrictedModeFirewallRule(newUidRule);
+ synchronized (mUidRulesFirstLock) {
+ final UidBlockedState uidBlockedState = updateBlockedReasonsForRestrictedModeUL(
+ uid);
+ final int newFirewallRule = getRestrictedModeFirewallRule(uidBlockedState);
- // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
- // non-default rules.
- if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
- mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+ // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
+ // non-default rules.
+ if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
+ mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+ }
}
-
- if (hasUidRuleChanged) {
- mUidRules.put(uid, newUidRule);
- mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
- }
- updateBlockedReasonsForRestrictedModeUL(uid);
});
if (mRestrictedNetworkingMode) {
// firewall rules only need to be set when this mode is being enabled.
@@ -4018,15 +3992,7 @@
@VisibleForTesting
@GuardedBy("mUidRulesFirstLock")
void updateRestrictedModeForUidUL(int uid) {
- final int oldUidRule = mUidRules.get(uid);
- final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
- final boolean hasUidRuleChanged = oldUidRule != newUidRule;
-
- if (hasUidRuleChanged) {
- mUidRules.put(uid, newUidRule);
- mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
- }
- updateBlockedReasonsForRestrictedModeUL(uid);
+ final UidBlockedState uidBlockedState = updateBlockedReasonsForRestrictedModeUL(uid);
// if restricted networking mode is on, and the app has an access exemption, the uid rule
// will not change, but the firewall rule will have to be updated.
@@ -4034,16 +4000,14 @@
// Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules.
// In this case, default firewall rules can also be added.
setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid,
- getRestrictedModeFirewallRule(newUidRule));
+ getRestrictedModeFirewallRule(uidBlockedState));
}
}
- private void updateBlockedReasonsForRestrictedModeUL(int uid) {
- UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
- if (uidBlockedState == null) {
- uidBlockedState = new UidBlockedState();
- mUidBlockedState.put(uid, uidBlockedState);
- }
+ @GuardedBy("mUidRulesFirstLock")
+ private UidBlockedState updateBlockedReasonsForRestrictedModeUL(int uid) {
+ final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+ mUidBlockedState, uid);
final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
if (mRestrictedNetworkingMode) {
uidBlockedState.blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
@@ -4053,27 +4017,20 @@
if (hasRestrictedModeAccess(uid)) {
uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
} else {
- uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
+ uidBlockedState.allowedReasons &= ~ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
}
uidBlockedState.updateEffectiveBlockedReasons();
if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
- mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
- uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
- .sendToTarget();
+ postBlockedReasonsChangedMsg(uid,
+ uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons);
+
+ postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
}
+ return uidBlockedState;
}
- private int getNewRestrictedModeUidRule(int uid, int oldUidRule) {
- int newRule = oldUidRule;
- newRule &= ~MASK_RESTRICTED_MODE_NETWORKS;
- if (mRestrictedNetworkingMode && !hasRestrictedModeAccess(uid)) {
- newRule |= RULE_REJECT_RESTRICTED_MODE;
- }
- return newRule;
- }
-
- private static int getRestrictedModeFirewallRule(int uidRule) {
- if ((uidRule & RULE_REJECT_RESTRICTED_MODE) != 0) {
+ private static int getRestrictedModeFirewallRule(UidBlockedState uidBlockedState) {
+ if ((uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_RESTRICTED_MODE) != 0) {
// rejected in restricted mode, this is the default behavior.
return FIREWALL_RULE_DEFAULT;
} else {
@@ -4281,16 +4238,12 @@
if (!isUidValidForDenylistRulesUL(uid)) {
continue;
}
- int oldRules = mUidRules.get(uid);
- if (enableChain) {
- // Chain wasn't enabled before and the other power-related
- // chains are allowlists, so we can clear the
- // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
- // the effective rules result in blocking network access.
- oldRules &= MASK_METERED_NETWORKS;
- } else {
- // Skip if it had no restrictions to begin with
- if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
+ final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+ mUidBlockedState, uid);
+ if (!enableChain && (uidBlockedState.blockedReasons & ~BLOCKED_METERED_REASON_MASK)
+ == BLOCKED_REASON_NONE) {
+ // Chain isn't enabled and the uid had no restrictions to begin with.
+ continue;
}
final boolean isUidIdle = !paroled && isUidIdle(uid);
if (isUidIdle && !mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid))
@@ -4300,13 +4253,7 @@
} else {
mUidFirewallStandbyRules.put(uid, FIREWALL_RULE_DEFAULT);
}
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules,
- isUidIdle);
- if (newUidRules == RULE_NONE) {
- mUidRules.delete(uid);
- } else {
- mUidRules.put(uid, newUidRules);
- }
+ updateRulesForPowerRestrictionsUL(uid, isUidIdle);
}
setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, blockedUids,
enableChain ? CHAIN_TOGGLE_ENABLE : CHAIN_TOGGLE_DISABLE);
@@ -4524,6 +4471,7 @@
mInternetPermissionMap.put(uid, hasPermission);
return hasPermission;
} catch (RemoteException e) {
+ // ignored; service lives in system_server
}
return true;
}
@@ -4534,7 +4482,7 @@
@GuardedBy("mUidRulesFirstLock")
private void onUidDeletedUL(int uid) {
// First cleanup in-memory state synchronously...
- mUidRules.delete(uid);
+ mUidBlockedState.delete(uid);
mUidPolicy.delete(uid);
mUidFirewallStandbyRules.delete(uid);
mUidFirewallDozableRules.delete(uid);
@@ -4620,7 +4568,7 @@
* permission, since there is no need to change the {@code iptables} rule if the app does not
* have permission to use the internet.
*
- * <p>The {@link #mUidRules} map is used to define the transtion of states of an UID.
+ * <p>The {@link #mUidBlockedState} map is used to define the transition of states of an UID.
*
*/
private void updateRulesForDataUsageRestrictionsUL(int uid) {
@@ -4635,6 +4583,7 @@
}
}
+ @GuardedBy("mUidRulesFirstLock")
private void updateRulesForDataUsageRestrictionsULInner(int uid) {
if (!isUidValidForAllowlistRulesUL(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
@@ -4642,38 +4591,17 @@
}
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
- final int oldUidRules = mUidRules.get(uid, RULE_NONE);
final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
- UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
- if (uidBlockedState == null) {
- uidBlockedState = new UidBlockedState();
- mUidBlockedState.put(uid, uidBlockedState);
- }
+ final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+ mUidBlockedState, uid);
+ final UidBlockedState previousUidBlockedState = getOrCreateUidBlockedStateForUid(
+ mTmpUidBlockedState, uid);
+ previousUidBlockedState.copyFrom(uidBlockedState);
final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
- // copy oldUidRules and clear out METERED_NETWORKS rules.
- int newUidRules = oldUidRules & (~MASK_METERED_NETWORKS);
-
- // First step: define the new rule based on user restrictions and foreground state.
- if (isRestrictedByAdmin) {
- newUidRules |= RULE_REJECT_METERED;
- } else if (isForeground) {
- if (isDenied || (mRestrictBackground && !isAllowed)) {
- newUidRules |= RULE_TEMPORARY_ALLOW_METERED;
- } else if (isAllowed) {
- newUidRules |= RULE_ALLOW_METERED;
- }
- } else {
- if (isDenied) {
- newUidRules |= RULE_REJECT_METERED;
- } else if (mRestrictBackground && isAllowed) {
- newUidRules |= RULE_ALLOW_METERED;
- }
- }
-
int newBlockedReasons = BLOCKED_REASON_NONE;
int newAllowedReasons = ALLOWED_REASON_NONE;
newBlockedReasons |= (isRestrictedByAdmin ? BLOCKED_METERED_REASON_ADMIN_DISABLED : 0);
@@ -4684,16 +4612,48 @@
newAllowedReasons |= (isForeground ? ALLOWED_METERED_REASON_FOREGROUND : 0);
newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
+ uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+ & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+ uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+ & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+ uidBlockedState.updateEffectiveBlockedReasons();
+ final int oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
+ final int newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+ if (oldEffectiveBlockedReasons != newEffectiveBlockedReasons) {
+ postBlockedReasonsChangedMsg(uid,
+ newEffectiveBlockedReasons, oldEffectiveBlockedReasons);
+
+ postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
+ }
+
+ // Note that the conditionals below are for avoiding unnecessary calls to netd.
+ // TODO: Measure the performance for doing a no-op call to netd so that we can
+ // remove the conditionals to simplify the logic below. We can also further reduce
+ // some calls to netd if they turn out to be costly.
+ final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+ | BLOCKED_METERED_REASON_USER_RESTRICTED;
+ if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
+ || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
+ setMeteredNetworkDenylist(uid,
+ (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
+ }
+ final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
+ | ALLOWED_METERED_REASON_USER_EXEMPTED;
+ final int oldAllowedReasons = previousUidBlockedState.allowedReasons;
+ if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
+ || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
+ setMeteredNetworkAllowlist(uid,
+ (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+ }
+
if (LOGV) {
Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
+ ": isForeground=" +isForeground
+ ", isDenied=" + isDenied
+ ", isAllowed=" + isAllowed
+ ", isRestrictedByAdmin=" + isRestrictedByAdmin
- + ", oldRule=" + uidRulesToString(oldUidRules & MASK_METERED_NETWORKS)
- + ", newRule=" + uidRulesToString(newUidRules & MASK_METERED_NETWORKS)
- + ", newUidRules=" + uidRulesToString(newUidRules)
- + ", oldUidRules=" + uidRulesToString(oldUidRules)
+ + ", oldBlockedState=" + previousUidBlockedState.toString()
+ + ", newBlockedState="
+ ", oldBlockedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
uidBlockedState.blockedReasons & BLOCKED_METERED_REASON_MASK)
+ ", oldBlockedMeteredEffectiveReasons="
@@ -4702,84 +4662,11 @@
+ ", oldAllowedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
uidBlockedState.allowedReasons & BLOCKED_METERED_REASON_MASK));
}
-
- if (newUidRules == RULE_NONE) {
- mUidRules.delete(uid);
- } else {
- mUidRules.put(uid, newUidRules);
- }
-
- // Second step: apply bw changes based on change of state.
- if (newUidRules != oldUidRules) {
- if (hasRule(newUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
- // Temporarily allow foreground app, removing from denylist if necessary
- // (since bw_penalty_box prevails over bw_happy_box).
-
- setMeteredNetworkAllowlist(uid, true);
- // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
- // but ideally it should be just:
- // setMeteredNetworkDenylist(uid, isDenied);
- if (isDenied) {
- setMeteredNetworkDenylist(uid, false);
- }
- } else if (hasRule(oldUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
- // Remove temporary exemption from app that is not on foreground anymore.
-
- // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
- // but ideally they should be just:
- // setMeteredNetworkAllowlist(uid, isAllowed);
- // setMeteredNetworkDenylist(uid, isDenied);
- if (!isAllowed) {
- setMeteredNetworkAllowlist(uid, false);
- }
- if (isDenied || isRestrictedByAdmin) {
- setMeteredNetworkDenylist(uid, true);
- }
- } else if (hasRule(newUidRules, RULE_REJECT_METERED)
- || hasRule(oldUidRules, RULE_REJECT_METERED)) {
- // Flip state because app was explicitly added or removed to denylist.
- setMeteredNetworkDenylist(uid, (isDenied || isRestrictedByAdmin));
- if (hasRule(oldUidRules, RULE_REJECT_METERED) && isAllowed) {
- // Since denial prevails over allowance, we need to handle the special case
- // where app is allowed and denied at the same time (although such
- // scenario should be blocked by the UI), then it is removed from the denylist.
- setMeteredNetworkAllowlist(uid, isAllowed);
- }
- } else if (hasRule(newUidRules, RULE_ALLOW_METERED)
- || hasRule(oldUidRules, RULE_ALLOW_METERED)) {
- // Flip state because app was explicitly added or removed to allowlist.
- setMeteredNetworkAllowlist(uid, isAllowed);
- } else {
- // All scenarios should have been covered above.
- Log.wtf(TAG, "Unexpected change of metered UID state for " + uid
- + ": foreground=" + isForeground
- + ", allowlisted=" + isAllowed
- + ", denylisted=" + isDenied
- + ", isRestrictedByAdmin=" + isRestrictedByAdmin
- + ", newRule=" + uidRulesToString(newUidRules)
- + ", oldRule=" + uidRulesToString(oldUidRules));
- }
-
- // Dispatch changed rule to existing listeners.
- mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
- }
-
- final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
- uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
- & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
- uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
- & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
- uidBlockedState.updateEffectiveBlockedReasons();
- if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
- mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
- uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
- .sendToTarget();
- }
}
/**
- * Updates the power-related part of the {@link #mUidRules} for a given map, and notify external
- * listeners in case of change.
+ * Updates the power-related part of the {@link #mUidBlockedState} for a given map, and
+ * notify external listeners in case of change.
* <p>
* There are 3 power-related rules that affects whether an app has background access on
* non-metered networks, and when the condition applies and the UID is not allowed for power
@@ -4790,23 +4677,15 @@
* <li>Battery Saver Mode is on: {@code fw_powersave} firewall chain.
* </ul>
* <p>
- * This method updates the power-related part of the {@link #mUidRules} for a given uid based on
- * these modes, the UID process state (foreground or not), and the UID allowlist state.
+ * This method updates the power-related part of the {@link #mUidBlockedState} for a given
+ * uid based on these modes, the UID process state (foreground or not), and the UID
+ * allowlist state.
* <p>
* <strong>NOTE: </strong>This method does not update the firewall rules on {@code netd}.
*/
@GuardedBy("mUidRulesFirstLock")
private void updateRulesForPowerRestrictionsUL(int uid) {
- final int oldUidRules = mUidRules.get(uid, RULE_NONE);
-
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules,
- isUidIdle(uid));
-
- if (newUidRules == RULE_NONE) {
- mUidRules.delete(uid);
- } else {
- mUidRules.put(uid, newUidRules);
- }
+ updateRulesForPowerRestrictionsUL(uid, isUidIdle(uid));
}
/**
@@ -4815,56 +4694,37 @@
* @param uid the uid of the app to update rules for
* @param oldUidRules the current rules for the uid, in order to determine if there's a change
* @param isUidIdle whether uid is idle or not
- *
- * @return the new computed rules for the uid
*/
@GuardedBy("mUidRulesFirstLock")
- private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean isUidIdle) {
+ private void updateRulesForPowerRestrictionsUL(int uid, boolean isUidIdle) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
- "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+ "updateRulesForPowerRestrictionsUL: " + uid + "/"
+ (isUidIdle ? "I" : "-"));
}
try {
- return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, isUidIdle);
+ updateRulesForPowerRestrictionsULInner(uid, isUidIdle);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@GuardedBy("mUidRulesFirstLock")
- private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules,
- boolean isUidIdle) {
+ private void updateRulesForPowerRestrictionsULInner(int uid, boolean isUidIdle) {
if (!isUidValidForDenylistRulesUL(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
- return RULE_NONE;
+ return;
}
- final boolean restrictMode = isUidIdle || mRestrictPower || mDeviceIdleMode;
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
- // Copy existing uid rules and clear ALL_NETWORK rules.
- int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS);
-
- UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
- if (uidBlockedState == null) {
- uidBlockedState = new UidBlockedState();
- mUidBlockedState.put(uid, uidBlockedState);
- }
-
- // First step: define the new rule based on user restrictions and foreground state.
-
- // NOTE: if statements below could be inlined, but it's easier to understand the logic
- // by considering the foreground and non-foreground states.
- if (isForeground) {
- if (restrictMode) {
- newUidRules |= RULE_ALLOW_ALL;
- }
- } else if (restrictMode) {
- newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
- }
+ final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+ mUidBlockedState, uid);
+ final UidBlockedState previousUidBlockedState = getOrCreateUidBlockedStateForUid(
+ mTmpUidBlockedState, uid);
+ previousUidBlockedState.copyFrom(uidBlockedState);
int newBlockedReasons = BLOCKED_REASON_NONE;
int newAllowedReasons = ALLOWED_REASON_NONE;
@@ -4880,6 +4740,20 @@
newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
+ uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+ & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+ uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+ & ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+ uidBlockedState.updateEffectiveBlockedReasons();
+ if (previousUidBlockedState.effectiveBlockedReasons
+ != uidBlockedState.effectiveBlockedReasons) {
+ postBlockedReasonsChangedMsg(uid,
+ uidBlockedState.effectiveBlockedReasons,
+ previousUidBlockedState.effectiveBlockedReasons);
+
+ postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
+ }
+
if (LOGV) {
Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
+ ", isIdle: " + isUidIdle
@@ -4887,43 +4761,9 @@
+ ", mDeviceIdleMode: " + mDeviceIdleMode
+ ", isForeground=" + isForeground
+ ", isWhitelisted=" + isWhitelisted
- + ", oldRule=" + uidRulesToString(oldUidRules & MASK_ALL_NETWORKS)
- + ", newRule=" + uidRulesToString(newUidRules & MASK_ALL_NETWORKS)
- + ", newUidRules=" + uidRulesToString(newUidRules)
- + ", oldUidRules=" + uidRulesToString(oldUidRules));
+ + ", oldUidBlockedState=" + previousUidBlockedState.toString()
+ + ", newUidBlockedState=" + uidBlockedState.toString());
}
-
- // Second step: notify listeners if state changed.
- if (newUidRules != oldUidRules) {
- if ((newUidRules & MASK_ALL_NETWORKS) == RULE_NONE || hasRule(newUidRules,
- RULE_ALLOW_ALL)) {
- if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
- } else if (hasRule(newUidRules, RULE_REJECT_ALL)) {
- if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
- } else {
- // All scenarios should have been covered above
- Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
- + ": foreground=" + isForeground
- + ", whitelisted=" + isWhitelisted
- + ", newRule=" + uidRulesToString(newUidRules)
- + ", oldRule=" + uidRulesToString(oldUidRules));
- }
- mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
- }
-
- final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
- uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
- & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
- uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
- & ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
- uidBlockedState.updateEffectiveBlockedReasons();
- if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
- mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
- uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
- .sendToTarget();
- }
-
- return newUidRules;
}
private class NetPolicyAppIdleStateChangeListener extends AppIdleStateChangeListener {
@@ -4951,10 +4791,23 @@
}
}
+ private void postBlockedReasonsChangedMsg(int uid, int newEffectiveBlockedReasons,
+ int oldEffectiveBlockedReasons) {
+ mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+ newEffectiveBlockedReasons, oldEffectiveBlockedReasons)
+ .sendToTarget();
+ }
+
+ private void postUidRulesChangedMsg(int uid, int uidRules) {
+ mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules)
+ .sendToTarget();
+ }
+
private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
try {
listener.onUidRulesChanged(uid, uidRules);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -4963,6 +4816,7 @@
try {
listener.onMeteredIfacesChanged(meteredIfaces);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -4971,6 +4825,7 @@
try {
listener.onRestrictBackgroundChanged(restrictBackground);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -4979,6 +4834,7 @@
try {
listener.onUidPoliciesChanged(uid, uidPolicies);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -4987,6 +4843,7 @@
try {
listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -4995,6 +4852,7 @@
try {
listener.onSubscriptionPlansChanged(subId, plans);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -5003,6 +4861,7 @@
try {
listener.onBlockedReasonChanged(uid, oldBlockedReasons, newBlockedReasons);
} catch (RemoteException ignored) {
+ // Ignore if there is an error sending the callback to the client.
}
}
@@ -5013,6 +4872,10 @@
case MSG_RULES_CHANGED: {
final int uid = msg.arg1;
final int uidRules = msg.arg2;
+ if (LOGV) {
+ Slog.v(TAG, "Dispatching rules=" + uidRulesToString(uidRules)
+ + " for uid=" + uid);
+ }
final int length = mListeners.beginBroadcast();
for (int i = 0; i < length; i++) {
final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
@@ -5581,7 +5444,7 @@
}
}
- private static void collectKeys(SparseArray<UidState> source, SparseBooleanArray target) {
+ private static <T> void collectKeys(SparseArray<T> source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
target.put(source.keyAt(i), true);
@@ -5629,90 +5492,38 @@
final long startTime = mStatLogger.getTime();
mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
- final int uidRules;
- final boolean isBackgroundRestricted;
+ int blockedReasons;
synchronized (mUidRulesFirstLock) {
- uidRules = mUidRules.get(uid, RULE_NONE);
- isBackgroundRestricted = mRestrictBackground;
+ final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ blockedReasons = uidBlockedState == null
+ ? BLOCKED_REASON_NONE : uidBlockedState.effectiveBlockedReasons;
+ if (!isNetworkMetered) {
+ blockedReasons &= ~BLOCKED_METERED_REASON_MASK;
+ }
+ mLogger.networkBlocked(uid, uidBlockedState);
}
- final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered,
- isBackgroundRestricted, mLogger);
mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime);
- return ret;
+ return blockedReasons != BLOCKED_REASON_NONE;
}
@Override
public boolean isUidRestrictedOnMeteredNetworks(int uid) {
mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
- final int uidRules;
- final boolean isBackgroundRestricted;
synchronized (mUidRulesFirstLock) {
- uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
- isBackgroundRestricted = mRestrictBackground;
+ final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ int blockedReasons = uidBlockedState == null
+ ? BLOCKED_REASON_NONE : uidBlockedState.effectiveBlockedReasons;
+ blockedReasons &= BLOCKED_METERED_REASON_MASK;
+ return blockedReasons != BLOCKED_REASON_NONE;
}
- // TODO(b/177490332): The logic here might not be correct because it doesn't consider
- // RULE_REJECT_METERED condition. And it could be replaced by
- // isUidNetworkingBlockedInternal().
- return isBackgroundRestricted
- && !hasRule(uidRules, RULE_ALLOW_METERED)
- && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED);
}
private static boolean isSystem(int uid) {
return uid < Process.FIRST_APPLICATION_UID;
}
- static boolean isUidNetworkingBlockedInternal(int uid, int uidRules, boolean isNetworkMetered,
- boolean isBackgroundRestricted, @Nullable NetworkPolicyLogger logger) {
- final int reason;
- // Networks are never blocked for system components
- if (isSystem(uid)) {
- reason = NTWK_ALLOWED_SYSTEM;
- } else if (hasRule(uidRules, RULE_REJECT_RESTRICTED_MODE)) {
- reason = NTWK_BLOCKED_RESTRICTED_MODE;
- } else if (hasRule(uidRules, RULE_REJECT_ALL)) {
- reason = NTWK_BLOCKED_POWER;
- } else if (!isNetworkMetered) {
- reason = NTWK_ALLOWED_NON_METERED;
- } else if (hasRule(uidRules, RULE_REJECT_METERED)) {
- reason = NTWK_BLOCKED_DENYLIST;
- } else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
- reason = NTWK_ALLOWED_ALLOWLIST;
- } else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
- reason = NTWK_ALLOWED_TMP_ALLOWLIST;
- } else if (isBackgroundRestricted) {
- reason = NTWK_BLOCKED_BG_RESTRICT;
- } else {
- reason = NTWK_ALLOWED_DEFAULT;
- }
-
- final boolean blocked;
- switch(reason) {
- case NTWK_ALLOWED_DEFAULT:
- case NTWK_ALLOWED_NON_METERED:
- case NTWK_ALLOWED_TMP_ALLOWLIST:
- case NTWK_ALLOWED_ALLOWLIST:
- case NTWK_ALLOWED_SYSTEM:
- blocked = false;
- break;
- case NTWK_BLOCKED_RESTRICTED_MODE:
- case NTWK_BLOCKED_POWER:
- case NTWK_BLOCKED_DENYLIST:
- case NTWK_BLOCKED_BG_RESTRICT:
- blocked = true;
- break;
- default:
- throw new IllegalArgumentException();
- }
- if (logger != null) {
- logger.networkBlocked(uid, reason);
- }
-
- return blocked;
- }
-
private class NetworkPolicyManagerInternalImpl extends NetworkPolicyManagerInternal {
@Override
@@ -5921,6 +5732,16 @@
return (bundle != null) ? bundle.getBoolean(key, defaultValue) : defaultValue;
}
+ private static UidBlockedState getOrCreateUidBlockedStateForUid(
+ SparseArray<UidBlockedState> uidBlockedStates, int uid) {
+ UidBlockedState uidBlockedState = uidBlockedStates.get(uid);
+ if (uidBlockedState == null) {
+ uidBlockedState = new UidBlockedState();
+ uidBlockedStates.put(uid, uidBlockedState);
+ }
+ return uidBlockedState;
+ }
+
@VisibleForTesting
static final class UidBlockedState {
public int blockedReasons;
@@ -5984,9 +5805,180 @@
}
return effectiveBlockedReasons;
}
+
+ @Override
+ public String toString() {
+ return toString(blockedReasons, allowedReasons, effectiveBlockedReasons);
+ }
+
+ public static String toString(int blockedReasons, int allowedReasons,
+ int effectiveBlockedReasons) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("blocked=").append(blockedReasonsToString(blockedReasons)).append(",");
+ sb.append("allowed=").append(allowedReasonsToString(allowedReasons)).append(",");
+ sb.append("effective=").append(blockedReasonsToString(effectiveBlockedReasons));
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static final int[] BLOCKED_REASONS = {
+ BLOCKED_REASON_BATTERY_SAVER,
+ BLOCKED_REASON_DOZE,
+ BLOCKED_REASON_APP_STANDBY,
+ BLOCKED_REASON_RESTRICTED_MODE,
+ BLOCKED_METERED_REASON_DATA_SAVER,
+ BLOCKED_METERED_REASON_USER_RESTRICTED,
+ BLOCKED_METERED_REASON_ADMIN_DISABLED,
+ };
+
+ private static final int[] ALLOWED_REASONS = {
+ ALLOWED_REASON_SYSTEM,
+ ALLOWED_REASON_FOREGROUND,
+ ALLOWED_REASON_POWER_SAVE_ALLOWLIST,
+ ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
+ ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
+ ALLOWED_METERED_REASON_USER_EXEMPTED,
+ ALLOWED_METERED_REASON_SYSTEM,
+ ALLOWED_METERED_REASON_FOREGROUND,
+ };
+
+ private static String blockedReasonToString(int blockedReason) {
+ switch (blockedReason) {
+ case BLOCKED_REASON_NONE:
+ return "NONE";
+ case BLOCKED_REASON_BATTERY_SAVER:
+ return "BATTERY_SAVER";
+ case BLOCKED_REASON_DOZE:
+ return "DOZE";
+ case BLOCKED_REASON_APP_STANDBY:
+ return "APP_STANDBY";
+ case BLOCKED_REASON_RESTRICTED_MODE:
+ return "RESTRICTED_MODE";
+ case BLOCKED_METERED_REASON_DATA_SAVER:
+ return "DATA_SAVER";
+ case BLOCKED_METERED_REASON_USER_RESTRICTED:
+ return "METERED_USER_RESTRICTED";
+ case BLOCKED_METERED_REASON_ADMIN_DISABLED:
+ return "METERED_ADMIN_DISABLED";
+ default:
+ Slog.wtfStack(TAG, "Unknown blockedReason: " + blockedReason);
+ return String.valueOf(blockedReason);
+ }
+ }
+
+ private static String allowedReasonToString(int allowedReason) {
+ switch (allowedReason) {
+ case ALLOWED_REASON_NONE:
+ return "NONE";
+ case ALLOWED_REASON_SYSTEM:
+ return "SYSTEM";
+ case ALLOWED_REASON_FOREGROUND:
+ return "FOREGROUND";
+ case ALLOWED_REASON_POWER_SAVE_ALLOWLIST:
+ return "POWER_SAVE_ALLOWLIST";
+ case ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST:
+ return "POWER_SAVE_EXCEPT_IDLE_ALLOWLIST";
+ case ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS:
+ return "RESTRICTED_MODE_PERMISSIONS";
+ case ALLOWED_METERED_REASON_USER_EXEMPTED:
+ return "METERED_USER_EXEMPTED";
+ case ALLOWED_METERED_REASON_SYSTEM:
+ return "METERED_SYSTEM";
+ case ALLOWED_METERED_REASON_FOREGROUND:
+ return "METERED_FOREGROUND";
+ default:
+ Slog.wtfStack(TAG, "Unknown allowedReason: " + allowedReason);
+ return String.valueOf(allowedReason);
+ }
+ }
+
+ public static String blockedReasonsToString(int blockedReasons) {
+ if (blockedReasons == BLOCKED_REASON_NONE) {
+ return blockedReasonToString(BLOCKED_REASON_NONE);
+ }
+ final StringBuilder sb = new StringBuilder();
+ for (int reason : BLOCKED_REASONS) {
+ if ((blockedReasons & reason) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|");
+ sb.append(blockedReasonToString(reason));
+ blockedReasons &= ~reason;
+ }
+ }
+ if (blockedReasons != 0) {
+ sb.append(sb.length() == 0 ? "" : "|");
+ sb.append(String.valueOf(blockedReasons));
+ Slog.wtfStack(TAG, "Unknown blockedReasons: " + blockedReasons);
+ }
+ return sb.toString();
+ }
+
+ public static String allowedReasonsToString(int allowedReasons) {
+ if (allowedReasons == ALLOWED_REASON_NONE) {
+ return allowedReasonToString(ALLOWED_REASON_NONE);
+ }
+ final StringBuilder sb = new StringBuilder();
+ for (int reason : ALLOWED_REASONS) {
+ if ((allowedReasons & reason) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|");
+ sb.append(allowedReasonToString(reason));
+ allowedReasons &= ~reason;
+ }
+ }
+ if (allowedReasons != 0) {
+ sb.append(sb.length() == 0 ? "" : "|");
+ sb.append(String.valueOf(allowedReasons));
+ Slog.wtfStack(TAG, "Unknown allowedReasons: " + allowedReasons);
+ }
+ return sb.toString();
+ }
+
+ public void copyFrom(UidBlockedState uidBlockedState) {
+ blockedReasons = uidBlockedState.blockedReasons;
+ allowedReasons = uidBlockedState.allowedReasons;
+ effectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+ }
+
+ public int deriveUidRules() {
+ int uidRule = RULE_NONE;
+ if ((effectiveBlockedReasons & BLOCKED_REASON_RESTRICTED_MODE) != 0) {
+ uidRule |= RULE_REJECT_RESTRICTED_MODE;
+ }
+
+ int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
+ | BLOCKED_REASON_DOZE
+ | BLOCKED_REASON_BATTERY_SAVER;
+ if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
+ uidRule |= RULE_REJECT_ALL;
+ } else if ((blockedReasons & powerBlockedReasons) != 0) {
+ uidRule |= RULE_ALLOW_ALL;
+ }
+
+ // UidRule doesn't include RestrictBackground (DataSaver) state, so not including in
+ // metered blocked reasons below.
+ int meteredBlockedReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+ | BLOCKED_METERED_REASON_USER_RESTRICTED;
+ if ((effectiveBlockedReasons & meteredBlockedReasons) != 0) {
+ uidRule |= RULE_REJECT_METERED;
+ } else if ((blockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED) != 0
+ && (allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
+ uidRule |= RULE_TEMPORARY_ALLOW_METERED;
+ } else if ((blockedReasons & BLOCKED_METERED_REASON_DATA_SAVER) != 0) {
+ if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) {
+ uidRule |= RULE_ALLOW_ALL;
+ } else if ((allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
+ uidRule |= RULE_TEMPORARY_ALLOW_METERED;
+ }
+ }
+ if (LOGV) {
+ Slog.v(TAG, "uidBlockedState=" + this.toString()
+ + " -> uidRule=" + uidRulesToString(uidRule));
+ }
+ return uidRule;
+ }
}
- private class NotificationId {
+ private static class NotificationId {
private final String mTag;
private final int mId;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b7744c7e..95c26d9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -36,6 +37,7 @@
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
@@ -258,6 +260,8 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.InstanceId;
@@ -291,6 +295,7 @@
import com.android.server.notification.toast.TextToastRecord;
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
@@ -474,6 +479,7 @@
private ActivityManagerInternal mAmi;
private IPackageManager mPackageManager;
private PackageManager mPackageManagerClient;
+ private PackageManagerInternal mPackageManagerInternal;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
// Can be null for wear
@@ -489,6 +495,7 @@
private UserManager mUm;
private IPlatformCompat mPlatformCompat;
private ShortcutHelper mShortcutHelper;
+ private PermissionHelper mPermissionHelper;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -556,6 +563,7 @@
ArrayList<String> mLights = new ArrayList<>();
private AppOpsManager mAppOps;
+ private IAppOpsService mAppOpsService;
private UsageStatsManagerInternal mAppUsageStats;
private DevicePolicyManagerInternal mDpm;
private StatsManager mStatsManager;
@@ -606,6 +614,7 @@
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
+ final boolean mEnableAppSettingMigration;
private MetricsLogger mMetricsLogger;
private TriPredicate<String, Integer, String> mAllowedManagedServicePackages;
@@ -1663,6 +1672,18 @@
}
};
+ @VisibleForTesting
+ final IAppOpsCallback mAppOpsCallback = new IAppOpsCallback.Stub() {
+ @Override public void opChanged(int op, int uid, String packageName) {
+ if (mEnableAppSettingMigration) {
+ int opValue = mAppOps.checkOpNoThrow(
+ AppOpsManager.OP_POST_NOTIFICATION, uid, packageName);
+ boolean blocked = op != MODE_ALLOWED;
+ sendAppBlockStateChangedBroadcast(packageName, uid, blocked);
+ }
+ }
+ };
+
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -2003,6 +2024,12 @@
mNotificationRecordLogger = notificationRecordLogger;
mNotificationInstanceIdSequence = notificationInstanceIdSequence;
Notification.processAllowlistToken = ALLOWLIST_TOKEN;
+ // TODO (b/194833441): remove when OS is ready for migration. This flag is checked once
+ // rather than having a settings observer because some of the behaviors (e.g. readXml) only
+ // happen on reboot
+ mEnableAppSettingMigration = Settings.Secure.getIntForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1;
}
// TODO - replace these methods with new fields in the VisibleForTesting constructor
@@ -2166,10 +2193,11 @@
ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
ActivityTaskManagerInternal atm, UsageStatsManagerInternal appUsageStats,
DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
- UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
+ UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, IAppOpsService iAppOps,
+ UserManager userManager,
NotificationHistoryManager historyManager, StatsManager statsManager,
TelephonyManager telephonyManager, ActivityManagerInternal ami,
- MultiRateLimiter toastRateLimiter) {
+ MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2185,7 +2213,15 @@
mUgmInternal = ugmInternal;
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mAppOps = appOps;
+ mAppOpsService = iAppOps;
+ try {
+ mAppOpsService.startWatchingMode(
+ AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not register OP_POST_NOTIFICATION listener");
+ }
mAppUsageStats = appUsageStats;
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mCompanionManager = companionManager;
@@ -2258,10 +2294,12 @@
});
}
});
+ mPermissionHelper = permissionHelper;
mPreferencesHelper = new PreferencesHelper(getContext(),
mPackageManagerClient,
mRankingHandler,
mZenModeHelper,
+ mPermissionHelper,
new NotificationChannelLoggerImpl(),
mAppOps,
new SysUiStatsEvent.BuilderFactory());
@@ -2473,14 +2511,17 @@
LocalServices.getService(DevicePolicyManagerInternal.class),
UriGrantsManager.getService(),
LocalServices.getService(UriGrantsManagerInternal.class),
- (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
+ getContext().getSystemService(AppOpsManager.class),
+ IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE)),
getContext().getSystemService(UserManager.class),
new NotificationHistoryManager(getContext(), handler),
mStatsManager = (StatsManager) getContext().getSystemService(
Context.STATS_MANAGER),
getContext().getSystemService(TelephonyManager.class),
LocalServices.getService(ActivityManagerInternal.class),
- createToastRateLimiter());
+ createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
+ PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
+ AppGlobals.getPermissionManager(), mEnableAppSettingMigration));
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -2709,6 +2750,19 @@
});
}
+ private void sendAppBlockStateChangedBroadcast(String pkg, int uid, boolean blocked) {
+ try {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about app block change", e);
+ }
+ }
+
@Override
public void onUserStopping(@NonNull TargetUser user) {
mHandler.post(() -> {
@@ -3411,17 +3465,31 @@
@Override
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
enforceSystemOrSystemUI("setNotificationsEnabledForPackage");
-
- synchronized (mNotificationLock) {
- boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid)
- != NotificationManager.IMPORTANCE_NONE;
-
+ if (mEnableAppSettingMigration) {
+ boolean wasEnabled = mPermissionHelper.hasPermission(uid);
if (wasEnabled == enabled) {
return;
}
- }
+ mPermissionHelper.setNotificationPermission(
+ pkg, UserHandle.getUserId(uid), enabled, true);
+ } else {
+ synchronized (mNotificationLock) {
+ boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid)
+ != NotificationManager.IMPORTANCE_NONE;
- mPreferencesHelper.setEnabled(pkg, uid, enabled);
+ if (wasEnabled == enabled) {
+ return;
+ }
+ }
+
+ mPreferencesHelper.setEnabled(pkg, uid, enabled);
+ // TODO (b/194833441): this is being ignored by app ops now that the permission
+ // exists, so send the broadcast manually
+ mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+ enabled ? MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+
+ sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
+ }
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
@@ -3432,19 +3500,6 @@
UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
}
- mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
- enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
- try {
- getContext().sendBroadcastAsUser(
- new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
- .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, !enabled)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .setPackage(pkg),
- UserHandle.of(UserHandle.getUserId(uid)), null);
- } catch (SecurityException e) {
- Slog.w(TAG, "Can't notify app about app block change", e);
- }
-
handleSavePolicyFile();
}
@@ -3489,7 +3544,11 @@
"canNotifyAsPackage for uid " + uid);
}
- return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
+ if (mEnableAppSettingMigration) {
+ return mPermissionHelper.hasPermission(uid);
+ } else {
+ return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
+ }
}
/**
@@ -3581,7 +3640,15 @@
@Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
+ if (mEnableAppSettingMigration) {
+ if (mPermissionHelper.hasPermission(Binder.getCallingUid())) {
+ return IMPORTANCE_DEFAULT;
+ } else {
+ return IMPORTANCE_NONE;
+ }
+ } else {
+ return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
+ }
}
@Override
@@ -4018,21 +4085,13 @@
}
@Override
- public int getBlockedAppCount(int userId) {
- checkCallerIsSystem();
- return mPreferencesHelper.getBlockedAppCount(userId);
- }
-
- @Override
- public int getAppsBypassingDndCount(int userId) {
- checkCallerIsSystem();
- return mPreferencesHelper.getAppsBypassingDndCount(userId);
- }
-
- @Override
public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(
String pkg, int userId) {
checkCallerIsSystem();
+ if (!areNotificationsEnabledForPackage(pkg,
+ mPackageManagerInternal.getPackageUid(pkg, 0, userId))) {
+ return ParceledListSlice.emptyList();
+ }
return mPreferencesHelper.getNotificationChannelsBypassingDnd(pkg, userId);
}
@@ -4166,7 +4225,7 @@
// noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
callingAttributionTag, null)
- == AppOpsManager.MODE_ALLOWED) {
+ == MODE_ALLOWED) {
synchronized (mNotificationLock) {
tmp = new StatusBarNotification[mNotificationList.size()];
final int N = mNotificationList.size();
@@ -4282,7 +4341,7 @@
// noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
callingAttributionTag, null)
- == AppOpsManager.MODE_ALLOWED) {
+ == MODE_ALLOWED) {
synchronized (mArchive) {
tmp = mArchive.getArray(count, includeSnoozed);
}
@@ -4308,7 +4367,7 @@
// noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
callingAttributionTag, null)
- == AppOpsManager.MODE_ALLOWED) {
+ == MODE_ALLOWED) {
IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryReadHistory");
try {
@@ -5022,7 +5081,6 @@
@Override
public boolean matchesCallFilter(Bundle extras) {
- enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
return mZenModeHelper.matchesCallFilter(
Binder.getCallingUserHandle(),
extras,
@@ -5032,6 +5090,12 @@
}
@Override
+ public void cleanUpCallersAfter(long timeThreshold) {
+ enforceSystemOrSystemUI("INotificationManager.cleanUpCallersAfter");
+ mZenModeHelper.cleanUpCallersAfter(timeThreshold);
+ }
+
+ @Override
public boolean isSystemConditionProviderEnabled(String path) {
enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled");
return mConditionProviders.isSystemProviderEnabled(path);
@@ -6248,11 +6312,17 @@
+ ", notificationUid=" + notificationUid
+ ", notification=" + notification;
Slog.e(TAG, noChannelStr);
- boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
- == NotificationManager.IMPORTANCE_NONE;
+ boolean appNotificationsOff;
+ if (mEnableAppSettingMigration) {
+ appNotificationsOff = !mPermissionHelper.hasPermission(notificationUid);
+ } else {
+ appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
+ == NotificationManager.IMPORTANCE_NONE;
+ }
if (!appNotificationsOff) {
- doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
+ doChannelWarningToast(notificationUid,
+ "Developer warning for package \"" + pkg + "\"\n" +
"Failed to post notification on channel \"" + channelId + "\"\n" +
"See log for more details");
}
@@ -6498,7 +6568,7 @@
}
};
- private void doChannelWarningToast(CharSequence toastText) {
+ protected void doChannelWarningToast(int forUid, CharSequence toastText) {
Binder.withCleanCallingIdentity(() -> {
final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0;
final boolean warningEnabled = Settings.Global.getInt(getContext().getContentResolver(),
@@ -7078,7 +7148,6 @@
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
- r.setInterruptive(isInterruptive);
}
mNotificationsByKey.put(n.getKey(), r);
@@ -9426,7 +9495,7 @@
record.getSystemGeneratedSmartActions(),
record.getSmartReplies(),
record.canBubble(),
- record.isInterruptive(),
+ record.isTextChanged(),
record.isConversation(),
record.getShortcutInfo(),
record.getRankingScore() == 0
@@ -10871,7 +10940,7 @@
}
}
-
+ // TODO (b/194833441): remove when we've fully migrated to a permission
class RoleObserver implements OnRoleHoldersChangedListener {
// Role name : user id : list of approved packages
private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 7e94f0d..64abe2e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1157,6 +1157,10 @@
return mIsInterruptive;
}
+ public boolean isTextChanged() {
+ return mTextChanged;
+ }
+
/** Returns the time the notification audibly alerted the user. */
public long getLastAudiblyAlertedMs() {
return mLastAudiblyAlertedMs;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
new file mode 100644
index 0000000..a3c009a
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+
+import android.Manifest;
+import android.annotation.UserIdInt;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+import android.permission.IPermissionManager;
+import android.util.Slog;
+
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * NotificationManagerService helper for querying/setting the app-level notification permission
+ */
+public final class PermissionHelper {
+ private static final String TAG = "PermissionHelper";
+
+ private static String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
+
+ private final PermissionManagerServiceInternal mPmi;
+ private final IPackageManager mPackageManager;
+ private final IPermissionManager mPermManager;
+ // TODO (b/194833441): Remove when the migration is enabled
+ private final boolean mMigrationEnabled;
+
+ public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
+ IPermissionManager permManager, boolean migrationEnabled) {
+ mPmi = pmi;
+ mPackageManager = packageManager;
+ mPermManager = permManager;
+ mMigrationEnabled = migrationEnabled;
+ }
+
+ public boolean isMigrationEnabled() {
+ return mMigrationEnabled;
+ }
+
+ /**
+ * Returns whether the given uid holds the notification permission. Must not be called
+ * with a lock held.
+ */
+ public boolean hasPermission(int uid) {
+ assertFlag();
+ return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+ }
+
+ /**
+ * Returns all of the apps that have requested the notification permission in a given user.
+ * Must not be called with a lock held. Format: uid, packageName
+ */
+ public Map<Integer, String> getAppsRequestingPermission(int userId) {
+ assertFlag();
+ Map<Integer, String> requested = new HashMap<>();
+ List<PackageInfo> pkgs = getInstalledPackages(userId);
+ for (PackageInfo pi : pkgs) {
+ // when data was stored in PreferencesHelper, we only had data for apps that
+ // had ever registered an intent to send a notification. To match that behavior,
+ // filter the app list to apps that have requested the notification permission.
+ if (pi.requestedPermissions == null) {
+ continue;
+ }
+ for (String perm : pi.requestedPermissions) {
+ if (NOTIFICATION_PERMISSION.equals(perm)) {
+ requested.put(pi.applicationInfo.uid, pi.packageName);
+ break;
+ }
+ }
+ }
+ return requested;
+ }
+
+ private List<PackageInfo> getInstalledPackages(int userId) {
+ ParceledListSlice<PackageInfo> parceledList = null;
+ try {
+ parceledList = mPackageManager.getInstalledPackages(GET_PERMISSIONS, userId);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Could not reach system server", e);
+ }
+ if (parceledList == null) {
+ return Collections.emptyList();
+ }
+ return parceledList.getList();
+ }
+
+ /**
+ * Returns a list of apps that hold the notification permission. Must not be called
+ * with a lock held. Format: uid, packageName.
+ */
+ public Map<Integer, String> getAppsGrantedPermission(int userId) {
+ assertFlag();
+ Map<Integer, String> granted = new HashMap<>();
+ ParceledListSlice<PackageInfo> parceledList = null;
+ try {
+ parceledList = mPackageManager.getPackagesHoldingPermissions(
+ new String[] {NOTIFICATION_PERMISSION}, 0, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not reach system server", e);
+ }
+ if (parceledList == null) {
+ return granted;
+ }
+ for (PackageInfo pi : parceledList.getList()) {
+ granted.put(pi.applicationInfo.uid, pi.packageName);
+ }
+ return granted;
+ }
+
+ /**
+ * Grants or revokes the notification permission for a given package/user. UserSet should
+ * only be true if this method is being called to migrate existing user choice, because it
+ * can prevent the user from seeing the in app permission dialog. Must not be called
+ * with a lock held.
+ */
+ public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
+ boolean userSet) {
+ assertFlag();
+ try {
+ if (grant) {
+ mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
+ } else {
+ mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId,
+ TAG);
+ }
+ if (userSet) {
+ mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
+ FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not reach system server", e);
+ }
+ }
+
+ private void assertFlag() {
+ if (!mMigrationEnabled) {
+ throw new IllegalStateException("Method called without checking flag value");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index e8a3a81..b94721a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -169,6 +169,7 @@
private final PackageManager mPm;
private final RankingHandler mRankingHandler;
private final ZenModeHelper mZenModeHelper;
+ private final PermissionHelper mPermissionHelper;
private final NotificationChannelLogger mNotificationChannelLogger;
private final AppOpsManager mAppOps;
@@ -187,12 +188,14 @@
private int mCurrentUserId = UserHandle.USER_NULL;
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
- ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger,
+ ZenModeHelper zenHelper, PermissionHelper permHelper,
+ NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager,
SysUiStatsEvent.BuilderFactory statsEventBuilderFactory) {
mContext = context;
mZenModeHelper = zenHelper;
mRankingHandler = rankingHandler;
+ mPermissionHelper = permHelper;
mPm = pm;
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
@@ -791,6 +794,7 @@
Objects.requireNonNull(group);
Objects.requireNonNull(group.getId());
Objects.requireNonNull(!TextUtils.isEmpty(group.getName()));
+ boolean needsDndChange = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -809,7 +813,7 @@
// but the system can
if (group.isBlocked() != oldGroup.isBlocked()) {
group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
- updateChannelsBypassingDnd();
+ needsDndChange = true;
}
}
}
@@ -822,6 +826,9 @@
}
r.groups.put(group.getId(), group);
}
+ if (needsDndChange) {
+ updateChannelsBypassingDnd();
+ }
}
@Override
@@ -831,7 +838,7 @@
Objects.requireNonNull(channel);
Objects.requireNonNull(channel.getId());
Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
- boolean needsPolicyFileChange = false, wasUndeleted = false;
+ boolean needsPolicyFileChange = false, wasUndeleted = false, needsDndChange = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -897,7 +904,7 @@
if (bypassDnd != mAreChannelsBypassingDnd
|| previousExistingImportance != existing.getImportance()) {
- updateChannelsBypassingDnd();
+ needsDndChange = true;
}
}
}
@@ -912,60 +919,64 @@
mNotificationChannelLogger.logNotificationChannelModified(existing, uid, pkg,
previousLoggingImportance, false);
}
- return needsPolicyFileChange;
- }
-
- if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
- throw new IllegalStateException("Limit exceed; cannot create more channels");
- }
-
- needsPolicyFileChange = true;
-
- if (channel.getImportance() < IMPORTANCE_NONE
- || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
- throw new IllegalArgumentException("Invalid importance level");
- }
-
- // Reset fields that apps aren't allowed to set.
- if (fromTargetApp && !hasDndAccess) {
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- }
- if (fromTargetApp) {
- channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(existing != null
- ? existing.getAllowBubbles()
- : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
- }
- clearLockedFieldsLocked(channel);
- channel.setImportanceLockedByOEM(r.oemLockedImportance);
- if (!channel.isImportanceLockedByOEM()) {
- if (r.oemLockedChannels.contains(channel.getId())) {
- channel.setImportanceLockedByOEM(true);
+ } else {
+ if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
+ throw new IllegalStateException("Limit exceed; cannot create more channels");
}
- }
- channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
- if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
- channel.setLockscreenVisibility(
- NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
- }
- if (!r.showBadge) {
- channel.setShowBadge(false);
- }
- channel.setOriginalImportance(channel.getImportance());
- // validate parent
- if (channel.getParentChannelId() != null) {
- Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
- "Tried to create a conversation channel without a preexisting parent");
- }
+ needsPolicyFileChange = true;
- r.channels.put(channel.getId(), channel);
- if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
+ if (channel.getImportance() < IMPORTANCE_NONE
+ || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
+ throw new IllegalArgumentException("Invalid importance level");
+ }
+
+ // Reset fields that apps aren't allowed to set.
+ if (fromTargetApp && !hasDndAccess) {
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ }
+ if (fromTargetApp) {
+ channel.setLockscreenVisibility(r.visibility);
+ channel.setAllowBubbles(existing != null
+ ? existing.getAllowBubbles()
+ : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ }
+ clearLockedFieldsLocked(channel);
+ channel.setImportanceLockedByOEM(r.oemLockedImportance);
+ if (!channel.isImportanceLockedByOEM()) {
+ if (r.oemLockedChannels.contains(channel.getId())) {
+ channel.setImportanceLockedByOEM(true);
+ }
+ }
+ channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
+ if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ channel.setLockscreenVisibility(
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ if (!r.showBadge) {
+ channel.setShowBadge(false);
+ }
+ channel.setOriginalImportance(channel.getImportance());
+
+ // validate parent
+ if (channel.getParentChannelId() != null) {
+ Preconditions.checkArgument(
+ r.channels.containsKey(channel.getParentChannelId()),
+ "Tried to create a conversation channel without a preexisting parent");
+ }
+
+ r.channels.put(channel.getId(), channel);
+ if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ needsDndChange = true;
+ }
+ MetricsLogger.action(getChannelLog(channel, pkg).setType(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+ mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
}
- MetricsLogger.action(getChannelLog(channel, pkg).setType(
- com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
- mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
+ }
+
+ if (needsDndChange) {
+ updateChannelsBypassingDnd();
}
return needsPolicyFileChange;
@@ -997,6 +1008,7 @@
boolean fromUser) {
Objects.requireNonNull(updatedChannel);
Objects.requireNonNull(updatedChannel.getId());
+ boolean needsDndChange = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -1050,9 +1062,12 @@
if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
|| channel.getImportance() != updatedChannel.getImportance()) {
- updateChannelsBypassingDnd();
+ needsDndChange = true;
}
}
+ if (needsDndChange) {
+ updateChannelsBypassingDnd();
+ }
updateConfig();
}
@@ -1126,6 +1141,8 @@
@Override
public boolean deleteNotificationChannel(String pkg, int uid, String channelId) {
+ boolean deletedChannel = false;
+ boolean channelBypassedDnd = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -1133,13 +1150,18 @@
}
NotificationChannel channel = r.channels.get(channelId);
if (channel != null) {
- return deleteNotificationChannelLocked(channel, pkg, uid);
+ channelBypassedDnd = channel.canBypassDnd();
+ deletedChannel = deleteNotificationChannelLocked(channel, pkg, uid);
}
- return false;
}
+ if (channelBypassedDnd) {
+ updateChannelsBypassingDnd();
+ }
+ return deletedChannel;
}
- private boolean deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid) {
+ private boolean deleteNotificationChannelLocked(NotificationChannel channel, String pkg,
+ int uid) {
if (!channel.isDeleted()) {
channel.setDeleted(true);
channel.setDeletedTimeMs(System.currentTimeMillis());
@@ -1147,10 +1169,6 @@
lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
MetricsLogger.action(lm);
mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg);
-
- if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
- updateChannelsBypassingDnd();
- }
return true;
}
return false;
@@ -1352,6 +1370,7 @@
public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
String groupId) {
List<NotificationChannel> deletedChannels = new ArrayList<>();
+ boolean groupBypassedDnd = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null || TextUtils.isEmpty(groupId)) {
@@ -1368,11 +1387,15 @@
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (groupId.equals(nc.getGroup())) {
+ groupBypassedDnd |= nc.canBypassDnd();
deleteNotificationChannelLocked(nc, pkg, uid);
deletedChannels.add(nc);
}
}
}
+ if (groupBypassedDnd) {
+ updateChannelsBypassingDnd();
+ }
return deletedChannels;
}
@@ -1495,8 +1518,8 @@
public @NonNull List<String> deleteConversations(String pkg, int uid,
Set<String> conversationIds) {
+ List<String> deletedChannelIds = new ArrayList<>();
synchronized (mPackagePreferences) {
- List<String> deletedChannelIds = new ArrayList<>();
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return deletedChannelIds;
@@ -1517,11 +1540,11 @@
deletedChannelIds.add(nc.getId());
}
}
- if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- return deletedChannelIds;
}
+ if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ return deletedChannelIds;
}
@Override
@@ -1554,8 +1577,7 @@
synchronized (mPackagePreferences) {
final PackagePreferences r = mPackagePreferences.get(
packagePreferencesKey(pkg, userId));
- // notifications from this package aren't blocked
- if (r != null && r.importance != IMPORTANCE_NONE) {
+ if (r != null) {
for (NotificationChannel channel : r.channels.values()) {
if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
channels.add(channel);
@@ -1621,48 +1643,6 @@
}
}
- public int getBlockedAppCount(int userId) {
- int count = 0;
- synchronized (mPackagePreferences) {
- final int N = mPackagePreferences.size();
- for (int i = 0; i < N; i++) {
- final PackagePreferences r = mPackagePreferences.valueAt(i);
- if (userId == UserHandle.getUserId(r.uid)
- && r.importance == IMPORTANCE_NONE) {
- count++;
- }
- }
- }
- return count;
- }
-
- /**
- * Returns the number of apps that have at least one notification channel that can bypass DND
- * for given particular user
- */
- public int getAppsBypassingDndCount(int userId) {
- int count = 0;
- synchronized (mPackagePreferences) {
- final int numPackagePreferences = mPackagePreferences.size();
- for (int i = 0; i < numPackagePreferences; i++) {
- final PackagePreferences r = mPackagePreferences.valueAt(i);
- // Package isn't associated with this userId or notifications from this package are
- // blocked
- if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
- continue;
- }
-
- for (NotificationChannel channel : r.channels.values()) {
- if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
- count++;
- break;
- }
- }
- }
- }
- return count;
- }
-
/**
* Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before
* updating
@@ -1670,41 +1650,56 @@
private void syncChannelsBypassingDnd() {
mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
& NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+
updateChannelsBypassingDnd();
}
/**
* Updates the user's NotificationPolicy based on whether the current userId
* has channels bypassing DND
- * @param userId
*/
private void updateChannelsBypassingDnd() {
+ ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
+
synchronized (mPackagePreferences) {
final int numPackagePreferences = mPackagePreferences.size();
for (int i = 0; i < numPackagePreferences; i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
- // Package isn't associated with the current userId or notifications from this
- // package are blocked
- if (mCurrentUserId != UserHandle.getUserId(r.uid)
- || r.importance == IMPORTANCE_NONE) {
+ // Package isn't associated with the current userId
+ if (mCurrentUserId != UserHandle.getUserId(r.uid)) {
continue;
}
for (NotificationChannel channel : r.channels.values()) {
if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
- if (!mAreChannelsBypassingDnd) {
- mAreChannelsBypassingDnd = true;
- updateZenPolicy(true);
- }
- return;
+ candidatePkgs.add(new Pair(r.pkg, r.uid));
+ break;
}
}
}
}
- // If no channels bypass DND, update the zen policy once to disable DND bypass.
- if (mAreChannelsBypassingDnd) {
- mAreChannelsBypassingDnd = false;
- updateZenPolicy(false);
+ for (int i = candidatePkgs.size() - 1; i >= 0; i--) {
+ Pair<String, Integer> app = candidatePkgs.valueAt(i);
+ if (mPermissionHelper.isMigrationEnabled()) {
+ if (!mPermissionHelper.hasPermission(app.second)) {
+ candidatePkgs.removeAt(i);
+ }
+ } else {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getPackagePreferencesLocked(app.first, app.second);
+ if (r == null) {
+ continue;
+ }
+ if (r.importance == IMPORTANCE_NONE) {
+ candidatePkgs.removeAt(i);
+ }
+ }
+ }
+ }
+ boolean haveBypassingApps = candidatePkgs.size() > 0;
+ if (mAreChannelsBypassingDnd != haveBypassingApps) {
+ mAreChannelsBypassingDnd = haveBypassingApps;
+ updateZenPolicy(mAreChannelsBypassingDnd);
}
}
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 1050835..7d7f3a9 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -65,6 +65,7 @@
private static final int TYPE_LISTENER_HINTS_CHANGED = 15;
private static final int TYPE_SET_NOTIFICATION_POLICY = 16;
private static final int TYPE_SET_CONSOLIDATED_ZEN_POLICY = 17;
+ private static final int TYPE_MATCHES_CALL_FILTER = 18;
private static int sNext;
private static int sSize;
@@ -166,6 +167,13 @@
+ hintsToString(newHints) + ",listeners=" + listenerCount);
}
+ /*
+ * Trace calls to matchesCallFilter with the result of the call and the reason for the result.
+ */
+ public static void traceMatchesCallFilter(boolean result, String reason) {
+ append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason);
+ }
+
private static String subscribeResult(IConditionProvider provider, RemoteException e) {
return provider == null ? "no provider" : e != null ? e.getMessage() : "ok";
}
@@ -189,6 +197,7 @@
case TYPE_LISTENER_HINTS_CHANGED: return "listener_hints_changed";
case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
+ case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter";
default: return "unknown";
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 4d19855..b186f61 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -89,20 +89,34 @@
public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
consolidatedPolicy, UserHandle userHandle, Bundle extras,
ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
- if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
+ if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ ZenLog.traceMatchesCallFilter(false, "no interruptions");
+ return false; // nothing gets through
+ }
+ if (zen == Global.ZEN_MODE_ALARMS) {
+ ZenLog.traceMatchesCallFilter(false, "alarms only");
+ return false; // not an alarm
+ }
if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
if (consolidatedPolicy.allowRepeatCallers()
&& REPEAT_CALLERS.isRepeat(context, extras)) {
+ ZenLog.traceMatchesCallFilter(true, "repeat caller");
return true;
}
- if (!consolidatedPolicy.allowCalls()) return false; // no other calls get through
+ if (!consolidatedPolicy.allowCalls()) {
+ ZenLog.traceMatchesCallFilter(false, "calls not allowed");
+ return false; // no other calls get through
+ }
if (validator != null) {
final float contactAffinity = validator.getContactAffinity(userHandle, extras,
contactsTimeoutMs, timeoutAffinity);
- return audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
+ boolean match =
+ audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
+ ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity);
+ return match;
}
}
+ ZenLog.traceMatchesCallFilter(true, "no restrictions");
return true;
}
@@ -311,6 +325,10 @@
}
}
+ protected void cleanUpCallersAfter(long timeThreshold) {
+ REPEAT_CALLERS.cleanUpCallsAfter(timeThreshold);
+ }
+
private static class RepeatCallers {
// Person : time
private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
@@ -346,6 +364,17 @@
}
}
+ // Clean up all calls that occurred after the given time.
+ // Used only for tests, to clean up after testing.
+ private synchronized void cleanUpCallsAfter(long timeThreshold) {
+ for (int i = mCalls.size() - 1; i >= 0; i--) {
+ final long time = mCalls.valueAt(i);
+ if (time > timeThreshold) {
+ mCalls.removeAt(i);
+ }
+ }
+ }
+
private void setThresholdMinutes(Context context) {
if (mThresholdMinutes <= 0) {
mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 16a0b7e..93f1b47 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -188,6 +188,10 @@
mFiltering.recordCall(record);
}
+ protected void cleanUpCallersAfter(long timeThreshold) {
+ mFiltering.cleanUpCallersAfter(timeThreshold);
+ }
+
public boolean shouldIntercept(NotificationRecord record) {
synchronized (mConfig) {
return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 7889ff2..37cb8a9 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -1035,17 +1035,6 @@
}
}
- private void checkDowngrade(PackageInfo existingApexPkg, PackageInfo newApexPkg)
- throws PackageManagerException {
- final long currentVersionCode = existingApexPkg.applicationInfo.longVersionCode;
- final long newVersionCode = newApexPkg.applicationInfo.longVersionCode;
- if (currentVersionCode > newVersionCode) {
- throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE,
- "Downgrade of APEX package " + newApexPkg.packageName
- + " is not allowed");
- }
- }
-
@Override
void installPackage(File apexFile, PackageParser2 packageParser)
throws PackageManagerException {
@@ -1069,7 +1058,6 @@
"It is forbidden to install new APEX packages");
}
checkApexSignature(existingApexPkg, newApexPkg);
- checkDowngrade(existingApexPkg, newApexPkg);
ApexInfo apexInfo = waitForApexService().installAndActivatePackage(
apexFile.getAbsolutePath());
final ParsedPackage parsedPackage2 = packageParser.parsePackage(
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 8387482..9696d3d 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -62,12 +62,21 @@
private final PackageManagerService mPm;
private final Installer mInstaller;
private final ArtManagerService mArtManagerService;
+ private final PackageManagerServiceInjector mInjector;
// TODO(b/198166813): remove PMS dependency
AppDataHelper(PackageManagerService pm) {
mPm = pm;
- mInstaller = mPm.mInjector.getInstaller();
- mArtManagerService = mPm.mInjector.getArtManagerService();
+ mInjector = mPm.mInjector;
+ mInstaller = mInjector.getInstaller();
+ mArtManagerService = mInjector.getArtManagerService();
+ }
+
+ AppDataHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
+ mPm = pm;
+ mInjector = injector;
+ mInstaller = injector.getInstaller();
+ mArtManagerService = injector.getArtManagerService();
}
/**
@@ -90,8 +99,8 @@
}
Installer.Batch batch = new Installer.Batch();
- UserManagerInternal umInternal = mPm.mInjector.getUserManagerInternal();
- StorageManagerInternal smInternal = mPm.mInjector.getLocalService(
+ UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(
StorageManagerInternal.class);
for (UserInfo user : umInternal.getUsers(false /*excludeDying*/)) {
final int flags;
@@ -233,7 +242,8 @@
// #performDexOptUpgrade. When we do that we should have a
// more granular check here and only update the existing
// profiles.
- if (mPm.mIsUpgrade || mPm.mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
+ if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()
+ || (userId != UserHandle.USER_SYSTEM)) {
mArtManagerService.prepareAppProfiles(pkg, userId,
/* updateReferenceProfileContent= */ false);
}
@@ -313,7 +323,7 @@
*/
@NonNull
public void reconcileAppsData(int userId, int flags, boolean migrateAppsData) {
- final StorageManager storage = mPm.mInjector.getSystemService(StorageManager.class);
+ final StorageManager storage = mInjector.getSystemService(StorageManager.class);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
synchronized (mPm.mInstallLock) {
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 1e8e43d..d86a4dc 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -17,6 +17,8 @@
package com.android.server.pm;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_NULL;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -24,6 +26,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
@@ -624,7 +627,7 @@
Set<String> protectedBroadcasts) {
List<ParsedIntentInfo> intents = component.getIntents();
for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
- IntentFilter intentFilter = intents.get(i);
+ IntentFilter intentFilter = intents.get(i).getIntentFilter();
if (matchesIntentFilter(intent, intentFilter, protectedBroadcasts)) {
return true;
}
@@ -701,7 +704,7 @@
synchronized (mCacheLock) {
if (mShouldFilterCache != null) {
updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
- settings, users, settings.size());
+ settings, users, USER_ALL, settings.size());
if (additionalChangedPackages != null) {
for (int index = 0; index < additionalChangedPackages.size(); index++) {
String changedPackage = additionalChangedPackages.valueAt(index);
@@ -714,7 +717,8 @@
}
updateShouldFilterCacheForPackage(mShouldFilterCache, null,
- changedPkgSetting, settings, users, settings.size());
+ changedPkgSetting, settings, users, USER_ALL,
+ settings.size());
}
}
} // else, rebuild entire cache when system is ready
@@ -851,9 +855,25 @@
}
private void updateEntireShouldFilterCache() {
+ updateEntireShouldFilterCache(USER_ALL);
+ }
+
+ private void updateEntireShouldFilterCache(int subjectUserId) {
mStateProvider.runWithState((settings, users) -> {
+ int userId = USER_NULL;
+ for (int u = 0; u < users.length; u++) {
+ if (subjectUserId == users[u].id) {
+ userId = subjectUserId;
+ break;
+ }
+ }
+ if (userId == USER_NULL) {
+ Slog.e(TAG, "We encountered a new user that isn't a member of known users, "
+ + "updating the whole cache");
+ userId = USER_ALL;
+ }
WatchedSparseBooleanMatrix cache =
- updateEntireShouldFilterCacheInner(settings, users);
+ updateEntireShouldFilterCacheInner(settings, users, userId);
synchronized (mCacheLock) {
mShouldFilterCache = cache;
}
@@ -861,12 +881,19 @@
}
private WatchedSparseBooleanMatrix updateEntireShouldFilterCacheInner(
- ArrayMap<String, PackageSetting> settings, UserInfo[] users) {
- WatchedSparseBooleanMatrix cache =
- new WatchedSparseBooleanMatrix(users.length * settings.size());
+ ArrayMap<String, PackageSetting> settings, UserInfo[] users, int subjectUserId) {
+ final WatchedSparseBooleanMatrix cache;
+ if (subjectUserId == USER_ALL) {
+ cache = new WatchedSparseBooleanMatrix(users.length * settings.size());
+ } else {
+ synchronized (mCacheLock) {
+ cache = mShouldFilterCache.snapshot();
+ }
+ cache.setCapacity(users.length * settings.size());
+ }
for (int i = settings.size() - 1; i >= 0; i--) {
updateShouldFilterCacheForPackage(cache,
- null /*skipPackage*/, settings.valueAt(i), settings, users, i);
+ null /*skipPackage*/, settings.valueAt(i), settings, users, subjectUserId, i);
}
return cache;
}
@@ -887,8 +914,8 @@
packagesCache.put(settings.keyAt(i), pkg);
}
});
- WatchedSparseBooleanMatrix cache =
- updateEntireShouldFilterCacheInner(settingsCopy, usersRef[0]);
+ WatchedSparseBooleanMatrix cache = updateEntireShouldFilterCacheInner(
+ settingsCopy, usersRef[0], USER_ALL);
boolean[] changed = new boolean[1];
// We have a cache, let's make sure the world hasn't changed out from under us.
mStateProvider.runWithState((settings, users) -> {
@@ -918,10 +945,19 @@
});
}
- public void onUsersChanged() {
+ public void onUserCreated(int newUserId) {
synchronized (mCacheLock) {
if (mShouldFilterCache != null) {
- updateEntireShouldFilterCache();
+ updateEntireShouldFilterCache(newUserId);
+ onChanged();
+ }
+ }
+ }
+
+ public void onUserDeleted(@UserIdInt int userId) {
+ synchronized (mCacheLock) {
+ if (mShouldFilterCache != null) {
+ removeShouldFilterCacheForUser(userId);
onChanged();
}
}
@@ -934,7 +970,7 @@
return;
}
updateShouldFilterCacheForPackage(mShouldFilterCache, null /* skipPackage */,
- settings.get(packageName), settings, users,
+ settings.get(packageName), settings, users, USER_ALL,
settings.size() /*maxIndex*/);
}
});
@@ -942,7 +978,7 @@
private void updateShouldFilterCacheForPackage(WatchedSparseBooleanMatrix cache,
@Nullable String skipPackageName, PackageSetting subjectSetting, ArrayMap<String,
- PackageSetting> allSettings, UserInfo[] allUsers, int maxIndex) {
+ PackageSetting> allSettings, UserInfo[] allUsers, int subjectUserId, int maxIndex) {
for (int i = Math.min(maxIndex, allSettings.size() - 1); i >= 0; i--) {
PackageSetting otherSetting = allSettings.valueAt(i);
if (subjectSetting.getAppId() == otherSetting.getAppId()) {
@@ -953,25 +989,57 @@
== skipPackageName) {
continue;
}
- final int userCount = allUsers.length;
- final int appxUidCount = userCount * allSettings.size();
- for (int su = 0; su < userCount; su++) {
- int subjectUser = allUsers[su].id;
- for (int ou = 0; ou < userCount; ou++) {
- int otherUser = allUsers[ou].id;
- int subjectUid = UserHandle.getUid(subjectUser, subjectSetting.getAppId());
- int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
- cache.put(subjectUid, otherUid,
- shouldFilterApplicationInternal(
- subjectUid, subjectSetting, otherSetting, otherUser));
- cache.put(otherUid, subjectUid,
- shouldFilterApplicationInternal(
- otherUid, otherSetting, subjectSetting, subjectUser));
+ if (subjectUserId == USER_ALL) {
+ for (int su = 0; su < allUsers.length; su++) {
+ updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+ allUsers[su].id);
}
+ } else {
+ updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+ subjectUserId);
}
}
}
+ private void updateShouldFilterCacheForUser(WatchedSparseBooleanMatrix cache,
+ PackageSetting subjectSetting, UserInfo[] allUsers, PackageSetting otherSetting,
+ int subjectUserId) {
+ for (int ou = 0; ou < allUsers.length; ou++) {
+ int otherUser = allUsers[ou].id;
+ int subjectUid = UserHandle.getUid(subjectUserId, subjectSetting.getAppId());
+ int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
+ cache.put(subjectUid, otherUid,
+ shouldFilterApplicationInternal(
+ subjectUid, subjectSetting, otherSetting, otherUser));
+ cache.put(otherUid, subjectUid,
+ shouldFilterApplicationInternal(
+ otherUid, otherSetting, subjectSetting, subjectUserId));
+ }
+ }
+
+ @GuardedBy("mCacheLock")
+ private void removeShouldFilterCacheForUser(int userId) {
+ // Sorted uids with the ascending order
+ final int[] cacheUids = mShouldFilterCache.keys();
+ final int size = cacheUids.length;
+ int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
+ final int fromIndex = (pos >= 0 ? pos : ~pos);
+ if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
+ Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+ + ", fromIndex=" + fromIndex);
+ return;
+ }
+ pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
+ final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
+ if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
+ Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+ + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
+ return;
+ }
+ mShouldFilterCache.removeRange(fromIndex, toIndex);
+ mShouldFilterCache.compact();
+ }
+
private static boolean isSystemSigned(@NonNull SigningDetails sysSigningDetails,
PackageSetting pkgSetting) {
return pkgSetting.isSystem()
@@ -1181,8 +1249,8 @@
continue;
}
updateShouldFilterCacheForPackage(mShouldFilterCache,
- setting.getPackageName(),
- siblingSetting, settings, users, settings.size());
+ setting.getPackageName(), siblingSetting, settings, users,
+ USER_ALL, settings.size());
}
}
@@ -1199,7 +1267,7 @@
}
updateShouldFilterCacheForPackage(mShouldFilterCache, null,
- changedPkgSetting, settings, users, settings.size());
+ changedPkgSetting, settings, users, USER_ALL, settings.size());
}
}
}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
new file mode 100644
index 0000000..d945274
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+/**
+ * JobService to run background dex optimization. This is a thin wrapper and most logic exits in
+ * {@link BackgroundDexOptService}.
+ */
+public final class BackgroundDexOptJobService extends JobService {
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ return BackgroundDexOptService.getService().onStartJob(this, params);
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return BackgroundDexOptService.getService().onStopJob(this, params);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 49a0a88..af9c401 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -18,11 +18,11 @@
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
-import android.app.job.JobService;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -30,63 +30,88 @@
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.os.BatteryManagerInternal;
+import android.os.Binder;
import android.os.Environment;
import android.os.IThermalService;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.PinnerService;
+import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.utils.TimingsTraceAndSlog;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
- * {@hide}
+ * Controls background dex optimization run as idle job or command line.
*/
-public class BackgroundDexOptService extends JobService {
+public final class BackgroundDexOptService {
private static final String TAG = "BackgroundDexOptService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int JOB_IDLE_OPTIMIZE = 800;
- private static final int JOB_POST_BOOT_UPDATE = 801;
+ @VisibleForTesting
+ static final int JOB_IDLE_OPTIMIZE = 800;
+ @VisibleForTesting
+ static final int JOB_POST_BOOT_UPDATE = 801;
private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
- private static ComponentName sDexoptServiceName = new ComponentName(
- "android",
- BackgroundDexOptService.class.getName());
+ private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
+
+ private static ComponentName sDexoptServiceName = new ComponentName("android",
+ BackgroundDexOptJobService.class.getName());
// Possible return codes of individual optimization steps.
+ /** Ok status: Optimizations finished, All packages were processed, can continue */
+ private static final int STATUS_OK = 0;
+ /** Optimizations should be aborted. Job scheduler requested it. */
+ private static final int STATUS_ABORT_BY_CANCELLATION = 1;
+ /** Optimizations should be aborted. No space left on device. */
+ private static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
+ /** Optimizations should be aborted. Thermal throttling level too high. */
+ private static final int STATUS_ABORT_THERMAL = 3;
+ /** Battery level too low */
+ private static final int STATUS_ABORT_BATTERY = 4;
+ /** {@link PackageDexOptimizer#DEX_OPT_FAILED} case */
+ private static final int STATUS_DEX_OPT_FAILED = 5;
- // Optimizations finished. All packages were processed.
- private static final int OPTIMIZE_PROCESSED = 0;
- // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
- private static final int OPTIMIZE_CONTINUE = 1;
- // Optimizations should be aborted. Job scheduler requested it.
- private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
- // Optimizations should be aborted. No space left on device.
- private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
- // Optimizations should be aborted. Thermal throttling level too high.
- private static final int OPTIMIZE_ABORT_THERMAL = 4;
+ @IntDef(prefix = {"STATUS_"}, value = {
+ STATUS_OK,
+ STATUS_ABORT_BY_CANCELLATION,
+ STATUS_ABORT_NO_SPACE_LEFT,
+ STATUS_ABORT_THERMAL,
+ STATUS_ABORT_BATTERY,
+ STATUS_DEX_OPT_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Status {
+ }
// Used for calculating space threshold for downgrading unused apps.
private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
@@ -94,87 +119,364 @@
// Thermal cutoff value used if one isn't defined by a system property.
private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
+ private final Injector mInjector;
+
+ private final Object mLock = new Object();
+
+ // Thread currently running dexopt. This will be null if dexopt is not running.
+ // The thread running dexopt make sure to set this into null when the pending dexopt is
+ // completed.
+ @GuardedBy("mLock")
+ @Nullable
+ private Thread mDexOptThread;
+
+ // Thread currently cancelling dexopt. This thread is in blocked wait state until
+ // cancellation is done. Only this thread can change states for control. The other threads, if
+ // need to wait for cancellation, should just wait without doing any control.
+ @GuardedBy("mLock")
+ @Nullable
+ private Thread mDexOptCancellingThread;
+
+ // Tells whether post boot update is completed or not.
+ @GuardedBy("mLock")
+ private boolean mFinishedPostBootUpdate;
+
+ @GuardedBy("mLock")
+ @Status private int mLastExecutionStatus = STATUS_OK;
+
+ // Keeps packages cancelled from PDO for last session. This is for debugging.
+ @GuardedBy("mLock")
+ private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>();
+
/**
* Set of failed packages remembered across job runs.
*/
- static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
- static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
+ @GuardedBy("mLock")
+ private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>();
+ @GuardedBy("mLock")
+ private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>();
- /**
- * Atomics set to true if the JobScheduler requests an abort.
- */
- private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
- private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+ private final long mDowngradeUnusedAppsThresholdInMillis;
- /**
- * Atomic set to true if one job should exit early because another job was started.
- */
- private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
-
- private final File mDataDir = Environment.getDataDirectory();
- private static final long mDowngradeUnusedAppsThresholdInMillis =
- getDowngradeUnusedAppsThresholdInMillis();
-
- private final IThermalService mThermalService =
- IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
-
- private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
+ private List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
- public static void schedule(Context context) {
- if (isBackgroundDexoptDisabled()) {
+ /** Listener for monitoring package change due to dexopt. */
+ public interface PackagesUpdatedListener {
+ /** Called when the packages are updated through dexopt */
+ void onPackagesUpdated(ArraySet<String> updatedPackages);
+ }
+
+ public BackgroundDexOptService(Context context, DexManager dexManager) {
+ this(new Injector(context, dexManager));
+ }
+
+ @VisibleForTesting
+ public BackgroundDexOptService(Injector injector) {
+ mInjector = injector;
+ LocalServices.addService(BackgroundDexOptService.class, this);
+ mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis();
+ }
+
+ /** Start scheduling job after boot completion */
+ public void systemReady() {
+ if (mInjector.isBackgroundDexOptDisabled()) {
return;
}
- final JobScheduler js = context.getSystemService(JobScheduler.class);
-
- // Schedule a one-off job which scans installed packages and updates
- // out-of-date oat files. Schedule it 10 minutes after the boot complete event,
- // so that we don't overload the boot with additional dex2oat compilations.
- context.registerReceiver(new BroadcastReceiver() {
+ mInjector.getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
- .setMinimumLatency(TimeUnit.MINUTES.toMillis(10))
- .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60))
- .build());
- context.unregisterReceiver(this);
+ mInjector.getContext().unregisterReceiver(this);
+ // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is
+ // completed.
+ scheduleAJob(JOB_POST_BOOT_UPDATE);
+ scheduleAJob(JOB_IDLE_OPTIMIZE);
if (DEBUG) {
- Slog.i(TAG, "BootBgDexopt scheduled");
+ Slog.d(TAG, "BootBgDexopt scheduled");
}
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+ }
- // Schedule a daily job which scans installed packages and compiles
- // those with fresh profiling data.
- js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
- .build());
-
- if (DEBUG) {
- Slog.d(TAG, "BgDexopt scheduled");
+ /** Dump the current state */
+ public void dump(IndentingPrintWriter writer) {
+ boolean disabled = mInjector.isBackgroundDexOptDisabled();
+ writer.print("enabled:");
+ writer.println(!disabled);
+ if (disabled) {
+ return;
+ }
+ synchronized (mLock) {
+ writer.print("mDexOptThread:");
+ writer.println(mDexOptThread);
+ writer.print("mDexOptCancellingThread:");
+ writer.println(mDexOptCancellingThread);
+ writer.print("mFinishedPostBootUpdate:");
+ writer.print(mFinishedPostBootUpdate);
+ writer.print(",mLastExecutionStatus:");
+ writer.println(mLastExecutionStatus);
+ writer.print("mLastCancelledPackages:");
+ writer.println(String.join(",", mLastCancelledPackages));
+ writer.print("mFailedPackageNamesPrimary:");
+ writer.println(String.join(",", mFailedPackageNamesPrimary));
+ writer.print("mFailedPackageNamesSecondary:");
+ writer.println(String.join(",", mFailedPackageNamesSecondary));
}
}
- public static void notifyPackageChanged(String packageName) {
- // The idle maintanance job skips packages which previously failed to
+ /** Gets the instance of the service */
+ public static BackgroundDexOptService getService() {
+ return LocalServices.getService(BackgroundDexOptService.class);
+ }
+
+ /**
+ * Executes the background dexopt job immediately for selected packages or all packages.
+ *
+ * <p>This is only for shell command and only root or shell user can use this.
+ *
+ * @param packageNames dex optimize the passed packages or all packages if null
+ *
+ * @return true if dex optimization is complete. false if the task is cancelled or if there was
+ * an error.
+ */
+ public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
+ enforceRootOrShell();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Do not cancel and wait for completion if there is pending task.
+ waitForDexOptThreadToFinishLocked();
+ resetStatesForNewDexOptRunLocked(Thread.currentThread());
+ }
+ PackageManagerService pm = mInjector.getPackageManagerService();
+ DexOptHelper dexOptHelper = new DexOptHelper(pm);
+ ArraySet<String> packagesToOptimize;
+ if (packageNames == null) {
+ packagesToOptimize = dexOptHelper.getOptimizablePackages();
+ } else {
+ packagesToOptimize = new ArraySet<>(packageNames);
+ }
+ return runIdleOptimization(pm, dexOptHelper, packagesToOptimize,
+ /* isPostBootUpdate= */ false);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ markDexOptCompleted();
+ }
+ }
+
+ /**
+ * Cancels currently running any idle optimization tasks started from JobScheduler
+ * or runIdleOptimizationsNow call.
+ *
+ * <p>This is only for shell command and only root or shell user can use this.
+ */
+ public void cancelBackgroundDexoptJob() {
+ enforceRootOrShell();
+ Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
+ }
+
+ /** Adds listener for package update */
+ public void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+ synchronized (mLock) {
+ mPackagesUpdatedListeners.add(listener);
+ }
+ }
+
+ /** Removes package update listener */
+ public void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+ synchronized (mLock) {
+ mPackagesUpdatedListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Notifies package change and removes the package from the failed package list so that
+ * the package can run dexopt again.
+ */
+ public void notifyPackageChanged(String packageName) {
+ // The idle maintenance job skips packages which previously failed to
// compile. The given package has changed and may successfully compile
// now. Remove it from the list of known failing packages.
- synchronized (sFailedPackageNamesPrimary) {
- sFailedPackageNamesPrimary.remove(packageName);
- }
- synchronized (sFailedPackageNamesSecondary) {
- sFailedPackageNamesSecondary.remove(packageName);
+ synchronized (mLock) {
+ mFailedPackageNamesPrimary.remove(packageName);
+ mFailedPackageNamesSecondary.remove(packageName);
}
}
- private long getLowStorageThreshold(Context context) {
- @SuppressWarnings("deprecation")
- final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
+ /** For BackgroundDexOptJobService to dispatch onStartJob event */
+ /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) {
+ Slog.i(TAG, "onStartJob:" + params.getJobId());
+
+ boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE;
+ // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
+ // the checks above. This check is not "live" - the value is determined by a background
+ // restart with a period of ~1 minute.
+ PackageManagerService pm = mInjector.getPackageManagerService();
+ if (pm.isStorageLow()) {
+ Slog.w(TAG, "Low storage, skipping this run");
+ markPostBootUpdateCompleted(params);
+ return false;
+ }
+
+ DexOptHelper dexOptHelper = new DexOptHelper(pm);
+ ArraySet<String> pkgs = dexOptHelper.getOptimizablePackages();
+ if (pkgs.isEmpty()) {
+ Slog.i(TAG, "No packages to optimize");
+ markPostBootUpdateCompleted(params);
+ return false;
+ }
+
+ mThermalStatusCutoff = mInjector.getDexOptThermalCutoff();
+
+ synchronized (mLock) {
+ if (mDexOptThread != null && mDexOptThread.isAlive()) {
+ // Other task is already running.
+ return false;
+ }
+ if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) {
+ // Post boot job not finished yet. Run post boot job first.
+ return false;
+ }
+ resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
+ "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
+ () -> {
+ TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG,
+ Trace.TRACE_TAG_PACKAGE_MANAGER);
+ tr.traceBegin("jobExecution");
+ boolean completed = false;
+ try {
+ completed = runIdleOptimization(pm, dexOptHelper, pkgs,
+ params.getJobId() == JOB_POST_BOOT_UPDATE);
+ } finally { // Those cleanup should be done always.
+ tr.traceEnd();
+ Slog.i(TAG, "dexopt finishing. jobid:" + params.getJobId()
+ + " completed:" + completed);
+
+ if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
+ if (completed) {
+ markPostBootUpdateCompleted(params);
+ }
+ // Reschedule when cancelled
+ job.jobFinished(params, !completed);
+ } else {
+ // Periodic job
+ job.jobFinished(params, true);
+ }
+ markDexOptCompleted();
+ }
+ }));
+ }
+ return true;
+ }
+
+ /** For BackgroundDexOptJobService to dispatch onStopJob event */
+ /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
+ Slog.i(TAG, "onStopJob:" + params.getJobId());
+ // This cannot block as it is in main thread, thus dispatch to a newly created thread and
+ // cancel it from there.
+ // As this event does not happen often, creating a new thread is justified rather than
+ // having one thread kept permanently.
+ mInjector.createAndStartThread("DexOptCancel", this::cancelDexOptAndWaitForCompletion);
+ // Always reschedule for cancellation.
+ return true;
+ }
+
+ /**
+ * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
+ * until cancellation is done.
+ */
+ private void cancelDexOptAndWaitForCompletion() {
+ synchronized (mLock) {
+ if (mDexOptThread == null) {
+ return;
+ }
+ if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) {
+ // No control, just wait
+ waitForDexOptThreadToFinishLocked();
+ // Do not wait for other cancellation's complete. That will be handled by the next
+ // start flow.
+ return;
+ }
+ mDexOptCancellingThread = Thread.currentThread();
+ // Take additional caution to make sure that we do not leave this call
+ // with controlDexOptBlockingLocked(true) state.
+ try {
+ controlDexOptBlockingLocked(true);
+ waitForDexOptThreadToFinishLocked();
+ } finally {
+ // Reset to default states regardless of previous states
+ mDexOptCancellingThread = null;
+ mDexOptThread = null;
+ controlDexOptBlockingLocked(false);
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void waitForDexOptThreadToFinishLocked() {
+ TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
+ tr.traceBegin("waitForDexOptThreadToFinishLocked");
+ try {
+ // Wait but check in regular internal to see if the thread is still alive.
+ while (mDexOptThread != null && mDexOptThread.isAlive()) {
+ mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS);
+ }
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while waiting for dexopt thread");
+ Thread.currentThread().interrupt();
+ }
+ tr.traceEnd();
+ }
+
+ private void markDexOptCompleted() {
+ synchronized (mLock) {
+ if (mDexOptThread != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread
+ + " current:" + Thread.currentThread());
+ }
+ mDexOptThread = null;
+ // Other threads may be waiting for completion.
+ mLock.notifyAll();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void resetStatesForNewDexOptRunLocked(Thread thread) {
+ mDexOptThread = thread;
+ mLastCancelledPackages.clear();
+ controlDexOptBlockingLocked(false);
+ }
+
+ private void enforceRootOrShell() {
+ int uid = Binder.getCallingUid();
+ if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+ throw new SecurityException("Should be shell or root user");
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void controlDexOptBlockingLocked(boolean block) {
+ PackageManagerService pm = mInjector.getPackageManagerService();
+ new DexOptHelper(pm).controlDexOptBlocking(block);
+ }
+
+ private void scheduleAJob(int jobId) {
+ JobScheduler js = mInjector.getJobScheduler();
+ JobInfo.Builder builder = new JobInfo.Builder(jobId, sDexoptServiceName)
+ .setRequiresDeviceIdle(true);
+ if (jobId == JOB_IDLE_OPTIMIZE) {
+ builder.setRequiresCharging(true)
+ .setPeriodic(IDLE_OPTIMIZATION_PERIOD);
+ }
+ js.schedule(builder.build());
+ }
+
+ private long getLowStorageThreshold() {
+ long lowThreshold = mInjector.getDataDirStorageLowBytes();
if (lowThreshold == 0) {
Slog.e(TAG, "Invalid low storage threshold");
}
@@ -182,125 +484,47 @@
return lowThreshold;
}
- private boolean runPostBootUpdate(final JobParameters jobParams,
- final PackageManagerService pm, final ArraySet<String> pkgs) {
- if (mExitPostBootUpdate.get()) {
- // This job has already been superseded. Do not start it.
- return false;
+ private void logStatus(int status) {
+ switch (status) {
+ case STATUS_OK:
+ Slog.i(TAG, "Idle optimizations completed.");
+ break;
+ case STATUS_ABORT_NO_SPACE_LEFT:
+ Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
+ break;
+ case STATUS_ABORT_BY_CANCELLATION:
+ Slog.w(TAG, "Idle optimizations aborted by cancellation.");
+ break;
+ case STATUS_ABORT_THERMAL:
+ Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
+ break;
+ case STATUS_ABORT_BATTERY:
+ Slog.w(TAG, "Idle optimizations aborted by low battery.");
+ break;
+ case STATUS_DEX_OPT_FAILED:
+ Slog.w(TAG, "Idle optimizations failed from dexopt.");
+ break;
+ default:
+ Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status);
+ break;
}
- new Thread("BackgroundDexOptService_PostBootUpdate") {
- @Override
- public void run() {
- postBootUpdate(jobParams, pm, pkgs);
- }
-
- }.start();
- return true;
}
- private void postBootUpdate(JobParameters jobParams, PackageManagerService pm,
- ArraySet<String> pkgs) {
- final BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- final long lowThreshold = getLowStorageThreshold(this);
-
- mAbortPostBootUpdate.set(false);
-
- ArraySet<String> updatedPackages = new ArraySet<>();
- for (String pkg : pkgs) {
- if (mAbortPostBootUpdate.get()) {
- // JobScheduler requested an early abort.
- return;
- }
- if (mExitPostBootUpdate.get()) {
- // Different job, which supersedes this one, is running.
- break;
- }
- if (batteryManagerInternal.getBatteryLevelLow()) {
- // Rather bail than completely drain the battery.
- break;
- }
- long usableSpace = mDataDir.getUsableSpace();
- if (usableSpace < lowThreshold) {
- // Rather bail than completely fill up the disk.
- Slog.w(TAG, "Aborting background dex opt job due to low storage: " +
- usableSpace);
- break;
- }
- if (DEBUG) {
- Slog.i(TAG, "Updating package " + pkg);
- }
-
- // Update package if needed. Note that there can be no race between concurrent
- // jobs because PackageDexOptimizer.performDexOpt is synchronized.
-
- // checkProfiles is false to avoid merging profiles during boot which
- // might interfere with background compilation (b/28612421).
- // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
- // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
- // trade-off worth doing to save boot time work.
- int result = pm.performDexOptWithStatus(new DexoptOptions(
- pkg,
- PackageManagerService.REASON_POST_BOOT,
- DexoptOptions.DEXOPT_BOOT_COMPLETE));
- if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
- }
+ /** Returns true if completed */
+ private boolean runIdleOptimization(PackageManagerService pm, DexOptHelper dexOptHelper,
+ ArraySet<String> pkgs, boolean isPostBootUpdate) {
+ long lowStorageThreshold = getLowStorageThreshold();
+ int status = idleOptimizePackages(pm, dexOptHelper, pkgs, lowStorageThreshold,
+ isPostBootUpdate);
+ logStatus(status);
+ synchronized (mLock) {
+ mLastExecutionStatus = status;
}
- notifyPinService(updatedPackages);
- notifyPackagesUpdated(updatedPackages);
- // Ran to completion, so we abandon our timeslice and do not reschedule.
- jobFinished(jobParams, /* reschedule */ false);
+
+ return status == STATUS_OK;
}
- private boolean runIdleOptimization(final JobParameters jobParams,
- final PackageManagerService pm, final ArraySet<String> pkgs) {
- new Thread("BackgroundDexOptService_IdleOptimization") {
- @Override
- public void run() {
- int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
- if (result == OPTIMIZE_PROCESSED) {
- Slog.i(TAG, "Idle optimizations completed.");
- } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
- Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
- } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
- Slog.w(TAG, "Idle optimizations aborted by job scheduler.");
- } else if (result == OPTIMIZE_ABORT_THERMAL) {
- Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
- } else {
- Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result);
- }
-
- if (result == OPTIMIZE_ABORT_THERMAL) {
- // Abandon our timeslice and reschedule
- jobFinished(jobParams, /* wantsReschedule */ true);
- } else if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
- // Abandon our timeslice and do not reschedule.
- jobFinished(jobParams, /* wantsReschedule */ false);
- }
- }
- }.start();
- return true;
- }
-
- // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
- private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
- Context context) {
- Slog.i(TAG, "Performing idle optimizations");
- // If post-boot update is still running, request that it exits early.
- mExitPostBootUpdate.set(true);
- mAbortIdleOptimization.set(false);
-
- long lowStorageThreshold = getLowStorageThreshold(context);
- int result = idleOptimizePackages(pm, pkgs, lowStorageThreshold);
- return result;
- }
-
- /**
- * Get the size of the directory. It uses recursion to go over all files.
- * @param f
- * @return
- */
+ /** Gets the size of the directory. It uses recursion to go over all files. */
private long getDirectorySize(File f) {
long size = 0;
if (f.isDirectory()) {
@@ -313,10 +537,7 @@
return size;
}
- /**
- * Get the size of a package.
- * @param pkg
- */
+ /** Gets the size of a package. */
private long getPackageSize(PackageManagerService pm, String pkg) {
PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
long size = 0;
@@ -340,17 +561,18 @@
return 0;
}
- private int idleOptimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold) {
+ @Status
+ private int idleOptimizePackages(PackageManagerService pm, DexOptHelper dexOptHelper,
+ ArraySet<String> pkgs, long lowStorageThreshold, boolean isPostBootUpdate) {
ArraySet<String> updatedPackages = new ArraySet<>();
ArraySet<String> updatedPackagesDueToSecondaryDex = new ArraySet<>();
try {
- final boolean supportSecondaryDex = supportSecondaryDex();
+ boolean supportSecondaryDex = mInjector.supportSecondaryDex();
if (supportSecondaryDex) {
- int result = reconcileSecondaryDexFiles(pm.getDexManager());
- if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ @Status int result = reconcileSecondaryDexFiles();
+ if (result != STATUS_OK) {
return result;
}
}
@@ -358,7 +580,7 @@
// Only downgrade apps when space is low on device.
// Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
// up disk before user hits the actual lowStorageThreshold.
- final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
+ long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
* lowStorageThreshold;
boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
if (DEBUG) {
@@ -368,21 +590,33 @@
Set<String> unusedPackages =
pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
if (DEBUG) {
- Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
+ Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
}
if (!unusedPackages.isEmpty()) {
for (String pkg : unusedPackages) {
- int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
- if (abortCode != OPTIMIZE_CONTINUE) {
+ @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
+ if (abortCode != STATUS_OK) {
// Should be aborted by the scheduler.
return abortCode;
}
- if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) {
+ @DexOptResult int downgradeResult = downgradePackage(pm, dexOptHelper, pkg,
+ /* isForPrimaryDex= */ true, isPostBootUpdate);
+ if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
}
+ @Status int status = convertPackageDexOptimizerStatusToInternal(
+ downgradeResult);
+ if (status != STATUS_OK) {
+ return status;
+ }
if (supportSecondaryDex) {
- downgradePackage(pm, pkg, /*isForPrimaryDex*/ false);
+ downgradeResult = downgradePackage(pm, dexOptHelper, pkg,
+ /* isForPrimaryDex= */false, isPostBootUpdate);
+ status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
+ if (status != STATUS_OK) {
+ return status;
+ }
}
}
@@ -391,18 +625,20 @@
}
}
- int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
- /*isForPrimaryDex*/ true, updatedPackages);
- if (primaryResult != OPTIMIZE_PROCESSED) {
+ @Status int primaryResult = optimizePackages(dexOptHelper, pkgs,
+ lowStorageThreshold, /*isForPrimaryDex=*/ true, updatedPackages,
+ isPostBootUpdate);
+ if (primaryResult != STATUS_OK) {
return primaryResult;
}
if (!supportSecondaryDex) {
- return OPTIMIZE_PROCESSED;
+ return STATUS_OK;
}
- int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
- /*isForPrimaryDex*/ false, updatedPackagesDueToSecondaryDex);
+ @Status int secondaryResult = optimizePackages(dexOptHelper, pkgs,
+ lowStorageThreshold, /*isForPrimaryDex*/ false,
+ updatedPackagesDueToSecondaryDex, isPostBootUpdate);
return secondaryResult;
} finally {
// Always let the pinner service know about changes.
@@ -414,21 +650,26 @@
}
}
- private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages) {
+ @Status
+ private int optimizePackages(DexOptHelper dexOptHelper,
+ ArraySet<String> pkgs, long lowStorageThreshold, boolean isForPrimaryDex,
+ ArraySet<String> updatedPackages, boolean isPostBootUpdate) {
for (String pkg : pkgs) {
int abortCode = abortIdleOptimizations(lowStorageThreshold);
- if (abortCode != OPTIMIZE_CONTINUE) {
+ if (abortCode != STATUS_OK) {
// Either aborted by the scheduler or no space left.
return abortCode;
}
- boolean dexOptPerformed = optimizePackage(pm, pkg, isForPrimaryDex);
- if (dexOptPerformed) {
+ @DexOptResult int result = optimizePackage(dexOptHelper, pkg, isForPrimaryDex,
+ isPostBootUpdate);
+ if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
+ } else if (result != PackageDexOptimizer.DEX_OPT_SKIPPED) {
+ return convertPackageDexOptimizerStatusToInternal(result);
}
}
- return OPTIMIZE_PROCESSED;
+ return STATUS_OK;
}
/**
@@ -436,21 +677,25 @@
* eg. if the package is in speed-profile the package will be downgraded to verify.
* @param pm PackageManagerService
* @param pkg The package to be downgraded.
- * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
- * @return true if the package was downgraded.
+ * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
+ * @return PackageDexOptimizer.DEX_*
*/
- private boolean downgradePackage(PackageManagerService pm, String pkg,
- boolean isForPrimaryDex) {
+ @DexOptResult
+ private int downgradePackage(PackageManagerService pm, DexOptHelper dexOptHelper, String pkg,
+ boolean isForPrimaryDex, boolean isPostBootUpdate) {
if (DEBUG) {
Slog.d(TAG, "Downgrading " + pkg);
}
- boolean dex_opt_performed = false;
+ if (isCancelling()) {
+ return PackageDexOptimizer.DEX_OPT_CANCELLED;
+ }
int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
- int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
- | DexoptOptions.DEXOPT_DOWNGRADE;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
+ if (!isPostBootUpdate) {
+ dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+ }
long package_size_before = getPackageSize(pm, pkg);
-
+ int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
// This applies for system apps or if packages location is not a directory, i.e.
// monolithic install.
@@ -459,73 +704,78 @@
// remove their compiler artifacts from dalvik cache.
pm.deleteOatArtifactsOfPackage(pkg);
} else {
- dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+ result = performDexOptPrimary(dexOptHelper, pkg, reason, dexoptFlags);
}
} else {
- dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ result = performDexOptSecondary(dexOptHelper, pkg, reason, dexoptFlags);
}
- if (dex_opt_performed) {
+ if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before,
getPackageSize(pm, pkg), /*aggressive=*/ false);
}
- return dex_opt_performed;
+ return result;
}
- private boolean supportSecondaryDex() {
- return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
- }
-
- private int reconcileSecondaryDexFiles(DexManager dm) {
+ @Status
+ private int reconcileSecondaryDexFiles() {
// TODO(calin): should we denylist packages for which we fail to reconcile?
- for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
- if (mAbortIdleOptimization.get()) {
- return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
+ if (isCancelling()) {
+ return STATUS_ABORT_BY_CANCELLATION;
}
- dm.reconcileSecondaryDexFiles(p);
+ mInjector.getDexManager().reconcileSecondaryDexFiles(p);
}
- return OPTIMIZE_PROCESSED;
+ return STATUS_OK;
}
/**
*
* Optimize package if needed. Note that there can be no race between
* concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- * @param pm An instance of PackageManagerService
+ * @param dexOptHelper An instance of DexOptHelper
* @param pkg The package to be downgraded.
- * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
- * @return true if the package was downgraded.
+ * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
+ * @param isPostBootUpdate is post boot update or not.
+ * @return PackageDexOptimizer#DEX_OPT_*
*/
- private boolean optimizePackage(PackageManagerService pm, String pkg,
- boolean isForPrimaryDex) {
- int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- | DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+ @DexOptResult
+ private int optimizePackage(DexOptHelper dexOptHelper, String pkg,
+ boolean isForPrimaryDex, boolean isPostBootUpdate) {
+ int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
+ : PackageManagerService.REASON_BACKGROUND_DEXOPT;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
+ if (!isPostBootUpdate) {
+ dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+ }
// System server share the same code path as primary dex files.
// PackageManagerService will select the right optimization path for it.
- return (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg))
- ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
- : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
+ return performDexOptPrimary(dexOptHelper, pkg, reason, dexoptFlags);
+ } else {
+ return performDexOptSecondary(dexOptHelper, pkg, reason, dexoptFlags);
+ }
}
- private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+ @DexOptResult
+ private int performDexOptPrimary(DexOptHelper dexOptHelper, String pkg, int reason,
int dexoptFlags) {
- int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
- () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
- return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+ () -> dexOptHelper.performDexOptWithStatus(
+ new DexoptOptions(pkg, reason, dexoptFlags)));
}
- private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+ @DexOptResult
+ private int performDexOptSecondary(DexOptHelper dexOptHelper, String pkg, int reason,
int dexoptFlags) {
DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
- int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
- () -> pm.performDexOpt(dexoptOptions)
+ return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+ () -> dexOptHelper.performDexOpt(dexoptOptions)
? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
);
- return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
/**
@@ -533,194 +783,212 @@
* the package is added to the list of failed packages.
* Return one of following result:
* {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+ * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
* {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
+ @DexOptResult
private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
Supplier<Integer> performDexOptWrapper) {
- ArraySet<String> sFailedPackageNames =
- isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
- synchronized (sFailedPackageNames) {
- if (sFailedPackageNames.contains(pkg)) {
+ ArraySet<String> failedPackageNames;
+ synchronized (mLock) {
+ failedPackageNames =
+ isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary;
+ if (failedPackageNames.contains(pkg)) {
// Skip previously failing package
return PackageDexOptimizer.DEX_OPT_SKIPPED;
}
- sFailedPackageNames.add(pkg);
}
int result = performDexOptWrapper.get();
- if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.remove(pkg);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
+ synchronized (mLock) {
+ failedPackageNames.add(pkg);
+ }
+ } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) {
+ synchronized (mLock) {
+ mLastCancelledPackages.add(pkg);
}
}
return result;
}
- // Evaluate whether or not idle optimizations should continue.
+ @Status
+ private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) {
+ switch (pdoStatus) {
+ case PackageDexOptimizer.DEX_OPT_CANCELLED:
+ return STATUS_ABORT_BY_CANCELLATION;
+ case PackageDexOptimizer.DEX_OPT_FAILED:
+ return STATUS_DEX_OPT_FAILED;
+ case PackageDexOptimizer.DEX_OPT_PERFORMED:
+ case PackageDexOptimizer.DEX_OPT_SKIPPED:
+ return STATUS_OK;
+ default:
+ Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus,
+ new RuntimeException());
+ return STATUS_DEX_OPT_FAILED;
+ }
+ }
+
+ /** Evaluate whether or not idle optimizations should continue. */
+ @Status
private int abortIdleOptimizations(long lowStorageThreshold) {
- if (mAbortIdleOptimization.get()) {
+ if (isCancelling()) {
// JobScheduler requested an early abort.
- return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ return STATUS_ABORT_BY_CANCELLATION;
}
// Abort background dexopt if the device is in a moderate or stronger thermal throttling
// state.
- try {
- final int thermalStatus = mThermalService.getCurrentThermalStatus();
-
- if (DEBUG) {
- Log.i(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
- }
-
- if (thermalStatus >= mThermalStatusCutoff) {
- return OPTIMIZE_ABORT_THERMAL;
- }
- } catch (RemoteException ex) {
- // Because this is a intra-process Binder call it is impossible for a RemoteException
- // to be raised.
+ int thermalStatus = mInjector.getCurrentThermalStatus();
+ if (DEBUG) {
+ Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
+ }
+ if (thermalStatus >= mThermalStatusCutoff) {
+ return STATUS_ABORT_THERMAL;
}
- long usableSpace = mDataDir.getUsableSpace();
+ if (mInjector.isBatteryLevelLow()) {
+ return STATUS_ABORT_BATTERY;
+ }
+
+ long usableSpace = mInjector.getDataDirUsableSpace();
if (usableSpace < lowStorageThreshold) {
// Rather bail than completely fill up the disk.
Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
- return OPTIMIZE_ABORT_NO_SPACE_LEFT;
+ return STATUS_ABORT_NO_SPACE_LEFT;
}
- return OPTIMIZE_CONTINUE;
+ return STATUS_OK;
}
// Evaluate whether apps should be downgraded.
private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
- long usableSpace = mDataDir.getUsableSpace();
- if (usableSpace < lowStorageThresholdForDowngrade) {
+ if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) {
return true;
}
return false;
}
- /**
- * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
- * then execute on all packages.
- */
- public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
- @Nullable List<String> packageNames) {
- // Create a new object to make sure we don't interfere with the scheduled jobs.
- // Note that this may still run at the same time with the job scheduled by the
- // JobScheduler but the scheduler will not be able to cancel it.
- BackgroundDexOptService bdos = new BackgroundDexOptService();
- ArraySet<String> packagesToOptimize;
- if (packageNames == null) {
- packagesToOptimize = pm.getOptimizablePackages();
- } else {
- packagesToOptimize = new ArraySet<>(packageNames);
+ private boolean isCancelling() {
+ synchronized (mLock) {
+ return mDexOptCancellingThread != null;
}
- int result = bdos.idleOptimization(pm, packagesToOptimize, context);
- return result == OPTIMIZE_PROCESSED;
}
- @Override
- public boolean onStartJob(JobParameters params) {
- if (DEBUG) {
- Slog.i(TAG, "onStartJob");
+ private void markPostBootUpdateCompleted(JobParameters params) {
+ if (params.getJobId() != JOB_POST_BOOT_UPDATE) {
+ return;
}
-
- // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
- // the checks above. This check is not "live" - the value is determined by a background
- // restart with a period of ~1 minute.
- PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
- if (pm.isStorageLow()) {
- Slog.i(TAG, "Low storage, skipping this run");
- return false;
- }
-
- final ArraySet<String> pkgs = pm.getOptimizablePackages();
- if (pkgs.isEmpty()) {
- Slog.i(TAG, "No packages to optimize");
- return false;
- }
-
- mThermalStatusCutoff =
- SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
-
- boolean result;
- if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
- result = runPostBootUpdate(params, pm, pkgs);
- } else {
- result = runIdleOptimization(params, pm, pkgs);
- }
-
- return result;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- if (DEBUG) {
- Slog.d(TAG, "onStopJob");
- }
-
- if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
- mAbortPostBootUpdate.set(true);
-
- // Do not reschedule.
- // TODO: We should reschedule if we didn't process all apps, yet.
- return false;
- } else {
- mAbortIdleOptimization.set(true);
-
- // Reschedule the run.
- // TODO: Should this be dependent on the stop reason?
- return true;
+ synchronized (mLock) {
+ if (!mFinishedPostBootUpdate) {
+ mFinishedPostBootUpdate = true;
+ JobScheduler js = mInjector.getJobScheduler();
+ js.cancel(JOB_POST_BOOT_UPDATE);
+ }
}
}
private void notifyPinService(ArraySet<String> updatedPackages) {
- PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+ PinnerService pinnerService = mInjector.getPinnerService();
if (pinnerService != null) {
Slog.i(TAG, "Pinning optimized code " + updatedPackages);
pinnerService.update(updatedPackages, false /* force */);
}
}
- public static interface PackagesUpdatedListener {
- /** Callback when packages have been updated by the bg-dexopt service. */
- public void onPackagesUpdated(ArraySet<String> updatedPackages);
- }
-
- public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
- synchronized (sPackagesUpdatedListeners) {
- sPackagesUpdatedListeners.add(listener);
- }
- }
-
- public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
- synchronized (sPackagesUpdatedListeners) {
- sPackagesUpdatedListeners.remove(listener);
- }
- }
-
/** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
- synchronized (sPackagesUpdatedListeners) {
- for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) {
+ synchronized (mLock) {
+ for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) {
listener.onPackagesUpdated(updatedPackages);
}
}
}
- private static long getDowngradeUnusedAppsThresholdInMillis() {
- final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
- String sysPropValue = SystemProperties.get(sysPropKey);
- if (sysPropValue == null || sysPropValue.isEmpty()) {
- Slog.w(TAG, "SysProp " + sysPropKey + " not set");
- return Long.MAX_VALUE;
- }
- return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
- }
+ /** Injector pattern for testing purpose */
+ @VisibleForTesting
+ static final class Injector {
+ private final Context mContext;
+ private final DexManager mDexManager;
+ private final File mDataDir = Environment.getDataDirectory();
- private static boolean isBackgroundDexoptDisabled() {
- return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
- false /* default */);
+ Injector(Context context, DexManager dexManager) {
+ mContext = context;
+ mDexManager = dexManager;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ PackageManagerService getPackageManagerService() {
+ return (PackageManagerService) ServiceManager.getService("package");
+ }
+
+ JobScheduler getJobScheduler() {
+ return mContext.getSystemService(JobScheduler.class);
+ }
+
+ DexManager getDexManager() {
+ return mDexManager;
+ }
+
+ PinnerService getPinnerService() {
+ return LocalServices.getService(PinnerService.class);
+ }
+
+ boolean isBackgroundDexOptDisabled() {
+ return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
+ false /* default */);
+ }
+
+ boolean isBatteryLevelLow() {
+ return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow();
+ }
+
+ long getDowngradeUnusedAppsThresholdInMillis() {
+ String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
+ String sysPropValue = SystemProperties.get(sysPropKey);
+ if (sysPropValue == null || sysPropValue.isEmpty()) {
+ Slog.w(TAG, "SysProp " + sysPropKey + " not set");
+ return Long.MAX_VALUE;
+ }
+ return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
+ }
+
+ boolean supportSecondaryDex() {
+ return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+ }
+
+ long getDataDirUsableSpace() {
+ return mDataDir.getUsableSpace();
+ }
+
+ long getDataDirStorageLowBytes() {
+ return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir);
+ }
+
+ int getCurrentThermalStatus() {
+ IThermalService thermalService = IThermalService.Stub
+ .asInterface(ServiceManager.getService(Context.THERMAL_SERVICE));
+ try {
+ return thermalService.getCurrentThermalStatus();
+ } catch (RemoteException e) {
+ return STATUS_ABORT_THERMAL;
+ }
+ }
+
+ int getDexOptThermalCutoff() {
+ return SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff",
+ THERMAL_CUTOFF_DEFAULT);
+ }
+
+ Thread createAndStartThread(String name, Runnable target) {
+ Thread thread = new Thread(target, name);
+ Slog.i(TAG, "Starting thread:" + name);
+ thread.start();
+ return thread;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index d6b9c34..9251f65 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -36,11 +36,13 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedComponent;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedProviderImpl;
import android.content.pm.parsing.component.ParsedService;
import android.content.pm.pkg.PackageUserState;
import android.os.UserHandle;
@@ -588,7 +590,8 @@
for (int i = protectedFilters.size() - 1; i >= 0; --i) {
final Pair<ParsedMainComponent, ParsedIntentInfo> pair = protectedFilters.get(i);
ParsedMainComponent component = pair.first;
- ParsedIntentInfo filter = pair.second;
+ ParsedIntentInfo intentInfo = pair.second;
+ IntentFilter filter = intentInfo.getIntentFilter();
String packageName = component.getPackageName();
String className = component.getClassName();
if (packageName.equals(setupWizardPackage)) {
@@ -744,7 +747,7 @@
String[] names = p.getAuthority().split(";");
// TODO(b/135203078): Remove this mutation
- p.setAuthority(null);
+ ComponentMutateUtils.setAuthority(p, null);
for (int j = 0; j < names.length; j++) {
if (j == 1 && p.isSyncable()) {
// We only want the first authority for a provider to possibly be
@@ -754,15 +757,15 @@
// to a provider that we don't want to change.
// Only do this for the second authority since the resulting provider
// object can be the same for all future authorities for this provider.
- p = new ParsedProvider(p);
- p.setSyncable(false);
+ p = new ParsedProviderImpl(p);
+ ComponentMutateUtils.setSyncable(p, false);
}
if (!mProvidersByAuthority.containsKey(names[j])) {
mProvidersByAuthority.put(names[j], p);
if (p.getAuthority() == null) {
- p.setAuthority(names[j]);
+ ComponentMutateUtils.setAuthority(p, names[j]);
} else {
- p.setAuthority(p.getAuthority() + ";" + names[j]);
+ ComponentMutateUtils.setAuthority(p, p.getAuthority() + ";" + names[j]);
}
if (DEBUG_PACKAGE_SCANNING && chatty) {
Log.d(TAG, "Registered content provider: " + names[j]
@@ -844,7 +847,7 @@
* MODIFIED. Do not pass in a list that should not be changed.
*/
private static <T> void getIntentListSubset(List<ParsedIntentInfo> intentList,
- Function<ParsedIntentInfo, Iterator<T>> generator, Iterator<T> searchIterator) {
+ Function<IntentFilter, Iterator<T>> generator, Iterator<T> searchIterator) {
// loop through the set of actions; every one must be found in the intent filter
while (searchIterator.hasNext()) {
// we must have at least one filter in the list to consider a match
@@ -862,7 +865,8 @@
// loop through the intent filter's selection criteria; at least one
// of them must match the searched criteria
- final Iterator<T> intentSelectionIter = generator.apply(intentInfo);
+ final Iterator<T> intentSelectionIter =
+ generator.apply(intentInfo.getIntentFilter());
while (intentSelectionIter != null && intentSelectionIter.hasNext()) {
final T intentSelection = intentSelectionIter.next();
if (intentSelection != null && intentSelection.equals(searchAction)) {
@@ -880,7 +884,7 @@
}
}
- private static boolean isProtectedAction(ParsedIntentInfo filter) {
+ private static boolean isProtectedAction(IntentFilter filter) {
final Iterator<String> actionsIter = filter.actionsIterator();
while (actionsIter != null && actionsIter.hasNext()) {
final String filterAction = actionsIter.next();
@@ -929,9 +933,10 @@
* allowed to obtain any priority on any action.
*/
private void adjustPriority(List<ParsedActivity> systemActivities, ParsedActivity activity,
- ParsedIntentInfo intent, String setupWizardPackage) {
+ ParsedIntentInfo intentInfo, String setupWizardPackage) {
// nothing to do; priority is fine as-is
- if (intent.getPriority() <= 0) {
+ IntentFilter intentFilter = intentInfo.getIntentFilter();
+ if (intentFilter.getPriority() <= 0) {
return;
}
@@ -946,13 +951,13 @@
Slog.i(TAG, "Non-privileged app; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
return;
}
- if (isProtectedAction(intent)) {
+ if (isProtectedAction(intentFilter)) {
if (mDeferProtectedFilters) {
// We can't deal with these just yet. No component should ever obtain a
// >0 priority for a protected actions, with ONE exception -- the setup
@@ -964,12 +969,12 @@
if (mProtectedFilters == null) {
mProtectedFilters = new ArrayList<>();
}
- mProtectedFilters.add(Pair.create(activity, intent));
+ mProtectedFilters.add(Pair.create(activity, intentInfo));
if (DEBUG_FILTERS) {
Slog.i(TAG, "Protected action; save for later;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
} else {
if (DEBUG_FILTERS && setupWizardPackage == null) {
@@ -979,10 +984,10 @@
if (packageName.equals(setupWizardPackage)) {
if (DEBUG_FILTERS) {
Slog.i(TAG, "Found setup wizard;"
- + " allow priority " + intent.getPriority() + ";"
+ + " allow priority " + intentFilter.getPriority() + ";"
+ " package: " + packageName
+ " activity: " + className
- + " priority: " + intent.getPriority());
+ + " priority: " + intentFilter.getPriority());
}
// setup wizard gets whatever it wants
return;
@@ -991,9 +996,9 @@
Slog.i(TAG, "Protected action; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
}
return;
}
@@ -1014,9 +1019,9 @@
Slog.i(TAG, "New activity; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
return;
}
@@ -1027,7 +1032,7 @@
new ArrayList<>(foundActivity.getIntents());
// find matching action subsets
- final Iterator<String> actionsIterator = intent.actionsIterator();
+ final Iterator<String> actionsIterator = intentFilter.actionsIterator();
if (actionsIterator != null) {
getIntentListSubset(intentListCopy, IntentFilter::actionsIterator, actionsIterator);
if (intentListCopy.size() == 0) {
@@ -1036,15 +1041,15 @@
Slog.i(TAG, "Mismatched action; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
return;
}
}
// find matching category subsets
- final Iterator<String> categoriesIterator = intent.categoriesIterator();
+ final Iterator<String> categoriesIterator = intentFilter.categoriesIterator();
if (categoriesIterator != null) {
getIntentListSubset(intentListCopy, IntentFilter::categoriesIterator,
categoriesIterator);
@@ -1054,15 +1059,15 @@
Slog.i(TAG, "Mismatched category; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
return;
}
}
// find matching schemes subsets
- final Iterator<String> schemesIterator = intent.schemesIterator();
+ final Iterator<String> schemesIterator = intentFilter.schemesIterator();
if (schemesIterator != null) {
getIntentListSubset(intentListCopy, IntentFilter::schemesIterator, schemesIterator);
if (intentListCopy.size() == 0) {
@@ -1071,16 +1076,16 @@
Slog.i(TAG, "Mismatched scheme; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
return;
}
}
// find matching authorities subsets
final Iterator<IntentFilter.AuthorityEntry> authoritiesIterator =
- intent.authoritiesIterator();
+ intentFilter.authoritiesIterator();
if (authoritiesIterator != null) {
getIntentListSubset(intentListCopy, IntentFilter::authoritiesIterator,
authoritiesIterator);
@@ -1090,9 +1095,9 @@
Slog.i(TAG, "Mismatched authority; cap priority to 0;"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(0);
+ intentFilter.setPriority(0);
return;
}
}
@@ -1100,17 +1105,18 @@
// we found matching filter(s); app gets the max priority of all intents
int cappedPriority = 0;
for (int i = intentListCopy.size() - 1; i >= 0; --i) {
- cappedPriority = Math.max(cappedPriority, intentListCopy.get(i).getPriority());
+ cappedPriority = Math.max(cappedPriority,
+ intentListCopy.get(i).getIntentFilter().getPriority());
}
- if (intent.getPriority() > cappedPriority) {
+ if (intentFilter.getPriority() > cappedPriority) {
if (DEBUG_FILTERS) {
Slog.i(TAG, "Found matching filter(s);"
+ " cap priority to " + cappedPriority + ";"
+ " package: " + packageName
+ " activity: " + className
- + " origPrio: " + intent.getPriority());
+ + " origPrio: " + intentFilter.getPriority());
}
- intent.setPriority(cappedPriority);
+ intentFilter.setPriority(cappedPriority);
return;
}
// all this for nothing; the requested priority was <= what was on the system
@@ -1425,14 +1431,15 @@
final int intentsSize = a.getIntents().size();
for (int j = 0; j < intentsSize; j++) {
ParsedIntentInfo intent = a.getIntents().get(j);
+ IntentFilter intentFilter = intent.getIntentFilter();
if (newIntents != null && "activity".equals(type)) {
newIntents.add(Pair.create(a, intent));
}
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
- intent.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
+ intentFilter.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
}
- if (!intent.debugCheck()) {
+ if (!intentFilter.debugCheck()) {
Log.w(TAG, "==> For Activity " + a.getName());
}
addFilter(Pair.create(a, intent));
@@ -1448,9 +1455,10 @@
final int intentsSize = a.getIntents().size();
for (int j = 0; j < intentsSize; j++) {
ParsedIntentInfo intent = a.getIntents().get(j);
+ IntentFilter intentFilter = intent.getIntentFilter();
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
- intent.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
+ intentFilter.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
}
removeFilter(Pair.create(a, intent));
}
@@ -1500,6 +1508,7 @@
int match, int userId) {
ParsedActivity activity = pair.first;
ParsedIntentInfo info = pair.second;
+ IntentFilter intentFilter = info.getIntentFilter();
if (!sUserManager.exists(userId)) {
if (DEBUG) {
@@ -1545,8 +1554,9 @@
(mFlags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean componentVisible =
matchVisibleToInstantApp
- && info.isVisibleToInstantApp()
- && (!matchExplicitlyVisibleOnly || info.isExplicitlyVisibleToInstantApp());
+ && intentFilter.isVisibleToInstantApp()
+ && (!matchExplicitlyVisibleOnly
+ || intentFilter.isExplicitlyVisibleToInstantApp());
final boolean matchInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
// throw out filters that aren't visible to ephemeral apps
if (matchVisibleToInstantApp && !(componentVisible || userState.isInstantApp())) {
@@ -1554,10 +1564,11 @@
log("Filter(s) not visible to ephemeral apps"
+ "; matchVisibleToInstantApp=" + matchVisibleToInstantApp
+ "; matchInstantApp=" + matchInstantApp
- + "; info.isVisibleToInstantApp()=" + info.isVisibleToInstantApp()
+ + "; info.isVisibleToInstantApp()="
+ + intentFilter.isVisibleToInstantApp()
+ "; matchExplicitlyVisibleOnly=" + matchExplicitlyVisibleOnly
+ "; info.isExplicitlyVisibleToInstantApp()="
- + info.isExplicitlyVisibleToInstantApp(),
+ + intentFilter.isExplicitlyVisibleToInstantApp(),
info, match, userId);
}
return null;
@@ -1577,13 +1588,14 @@
}
return null;
}
- final ResolveInfo res = new ResolveInfo(info.hasCategory(Intent.CATEGORY_BROWSABLE));
+ final ResolveInfo res =
+ new ResolveInfo(intentFilter.hasCategory(Intent.CATEGORY_BROWSABLE));
res.activityInfo = ai;
if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
- res.filter = info;
+ res.filter = intentFilter;
}
- res.handleAllWebDataURI = info.handleAllWebDataURI();
- res.priority = info.getPriority();
+ res.handleAllWebDataURI = intentFilter.handleAllWebDataURI();
+ res.priority = intentFilter.getPriority();
// TODO(b/135203078): This field was unwritten and does nothing
// res.preferredOrder = pkg.getPreferredOrder();
//System.out.println("Result: " + res.activityInfo.className +
@@ -1645,7 +1657,7 @@
@Override
protected IntentFilter getIntentFilter(
@NonNull Pair<ParsedActivity, ParsedIntentInfo> input) {
- return input.second;
+ return input.second.getIntentFilter();
}
protected List<ParsedActivity> getResolveList(AndroidPackage pkg) {
@@ -1756,11 +1768,12 @@
int j;
for (j = 0; j < intentsSize; j++) {
ParsedIntentInfo intent = p.getIntents().get(j);
+ IntentFilter intentFilter = intent.getIntentFilter();
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
- intent.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
+ intentFilter.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
}
- if (!intent.debugCheck()) {
+ if (!intentFilter.debugCheck()) {
Log.w(TAG, "==> For Provider " + p.getName());
}
addFilter(Pair.create(p, intent));
@@ -1777,9 +1790,10 @@
int j;
for (j = 0; j < intentsSize; j++) {
ParsedIntentInfo intent = p.getIntents().get(j);
+ IntentFilter intentFilter = intent.getIntentFilter();
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
- intent.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
+ intentFilter.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
}
removeFilter(Pair.create(p, intent));
}
@@ -1824,7 +1838,8 @@
}
ParsedProvider provider = pair.first;
- ParsedIntentInfo filter = pair.second;
+ ParsedIntentInfo intentInfo = pair.second;
+ IntentFilter filter = intentInfo.getIntentFilter();
AndroidPackage pkg = sPackageManagerInternal.getPackage(provider.getPackageName());
if (pkg == null) {
@@ -1877,10 +1892,10 @@
// TODO(b/135203078): This field was unwritten and does nothing
// res.preferredOrder = pkg.getPreferredOrder();
res.match = match;
- res.isDefault = filter.isHasDefault();
- res.labelRes = filter.getLabelRes();
- res.nonLocalizedLabel = filter.getNonLocalizedLabel();
- res.icon = filter.getIcon();
+ res.isDefault = intentInfo.isHasDefault();
+ res.labelRes = intentInfo.getLabelRes();
+ res.nonLocalizedLabel = intentInfo.getNonLocalizedLabel();
+ res.icon = intentInfo.getIcon();
res.system = res.providerInfo.applicationInfo.isSystemApp();
return res;
}
@@ -1928,7 +1943,7 @@
@Override
protected IntentFilter getIntentFilter(
@NonNull Pair<ParsedProvider, ParsedIntentInfo> input) {
- return input.second;
+ return input.second.getIntentFilter();
}
private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
@@ -2001,11 +2016,12 @@
int j;
for (j = 0; j < intentsSize; j++) {
ParsedIntentInfo intent = s.getIntents().get(j);
+ IntentFilter intentFilter = intent.getIntentFilter();
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
- intent.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
+ intentFilter.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
}
- if (!intent.debugCheck()) {
+ if (!intentFilter.debugCheck()) {
Log.w(TAG, "==> For Service " + s.getName());
}
addFilter(Pair.create(s, intent));
@@ -2022,9 +2038,10 @@
int j;
for (j = 0; j < intentsSize; j++) {
ParsedIntentInfo intent = s.getIntents().get(j);
+ IntentFilter intentFilter = intent.getIntentFilter();
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
- intent.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
+ intentFilter.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
}
removeFilter(Pair.create(s, intent));
}
@@ -2066,7 +2083,8 @@
if (!sUserManager.exists(userId)) return null;
ParsedService service = pair.first;
- ParsedIntentInfo filter = pair.second;
+ ParsedIntentInfo intentInfo = pair.second;
+ IntentFilter filter = intentInfo.getIntentFilter();
AndroidPackage pkg = sPackageManagerInternal.getPackage(service.getPackageName());
if (pkg == null) {
@@ -2114,10 +2132,10 @@
// TODO(b/135203078): This field was unwritten and does nothing
// res.preferredOrder = pkg.getPreferredOrder();
res.match = match;
- res.isDefault = filter.isHasDefault();
- res.labelRes = filter.getLabelRes();
- res.nonLocalizedLabel = filter.getNonLocalizedLabel();
- res.icon = filter.getIcon();
+ res.isDefault = intentInfo.isHasDefault();
+ res.labelRes = intentInfo.getLabelRes();
+ res.nonLocalizedLabel = intentInfo.getNonLocalizedLabel();
+ res.icon = intentInfo.getIcon();
res.system = res.serviceInfo.applicationInfo.isSystemApp();
return res;
}
@@ -2168,7 +2186,7 @@
@Override
protected IntentFilter getIntentFilter(
@NonNull Pair<ParsedService, ParsedIntentInfo> input) {
- return input.second;
+ return input.second.getIntentFilter();
}
// Keys are String (activity class name), values are Activity.
@@ -2288,45 +2306,6 @@
return !ps.isSystem() && ps.getStopped(userId);
}
- /** Generic to create an {@link Iterator} for a data type */
- static class IterGenerator<E> {
- public Iterator<E> generate(ParsedIntentInfo info) {
- return null;
- }
- }
-
- /** Create an {@link Iterator} for intent actions */
- static class ActionIterGenerator extends IterGenerator<String> {
- @Override
- public Iterator<String> generate(ParsedIntentInfo info) {
- return info.actionsIterator();
- }
- }
-
- /** Create an {@link Iterator} for intent categories */
- static class CategoriesIterGenerator extends IterGenerator<String> {
- @Override
- public Iterator<String> generate(ParsedIntentInfo info) {
- return info.categoriesIterator();
- }
- }
-
- /** Create an {@link Iterator} for intent schemes */
- static class SchemesIterGenerator extends IterGenerator<String> {
- @Override
- public Iterator<String> generate(ParsedIntentInfo info) {
- return info.schemesIterator();
- }
- }
-
- /** Create an {@link Iterator} for intent authorities */
- static class AuthoritiesIterGenerator extends IterGenerator<IntentFilter.AuthorityEntry> {
- @Override
- public Iterator<IntentFilter.AuthorityEntry> generate(ParsedIntentInfo info) {
- return info.authoritiesIterator();
- }
- }
-
/**
* Removes MIME type from the group, by delegating to IntentResolvers
* @return true if any intent filters were changed due to this update
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 34e9428..aa4d6bb 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -179,6 +179,7 @@
private final PackageDexOptimizer mPackageDexOptimizer;
private final DexManager mDexManager;
private final CompilerStats mCompilerStats;
+ private final BackgroundDexOptService mBackgroundDexOptService;
// PackageManagerService attributes that are primitives are referenced through the
// pms object directly. Primitives are the only attributes so referenced.
@@ -228,6 +229,7 @@
mPackageDexOptimizer = args.service.mPackageDexOptimizer;
mDexManager = args.service.getDexManager();
mCompilerStats = args.service.mCompilerStats;
+ mBackgroundDexOptService = args.service.mBackgroundDexOptService;
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.
@@ -1809,7 +1811,7 @@
@Nullable
public final SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) {
- return PackageManagerService.getSharedLibraryInfo(
+ return SharedLibraryHelper.getSharedLibraryInfo(
name, version, mSharedLibraries, null);
}
@@ -2929,6 +2931,10 @@
mDexManager.getPackageUseInfoOrDefault(pkgName));
ipw.decreaseIndent();
}
+ ipw.println("BgDexopt state:");
+ ipw.increaseIndent();
+ mBackgroundDexOptService.dump(ipw);
+ ipw.decreaseIndent();
break;
}
diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java
index f63cc4e..b83f987 100644
--- a/services/core/java/com/android/server/pm/ComputerLocked.java
+++ b/services/core/java/com/android/server/pm/ComputerLocked.java
@@ -61,7 +61,7 @@
return mService.mInstantAppInstallerActivity;
}
protected ApplicationInfo androidApplication() {
- return mService.mAndroidApplication;
+ return mService.getCoreAndroidApplication();
}
public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 37879d7..0feb9c5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -37,6 +37,7 @@
import android.annotation.Nullable;
import android.app.ApplicationPackageManager;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInstaller;
@@ -50,6 +51,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -73,6 +75,7 @@
* Relies on RemovePackageHelper to clear internal data structures.
*/
final class DeletePackageHelper {
+ private static final boolean DEBUG_CLEAN_APKS = false;
// ------- apps on sdcard specific code -------
private static final boolean DEBUG_SD_INSTALL = false;
@@ -80,6 +83,7 @@
private final UserManagerInternal mUserManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final RemovePackageHelper mRemovePackageHelper;
+ // TODO(b/201815903): remove dependency to InitAndSystemPackageHelper
private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
private final AppDataHelper mAppDataHelper;
@@ -101,8 +105,7 @@
mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
mRemovePackageHelper = new RemovePackageHelper(mPm, mAppDataHelper);
- mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(mPm, mRemovePackageHelper,
- mAppDataHelper);
+ mInitAndSystemPackageHelper = mPm.getInitAndSystemPackageHelper();
}
/**
@@ -279,8 +282,7 @@
Slog.i(TAG, "Enabling system stub after removal; pkg: "
+ stubPkg.getPackageName());
}
- mInitAndSystemPackageHelper.enableCompressedPackage(stubPkg, stubPs,
- mPm.mDefParseFlags, mPm.getDirsToScanAsSystem());
+ mInitAndSystemPackageHelper.enableCompressedPackage(stubPkg, stubPs);
} else if (DEBUG_COMPRESSION) {
Slog.i(TAG, "System stub disabled for all users, leaving uncompressed "
+ "after removal; pkg: " + stubPkg.getPackageName());
@@ -421,8 +423,7 @@
PackageSetting disabledPs = deleteInstalledSystemPackage(action, ps, allUserHandles,
flags, outInfo, writeSettings);
mInitAndSystemPackageHelper.restoreDisabledSystemPackageLIF(
- action, ps, allUserHandles, outInfo, writeSettings, mPm.mDefParseFlags,
- mPm.getDirsToScanAsSystem(), disabledPs);
+ action, ps, allUserHandles, outInfo, writeSettings, disabledPs);
} else {
if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
@@ -458,10 +459,9 @@
mAppDataHelper.destroyAppProfilesLIF(pkg);
final SharedUserSetting sus = ps.getSharedUser();
- List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : null;
- if (sharedUserPkgs == null) {
- sharedUserPkgs = Collections.emptyList();
- }
+ final List<AndroidPackage> sharedUserPkgs =
+ sus != null ? sus.getPackages() : Collections.emptyList();
+ final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
: new int[] {userId};
for (int nextUserId : userIds) {
@@ -475,7 +475,8 @@
}
PackageManagerService.removeKeystoreDataIfNeeded(mUserManagerInternal, nextUserId,
ps.getAppId());
- mPm.clearPackagePreferredActivities(ps.getPackageName(), nextUserId);
+ preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(),
+ nextUserId);
mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
}
mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), pkg,
@@ -511,9 +512,9 @@
// Delete application code and resources only for parent packages
if (deleteCodeAndResources && (outInfo != null)) {
- outInfo.mArgs = mPm.createInstallArgsForExisting(
+ outInfo.mArgs = new FileInstallArgs(
ps.getPathString(), getAppDexInstructionSets(
- ps.getPrimaryCpuAbi(), ps.getSecondaryCpuAbi()));
+ ps.getPrimaryCpuAbi(), ps.getSecondaryCpuAbi()), mPm);
if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.mArgs);
}
}
@@ -813,4 +814,89 @@
this.installed = installed;
}
}
+
+ /**
+ * We're removing userId and would like to remove any downloaded packages
+ * that are no longer in use by any other user.
+ * @param userId the user being removed
+ */
+ @GuardedBy("mPm.mLock")
+ public void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
+ int [] users = userManager.getUserIds();
+ final int numPackages = mPm.mSettings.getPackagesLocked().size();
+ for (int index = 0; index < numPackages; index++) {
+ final PackageSetting ps = mPm.mSettings.getPackagesLocked().valueAt(index);
+ if (ps.getPkg() == null) {
+ continue;
+ }
+ final String packageName = ps.getPkg().getPackageName();
+ // Skip over if system app or static shared library
+ if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())) {
+ continue;
+ }
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, "Checking package " + packageName);
+ }
+ boolean keep = mPm.shouldKeepUninstalledPackageLPr(packageName);
+ if (keep) {
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Keeping package " + packageName + " - requested by DO");
+ }
+ } else {
+ for (int i = 0; i < users.length; i++) {
+ if (users[i] != userId && ps.getInstalled(users[i])) {
+ keep = true;
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Keeping package " + packageName + " for user "
+ + users[i]);
+ }
+ break;
+ }
+ }
+ }
+ if (!keep) {
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Removing package " + packageName);
+ }
+ //end run
+ mPm.mHandler.post(() -> deletePackageX(
+ packageName, PackageManager.VERSION_CODE_HIGHEST,
+ userId, 0, true /*removedBySystem*/));
+ }
+ }
+ }
+
+ public void deleteExistingPackageAsUser(VersionedPackage versionedPackage,
+ final IPackageDeleteObserver2 observer, final int userId) {
+ mPm.mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DELETE_PACKAGES, null);
+ Preconditions.checkNotNull(versionedPackage);
+ Preconditions.checkNotNull(observer);
+ final String packageName = versionedPackage.getPackageName();
+ final long versionCode = versionedPackage.getLongVersionCode();
+
+ int installedForUsersCount = 0;
+ synchronized (mPm.mLock) {
+ // Normalize package name to handle renamed packages and static libs
+ final String internalPkgName = mPm.resolveInternalPackageNameLPr(packageName,
+ versionCode);
+ final PackageSetting ps = mPm.mSettings.getPackageLPr(internalPkgName);
+ if (ps != null) {
+ int[] installedUsers = ps.queryInstalledUsers(mUserManagerInternal.getUserIds(),
+ true);
+ installedForUsersCount = installedUsers.length;
+ }
+ }
+
+ if (installedForUsersCount > 1) {
+ deletePackageVersionedInternal(versionedPackage, observer, userId, 0, true);
+ } else {
+ try {
+ observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+ null);
+ } catch (RemoteException re) {
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
new file mode 100644
index 0000000..9390284
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
+import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
+import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
+import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
+import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.dex.ArtManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
+import com.android.server.pm.dex.DexManager;
+import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+final class DexOptHelper {
+ private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
+
+ private final PackageManagerService mPm;
+
+ public boolean isDexOptDialogShown() {
+ return mDexOptDialogShown;
+ }
+
+ @GuardedBy("mPm.mLock")
+ private boolean mDexOptDialogShown;
+
+ DexOptHelper(PackageManagerService pm) {
+ mPm = pm;
+ }
+
+ /*
+ * Return the prebuilt profile path given a package base code path.
+ */
+ private static String getPrebuildProfilePath(AndroidPackage pkg) {
+ return pkg.getBaseApkPath() + ".prof";
+ }
+
+ /**
+ * Performs dexopt on the set of packages in {@code packages} and returns an int array
+ * containing statistics about the invocation. The array consists of three elements,
+ * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
+ * and {@code numberOfPackagesFailed}.
+ */
+ public int[] performDexOptUpgrade(List<AndroidPackage> pkgs, boolean showDialog,
+ final int compilationReason, boolean bootComplete) {
+
+ int numberOfPackagesVisited = 0;
+ int numberOfPackagesOptimized = 0;
+ int numberOfPackagesSkipped = 0;
+ int numberOfPackagesFailed = 0;
+ final int numberOfPackagesToDexopt = pkgs.size();
+
+ for (AndroidPackage pkg : pkgs) {
+ numberOfPackagesVisited++;
+
+ boolean useProfileForDexopt = false;
+
+ if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && pkg.isSystem()) {
+ // Copy over initial preopt profiles since we won't get any JIT samples for methods
+ // that are already compiled.
+ File profileFile = new File(getPrebuildProfilePath(pkg));
+ // Copy profile if it exists.
+ if (profileFile.exists()) {
+ try {
+ // We could also do this lazily before calling dexopt in
+ // PackageDexOptimizer to prevent this happening on first boot. The issue
+ // is that we don't have a good way to say "do this only once".
+ if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+ pkg.getUid(), pkg.getPackageName(),
+ ArtManager.getProfileName(null))) {
+ Log.e(TAG, "Installer failed to copy system profile!");
+ } else {
+ // Disabled as this causes speed-profile compilation during first boot
+ // even if things are already compiled.
+ // useProfileForDexopt = true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
+ e);
+ }
+ } else {
+ PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
+ pkg.getPackageName());
+ // Handle compressed APKs in this path. Only do this for stubs with profiles to
+ // minimize the number off apps being speed-profile compiled during first boot.
+ // The other paths will not change the filter.
+ if (disabledPs != null && disabledPs.getPkg().isStub()) {
+ // The package is the stub one, remove the stub suffix to get the normal
+ // package and APK names.
+ String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg())
+ .replace(STUB_SUFFIX, "");
+ profileFile = new File(systemProfilePath);
+ // If we have a profile for a compressed APK, copy it to the reference
+ // location.
+ // Note that copying the profile here will cause it to override the
+ // reference profile every OTA even though the existing reference profile
+ // may have more data. We can't copy during decompression since the
+ // directories are not set up at that point.
+ if (profileFile.exists()) {
+ try {
+ // We could also do this lazily before calling dexopt in
+ // PackageDexOptimizer to prevent this happening on first boot. The
+ // issue is that we don't have a good way to say "do this only
+ // once".
+ if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+ pkg.getUid(), pkg.getPackageName(),
+ ArtManager.getProfileName(null))) {
+ Log.e(TAG, "Failed to copy system profile for stub package!");
+ } else {
+ useProfileForDexopt = true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy profile "
+ + profileFile.getAbsolutePath() + " ", e);
+ }
+ }
+ }
+ }
+ }
+
+ if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
+ }
+ numberOfPackagesSkipped++;
+ continue;
+ }
+
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of "
+ + numberOfPackagesToDexopt + ": " + pkg.getPackageName());
+ }
+
+ if (showDialog) {
+ try {
+ ActivityManager.getService().showBootMessage(
+ mPm.mContext.getResources().getString(R.string.android_upgrading_apk,
+ numberOfPackagesVisited, numberOfPackagesToDexopt), true);
+ } catch (RemoteException e) {
+ }
+ synchronized (mPm.mLock) {
+ mDexOptDialogShown = true;
+ }
+ }
+
+ int pkgCompilationReason = compilationReason;
+ if (useProfileForDexopt) {
+ // Use background dexopt mode to try and use the profile. Note that this does not
+ // guarantee usage of the profile.
+ pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+ }
+
+ if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
+ mPm.mArtManagerService.compileLayouts(pkg);
+ }
+
+ // checkProfiles is false to avoid merging profiles during boot which
+ // might interfere with background compilation (b/28612421).
+ // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
+ // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
+ // trade-off worth doing to save boot time work.
+ int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
+ if (compilationReason == REASON_FIRST_BOOT) {
+ // TODO: This doesn't cover the upgrade case, we should check for this too.
+ dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
+ }
+ int primaryDexOptStatus = performDexOptTraced(new DexoptOptions(
+ pkg.getPackageName(),
+ pkgCompilationReason,
+ dexoptFlags));
+
+ switch (primaryDexOptStatus) {
+ case PackageDexOptimizer.DEX_OPT_PERFORMED:
+ numberOfPackagesOptimized++;
+ break;
+ case PackageDexOptimizer.DEX_OPT_SKIPPED:
+ numberOfPackagesSkipped++;
+ break;
+ case PackageDexOptimizer.DEX_OPT_CANCELLED:
+ // ignore this case
+ break;
+ case PackageDexOptimizer.DEX_OPT_FAILED:
+ numberOfPackagesFailed++;
+ break;
+ default:
+ Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStatus);
+ break;
+ }
+ }
+
+ return new int[]{numberOfPackagesOptimized, numberOfPackagesSkipped,
+ numberOfPackagesFailed};
+ }
+
+ public void performPackageDexOptUpgradeIfNeeded() {
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "Only the system can request package update");
+
+ // We need to re-extract after an OTA.
+ boolean causeUpgrade = mPm.isDeviceUpgrading();
+
+ // First boot or factory reset.
+ // Note: we also handle devices that are upgrading to N right now as if it is their
+ // first boot, as they do not have profile data.
+ boolean causeFirstBoot = mPm.isFirstBoot() || mPm.isPreNUpgrade();
+
+ if (!causeUpgrade && !causeFirstBoot) {
+ return;
+ }
+
+ List<PackageSetting> pkgSettings;
+ synchronized (mPm.mLock) {
+ pkgSettings = getPackagesForDexopt(mPm.mSettings.getPackagesLocked().values(), mPm);
+ }
+
+ List<AndroidPackage> pkgs = new ArrayList<>(pkgSettings.size());
+ for (int index = 0; index < pkgSettings.size(); index++) {
+ pkgs.add(pkgSettings.get(index).getPkg());
+ }
+
+ final long startTime = System.nanoTime();
+ final int[] stats = performDexOptUpgrade(pkgs, mPm.isPreNUpgrade() /* showDialog */,
+ causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
+ false /* bootComplete */);
+
+ final int elapsedTimeSeconds =
+ (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
+
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", stats[0]);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", stats[1]);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", stats[2]);
+ MetricsLogger.histogram(
+ mPm.mContext, "opt_dialog_num_total", getOptimizablePackages().size());
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds);
+ }
+
+ public ArraySet<String> getOptimizablePackages() {
+ ArraySet<String> pkgs = new ArraySet<>();
+ synchronized (mPm.mLock) {
+ for (AndroidPackage p : mPm.mPackages.values()) {
+ if (PackageDexOptimizer.canOptimizePackage(p)) {
+ pkgs.add(p.getPackageName());
+ }
+ }
+ }
+ if (AppHibernationService.isAppHibernationEnabled()) {
+ AppHibernationManagerInternal appHibernationManager =
+ mPm.mInjector.getLocalService(AppHibernationManagerInternal.class);
+ pkgs.removeIf(pkgName -> appHibernationManager.isHibernatingGlobally(pkgName));
+ }
+ return pkgs;
+ }
+
+ /*package*/ boolean performDexOpt(DexoptOptions options) {
+ if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ return false;
+ } else if (mPm.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
+ return false;
+ }
+
+ if (options.isDexoptOnlySecondaryDex()) {
+ return mPm.getDexManager().dexoptSecondaryDex(options);
+ } else {
+ int dexoptStatus = performDexOptWithStatus(options);
+ return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
+ }
+ }
+
+ /**
+ * Perform dexopt on the given package and return one of following result:
+ * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+ * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+ * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
+ * {@link PackageDexOptimizer#DEX_OPT_FAILED}
+ */
+ @PackageDexOptimizer.DexOptResult
+ /* package */ int performDexOptWithStatus(DexoptOptions options) {
+ return performDexOptTraced(options);
+ }
+
+ private int performDexOptTraced(DexoptOptions options) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ try {
+ return performDexOptInternal(options);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
+ // if the package can now be considered up to date for the given filter.
+ private int performDexOptInternal(DexoptOptions options) {
+ AndroidPackage p;
+ PackageSetting pkgSetting;
+ synchronized (mPm.mLock) {
+ p = mPm.mPackages.get(options.getPackageName());
+ pkgSetting = mPm.mSettings.getPackageLPr(options.getPackageName());
+ if (p == null || pkgSetting == null) {
+ // Package could not be found. Report failure.
+ return PackageDexOptimizer.DEX_OPT_FAILED;
+ }
+ mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
+ mPm.mCompilerStats.maybeWriteAsync();
+ }
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPm.mInstallLock) {
+ return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
+ @NonNull PackageSetting pkgSetting, DexoptOptions options) {
+ // System server gets a special path.
+ if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
+ return mPm.getDexManager().dexoptSystemServer(options);
+ }
+
+ // Select the dex optimizer based on the force parameter.
+ // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+ // allocate an object here.
+ PackageDexOptimizer pdo = options.isForce()
+ ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPm.mPackageDexOptimizer)
+ : mPm.mPackageDexOptimizer;
+
+ // Dexopt all dependencies first. Note: we ignore the return value and march on
+ // on errors.
+ // Note that we are going to call performDexOpt on those libraries as many times as
+ // they are referenced in packages. When we do a batch of performDexOpt (for example
+ // at boot, or background job), the passed 'targetCompilerFilter' stays the same,
+ // and the first package that uses the library will dexopt it. The
+ // others will see that the compiled code for the library is up to date.
+ Collection<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting);
+ final String[] instructionSets = getAppDexInstructionSets(
+ AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting),
+ AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting));
+ if (!deps.isEmpty()) {
+ DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
+ options.getCompilationReason(), options.getCompilerFilter(),
+ options.getSplitName(),
+ options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
+ for (SharedLibraryInfo info : deps) {
+ AndroidPackage depPackage = null;
+ PackageSetting depPackageSetting = null;
+ synchronized (mPm.mLock) {
+ depPackage = mPm.mPackages.get(info.getPackageName());
+ depPackageSetting = mPm.mSettings.getPackageLPr(info.getPackageName());
+ }
+ if (depPackage != null && depPackageSetting != null) {
+ // TODO: Analyze and investigate if we (should) profile libraries.
+ pdo.performDexOpt(depPackage, depPackageSetting, instructionSets,
+ mPm.getOrCreateCompilerPackageStats(depPackage),
+ mPm.getDexManager().getPackageUseInfoOrDefault(
+ depPackage.getPackageName()), libraryOptions);
+ } else {
+ // TODO(ngeoffray): Support dexopting system shared libraries.
+ }
+ }
+ }
+
+ return pdo.performDexOpt(p, pkgSetting, instructionSets,
+ mPm.getOrCreateCompilerPackageStats(p),
+ mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options);
+ }
+
+ public void forceDexOpt(String packageName) {
+ PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
+
+ AndroidPackage pkg;
+ PackageSetting pkgSetting;
+ synchronized (mPm.mLock) {
+ pkg = mPm.mPackages.get(packageName);
+ pkgSetting = mPm.mSettings.getPackageLPr(packageName);
+ if (pkg == null || pkgSetting == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ }
+
+ synchronized (mPm.mInstallLock) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+
+ // Whoever is calling forceDexOpt wants a compiled package.
+ // Don't use profiles since that may cause compilation to be skipped.
+ final int res = performDexOptInternalWithDependenciesLI(pkg, pkgSetting,
+ new DexoptOptions(packageName,
+ getDefaultCompilerFilter(),
+ DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
+ throw new IllegalStateException("Failed to dexopt: " + res);
+ }
+ }
+ }
+
+ public boolean performDexOptMode(String packageName,
+ boolean checkProfiles, String targetCompilerFilter, boolean force,
+ boolean bootComplete, String splitName) {
+ PackageManagerServiceUtils.enforceSystemOrRootOrShell("performDexOptMode");
+
+ int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0)
+ | (force ? DexoptOptions.DEXOPT_FORCE : 0)
+ | (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
+ return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
+ targetCompilerFilter, splitName, flags));
+ }
+
+ public boolean performDexOptSecondary(String packageName, String compilerFilter,
+ boolean force) {
+ int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX
+ | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | (force ? DexoptOptions.DEXOPT_FORCE : 0);
+ return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags));
+ }
+
+ // Sort apps by importance for dexopt ordering. Important apps are given
+ // more priority in case the device runs out of space.
+ public static List<PackageSetting> getPackagesForDexopt(
+ Collection<PackageSetting> packages,
+ PackageManagerService packageManagerService) {
+ return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT);
+ }
+
+ public static List<PackageSetting> getPackagesForDexopt(
+ Collection<PackageSetting> pkgSettings,
+ PackageManagerService packageManagerService,
+ boolean debug) {
+ List<PackageSetting> result = new LinkedList<>();
+ ArrayList<PackageSetting> remainingPkgSettings = new ArrayList<>(pkgSettings);
+
+ // First, remove all settings without available packages
+ remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG);
+
+ ArrayList<PackageSetting> sortTemp = new ArrayList<>(remainingPkgSettings.size());
+
+ // Give priority to core apps.
+ applyPackageFilter(pkgSetting -> pkgSetting.getPkg().isCoreApp(), result,
+ remainingPkgSettings, sortTemp, packageManagerService);
+
+ // Give priority to system apps that listen for pre boot complete.
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+ applyPackageFilter(pkgSetting -> pkgNames.contains(pkgSetting.getPackageName()), result,
+ remainingPkgSettings, sortTemp, packageManagerService);
+
+ // Give priority to apps used by other apps.
+ DexManager dexManager = packageManagerService.getDexManager();
+ applyPackageFilter(pkgSetting ->
+ dexManager.getPackageUseInfoOrDefault(pkgSetting.getPackageName())
+ .isAnyCodePathUsedByOtherApps(),
+ result, remainingPkgSettings, sortTemp, packageManagerService);
+
+ // Filter out packages that aren't recently used, add all remaining apps.
+ // TODO: add a property to control this?
+ Predicate<PackageSetting> remainingPredicate;
+ if (!remainingPkgSettings.isEmpty()
+ && packageManagerService.isHistoricalPackageUsageAvailable()) {
+ if (debug) {
+ Log.i(TAG, "Looking at historical package use");
+ }
+ // Get the package that was used last.
+ PackageSetting lastUsed = Collections.max(remainingPkgSettings,
+ (pkgSetting1, pkgSetting2) -> Long.compare(
+ pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills(),
+ pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills()));
+ if (debug) {
+ Log.i(TAG, "Taking package " + lastUsed.getPackageName()
+ + " as reference in time use");
+ }
+ long estimatedPreviousSystemUseTime = lastUsed.getPkgState()
+ .getLatestForegroundPackageUseTimeInMills();
+ // Be defensive if for some reason package usage has bogus data.
+ if (estimatedPreviousSystemUseTime != 0) {
+ final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
+ remainingPredicate = pkgSetting -> pkgSetting.getPkgState()
+ .getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
+ } else {
+ // No meaningful historical info. Take all.
+ remainingPredicate = pkgSetting -> true;
+ }
+ sortPackagesByUsageDate(remainingPkgSettings, packageManagerService);
+ } else {
+ // No historical info. Take all.
+ remainingPredicate = pkgSetting -> true;
+ }
+ applyPackageFilter(remainingPredicate, result, remainingPkgSettings, sortTemp,
+ packageManagerService);
+
+ if (debug) {
+ Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
+ Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings));
+ }
+
+ return result;
+ }
+
+ // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
+ // package will be removed from {@code packages} and added to {@code result} with its
+ // dependencies. If usage data is available, the positive packages will be sorted by usage
+ // data (with {@code sortTemp} as temporary storage).
+ private static void applyPackageFilter(
+ Predicate<PackageSetting> filter,
+ Collection<PackageSetting> result,
+ Collection<PackageSetting> packages,
+ @NonNull List<PackageSetting> sortTemp,
+ PackageManagerService packageManagerService) {
+ for (PackageSetting pkgSetting : packages) {
+ if (filter.test(pkgSetting)) {
+ sortTemp.add(pkgSetting);
+ }
+ }
+
+ sortPackagesByUsageDate(sortTemp, packageManagerService);
+ packages.removeAll(sortTemp);
+
+ for (PackageSetting pkgSetting : sortTemp) {
+ result.add(pkgSetting);
+
+ List<PackageSetting> deps =
+ packageManagerService.findSharedNonSystemLibraries(pkgSetting);
+ if (!deps.isEmpty()) {
+ deps.removeAll(result);
+ result.addAll(deps);
+ packages.removeAll(deps);
+ }
+ }
+
+ sortTemp.clear();
+ }
+
+ // Sort a list of apps by their last usage, most recently used apps first. The order of
+ // packages without usage data is undefined (but they will be sorted after the packages
+ // that do have usage data).
+ private static void sortPackagesByUsageDate(List<PackageSetting> pkgSettings,
+ PackageManagerService packageManagerService) {
+ if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
+ return;
+ }
+
+ Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) ->
+ Long.compare(
+ pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills(),
+ pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills())
+ );
+ }
+
+ private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
+ List<ResolveInfo> ris = null;
+ try {
+ ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
+ .getList();
+ } catch (RemoteException e) {
+ }
+ ArraySet<String> pkgNames = new ArraySet<String>();
+ if (ris != null) {
+ for (ResolveInfo ri : ris) {
+ pkgNames.add(ri.activityInfo.packageName);
+ }
+ }
+ return pkgNames;
+ }
+
+ public static String packagesToString(List<PackageSetting> pkgSettings) {
+ StringBuilder sb = new StringBuilder();
+ for (int index = 0; index < pkgSettings.size(); index++) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(pkgSettings.get(index).getPackageName());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Requests that files preopted on a secondary system partition be copied to the data partition
+ * if possible. Note that the actual copying of the files is accomplished by init for security
+ * reasons. This simply requests that the copy takes place and awaits confirmation of its
+ * completion. See platform/system/extras/cppreopt/ for the implementation of the actual copy.
+ */
+ public static void requestCopyPreoptedFiles() {
+ final int WAIT_TIME_MS = 100;
+ final String CP_PREOPT_PROPERTY = "sys.cppreopt";
+ if (SystemProperties.getInt("ro.cp_system_other_odex", 0) == 1) {
+ SystemProperties.set(CP_PREOPT_PROPERTY, "requested");
+ // We will wait for up to 100 seconds.
+ final long timeStart = SystemClock.uptimeMillis();
+ final long timeEnd = timeStart + 100 * 1000;
+ long timeNow = timeStart;
+ while (!SystemProperties.get(CP_PREOPT_PROPERTY).equals("finished")) {
+ try {
+ Thread.sleep(WAIT_TIME_MS);
+ } catch (InterruptedException e) {
+ // Do nothing
+ }
+ timeNow = SystemClock.uptimeMillis();
+ if (timeNow > timeEnd) {
+ SystemProperties.set(CP_PREOPT_PROPERTY, "timed-out");
+ Slog.wtf(TAG, "cppreopt did not finish!");
+ break;
+ }
+ }
+
+ Slog.i(TAG, "cppreopts took " + (timeNow - timeStart) + " ms");
+ }
+ }
+
+ /*package*/ void controlDexOptBlocking(boolean block) {
+ mPm.mPackageDexOptimizer.controlDexOptBlocking(block);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
new file mode 100644
index 0000000..d43b681
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
+
+import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
+
+import android.content.ComponentName;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.SharedLibraryInfo;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.os.incremental.PerUidReadTimeouts;
+import android.service.pm.PackageServiceDumpProto;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Dumps PackageManagerService internal states.
+ */
+final class DumpHelper {
+ final PackageManagerService mPm;
+
+ DumpHelper(PackageManagerService pm) {
+ mPm = pm;
+ }
+
+ public void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ DumpState dumpState = new DumpState();
+ ArraySet<String> permissionNames = null;
+
+ int opti = 0;
+ while (opti < args.length) {
+ String opt = args[opti];
+ if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+ break;
+ }
+ opti++;
+
+ if ("-a".equals(opt)) {
+ // Right now we only know how to print all.
+ } else if ("-h".equals(opt)) {
+ printHelp(pw);
+ return;
+ } else if ("--checkin".equals(opt)) {
+ dumpState.setCheckIn(true);
+ } else if ("--all-components".equals(opt)) {
+ dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
+ } else if ("-f".equals(opt)) {
+ dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+ } else if ("--proto".equals(opt)) {
+ dumpProto(fd);
+ return;
+ } else {
+ pw.println("Unknown argument: " + opt + "; use -h for help");
+ }
+ }
+
+ // Is the caller requesting to dump a particular piece of data?
+ if (opti < args.length) {
+ String cmd = args[opti];
+ opti++;
+ // Is this a package name?
+ if ("android".equals(cmd) || cmd.contains(".")) {
+ dumpState.setTargetPackageName(cmd);
+ // When dumping a single package, we always dump all of its
+ // filter information since the amount of data will be reasonable.
+ dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+ } else if ("check-permission".equals(cmd)) {
+ if (opti >= args.length) {
+ pw.println("Error: check-permission missing permission argument");
+ return;
+ }
+ String perm = args[opti];
+ opti++;
+ if (opti >= args.length) {
+ pw.println("Error: check-permission missing package argument");
+ return;
+ }
+
+ String pkg = args[opti];
+ opti++;
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ if (opti < args.length) {
+ try {
+ user = Integer.parseInt(args[opti]);
+ } catch (NumberFormatException e) {
+ pw.println("Error: check-permission user argument is not a number: "
+ + args[opti]);
+ return;
+ }
+ }
+
+ // Normalize package name to handle renamed packages and static libs
+ pkg = mPm.resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
+
+ pw.println(mPm.checkPermission(perm, pkg, user));
+ return;
+ } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_LIBS);
+ } else if ("f".equals(cmd) || "features".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_FEATURES);
+ } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
+ if (opti >= args.length) {
+ dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
+ | DumpState.DUMP_SERVICE_RESOLVERS
+ | DumpState.DUMP_RECEIVER_RESOLVERS
+ | DumpState.DUMP_CONTENT_RESOLVERS);
+ } else {
+ while (opti < args.length) {
+ String name = args[opti];
+ if ("a".equals(name) || "activity".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
+ } else if ("s".equals(name) || "service".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
+ } else if ("r".equals(name) || "receiver".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
+ } else if ("c".equals(name) || "content".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
+ } else {
+ pw.println("Error: unknown resolver table type: " + name);
+ return;
+ }
+ opti++;
+ }
+ }
+ } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PERMISSIONS);
+ } else if ("permission".equals(cmd)) {
+ if (opti >= args.length) {
+ pw.println("Error: permission requires permission name");
+ return;
+ }
+ permissionNames = new ArraySet<>();
+ while (opti < args.length) {
+ permissionNames.add(args[opti]);
+ opti++;
+ }
+ dumpState.setDump(DumpState.DUMP_PERMISSIONS
+ | DumpState.DUMP_PACKAGES | DumpState.DUMP_SHARED_USERS);
+ } else if ("pref".equals(cmd) || "preferred".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PREFERRED);
+ } else if ("preferred-xml".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
+ if (opti < args.length && "--full".equals(args[opti])) {
+ dumpState.setFullPreferred(true);
+ opti++;
+ }
+ } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED);
+ } else if ("p".equals(cmd) || "packages".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PACKAGES);
+ } else if ("q".equals(cmd) || "queries".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_QUERIES);
+ } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_SHARED_USERS);
+ if (opti < args.length && "noperm".equals(args[opti])) {
+ dumpState.setOptionEnabled(DumpState.OPTION_SKIP_PERMISSIONS);
+ }
+ } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PROVIDERS);
+ } else if ("m".equals(cmd) || "messages".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_MESSAGES);
+ } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_VERIFIERS);
+ } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
+ } else if ("version".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_VERSION);
+ } else if ("k".equals(cmd) || "keysets".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_KEYSETS);
+ } else if ("installs".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_INSTALLS);
+ } else if ("frozen".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_FROZEN);
+ } else if ("volumes".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_VOLUMES);
+ } else if ("dexopt".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DEXOPT);
+ } else if ("compiler-stats".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
+ } else if ("changes".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_CHANGES);
+ } else if ("service-permissions".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
+ } else if ("known-packages".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
+ } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
+ } else if ("snapshot".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
+ if (opti < args.length) {
+ if ("--full".equals(args[opti])) {
+ dumpState.setBrief(false);
+ opti++;
+ } else if ("--brief".equals(args[opti])) {
+ dumpState.setBrief(true);
+ opti++;
+ }
+ }
+ } else if ("protected-broadcasts".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PROTECTED_BROADCASTS);
+ } else if ("write".equals(cmd)) {
+ synchronized (mPm.mLock) {
+ mPm.writeSettingsLPrTEMP();
+ pw.println("Settings written.");
+ return;
+ }
+ }
+ }
+
+ final String packageName = dumpState.getTargetPackageName();
+ final boolean checkin = dumpState.isCheckIn();
+
+ // Return if the package doesn't exist.
+ if (packageName != null
+ && mPm.getPackageSetting(packageName) == null
+ && !mPm.mApexManager.isApexPackage(packageName)) {
+ pw.println("Unable to find package: " + packageName);
+ return;
+ }
+
+ if (checkin) {
+ pw.println("vers,1");
+ }
+
+ // reader
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_VERSION)
+ && packageName == null) {
+ mPm.dumpComputer(DumpState.DUMP_VERSION, fd, pw, dumpState);
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ ipw.println("Known Packages:");
+ ipw.increaseIndent();
+ for (int i = 0; i <= LAST_KNOWN_PACKAGE; i++) {
+ final String knownPackage = PackageManagerInternal.knownPackageToString(i);
+ ipw.print(knownPackage);
+ ipw.println(":");
+ final String[] pkgNames = mPm.getKnownPackageNamesInternal(i,
+ UserHandle.USER_SYSTEM);
+ ipw.increaseIndent();
+ if (ArrayUtils.isEmpty(pkgNames)) {
+ ipw.println("none");
+ } else {
+ for (String name : pkgNames) {
+ ipw.println(name);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_VERIFIERS)
+ && packageName == null) {
+ final String requiredVerifierPackage = mPm.mRequiredVerifierPackage;
+ if (!checkin) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Verifiers:");
+ pw.print(" Required: ");
+ pw.print(requiredVerifierPackage);
+ pw.print(" (uid=");
+ pw.print(mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.USER_SYSTEM));
+ pw.println(")");
+ } else if (requiredVerifierPackage != null) {
+ pw.print("vrfy,"); pw.print(requiredVerifierPackage);
+ pw.print(",");
+ pw.println(mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.USER_SYSTEM));
+ }
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER)
+ && packageName == null) {
+ final DomainVerificationProxy proxy = mPm.mDomainVerificationManager.getProxy();
+ final ComponentName verifierComponent = proxy.getComponentName();
+ if (verifierComponent != null) {
+ String verifierPackageName = verifierComponent.getPackageName();
+ if (!checkin) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Domain Verifier:");
+ pw.print(" Using: ");
+ pw.print(verifierPackageName);
+ pw.print(" (uid=");
+ pw.print(mPm.getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.USER_SYSTEM));
+ pw.println(")");
+ } else if (verifierPackageName != null) {
+ pw.print("dv,"); pw.print(verifierPackageName);
+ pw.print(",");
+ pw.println(mPm.getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.USER_SYSTEM));
+ }
+ } else {
+ pw.println();
+ pw.println("No Domain Verifier available!");
+ }
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_LIBS)
+ && packageName == null) {
+ mPm.dumpComputer(DumpState.DUMP_LIBS, fd, pw, dumpState);
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_FEATURES)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ if (!checkin) {
+ pw.println("Features:");
+ }
+
+ synchronized (mPm.mAvailableFeatures) {
+ for (FeatureInfo feat : mPm.mAvailableFeatures.values()) {
+ if (!checkin) {
+ pw.print(" ");
+ pw.print(feat.name);
+ if (feat.version > 0) {
+ pw.print(" version=");
+ pw.print(feat.version);
+ }
+ pw.println();
+ } else {
+ pw.print("feat,");
+ pw.print(feat.name);
+ pw.print(",");
+ pw.println(feat.version);
+ }
+ }
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
+ synchronized (mPm.mLock) {
+ mPm.mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
+ }
+ }
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
+ synchronized (mPm.mLock) {
+ mPm.mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
+ }
+ }
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
+ synchronized (mPm.mLock) {
+ mPm.mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
+ }
+ }
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
+ synchronized (mPm.mLock) {
+ mPm.mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+ mPm.dumpComputer(DumpState.DUMP_PREFERRED, fd, pw, dumpState);
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)
+ && packageName == null) {
+ mPm.dumpComputer(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState);
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ mPm.dumpComputer(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState);
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+ synchronized (mPm.mLock) {
+ mPm.mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+ synchronized (mPm.mLock) {
+ mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
+ synchronized (mPm.mLock) {
+ mPm.mSettings.getKeySetManagerService().dumpLPr(pw, packageName, dumpState);
+ }
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
+ // This cannot be moved to ComputerEngine since some variables of the collections
+ // in PackageUserState such as suspendParams, disabledComponents and enabledComponents
+ // do not have a copy.
+ synchronized (mPm.mLock) {
+ mPm.mSettings.dumpPackagesLPr(pw, packageName, permissionNames, dumpState, checkin);
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_QUERIES)) {
+ mPm.dumpComputer(DumpState.DUMP_QUERIES, fd, pw, dumpState);
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
+ // This cannot be moved to ComputerEngine since the set of packages in the
+ // SharedUserSetting do not have a copy.
+ synchronized (mPm.mLock) {
+ mPm.mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState,
+ checkin);
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_CHANGES)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Package Changes:");
+ synchronized (mPm.mLock) {
+ pw.print(" Sequence number="); pw.println(mPm.mChangedPackagesSequenceNumber);
+ final int numChangedPackages = mPm.mChangedPackages.size();
+ for (int i = 0; i < numChangedPackages; i++) {
+ final SparseArray<String> changes = mPm.mChangedPackages.valueAt(i);
+ pw.print(" User "); pw.print(mPm.mChangedPackages.keyAt(i)); pw.println(":");
+ final int numChanges = changes.size();
+ if (numChanges == 0) {
+ pw.print(" "); pw.println("No packages changed");
+ } else {
+ for (int j = 0; j < numChanges; j++) {
+ final String pkgName = changes.valueAt(j);
+ final int sequenceNumber = changes.keyAt(j);
+ pw.print(" ");
+ pw.print("seq=");
+ pw.print(sequenceNumber);
+ pw.print(", package=");
+ pw.println(pkgName);
+ }
+ }
+ }
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_FROZEN)
+ && packageName == null) {
+ // XXX should handle packageName != null by dumping only install data that
+ // the given package is involved with.
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ ipw.println();
+ ipw.println("Frozen packages:");
+ ipw.increaseIndent();
+ synchronized (mPm.mLock) {
+ if (mPm.mFrozenPackages.size() == 0) {
+ ipw.println("(none)");
+ } else {
+ for (int i = 0; i < mPm.mFrozenPackages.size(); i++) {
+ ipw.println(mPm.mFrozenPackages.valueAt(i));
+ }
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_VOLUMES)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ ipw.println();
+ ipw.println("Loaded volumes:");
+ ipw.increaseIndent();
+ synchronized (mPm.mLoadedVolumes) {
+ if (mPm.mLoadedVolumes.size() == 0) {
+ ipw.println("(none)");
+ } else {
+ for (int i = 0; i < mPm.mLoadedVolumes.size(); i++) {
+ ipw.println(mPm.mLoadedVolumes.valueAt(i));
+ }
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS)
+ && packageName == null) {
+ synchronized (mPm.mLock) {
+ mPm.mComponentResolver.dumpServicePermissions(pw, dumpState);
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_DEXOPT)) {
+ mPm.dumpComputer(DumpState.DUMP_DEXOPT, fd, pw, dumpState);
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) {
+ mPm.dumpComputer(DumpState.DUMP_COMPILER_STATS, fd, pw, dumpState);
+ }
+
+ if (dumpState.isDumping(DumpState.DUMP_MESSAGES)
+ && packageName == null) {
+ if (!checkin) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ synchronized (mPm.mLock) {
+ mPm.mSettings.dumpReadMessagesLPr(pw, dumpState);
+ }
+ pw.println();
+ pw.println("Package warning messages:");
+ dumpCriticalInfo(pw, null);
+ } else {
+ dumpCriticalInfo(pw, "msg,");
+ }
+ }
+
+ // PackageInstaller should be called outside of mPackages lock
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_INSTALLS)
+ && packageName == null) {
+ // XXX should handle packageName != null by dumping only install data that
+ // the given package is involved with.
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ mPm.mInstallerService.dump(new IndentingPrintWriter(pw, " ", 120));
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_APEX)
+ && (packageName == null || mPm.mApexManager.isApexPackage(packageName))) {
+ mPm.mApexManager.dump(pw, packageName);
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_PER_UID_READ_TIMEOUTS)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Per UID read timeouts:");
+ pw.println(" Default timeouts flag: " + PackageManagerService.getDefaultTimeouts());
+ pw.println(" Known digesters list flag: "
+ + PackageManagerService.getKnownDigestersList());
+
+ PerUidReadTimeouts[] items = mPm.getPerUidReadTimeouts();
+ pw.println(" Timeouts (" + items.length + "):");
+ for (PerUidReadTimeouts item : items) {
+ pw.print(" (");
+ pw.print("uid=" + item.uid + ", ");
+ pw.print("minTimeUs=" + item.minTimeUs + ", ");
+ pw.print("minPendingTimeUs=" + item.minPendingTimeUs + ", ");
+ pw.print("maxPendingTimeUs=" + item.maxPendingTimeUs);
+ pw.println(")");
+ }
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Snapshot statistics");
+ mPm.dumpSnapshotStats(pw, dumpState.isBrief());
+ }
+
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_PROTECTED_BROADCASTS)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Protected broadcast actions:");
+ synchronized (mPm.mProtectedBroadcasts) {
+ for (int i = 0; i < mPm.mProtectedBroadcasts.size(); i++) {
+ pw.print(" ");
+ pw.println(mPm.mProtectedBroadcasts.valueAt(i));
+ }
+ }
+
+ }
+ }
+
+ private void printHelp(PrintWriter pw) {
+ pw.println("Package manager dump options:");
+ pw.println(" [-h] [-f] [--checkin] [--all-components] [cmd] ...");
+ pw.println(" --checkin: dump for a checkin");
+ pw.println(" -f: print details of intent filters");
+ pw.println(" -h: print this help");
+ pw.println(" --all-components: include all component names in package dump");
+ pw.println(" cmd may be one of:");
+ pw.println(" apex: list active APEXes and APEX session state");
+ pw.println(" l[ibraries]: list known shared libraries");
+ pw.println(" f[eatures]: list device features");
+ pw.println(" k[eysets]: print known keysets");
+ pw.println(" r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
+ pw.println(" perm[issions]: dump permissions");
+ pw.println(" permission [name ...]: dump declaration and use of given permission");
+ pw.println(" pref[erred]: print preferred package settings");
+ pw.println(" preferred-xml [--full]: print preferred package settings as xml");
+ pw.println(" prov[iders]: dump content providers");
+ pw.println(" p[ackages]: dump installed packages");
+ pw.println(" q[ueries]: dump app queryability calculations");
+ pw.println(" s[hared-users]: dump shared user IDs");
+ pw.println(" m[essages]: print collected runtime messages");
+ pw.println(" v[erifiers]: print package verifier info");
+ pw.println(" d[omain-preferred-apps]: print domains preferred apps");
+ pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
+ pw.println(" t[imeouts]: print read timeouts for known digesters");
+ pw.println(" version: print database version info");
+ pw.println(" write: write current settings now");
+ pw.println(" installs: details about install sessions");
+ pw.println(" check-permission <permission> <package> [<user>]: does pkg hold perm?");
+ pw.println(" dexopt: dump dexopt state");
+ pw.println(" compiler-stats: dump compiler statistics");
+ pw.println(" service-permissions: dump permissions required by services");
+ pw.println(" snapshot: dump snapshot statistics");
+ pw.println(" protected-broadcasts: print list of protected broadcast actions");
+ pw.println(" known-packages: dump known packages");
+ pw.println(" <package.name>: info about given package");
+ }
+
+ private void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ synchronized (mPm.mLock) {
+ final long requiredVerifierPackageToken =
+ proto.start(PackageServiceDumpProto.REQUIRED_VERIFIER_PACKAGE);
+ proto.write(PackageServiceDumpProto.PackageShortProto.NAME,
+ mPm.mRequiredVerifierPackage);
+ proto.write(
+ PackageServiceDumpProto.PackageShortProto.UID,
+ mPm.getPackageUid(
+ mPm.mRequiredVerifierPackage,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.USER_SYSTEM));
+ proto.end(requiredVerifierPackageToken);
+
+ DomainVerificationProxy proxy = mPm.mDomainVerificationManager.getProxy();
+ ComponentName verifierComponent = proxy.getComponentName();
+ if (verifierComponent != null) {
+ String verifierPackageName = verifierComponent.getPackageName();
+ final long verifierPackageToken =
+ proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
+ proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
+ proto.write(
+ PackageServiceDumpProto.PackageShortProto.UID,
+ mPm.getPackageUid(
+ verifierPackageName,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.USER_SYSTEM));
+ proto.end(verifierPackageToken);
+ }
+
+ dumpSharedLibrariesProto(proto);
+ dumpFeaturesProto(proto);
+ mPm.mSettings.dumpPackagesProto(proto);
+ mPm.mSettings.dumpSharedUsersProto(proto);
+ dumpCriticalInfo(proto);
+ }
+ proto.flush();
+ }
+
+ private void dumpFeaturesProto(ProtoOutputStream proto) {
+ synchronized (mPm.mAvailableFeatures) {
+ final int count = mPm.mAvailableFeatures.size();
+ for (int i = 0; i < count; i++) {
+ mPm.mAvailableFeatures.valueAt(i).dumpDebug(proto,
+ PackageServiceDumpProto.FEATURES);
+ }
+ }
+ }
+
+ private void dumpSharedLibrariesProto(ProtoOutputStream proto) {
+ final int count = mPm.mSharedLibraries.size();
+ for (int i = 0; i < count; i++) {
+ final String libName = mPm.mSharedLibraries.keyAt(i);
+ WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
+ mPm.mSharedLibraries.get(libName);
+ if (versionedLib == null) {
+ continue;
+ }
+ final int versionCount = versionedLib.size();
+ for (int j = 0; j < versionCount; j++) {
+ final SharedLibraryInfo libraryInfo = versionedLib.valueAt(j);
+ final long sharedLibraryToken =
+ proto.start(PackageServiceDumpProto.SHARED_LIBRARIES);
+ proto.write(PackageServiceDumpProto.SharedLibraryProto.NAME, libraryInfo.getName());
+ final boolean isJar = (libraryInfo.getPath() != null);
+ proto.write(PackageServiceDumpProto.SharedLibraryProto.IS_JAR, isJar);
+ if (isJar) {
+ proto.write(PackageServiceDumpProto.SharedLibraryProto.PATH,
+ libraryInfo.getPath());
+ } else {
+ proto.write(PackageServiceDumpProto.SharedLibraryProto.APK,
+ libraryInfo.getPackageName());
+ }
+ proto.end(sharedLibraryToken);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/FileInstallArgs.java b/services/core/java/com/android/server/pm/FileInstallArgs.java
index cb174eb..02c8c12 100644
--- a/services/core/java/com/android/server/pm/FileInstallArgs.java
+++ b/services/core/java/com/android/server/pm/FileInstallArgs.java
@@ -71,7 +71,10 @@
super(params);
}
- /** Existing install */
+ /**
+ * Create args that describe an existing installed package. Typically used
+ * when cleaning up old installs, or used as a move source.
+ */
FileInstallArgs(String codePath, String[] instructionSets, PackageManagerService pm) {
super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 9ba69f8..722198f 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -35,8 +35,12 @@
import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
+import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
+import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
@@ -55,6 +59,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.system.ErrnoException;
+import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -62,6 +67,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
@@ -90,60 +96,129 @@
private final RemovePackageHelper mRemovePackageHelper;
private final AppDataHelper mAppDataHelper;
+ private final List<ScanPartition> mDirsToScanAsSystem;
+ private final int mScanFlags;
+ private final int mSystemParseFlags;
+ private final int mSystemScanFlags;
+
+ /**
+ * Tracks new system packages [received in an OTA] that we expect to
+ * find updated user-installed versions. Keys are package name, values
+ * are package location.
+ */
+ private final ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
+
// TODO(b/198166813): remove PMS dependency
InitAndSystemPackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper,
AppDataHelper appDataHelper) {
mPm = pm;
mRemovePackageHelper = removePackageHelper;
mAppDataHelper = appDataHelper;
+ mDirsToScanAsSystem = getSystemScanPartitions();
+ // Set flag to monitor and not change apk file paths when scanning install directories.
+ int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
+ if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()) {
+ mScanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
+ } else {
+ mScanFlags = scanFlags;
+ }
+ mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
+ mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
}
+ private List<ScanPartition> getSystemScanPartitions() {
+ final List<ScanPartition> scanPartitions = new ArrayList<>();
+ scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
+ scanPartitions.addAll(getApexScanPartitions());
+ Slog.d(TAG, "Directories scanned as system partitions: " + scanPartitions);
+ return scanPartitions;
+ }
+
+ private List<ScanPartition> getApexScanPartitions() {
+ final List<ScanPartition> scanPartitions = new ArrayList<>();
+ final List<ApexManager.ActiveApexInfo> activeApexInfos =
+ mPm.mApexManager.getActiveApexInfos();
+ for (int i = 0; i < activeApexInfos.size(); i++) {
+ final ScanPartition scanPartition = resolveApexToScanPartition(activeApexInfos.get(i));
+ if (scanPartition != null) {
+ scanPartitions.add(scanPartition);
+ }
+ }
+ return scanPartitions;
+ }
+
+ private static @Nullable ScanPartition resolveApexToScanPartition(
+ ApexManager.ActiveApexInfo apexInfo) {
+ for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+ ScanPartition sp = SYSTEM_PARTITIONS.get(i);
+ if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
+ sp.getFolder().getAbsolutePath())) {
+ return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
+ }
+ }
+ return null;
+ }
+
+ public OverlayConfig setUpSystemPackages(
+ WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
+ long startTime) {
+ PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
+
+ ExecutorService executorService = ParallelPackageParser.makeExecutorService();
+ // Prepare apex package info before scanning APKs, these information are needed when
+ // scanning apk in apex.
+ mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
+
+ scanSystemDirs(packageParser, executorService);
+ // Parse overlay configuration files to set default enable state, mutability, and
+ // priority of system overlays.
+ OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
+ consumer -> mPm.forEachPackage(
+ pkg -> consumer.accept(pkg, pkg.isSystem())));
+ cleanupSystemPackagesAndInstallStubs(packageParser, executorService, packageSettings,
+ startTime, userIds);
+ packageParser.close();
+ return overlayConfig;
+ }
/**
* First part of init dir scanning
*/
- // TODO(b/197876467): consolidate this with cleanupSystemPackagesAndInstallStubs
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- public void scanSystemDirs(List<ScanPartition> dirsToScanAsSystem,
- boolean isUpgrade, PackageParser2 packageParser,
- ExecutorService executorService, AndroidPackage platformPackage,
- boolean isPreNMR1Upgrade, int systemParseFlags, int systemScanFlags) {
+ private void scanSystemDirs(PackageParser2 packageParser, ExecutorService executorService) {
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
// Collect vendor/product/system_ext overlay packages. (Do this before scanning
// any apps.)
// For security and version matching reason, only consider overlay packages if they
// reside in the right directory.
- for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) {
- final ScanPartition partition = dirsToScanAsSystem.get(i);
+ for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
+ final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getOverlayFolder() == null) {
continue;
}
- scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags,
- systemScanFlags | partition.scanFlag, 0,
- packageParser, executorService, platformPackage, isUpgrade,
- isPreNMR1Upgrade);
+ scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
+ mSystemScanFlags | partition.scanFlag, 0,
+ packageParser, executorService);
}
- scanDirTracedLI(frameworkDir, systemParseFlags,
- systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
- packageParser, executorService, platformPackage, isUpgrade, isPreNMR1Upgrade);
+ scanDirTracedLI(frameworkDir, mSystemParseFlags,
+ mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
+ packageParser, executorService);
if (!mPm.mPackages.containsKey("android")) {
throw new IllegalStateException(
"Failed to load frameworks package; check log for warnings");
}
- for (int i = 0, size = dirsToScanAsSystem.size(); i < size; i++) {
- final ScanPartition partition = dirsToScanAsSystem.get(i);
+ for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
+ final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getPrivAppFolder() != null) {
- scanDirTracedLI(partition.getPrivAppFolder(), systemParseFlags,
- systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
- packageParser, executorService, platformPackage, isUpgrade,
- isPreNMR1Upgrade);
+ scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
+ mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
+ packageParser, executorService);
}
- scanDirTracedLI(partition.getAppFolder(), systemParseFlags,
- systemScanFlags | partition.scanFlag, 0,
- packageParser, executorService, platformPackage, isUpgrade,
- isPreNMR1Upgrade);
+ scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
+ mSystemScanFlags | partition.scanFlag, 0,
+ packageParser, executorService);
}
}
@@ -151,20 +226,17 @@
* Second part of init dir scanning
*/
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- public void cleanupSystemPackagesAndInstallStubs(List<ScanPartition> dirsToScanAsSystem,
- boolean isUpgrade, PackageParser2 packageParser,
- ExecutorService executorService, boolean onlyCore,
+ private void cleanupSystemPackagesAndInstallStubs(PackageParser2 packageParser,
+ ExecutorService executorService,
WatchedArrayMap<String, PackageSetting> packageSettings,
- long startTime, File appInstallDir, AndroidPackage platformPackage,
- boolean isPreNMR1Upgrade, int scanFlags, int systemParseFlags, int systemScanFlags,
- int[] userIds) {
+ long startTime, int[] userIds) {
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
// Stub packages must either be replaced with full versions in the /data
// partition or be disabled.
final List<String> stubSystemApps = new ArrayList<>();
- if (!onlyCore) {
+ if (!mPm.isOnlyCoreApps()) {
// do this first before mucking with mPackages for the "expecting better" case
final int numPackages = mPm.mPackages.size();
for (int index = 0; index < numPackages; index++) {
@@ -209,7 +281,7 @@
+ "; scanned versionCode="
+ scannedPkg.getLongVersionCode());
mRemovePackageHelper.removePackageLI(scannedPkg, true);
- mPm.mExpectingBetter.put(ps.getPackageName(), ps.getPath());
+ mExpectingBetter.put(ps.getPackageName(), ps.getPath());
}
continue;
@@ -233,9 +305,7 @@
// We're expecting that the system app should remain disabled, but add
// it to expecting better to recover in case the data version cannot
// be scanned.
- // TODO(b/197869066): mExpectingBetter should be able to pulled out into
- // this class and used only by the PMS initialization
- mPm.mExpectingBetter.put(disabledPs.getPackageName(), disabledPs.getPath());
+ mExpectingBetter.put(disabledPs.getPackageName(), disabledPs.getPath());
}
}
}
@@ -252,19 +322,18 @@
+ " , timePerPackage: "
+ (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
+ " , cached: " + cachedSystemApps);
- if (isUpgrade && systemPackagesCount > 0) {
+ if (mPm.isDeviceUpgrading() && systemPackagesCount > 0) {
//CHECKSTYLE:OFF IndentationCheck
FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME,
systemScanTime / systemPackagesCount);
//CHECKSTYLE:ON IndentationCheck
}
- if (!onlyCore) {
+ if (!mPm.isOnlyCoreApps()) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
- scanDirTracedLI(appInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
- packageParser, executorService, platformPackage, isUpgrade,
- isPreNMR1Upgrade);
+ scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
+ packageParser, executorService);
}
@@ -274,7 +343,7 @@
+ unfinishedTasks);
}
- if (!onlyCore) {
+ if (!mPm.isOnlyCoreApps()) {
final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
// Remove disable package settings for updated system apps that were
// removed via an OTA. If the update is no longer present, remove the
@@ -308,7 +377,7 @@
mRemovePackageHelper.removePackageLI(pkg, true);
try {
final File codePath = new File(pkg.getPath());
- scanPackageHelper.scanPackageTracedLI(codePath, 0, scanFlags, 0, null);
+ scanPackageHelper.scanPackageTracedLI(codePath, 0, mScanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse updated, ex-system package: "
+ e.getMessage());
@@ -332,27 +401,27 @@
* the userdata partition actually showed up. If they never
* appeared, crawl back and revive the system version.
*/
- for (int i = 0; i < mPm.mExpectingBetter.size(); i++) {
- final String packageName = mPm.mExpectingBetter.keyAt(i);
+ for (int i = 0; i < mExpectingBetter.size(); i++) {
+ final String packageName = mExpectingBetter.keyAt(i);
if (!mPm.mPackages.containsKey(packageName)) {
- final File scanFile = mPm.mExpectingBetter.valueAt(i);
+ final File scanFile = mExpectingBetter.valueAt(i);
logCriticalInfo(Log.WARN, "Expected better " + packageName
+ " but never showed up; reverting to system");
@ParsingPackageUtils.ParseFlags int reparseFlags = 0;
@PackageManagerService.ScanFlags int rescanFlags = 0;
- for (int i1 = dirsToScanAsSystem.size() - 1; i1 >= 0; i1--) {
- final ScanPartition partition = dirsToScanAsSystem.get(i1);
+ for (int i1 = mDirsToScanAsSystem.size() - 1; i1 >= 0; i1--) {
+ final ScanPartition partition = mDirsToScanAsSystem.get(i1);
if (partition.containsPrivApp(scanFile)) {
- reparseFlags = systemParseFlags;
- rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
+ reparseFlags = mSystemParseFlags;
+ rescanFlags = mSystemScanFlags | SCAN_AS_PRIVILEGED
| partition.scanFlag;
break;
}
if (partition.containsApp(scanFile)) {
- reparseFlags = systemParseFlags;
- rescanFlags = systemScanFlags | partition.scanFlag;
+ reparseFlags = mSystemParseFlags;
+ rescanFlags = mSystemScanFlags | partition.scanFlag;
break;
}
}
@@ -378,7 +447,7 @@
// Uncompress and install any stubbed system applications.
// This must be done last to ensure all stubs are replaced or disabled.
- installSystemStubPackages(stubSystemApps, scanFlags);
+ installSystemStubPackages(stubSystemApps, mScanFlags);
final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
- cachedSystemApps;
@@ -390,7 +459,7 @@
+ " , timePerPackage: "
+ (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
+ " , cached: " + cachedNonSystemApps);
- if (isUpgrade && dataPackagesCount > 0) {
+ if (mPm.isDeviceUpgrading() && dataPackagesCount > 0) {
//CHECKSTYLE:OFF IndentationCheck
FrameworkStatsLog.write(
FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
@@ -399,19 +468,17 @@
//CHECKSTYLE:OFF IndentationCheck
}
}
- mPm.mExpectingBetter.clear();
+ mExpectingBetter.clear();
mPm.mSettings.pruneRenamedPackagesLPw();
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
- long currentTime, PackageParser2 packageParser, ExecutorService executorService,
- AndroidPackage platformPackage, boolean isUpgrade, boolean isPreNMR1Upgrade) {
+ long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
- scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService,
- platformPackage, isUpgrade, isPreNMR1Upgrade);
+ scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -419,8 +486,7 @@
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
- PackageParser2 packageParser, ExecutorService executorService,
- AndroidPackage platformPackage, boolean isUpgrade, boolean isPreNMR1Upgrade) {
+ PackageParser2 packageParser, ExecutorService executorService) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + scanDir);
@@ -465,8 +531,7 @@
}
try {
scanPackageHelper.addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
- currentTime, null, platformPackage, isUpgrade,
- isPreNMR1Upgrade);
+ currentTime, null);
} catch (PackageManagerException e) {
errorCode = e.error;
errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
@@ -564,9 +629,8 @@
*/
@GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
public boolean enableCompressedPackage(AndroidPackage stubPkg,
- @NonNull PackageSetting stubPkgSetting, int defParseFlags,
- List<ScanPartition> dirsToScanAsSystem) {
- final int parseFlags = defParseFlags | ParsingPackageUtils.PARSE_CHATTY
+ @NonNull PackageSetting stubPkgSetting) {
+ final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
| ParsingPackageUtils.PARSE_ENFORCE_CODE;
synchronized (mPm.mInstallLock) {
final AndroidPackage pkg;
@@ -599,7 +663,7 @@
installPackageFromSystemLIF(stubPkg.getPath(),
mPm.mUserManager.getUserIds() /*allUserHandles*/,
null /*origUserHandles*/,
- true /*writeSettings*/, defParseFlags, dirsToScanAsSystem);
+ true /*writeSettings*/);
} catch (PackageManagerException pme) {
// Serious WTF; we have to be able to install the stub
Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(),
@@ -715,7 +779,7 @@
// we cannot retrieve the setting {@link Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL}.
// When we no longer need to read that setting, cblock release can occur always
// occur here directly
- if (!mPm.mSystemReady) {
+ if (!mPm.isSystemReady()) {
if (mPm.mReleaseOnSystemReady == null) {
mPm.mReleaseOnSystemReady = new ArrayList<>();
}
@@ -749,7 +813,7 @@
public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
PackageSetting deletedPs, @NonNull int[] allUserHandles,
@Nullable PackageRemovedInfo outInfo,
- boolean writeSettings, int defParseFlags, List<ScanPartition> dirsToScanAsSystem,
+ boolean writeSettings,
PackageSetting disabledPs)
throws SystemDeleteException {
// writer
@@ -768,8 +832,7 @@
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
try {
installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
- outInfo == null ? null : outInfo.mOrigUsers, writeSettings, defParseFlags,
- dirsToScanAsSystem);
+ outInfo == null ? null : outInfo.mOrigUsers, writeSettings);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
+ e.getMessage());
@@ -808,17 +871,16 @@
*/
@GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
private void installPackageFromSystemLIF(@NonNull String codePathString,
- @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings,
- int defParseFlags, List<ScanPartition> dirsToScanAsSystem)
+ @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings)
throws PackageManagerException {
final File codePath = new File(codePathString);
@ParsingPackageUtils.ParseFlags int parseFlags =
- defParseFlags
+ mPm.getDefParseFlags()
| ParsingPackageUtils.PARSE_MUST_BE_APK
| ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
@PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM;
- for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) {
- ScanPartition partition = dirsToScanAsSystem.get(i);
+ for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
+ ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.containsFile(codePath)) {
scanFlags |= partition.scanFlag;
if (partition.containsPrivApp(codePath)) {
@@ -894,4 +956,8 @@
}
}
}
+
+ public boolean isExpectingBetter(String packageName) {
+ return mExpectingBetter.containsKey(packageName);
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
new file mode 100644
index 0000000..7569900
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -0,0 +1,880 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
+import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.POST_INSTALL;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_IGNORE_FROZEN;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.backup.IBackupManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.DataLoaderType;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ComponentMutateUtils;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.os.Binder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.rollback.RollbackManagerInternal;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+final class InstallPackageHelper {
+ private final PackageManagerService mPm;
+ private final AppDataHelper mAppDataHelper;
+ private final PackageManagerServiceInjector mInjector;
+
+ // TODO(b/198166813): remove PMS dependency
+ InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
+ mPm = pm;
+ mInjector = pm.mInjector;
+ mAppDataHelper = appDataHelper;
+ }
+
+ InstallPackageHelper(PackageManagerService pm) {
+ this(pm, new AppDataHelper(pm));
+ }
+
+ InstallPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
+ mPm = pm;
+ mInjector = injector;
+ mAppDataHelper = new AppDataHelper(pm, mInjector);
+ }
+
+ @GuardedBy("mPm.mLock")
+ public Map<String, ReconciledPackage> reconcilePackagesLocked(
+ final ReconcileRequest request, KeySetManagerService ksms,
+ PackageManagerServiceInjector injector)
+ throws ReconcileFailure {
+ final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
+
+ final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
+
+ // make a copy of the existing set of packages so we can combine them with incoming packages
+ final ArrayMap<String, AndroidPackage> combinedPackages =
+ new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
+
+ combinedPackages.putAll(request.mAllPackages);
+
+ final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
+ new ArrayMap<>();
+
+ for (String installPackageName : scannedPackages.keySet()) {
+ final ScanResult scanResult = scannedPackages.get(installPackageName);
+
+ // add / replace existing with incoming packages
+ combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
+ scanResult.mRequest.mParsedPackage);
+
+ // in the first pass, we'll build up the set of incoming shared libraries
+ final List<SharedLibraryInfo> allowedSharedLibInfos =
+ SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
+ request.mSharedLibrarySource);
+ final SharedLibraryInfo staticLib = scanResult.mStaticSharedLibraryInfo;
+ if (allowedSharedLibInfos != null) {
+ for (SharedLibraryInfo info : allowedSharedLibInfos) {
+ if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
+ incomingSharedLibraries, info)) {
+ throw new ReconcileFailure("Static Shared Library " + staticLib.getName()
+ + " is being installed twice in this set!");
+ }
+ }
+ }
+
+ // the following may be null if we're just reconciling on boot (and not during install)
+ final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
+ final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
+ final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
+ final boolean isInstall = installArgs != null;
+ if (isInstall && (res == null || prepareResult == null)) {
+ throw new ReconcileFailure("Reconcile arguments are not balanced for "
+ + installPackageName + "!");
+ }
+
+ final DeletePackageAction deletePackageAction;
+ // we only want to try to delete for non system apps
+ if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
+ final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
+ final int deleteFlags = PackageManager.DELETE_KEEP_DATA
+ | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
+ deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
+ prepareResult.mOriginalPs, prepareResult.mDisabledPs,
+ deleteFlags, null /* all users */);
+ if (deletePackageAction == null) {
+ throw new ReconcileFailure(
+ PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+ "May not delete " + installPackageName + " to replace");
+ }
+ } else {
+ deletePackageAction = null;
+ }
+
+ final int scanFlags = scanResult.mRequest.mScanFlags;
+ final int parseFlags = scanResult.mRequest.mParseFlags;
+ final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
+
+ final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
+ final PackageSetting lastStaticSharedLibSetting =
+ request.mLastStaticSharedLibSettings.get(installPackageName);
+ final PackageSetting signatureCheckPs =
+ (prepareResult != null && lastStaticSharedLibSetting != null)
+ ? lastStaticSharedLibSetting
+ : scanResult.mPkgSetting;
+ boolean removeAppKeySetData = false;
+ boolean sharedUserSignaturesChanged = false;
+ SigningDetails signingDetails = null;
+ if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+ if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
+ // We just determined the app is signed correctly, so bring
+ // over the latest parsed certs.
+ } else {
+ if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+ throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "Package " + parsedPackage.getPackageName()
+ + " upgrade keys do not match the previously installed"
+ + " version");
+ } else {
+ String msg = "System package " + parsedPackage.getPackageName()
+ + " signature changed; retaining data.";
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ }
+ }
+ signingDetails = parsedPackage.getSigningDetails();
+ } else {
+ try {
+ final Settings.VersionInfo versionInfo =
+ request.mVersionInfos.get(installPackageName);
+ final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
+ final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+ final boolean isRollback = installArgs != null
+ && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+ final boolean compatMatch = verifySignatures(signatureCheckPs,
+ disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
+ compareRecover, isRollback);
+ // The new KeySets will be re-added later in the scanning process.
+ if (compatMatch) {
+ removeAppKeySetData = true;
+ }
+ // We just determined the app is signed correctly, so bring
+ // over the latest parsed certs.
+ signingDetails = parsedPackage.getSigningDetails();
+
+ // if this is is a sharedUser, check to see if the new package is signed by a
+ // newer
+ // signing certificate than the existing one, and if so, copy over the new
+ // details
+ if (signatureCheckPs.getSharedUser() != null) {
+ // Attempt to merge the existing lineage for the shared SigningDetails with
+ // the lineage of the new package; if the shared SigningDetails are not
+ // returned this indicates the new package added new signers to the lineage
+ // and/or changed the capabilities of existing signers in the lineage.
+ SigningDetails sharedSigningDetails =
+ signatureCheckPs.getSharedUser().signatures.mSigningDetails;
+ SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
+ signingDetails);
+ if (mergedDetails != sharedSigningDetails) {
+ signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+ mergedDetails;
+ }
+ if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
+ signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
+ }
+ }
+ } catch (PackageManagerException e) {
+ if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+ throw new ReconcileFailure(e);
+ }
+ signingDetails = parsedPackage.getSigningDetails();
+
+ // If the system app is part of a shared user we allow that shared user to
+ // change
+ // signatures as well as part of an OTA. We still need to verify that the
+ // signatures
+ // are consistent within the shared user for a given boot, so only allow
+ // updating
+ // the signatures on the first package scanned for the shared user (i.e. if the
+ // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
+ if (signatureCheckPs.getSharedUser() != null) {
+ final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
+ .signatures.mSigningDetails.getSignatures();
+ if (signatureCheckPs.getSharedUser().signaturesChanged != null
+ && compareSignatures(sharedUserSignatures,
+ parsedPackage.getSigningDetails().getSignatures())
+ != PackageManager.SIGNATURE_MATCH) {
+ if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
+ // Mismatched signatures is an error and silently skipping system
+ // packages will likely break the device in unforeseen ways.
+ // However, we allow the device to boot anyway because, prior to Q,
+ // vendors were not expecting the platform to crash in this
+ // situation.
+ // This WILL be a hard failure on any new API levels after Q.
+ throw new ReconcileFailure(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ "Signature mismatch for shared user: "
+ + scanResult.mPkgSetting.getSharedUser());
+ } else {
+ // Treat mismatched signatures on system packages using a shared
+ // UID as
+ // fatal for the system overall, rather than just failing to install
+ // whichever package happened to be scanned later.
+ throw new IllegalStateException(
+ "Signature mismatch on system package "
+ + parsedPackage.getPackageName()
+ + " for shared user "
+ + scanResult.mPkgSetting.getSharedUser());
+ }
+ }
+
+ sharedUserSignaturesChanged = true;
+ signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+ parsedPackage.getSigningDetails();
+ signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
+ }
+ // File a report about this.
+ String msg = "System package " + parsedPackage.getPackageName()
+ + " signature changed; retaining data.";
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ } catch (IllegalArgumentException e) {
+ // should never happen: certs matched when checking, but not when comparing
+ // old to new for sharedUser
+ throw new RuntimeException(
+ "Signing certificates comparison made on incomparable signing details"
+ + " but somehow passed verifySignatures!", e);
+ }
+ }
+
+ result.put(installPackageName,
+ new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
+ res, request.mPreparedPackages.get(installPackageName), scanResult,
+ deletePackageAction, allowedSharedLibInfos, signingDetails,
+ sharedUserSignaturesChanged, removeAppKeySetData));
+ }
+
+ for (String installPackageName : scannedPackages.keySet()) {
+ // Check all shared libraries and map to their actual file path.
+ // We only do this here for apps not on a system dir, because those
+ // are the only ones that can fail an install due to this. We
+ // will take care of the system apps by updating all of their
+ // library paths after the scan is done. Also during the initial
+ // scan don't update any libs as we do this wholesale after all
+ // apps are scanned to avoid dependency based scanning.
+ final ScanResult scanResult = scannedPackages.get(installPackageName);
+ if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
+ || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+ != 0) {
+ continue;
+ }
+ try {
+ result.get(installPackageName).mCollectedSharedLibraryInfos =
+ SharedLibraryHelper.collectSharedLibraryInfos(
+ scanResult.mRequest.mParsedPackage,
+ combinedPackages, request.mSharedLibrarySource,
+ incomingSharedLibraries, injector.getCompatibility());
+
+ } catch (PackageManagerException e) {
+ throw new ReconcileFailure(e.error, e.getMessage());
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Commits the package scan and modifies system state.
+ * <p><em>WARNING:</em> The method may throw an exception in the middle
+ * of committing the package, leaving the system in an inconsistent state.
+ * This needs to be fixed so, once we get to this point, no errors are
+ * possible and the system is not left in an inconsistent state.
+ */
+ @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+ public AndroidPackage commitReconciledScanResultLocked(
+ @NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
+ final ScanResult result = reconciledPkg.mScanResult;
+ final ScanRequest request = result.mRequest;
+ // TODO(b/135203078): Move this even further away
+ ParsedPackage parsedPackage = request.mParsedPackage;
+ if ("android".equals(parsedPackage.getPackageName())) {
+ // TODO(b/135203078): Move this to initial parse
+ parsedPackage.setVersionCode(mPm.getSdkVersion())
+ .setVersionCodeMajor(0);
+ }
+
+ final AndroidPackage oldPkg = request.mOldPkg;
+ final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
+ final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
+ final PackageSetting oldPkgSetting = request.mOldPkgSetting;
+ final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
+ final UserHandle user = request.mUser;
+ final String realPkgName = request.mRealPkgName;
+ final List<String> changedAbiCodePath = result.mChangedAbiCodePath;
+ final PackageSetting pkgSetting;
+ if (request.mPkgSetting != null && request.mPkgSetting.getSharedUser() != null
+ && request.mPkgSetting.getSharedUser() != result.mPkgSetting.getSharedUser()) {
+ // shared user changed, remove from old shared user
+ request.mPkgSetting.getSharedUser().removePackage(request.mPkgSetting);
+ }
+ if (result.mExistingSettingCopied) {
+ pkgSetting = request.mPkgSetting;
+ pkgSetting.updateFrom(result.mPkgSetting);
+ } else {
+ pkgSetting = result.mPkgSetting;
+ if (originalPkgSetting != null) {
+ mPm.mSettings.addRenamedPackageLPw(
+ AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
+ originalPkgSetting.getPackageName());
+ mPm.mTransferredPackages.add(originalPkgSetting.getPackageName());
+ } else {
+ mPm.mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
+ }
+ }
+ if (pkgSetting.getSharedUser() != null) {
+ pkgSetting.getSharedUser().addPackage(pkgSetting);
+ }
+ if (reconciledPkg.mInstallArgs != null
+ && reconciledPkg.mInstallArgs.mForceQueryableOverride) {
+ pkgSetting.setForceQueryableOverride(true);
+ }
+
+ // If this is part of a standard install, set the initiating package name, else rely on
+ // previous device state.
+ if (reconciledPkg.mInstallArgs != null) {
+ InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource;
+ if (installSource.initiatingPackageName != null) {
+ final PackageSetting ips = mPm.mSettings.getPackageLPr(
+ installSource.initiatingPackageName);
+ if (ips != null) {
+ installSource = installSource.setInitiatingPackageSignatures(
+ ips.getSignatures());
+ }
+ }
+ pkgSetting.setInstallSource(installSource);
+ }
+
+ // TODO(toddke): Consider a method specifically for modifying the Package object
+ // post scan; or, moving this stuff out of the Package object since it has nothing
+ // to do with the package on disk.
+ // We need to have this here because addUserToSettingLPw() is sometimes responsible
+ // for creating the application ID. If we did this earlier, we would be saving the
+ // correct ID.
+ parsedPackage.setUid(pkgSetting.getAppId());
+ final AndroidPackage pkg = parsedPackage.hideAsFinal();
+
+ mPm.mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting);
+
+ if (realPkgName != null) {
+ mPm.mTransferredPackages.add(pkg.getPackageName());
+ }
+
+ if (reconciledPkg.mCollectedSharedLibraryInfos != null) {
+ mPm.executeSharedLibrariesUpdateLPr(pkg, pkgSetting, null, null,
+ reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
+ }
+
+ final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+ if (reconciledPkg.mRemoveAppKeySetData) {
+ ksms.removeAppKeySetDataLPw(pkg.getPackageName());
+ }
+ if (reconciledPkg.mSharedUserSignaturesChanged) {
+ pkgSetting.getSharedUser().signaturesChanged = Boolean.TRUE;
+ pkgSetting.getSharedUser().signatures.mSigningDetails = reconciledPkg.mSigningDetails;
+ }
+ pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
+
+ if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
+ for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
+ final String codePathString = changedAbiCodePath.get(i);
+ try {
+ mPm.mInstaller.rmdex(codePathString,
+ getDexCodeInstructionSet(getPreferredInstructionSet()));
+ } catch (Installer.InstallerException ignored) {
+ }
+ }
+ }
+
+ final int userId = user == null ? 0 : user.getIdentifier();
+ // Modify state for the given package setting
+ commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
+ (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
+ if (pkgSetting.getInstantApp(userId)) {
+ mPm.mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
+ }
+ pkgSetting.setStatesOnCommit();
+
+ return pkg;
+ }
+
+ /**
+ * Adds a scanned package to the system. When this method is finished, the package will
+ * be available for query, resolution, etc...
+ */
+ private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+ @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
+ final @PackageManagerService.ScanFlags int scanFlags, boolean chatty,
+ ReconciledPackage reconciledPkg) {
+ final String pkgName = pkg.getPackageName();
+ if (mPm.mCustomResolverComponentName != null
+ && mPm.mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
+ mPm.setUpCustomResolverActivity(pkg, pkgSetting);
+ }
+
+ if (pkg.getPackageName().equals("android")) {
+ mPm.setPlatformPackage(pkg, pkgSetting);
+ }
+
+ ArrayList<AndroidPackage> clientLibPkgs = null;
+ // writer
+ synchronized (mPm.mLock) {
+ if (!ArrayUtils.isEmpty(reconciledPkg.mAllowedSharedLibraryInfos)) {
+ for (SharedLibraryInfo info : reconciledPkg.mAllowedSharedLibraryInfos) {
+ mPm.commitSharedLibraryInfoLocked(info);
+ }
+ final Map<String, AndroidPackage> combinedSigningDetails =
+ reconciledPkg.getCombinedAvailablePackages();
+ try {
+ // Shared libraries for the package need to be updated.
+ mPm.updateSharedLibrariesLocked(pkg, pkgSetting, null, null,
+ combinedSigningDetails);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e);
+ }
+ // Update all applications that use this library. Skip when booting
+ // since this will be done after all packages are scaned.
+ if ((scanFlags & SCAN_BOOTING) == 0) {
+ clientLibPkgs = mPm.updateAllSharedLibrariesLocked(pkg, pkgSetting,
+ combinedSigningDetails);
+ }
+ }
+ }
+ if (reconciledPkg.mInstallResult != null) {
+ reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs;
+ }
+
+ if ((scanFlags & SCAN_BOOTING) != 0) {
+ // No apps can run during boot scan, so they don't need to be frozen
+ } else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) {
+ // Caller asked to not kill app, so it's probably not frozen
+ } else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) {
+ // Caller asked us to ignore frozen check for some reason; they
+ // probably didn't know the package name
+ } else {
+ // We're doing major surgery on this package, so it better be frozen
+ // right now to keep it from launching
+ mPm.checkPackageFrozen(pkgName);
+ }
+
+ // Also need to kill any apps that are dependent on the library.
+ if (clientLibPkgs != null) {
+ for (int i = 0; i < clientLibPkgs.size(); i++) {
+ AndroidPackage clientPkg = clientLibPkgs.get(i);
+ mPm.killApplication(clientPkg.getPackageName(),
+ clientPkg.getUid(), "update lib");
+ }
+ }
+
+ // writer
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
+
+ synchronized (mPm.mLock) {
+ // We don't expect installation to fail beyond this point
+ // Add the new setting to mSettings
+ mPm.mSettings.insertPackageSettingLPw(pkgSetting, pkg);
+ // Add the new setting to mPackages
+ mPm.mPackages.put(pkg.getPackageName(), pkg);
+ if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+ mPm.mApexManager.registerApkInApex(pkg);
+ }
+
+ // Add the package's KeySets to the global KeySetManagerService
+ KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+ ksms.addScannedPackageLPw(pkg);
+
+ mPm.mComponentResolver.addAllComponents(pkg, chatty);
+ final boolean isReplace =
+ reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
+ mPm.mAppsFilter.addPackage(pkgSetting, isReplace);
+ mPm.addAllPackageProperties(pkg);
+
+ if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
+ mPm.mDomainVerificationManager.addPackage(pkgSetting);
+ } else {
+ mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+ }
+
+ int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
+ StringBuilder r = null;
+ int i;
+ for (i = 0; i < collectionSize; i++) {
+ ParsedInstrumentation a = pkg.getInstrumentations().get(i);
+ ComponentMutateUtils.setPackageName(a, pkg.getPackageName());
+ mPm.addInstrumentation(a.getComponentName(), a);
+ if (chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(a.getName());
+ }
+ }
+ if (r != null) {
+ if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Instrumentation: " + r);
+ }
+
+ final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
+ if (!protectedBroadcasts.isEmpty()) {
+ synchronized (mPm.mProtectedBroadcasts) {
+ mPm.mProtectedBroadcasts.addAll(protectedBroadcasts);
+ }
+ }
+
+ mPm.mPermissionManager.onPackageAdded(pkg,
+ (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
+ }
+
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ /**
+ * If the database version for this type of package (internal storage or
+ * external storage) is less than the version where package signatures
+ * were updated, return true.
+ */
+ public boolean isCompatSignatureUpdateNeeded(AndroidPackage pkg) {
+ return isCompatSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
+ }
+
+ public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
+ return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
+ }
+
+ public boolean isRecoverSignatureUpdateNeeded(AndroidPackage pkg) {
+ return isRecoverSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
+ }
+
+ public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
+ return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
+ }
+
+ public int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
+ @PackageManager.InstallFlags int installFlags,
+ @PackageManager.InstallReason int installReason,
+ @Nullable List<String> allowlistedRestrictedPermissions,
+ @Nullable IntentSender intentSender) {
+ if (DEBUG_INSTALL) {
+ Log.v(TAG, "installExistingPackageAsUser package=" + packageName + " userId=" + userId
+ + " installFlags=" + installFlags + " installReason=" + installReason
+ + " allowlistedRestrictedPermissions=" + allowlistedRestrictedPermissions);
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ if (mPm.mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED
+ && mPm.mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.INSTALL_EXISTING_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Neither user " + callingUid + " nor current process has "
+ + android.Manifest.permission.INSTALL_PACKAGES + ".");
+ }
+ PackageSetting pkgSetting;
+ mPm.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+ true /* checkShell */, "installExistingPackage for user " + userId);
+ if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
+ return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
+ }
+
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ boolean installed = false;
+ final boolean instantApp =
+ (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ final boolean fullApp =
+ (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+
+ // writer
+ synchronized (mPm.mLock) {
+ pkgSetting = mPm.mSettings.getPackageLPr(packageName);
+ if (pkgSetting == null) {
+ return PackageManager.INSTALL_FAILED_INVALID_URI;
+ }
+ if (!mPm.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
+ // only allow the existing package to be used if it's installed as a full
+ // application for at least one user
+ boolean installAllowed = false;
+ for (int checkUserId : mPm.mUserManager.getUserIds()) {
+ installAllowed = !pkgSetting.getInstantApp(checkUserId);
+ if (installAllowed) {
+ break;
+ }
+ }
+ if (!installAllowed) {
+ return PackageManager.INSTALL_FAILED_INVALID_URI;
+ }
+ }
+ if (!pkgSetting.getInstalled(userId)) {
+ pkgSetting.setInstalled(true, userId);
+ pkgSetting.setHidden(false, userId);
+ pkgSetting.setInstallReason(installReason, userId);
+ pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId);
+ mPm.mSettings.writePackageRestrictionsLPr(userId);
+ mPm.mSettings.writeKernelMappingLPr(pkgSetting);
+ installed = true;
+ } else if (fullApp && pkgSetting.getInstantApp(userId)) {
+ // upgrade app from instant to full; we don't allow app downgrade
+ installed = true;
+ }
+ mPm.setInstantAppForUser(mInjector, pkgSetting, userId, instantApp, fullApp);
+ }
+
+ if (installed) {
+ if (pkgSetting.getPkg() != null) {
+ final PermissionManagerServiceInternal.PackageInstalledParams.Builder
+ permissionParamsBuilder =
+ new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
+ if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS)
+ != 0) {
+ permissionParamsBuilder.setAllowlistedRestrictedPermissions(
+ pkgSetting.getPkg().getRequestedPermissions());
+ }
+ mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
+ Process.INVALID_UID /* previousAppId */,
+ permissionParamsBuilder.build(), userId);
+ }
+
+ if (pkgSetting.getPkg() != null) {
+ synchronized (mPm.mInstallLock) {
+ // We don't need to freeze for a brand new install
+ mAppDataHelper.prepareAppDataAfterInstallLIF(pkgSetting.getPkg());
+ }
+ }
+ mPm.sendPackageAddedForUser(packageName, pkgSetting, userId, DataLoaderType.NONE);
+ synchronized (mPm.mLock) {
+ mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId });
+ }
+ // start async restore with no post-install since we finish install here
+ PackageInstalledInfo res = new PackageInstalledInfo(
+ PackageManager.INSTALL_SUCCEEDED);
+ res.mPkg = pkgSetting.getPkg();
+ res.mNewUsers = new int[]{ userId };
+
+ PostInstallData postInstallData =
+ new PostInstallData(null, res, () -> {
+ mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
+ userId);
+ if (intentSender != null) {
+ onRestoreComplete(res.mReturnCode, mPm.mContext, intentSender);
+ }
+ });
+ restoreAndPostInstall(userId, res, postInstallData);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+
+ return PackageManager.INSTALL_SUCCEEDED;
+ }
+
+ private static void onRestoreComplete(int returnCode, Context context, IntentSender target) {
+ Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
+ PackageManager.installStatusToPublicStatus(returnCode));
+ try {
+ target.sendIntent(context, 0, fillIn, null, null);
+ } catch (IntentSender.SendIntentException ignored) {
+ }
+ }
+
+ /** @param data Post-install is performed only if this is non-null. */
+ public void restoreAndPostInstall(
+ int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
+ if (DEBUG_INSTALL) {
+ Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg);
+ }
+
+ // A restore should be requested at this point if (a) the install
+ // succeeded, (b) the operation is not an update.
+ final boolean update = res.mRemovedInfo != null
+ && res.mRemovedInfo.mRemovedPackage != null;
+ boolean doRestore = !update && res.mPkg != null;
+
+ // Set up the post-install work request bookkeeping. This will be used
+ // and cleaned up by the post-install event handling regardless of whether
+ // there's a restore pass performed. Token values are >= 1.
+ int token;
+ if (mPm.mNextInstallToken < 0) mPm.mNextInstallToken = 1;
+ token = mPm.mNextInstallToken++;
+ if (data != null) {
+ mPm.mRunningInstalls.put(token, data);
+ } else if (DEBUG_INSTALL) {
+ Log.v(TAG, "No post-install required for " + token);
+ }
+
+ if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
+
+ if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+ // Pass responsibility to the Backup Manager. It will perform a
+ // restore if appropriate, then pass responsibility back to the
+ // Package Manager to run the post-install observer callbacks
+ // and broadcasts.
+ if (res.mFreezer != null) {
+ res.mFreezer.close();
+ }
+ doRestore = performBackupManagerRestore(userId, token, res);
+ }
+
+ // If this is an update to a package that might be potentially downgraded, then we
+ // need to check with the rollback manager whether there's any userdata that might
+ // need to be snapshotted or restored for the package.
+ //
+ // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+ if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+ doRestore = performRollbackManagerRestore(userId, token, res, data);
+ }
+
+ if (!doRestore) {
+ // No restore possible, or the Backup Manager was mysteriously not
+ // available -- just fire the post-install work request directly.
+ if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
+
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
+
+ Message msg = mPm.mHandler.obtainMessage(POST_INSTALL, token, 0);
+ mPm.mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
+ * Perform Backup Manager restore for a given {@link PackageInstalledInfo}.
+ * Returns whether the restore successfully completed.
+ */
+ private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (bm != null) {
+ // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
+ // in the BackupManager. USER_ALL is used in compatibility tests.
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ }
+ if (DEBUG_INSTALL) {
+ Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId);
+ }
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
+ try {
+ if (bm.isUserReadyForBackup(userId)) {
+ bm.restoreAtInstallForUser(
+ userId, res.mPkg.getPackageName(), token);
+ } else {
+ Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
+ + "didn't take place.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ // can't happen; the backup manager is local
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception trying to enqueue restore", e);
+ return false;
+ }
+ } else {
+ Slog.e(TAG, "Backup Manager not found!");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}.
+ * Returns whether the restore successfully completed.
+ */
+ private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
+ PostInstallData data) {
+ RollbackManagerInternal rm = mInjector.getLocalService(RollbackManagerInternal.class);
+
+ final String packageName = res.mPkg.getPackageName();
+ final int[] allUsers = mPm.mUserManager.getUserIds();
+ final int[] installedUsers;
+
+ final PackageSetting ps;
+ int appId = -1;
+ long ceDataInode = -1;
+ synchronized (mPm.mLock) {
+ ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps != null) {
+ appId = ps.getAppId();
+ ceDataInode = ps.getCeDataInode(userId);
+ }
+
+ // NOTE: We ignore the user specified in the InstallParam because we know this is
+ // an update, and hence need to restore data for all installed users.
+ installedUsers = ps.queryInstalledUsers(allUsers, true);
+ }
+
+ boolean doSnapshotOrRestore = data != null && data.args != null
+ && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+ || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
+
+ if (ps != null && doSnapshotOrRestore) {
+ final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
+ rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
+ appId, ceDataInode, seInfo, token);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index 5a18d40..bfb5f76 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -87,6 +87,7 @@
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ParsedPermission;
import android.content.pm.parsing.component.ParsedPermissionGroup;
import android.content.pm.parsing.result.ParseResult;
@@ -163,6 +164,7 @@
final int mDataLoaderType;
final long mRequiredInstalledVersionCode;
final PackageLite mPackageLite;
+ final InstallPackageHelper mInstallPackageHelper;
InstallParams(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
@@ -187,6 +189,7 @@
mDataLoaderType = DataLoaderType.NONE;
mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
mPackageLite = packageLite;
+ mInstallPackageHelper = new InstallPackageHelper(mPm);
}
InstallParams(File stagedDir, IPackageInstallObserver2 observer,
@@ -213,6 +216,7 @@
? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
mRequiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
mPackageLite = packageLite;
+ mInstallPackageHelper = new InstallPackageHelper(mPm);
}
@Override
@@ -463,7 +467,7 @@
}
}
for (InstallRequest request : apkInstallRequests) {
- mPm.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
+ mInstallPackageHelper.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
request.mInstallResult,
new PostInstallData(request.mArgs,
request.mInstallResult, null));
@@ -624,7 +628,7 @@
Map<String, ReconciledPackage> reconciledPackages;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
- reconciledPackages = mPm.reconcilePackagesLocked(
+ reconciledPackages = mInstallPackageHelper.reconcilePackagesLocked(
reconcileRequest, mPm.mSettings.getKeySetManagerService(),
mPm.mInjector);
} catch (ReconcileFailure e) {
@@ -730,7 +734,7 @@
// Retrieve PackageSettings and parse package
@ParsingPackageUtils.ParseFlags final int parseFlags =
- mPm.mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY
+ mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
| ParsingPackageUtils.PARSE_ENFORCE_CODE
| (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
@@ -882,10 +886,10 @@
}
} else {
try {
- final boolean compareCompat = mPm.isCompatSignatureUpdateNeeded(
- parsedPackage);
- final boolean compareRecover = mPm.isRecoverSignatureUpdateNeeded(
- parsedPackage);
+ final boolean compareCompat =
+ mInstallPackageHelper.isCompatSignatureUpdateNeeded(parsedPackage);
+ final boolean compareRecover =
+ mInstallPackageHelper.isRecoverSignatureUpdateNeeded(parsedPackage);
// We don't care about disabledPkgSetting on install for now.
final boolean compatMatch = verifySignatures(signatureCheckPs, null,
parsedPackage.getSigningDetails(), compareCompat, compareRecover,
@@ -945,7 +949,7 @@
Slog.w(TAG, "Non-System package " + parsedPackage.getPackageName()
+ " attempting to delcare ephemeral permission "
+ perm.getName() + "; Removing ephemeral.");
- perm.setProtectionLevel(
+ ComponentMutateUtils.setProtectionLevel(perm,
perm.getProtectionLevel() & ~PermissionInfo.PROTECTION_FLAG_INSTANT);
}
@@ -985,7 +989,8 @@
+ " trying to change a non-runtime permission "
+ perm.getName()
+ " to runtime; keeping old protection level");
- perm.setProtectionLevel(bp.getProtectionLevel());
+ ComponentMutateUtils.setProtectionLevel(perm,
+ bp.getProtectionLevel());
}
}
}
@@ -1561,13 +1566,13 @@
// We didn't need to disable the .apk as a current system package,
// which means we are replacing another update that is already
// installed. We need to make sure to delete the older one's .apk.
- res.mRemovedInfo.mArgs = mPm.createInstallArgsForExisting(
+ res.mRemovedInfo.mArgs = new FileInstallArgs(
oldPackage.getPath(),
getAppDexInstructionSets(
AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
deletedPkgSetting),
AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
- deletedPkgSetting)));
+ deletedPkgSetting)), mPm);
} else {
res.mRemovedInfo.mArgs = null;
}
@@ -1627,8 +1632,8 @@
}
}
- AndroidPackage pkg = mPm.commitReconciledScanResultLocked(reconciledPkg,
- request.mAllUsers);
+ AndroidPackage pkg = mInstallPackageHelper.commitReconciledScanResultLocked(
+ reconciledPkg, request.mAllUsers);
updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res);
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
@@ -1973,7 +1978,7 @@
// If this is an update of a package which used to fail to compile,
// BackgroundDexOptService will remove it from its denylist.
// TODO: Layering violation
- BackgroundDexOptService.notifyPackageChanged(packageName);
+ BackgroundDexOptService.getService().notifyPackageChanged(packageName);
notifyPackageChangeObserversOnUpdate(reconciledPkg);
}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java
deleted file mode 100644
index 399b03c..0000000
--- a/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import java.util.Arrays;
-
-/**
- * This is the key for the map of {@link android.content.pm.IntentFilterVerificationInfo}s
- * maintained by the {@link com.android.server.pm.PackageManagerService}
- */
-class IntentFilterVerificationKey {
- public String domains;
- public String packageName;
- public String className;
-
- public IntentFilterVerificationKey(String[] domains, String packageName, String className) {
- StringBuilder sb = new StringBuilder();
- for (String host : domains) {
- sb.append(host);
- }
- this.domains = sb.toString();
- this.packageName = packageName;
- this.className = className;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- IntentFilterVerificationKey that = (IntentFilterVerificationKey) o;
-
- if (domains != null ? !domains.equals(that.domains) : that.domains != null) return false;
- if (className != null ? !className.equals(that.className) : that.className != null)
- return false;
- if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null)
- return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = domains != null ? domains.hashCode() : 0;
- result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
- result = 31 * result + (className != null ? className.hashCode() : 0);
- return result;
- }
-}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java
deleted file mode 100644
index ead399b..0000000
--- a/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-
-import java.util.List;
-
-/* package private */ class IntentFilterVerificationResponse {
- public final int callerUid;
- public final int code;
- public final List<String> failedDomains;
-
- public IntentFilterVerificationResponse(int callerUid, int code, List<String> failedDomains) {
- this.callerUid = callerUid;
- this.code = code;
- this.failedDomains = failedDomains;
- }
-
- public String getFailedDomainsString() {
- StringBuilder sb = new StringBuilder();
- for (String domain : failedDomains) {
- if (sb.length() > 0) {
- sb.append(" ");
- }
- sb.append(domain);
- }
- return sb.toString();
- }
-}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
deleted file mode 100644
index 9dc545a..0000000
--- a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.pm.PackageManager;
-import android.content.pm.parsing.component.ParsedIntentInfo;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import java.util.ArrayList;
-
-public class IntentFilterVerificationState {
- static final String TAG = IntentFilterVerificationState.class.getName();
-
- public final static int STATE_UNDEFINED = 0;
- public final static int STATE_VERIFICATION_PENDING = 1;
- public final static int STATE_VERIFICATION_SUCCESS = 2;
- public final static int STATE_VERIFICATION_FAILURE = 3;
-
- private int mRequiredVerifierUid = 0;
-
- private int mState;
-
- private ArrayList<ParsedIntentInfo> mFilters = new ArrayList<>();
- private ArraySet<String> mHosts = new ArraySet<>();
- private int mUserId;
-
- private String mPackageName;
- private boolean mVerificationComplete;
-
- public IntentFilterVerificationState(int verifierUid, int userId, String packageName) {
- mRequiredVerifierUid = verifierUid;
- mUserId = userId;
- mPackageName = packageName;
- mState = STATE_UNDEFINED;
- mVerificationComplete = false;
- }
-
- public void setState(int state) {
- if (state > STATE_VERIFICATION_FAILURE || state < STATE_UNDEFINED) {
- mState = STATE_UNDEFINED;
- } else {
- mState = state;
- }
- }
-
- public int getState() {
- return mState;
- }
-
- public void setPendingState() {
- setState(STATE_VERIFICATION_PENDING);
- }
-
- public ArrayList<ParsedIntentInfo> getFilters() {
- return mFilters;
- }
-
- public boolean isVerificationComplete() {
- return mVerificationComplete;
- }
-
- public boolean isVerified() {
- if (mVerificationComplete) {
- return (mState == STATE_VERIFICATION_SUCCESS);
- }
- return false;
- }
-
- public int getUserId() {
- return mUserId;
- }
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public String getHostsString() {
- StringBuilder sb = new StringBuilder();
- final int count = mHosts.size();
- for (int i=0; i<count; i++) {
- if (i > 0) {
- sb.append(" ");
- }
- String host = mHosts.valueAt(i);
- // "*.example.tld" is validated via https://example.tld
- if (host.startsWith("*.")) {
- host = host.substring(2);
- }
- sb.append(host);
- }
- return sb.toString();
- }
-
- public boolean setVerifierResponse(int callerUid, int code) {
- if (mRequiredVerifierUid == callerUid) {
- int state = STATE_UNDEFINED;
- if (code == PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS) {
- state = STATE_VERIFICATION_SUCCESS;
- } else if (code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
- state = STATE_VERIFICATION_FAILURE;
- }
- mVerificationComplete = true;
- setState(state);
- return true;
- }
- Slog.d(TAG, "Cannot set verifier response with callerUid:" + callerUid + " and code:" +
- code + " as required verifierUid is:" + mRequiredVerifierUid);
- return false;
- }
-
- public void addFilter(ParsedIntentInfo filter) {
- mFilters.add(filter);
- mHosts.addAll(filter.getHostsList());
- }
-}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8721603..9c11cd4 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -746,11 +746,15 @@
@Override
public Bundle getSuspendedPackageLauncherExtras(String packageName,
UserHandle user) {
- if (!canAccessProfile(user.getIdentifier(), "Cannot get launcher extras")) {
+ final int callingUid = injectBinderCallingUid();
+ final int userId = user.getIdentifier();
+ if (!canAccessProfile(userId, "Cannot get launcher extras")) {
return null;
}
- return mPackageManagerInternal.getSuspendedPackageLauncherExtras(packageName,
- user.getIdentifier());
+ if (mPackageManagerInternal.filterAppAccess(packageName, callingUid, userId)) {
+ return null;
+ }
+ return mPackageManagerInternal.getSuspendedPackageLauncherExtras(packageName, userId);
}
@Override
@@ -1259,7 +1263,7 @@
/** Returns whether or not the given appId is in allow list */
private static boolean isCallingAppIdAllowed(int[] appIdAllowList, @AppIdInt int appId) {
- if (appIdAllowList == null) {
+ if (appIdAllowList == null || appId < Process.FIRST_APPLICATION_UID) {
return true;
}
return Arrays.binarySearch(appIdAllowList, appId) > -1;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 9595e15..68801d6 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -128,7 +128,7 @@
PLATFORM_PACKAGE_NAME.equals(pkgSetting.getPkg().getPackageName());
synchronized (mPackageManagerService.mLock) {
// Important: the packages we need to run with ab-ota compiler-reason.
- important = PackageManagerServiceUtils.getPackagesForDexopt(
+ important = DexOptHelper.getPackagesForDexopt(
mPackageManagerService.mSettings.getPackagesLocked().values(),
mPackageManagerService, DEBUG_DEXOPT);
// Remove Platform Package from A/B OTA b/160735835.
@@ -160,7 +160,7 @@
long spaceAvailable = getAvailableSpace();
if (spaceAvailable < BULK_DELETE_THRESHOLD) {
Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
- + PackageManagerServiceUtils.packagesToString(others));
+ + DexOptHelper.packagesToString(others));
for (PackageSetting pkg : others) {
mPackageManagerService.deleteOatArtifactsOfPackage(pkg.getPackageName());
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 449cfe2..2595bb4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -688,11 +688,6 @@
if (params.isMultiPackage) {
throw new IllegalArgumentException("A multi-session can't be set as APEX.");
}
- if (!params.isStaged
- && (params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
- throw new IllegalArgumentException(
- "Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK");
- }
if (isCalledBySystemOrShell(callingUid) || mBypassNextAllowedApexUpdateCheck) {
params.installFlags |= PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
} else {
@@ -1120,9 +1115,10 @@
@Override
public void installExistingPackage(String packageName, int installFlags, int installReason,
- IntentSender statusReceiver, int userId, List<String> whiteListedPermissions) {
- mPm.installExistingPackageAsUser(packageName, userId, installFlags, installReason,
- whiteListedPermissions, statusReceiver);
+ IntentSender statusReceiver, int userId, List<String> allowListedPermissions) {
+ final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
+ installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+ installReason, allowListedPermissions, statusReceiver);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 020c23d..3805977 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2406,13 +2406,6 @@
final VerificationParams verifyingSession = prepareForVerification();
if (isMultiPackage()) {
final List<PackageInstallerSession> childSessions = getChildSessions();
- // Spot check to reject a non-staged multi package install of APEXes and APKs.
- if (!params.isStaged && containsApkSession()
- && sessionContains(s -> s.isApexSession())) {
- throw new PackageManagerException(
- PackageManager.INSTALL_FAILED_SESSION_INVALID,
- "Non-staged multi package install of APEX and APK packages is not supported");
- }
List<VerificationParams> verifyingChildSessions =
new ArrayList<>(childSessions.size());
boolean success = true;
@@ -4172,6 +4165,12 @@
+ childSession.sessionId + " and session " + sessionId
+ " have inconsistent rollback settings");
}
+ boolean hasAPK = containsApkSession() || !childSession.isApexSession();
+ boolean hasAPEX = sessionContains(s -> s.isApexSession()) || childSession.isApexSession();
+ if (!params.isStaged && hasAPK && hasAPEX) {
+ throw new IllegalStateException("Mix of APK and APEX is not supported for "
+ + "non-staged multi-package session");
+ }
try {
acquireTransactionLock();
diff --git a/services/core/java/com/android/server/pm/PackageManagerNative.java b/services/core/java/com/android/server/pm/PackageManagerNative.java
new file mode 100644
index 0000000..37daf11
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerNative.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
+
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageChangeObserver;
+import android.content.pm.IPackageManagerNative;
+import android.content.pm.IStagedApexObserver;
+import android.content.pm.PackageInfo;
+import android.content.pm.StagedApexInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.Arrays;
+
+final class PackageManagerNative extends IPackageManagerNative.Stub {
+ private final PackageManagerService mPm;
+
+ PackageManagerNative(PackageManagerService pm) {
+ mPm = pm;
+ }
+
+ @Override
+ public void registerPackageChangeObserver(@NonNull IPackageChangeObserver observer) {
+ synchronized (mPm.mPackageChangeObservers) {
+ try {
+ observer.asBinder().linkToDeath(
+ new PackageChangeObserverDeathRecipient(observer), 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ mPm.mPackageChangeObservers.add(observer);
+ Log.d(TAG, "Size of mPackageChangeObservers after registry is "
+ + mPm.mPackageChangeObservers.size());
+ }
+ }
+
+ @Override
+ public void unregisterPackageChangeObserver(@NonNull IPackageChangeObserver observer) {
+ synchronized (mPm.mPackageChangeObservers) {
+ mPm.mPackageChangeObservers.remove(observer);
+ Log.d(TAG, "Size of mPackageChangeObservers after unregistry is "
+ + mPm.mPackageChangeObservers.size());
+ }
+ }
+
+ @Override
+ public String[] getAllPackages() {
+ return mPm.getAllPackages().toArray(new String[0]);
+ }
+
+ @Override
+ public String[] getNamesForUids(int[] uids) throws RemoteException {
+ String[] names = null;
+ String[] results = null;
+ try {
+ if (uids == null || uids.length == 0) {
+ return null;
+ }
+ names = mPm.getNamesForUids(uids);
+ results = (names != null) ? names : new String[uids.length];
+ // massage results so they can be parsed by the native binder
+ for (int i = results.length - 1; i >= 0; --i) {
+ if (results[i] == null) {
+ results[i] = "";
+ }
+ }
+ return results;
+ } catch (Throwable t) {
+ // STOPSHIP(186558987): revert addition of try/catch/log
+ Slog.e(TAG, "uids: " + Arrays.toString(uids));
+ Slog.e(TAG, "names: " + Arrays.toString(names));
+ Slog.e(TAG, "results: " + Arrays.toString(results));
+ Slog.e(TAG, "throwing exception", t);
+ throw t;
+ }
+ }
+
+ // NB: this differentiates between preloads and sideloads
+ @Override
+ public String getInstallerForPackage(String packageName) throws RemoteException {
+ final String installerName = mPm.getInstallerPackageName(packageName);
+ if (!TextUtils.isEmpty(installerName)) {
+ return installerName;
+ }
+ // differentiate between preload and sideload
+ int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageName,
+ /*flags*/ 0,
+ /*userId*/ callingUser);
+ if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return "preload";
+ }
+ return "";
+ }
+
+ @Override
+ public long getVersionCodeForPackage(String packageName) throws RemoteException {
+ try {
+ int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+ PackageInfo pInfo = mPm.getPackageInfo(packageName, 0, callingUser);
+ if (pInfo != null) {
+ return pInfo.getLongVersionCode();
+ }
+ } catch (Exception e) {
+ }
+ return 0;
+ }
+
+ @Override
+ public int getTargetSdkVersionForPackage(String packageName) throws RemoteException {
+ int targetSdk = mPm.getTargetSdkVersion(packageName);
+ if (targetSdk != -1) {
+ return targetSdk;
+ }
+
+ throw new RemoteException("Couldn't get targetSdkVersion for package " + packageName);
+ }
+
+ @Override
+ public boolean isPackageDebuggable(String packageName) throws RemoteException {
+ int callingUser = UserHandle.getCallingUserId();
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0, callingUser);
+ if (appInfo != null) {
+ return (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+ }
+
+ throw new RemoteException("Couldn't get debug flag for package " + packageName);
+ }
+
+ @Override
+ public boolean[] isAudioPlaybackCaptureAllowed(String[] packageNames)
+ throws RemoteException {
+ int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+ boolean[] results = new boolean[packageNames.length];
+ for (int i = results.length - 1; i >= 0; --i) {
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageNames[i], 0, callingUser);
+ results[i] = appInfo != null && appInfo.isAudioPlaybackCaptureAllowed();
+ }
+ return results;
+ }
+
+ @Override
+ public int getLocationFlags(String packageName) throws RemoteException {
+ int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageName,
+ /*flags*/ 0,
+ /*userId*/ callingUser);
+ if (appInfo == null) {
+ throw new RemoteException(
+ "Couldn't get ApplicationInfo for package " + packageName);
+ }
+ return ((appInfo.isSystemApp() ? IPackageManagerNative.LOCATION_SYSTEM : 0)
+ | (appInfo.isVendor() ? IPackageManagerNative.LOCATION_VENDOR : 0)
+ | (appInfo.isProduct() ? IPackageManagerNative.LOCATION_PRODUCT : 0));
+ }
+
+ @Override
+ public String getModuleMetadataPackageName() throws RemoteException {
+ return mPm.getModuleMetadataPackageName();
+ }
+
+ @Override
+ public boolean hasSha256SigningCertificate(String packageName, byte[] certificate)
+ throws RemoteException {
+ return mPm.hasSigningCertificate(packageName, certificate, CERT_INPUT_SHA256);
+ }
+
+ @Override
+ public boolean hasSystemFeature(String featureName, int version) {
+ return mPm.hasSystemFeature(featureName, version);
+ }
+
+ @Override
+ public void registerStagedApexObserver(IStagedApexObserver observer) {
+ mPm.mInstallerService.getStagingManager().registerStagedApexObserver(observer);
+ }
+
+ @Override
+ public void unregisterStagedApexObserver(IStagedApexObserver observer) {
+ mPm.mInstallerService.getStagingManager().unregisterStagedApexObserver(observer);
+ }
+
+ @Override
+ public String[] getStagedApexModuleNames() {
+ return mPm.mInstallerService.getStagingManager()
+ .getStagedApexModuleNames().toArray(new String[0]);
+ }
+
+ @Override
+ @Nullable
+ public StagedApexInfo getStagedApexInfo(String moduleName) {
+ return mPm.mInstallerService.getStagingManager().getStagedApexInfo(moduleName);
+ }
+
+ private final class PackageChangeObserverDeathRecipient implements IBinder.DeathRecipient {
+ private final IPackageChangeObserver mObserver;
+
+ PackageChangeObserverDeathRecipient(IPackageChangeObserver observer) {
+ mObserver = observer;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mPm.mPackageChangeObservers) {
+ mPm.mPackageChangeObservers.remove(mObserver);
+ Log.d(TAG, "Size of mPackageChangeObservers after removing dead observer is "
+ + mPm.mPackageChangeObservers.size());
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2bec8be..3eaa26e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -21,7 +21,6 @@
import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -29,10 +28,6 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
-import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
@@ -45,7 +40,6 @@
import static android.content.pm.PackageManager.TYPE_PROVIDER;
import static android.content.pm.PackageManager.TYPE_RECEIVER;
import static android.content.pm.PackageManager.TYPE_UNKNOWN;
-import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
@@ -54,14 +48,10 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
-import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
-import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import android.Manifest;
import android.annotation.AppIdInt;
@@ -75,10 +65,8 @@
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
-import android.app.PendingIntent;
import android.app.admin.IDevicePolicyManager;
import android.app.admin.SecurityLog;
-import android.app.backup.IBackupManager;
import android.app.role.RoleManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -87,7 +75,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.IIntentReceiver;
-import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -111,10 +98,8 @@
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageLoadingProgressCallback;
import android.content.pm.IPackageManager;
-import android.content.pm.IPackageManagerNative;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
-import android.content.pm.IStagedApexObserver;
import android.content.pm.IncrementalStatesInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.InstantAppInfo;
@@ -148,19 +133,18 @@
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
-import android.content.pm.StagedApexInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
-import android.content.pm.dex.ArtManager;
import android.content.pm.dex.IArtManager;
import android.content.pm.overlay.OverlayPaths;
import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedActivityImpl;
import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedIntentInfo;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.content.pm.parsing.component.ParsedProvider;
import android.content.pm.pkg.PackageUserState;
@@ -205,7 +189,6 @@
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.security.KeyStore;
-import android.service.pm.PackageServiceDumpProto;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
@@ -215,17 +198,13 @@
import android.util.ExceptionUtils;
import android.util.IntArray;
import android.util.Log;
-import android.util.LogPrinter;
-import android.util.PackageUtils;
import android.util.Pair;
-import android.util.PrintStreamPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
-import android.util.proto.ProtoOutputStream;
import android.view.Display;
import com.android.internal.R;
@@ -235,7 +214,6 @@
import com.android.internal.content.F2fsUtils;
import com.android.internal.content.PackageHelper;
import com.android.internal.content.om.OverlayConfig;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.telephony.CarrierAppUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -243,7 +221,6 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FunctionalUtils;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.EventLogTags;
@@ -255,17 +232,13 @@
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
-import com.android.server.apphibernation.AppHibernationService;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
-import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.ArtUtils;
import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.dex.ViewCompiler;
import com.android.server.pm.parsing.PackageCacher;
@@ -285,7 +258,6 @@
import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
-import com.android.server.rollback.RollbackManagerInternal;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.SnapshotCache;
@@ -303,9 +275,6 @@
import libcore.util.EmptyArray;
import libcore.util.HexEncoding;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -315,7 +284,6 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
@@ -324,7 +292,6 @@
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -336,9 +303,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@@ -400,7 +365,6 @@
public static final boolean DEBUG_PACKAGE_SCANNING = false;
static final boolean DEBUG_VERIFY = false;
public static final boolean DEBUG_PERMISSIONS = false;
- private static final boolean DEBUG_SHARED_LIBRARIES = false;
public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
public static final boolean TRACE_SNAPSHOTS = false;
private static final boolean DEBUG_PER_UID_READ_TIMEOUTS = false;
@@ -413,9 +377,6 @@
static final boolean DEBUG_ABI_SELECTION = false;
public static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
- /** REMOVE. According to Svet, this was only used to reset permissions during development. */
- static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
-
static final boolean HIDE_EPHEMERAL_APIS = false;
static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts";
@@ -482,40 +443,40 @@
PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface PackageStartability {}
+ private @interface PackageStartability {}
/**
* Used as the result code of the {@link #getPackageStartability} to indicate
* the given package is allowed to start.
*/
- static final int PACKAGE_STARTABILITY_OK = 0;
+ private static final int PACKAGE_STARTABILITY_OK = 0;
/**
* Used as the result code of the {@link #getPackageStartability} to indicate
* the given package is <b>not</b> allowed to start because it's not found
* (could be due to that package is invisible to the given user).
*/
- static final int PACKAGE_STARTABILITY_NOT_FOUND = 1;
+ private static final int PACKAGE_STARTABILITY_NOT_FOUND = 1;
/**
* Used as the result code of the {@link #getPackageStartability} to indicate
* the given package is <b>not</b> allowed to start because it's not a system app
* and the system is running in safe mode.
*/
- static final int PACKAGE_STARTABILITY_NOT_SYSTEM = 2;
+ private static final int PACKAGE_STARTABILITY_NOT_SYSTEM = 2;
/**
* Used as the result code of the {@link #getPackageStartability} to indicate
* the given package is <b>not</b> allowed to start because it's currently frozen.
*/
- static final int PACKAGE_STARTABILITY_FROZEN = 3;
+ private static final int PACKAGE_STARTABILITY_FROZEN = 3;
/**
* Used as the result code of the {@link #getPackageStartability} to indicate
* the given package is <b>not</b> allowed to start because it doesn't support
* direct boot.
*/
- static final int PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED = 4;
+ private static final int PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED = 4;
private static final String STATIC_SHARED_LIB_DELIMITER = "_";
/** Extension of the compressed packages */
@@ -584,23 +545,6 @@
private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE =
150857253;
- /**
- * Apps targeting Android S and above need to declare dependencies to the public native
- * shared libraries that are defined by the device maker using {@code uses-native-library} tag
- * in its {@code AndroidManifest.xml}.
- *
- * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist,
- * the package manager rejects to install the app. The dependency can be specified as optional
- * using {@code android:required} attribute in the tag, in which case failing to satisfy the
- * dependency doesn't stop the installation.
- * <p>Once installed, an app is provided with only the native shared libraries that are
- * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear
- * in the app manifest will fail even if it actually exists on the device.
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
- private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
-
public static final String PLATFORM_PACKAGE_NAME = "android";
static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
@@ -627,19 +571,6 @@
public static final int REASON_LAST = REASON_SHARED;
- /**
- * The initial enabled state of the cache before other checks are done.
- */
- private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true;
-
- /**
- * Whether to skip all other checks and force the cache to be enabled.
- *
- * Setting this to true will cause the cache to be named "debug" to avoid eviction from
- * build fingerprint changes.
- */
- private static final boolean FORCE_PACKAGE_PARSED_CACHE_ENABLED = false;
-
static final String RANDOM_DIR_PREFIX = "~~";
final Handler mHandler;
@@ -648,20 +579,17 @@
private final boolean mEnableFreeCacheV2;
- final int mSdkVersion;
+ private final int mSdkVersion;
final Context mContext;
final boolean mFactoryTest;
- final boolean mOnlyCore;
+ private final boolean mOnlyCore;
final DisplayMetrics mMetrics;
- final int mDefParseFlags;
- final String[] mSeparateProcesses;
- final boolean mIsUpgrade;
- final boolean mIsPreNUpgrade;
- final boolean mIsPreNMR1Upgrade;
- final boolean mIsPreQUpgrade;
-
- @GuardedBy("mLock")
- private boolean mDexOptDialogShown;
+ private final int mDefParseFlags;
+ private final String[] mSeparateProcesses;
+ private final boolean mIsUpgrade;
+ private final boolean mIsPreNUpgrade;
+ private final boolean mIsPreNMR1Upgrade;
+ private final boolean mIsPreQUpgrade;
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@@ -709,13 +637,6 @@
"PackageManagerService.mIsolatedOwners");
/**
- * Tracks new system packages [received in an OTA] that we expect to
- * find updated user-installed versions. Keys are package name, values
- * are package location.
- */
- final ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
-
- /**
* Tracks existing packages prior to receiving an OTA. Keys are package name.
* Only non-null during an OTA, and even then it is nulled again once systemReady().
*/
@@ -761,7 +682,7 @@
@GuardedBy("mLoadedVolumes")
final ArraySet<String> mLoadedVolumes = new ArraySet<>();
- boolean mFirstBoot;
+ private boolean mFirstBoot;
final boolean mIsEngBuild;
private final boolean mIsUserDebugBuild;
@@ -807,12 +728,10 @@
public static final List<ScanPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
PackagePartitions.getOrderedPartitions(ScanPartition::new));
- private final List<ScanPartition> mDirsToScanAsSystem;
-
- final OverlayConfig mOverlayConfig;
+ private @NonNull final OverlayConfig mOverlayConfig;
@GuardedBy("itself")
- final private ArrayList<IPackageChangeObserver> mPackageChangeObservers =
+ final ArrayList<IPackageChangeObserver> mPackageChangeObservers =
new ArrayList<>();
// Cached parsed flag value. Invalidated on each flag change.
@@ -899,6 +818,7 @@
final ArtManagerService mArtManagerService;
final PackageDexOptimizer mPackageDexOptimizer;
+ final BackgroundDexOptService mBackgroundDexOptService;
// DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
// is used by other apps).
private final DexManager mDexManager;
@@ -918,7 +838,7 @@
int mPendingEnableRollbackToken = 0;
@Watched(manual = true)
- volatile boolean mSystemReady;
+ private volatile boolean mSystemReady;
@Watched(manual = true)
private volatile boolean mSafeMode;
@Watched
@@ -926,16 +846,16 @@
new WatchedSparseBooleanArray();
@Watched(manual = true)
- ApplicationInfo mAndroidApplication;
+ private ApplicationInfo mAndroidApplication;
@Watched(manual = true)
- final ActivityInfo mResolveActivity = new ActivityInfo();
- final ResolveInfo mResolveInfo = new ResolveInfo();
+ private final ActivityInfo mResolveActivity = new ActivityInfo();
+ private final ResolveInfo mResolveInfo = new ResolveInfo();
@Watched(manual = true)
- private ComponentName mResolveComponentName;
- AndroidPackage mPlatformPackage;
+ ComponentName mResolveComponentName;
+ private AndroidPackage mPlatformPackage;
ComponentName mCustomResolverComponentName;
- boolean mResolverReplaced = false;
+ private boolean mResolverReplaced = false;
@NonNull
final DomainVerificationManagerInternal mDomainVerificationManager;
@@ -1022,10 +942,6 @@
final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<>();
int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows
- // XML tags for backup/restore of various bits of state
- private static final String TAG_PREFERRED_BACKUP = "pa";
- private static final String TAG_DEFAULT_APPS = "da";
-
final @Nullable String mRequiredVerifierPackage;
final @NonNull String mRequiredInstallerPackage;
final @NonNull String mRequiredUninstallerPackage;
@@ -1054,6 +970,9 @@
private final DeletePackageHelper mDeletePackageHelper;
private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
private final AppDataHelper mAppDataHelper;
+ private final PreferredActivityHelper mPreferredActivityHelper;
+ private final ResolveIntentHelper mResolveIntentHelper;
+ private final DexOptHelper mDexOptHelper;
/**
* Invalidate the package info cache, which includes updating the cached computer.
@@ -1281,7 +1200,7 @@
* Report a locally-detected change to observers. The <what> parameter is left null,
* but it signifies that the change was detected by PackageManagerService itself.
*/
- private static void onChanged() {
+ static void onChanged() {
onChange(null);
}
@@ -1448,7 +1367,7 @@
scheduleWritePackageRestrictionsLocked(userId);
}
- private void scheduleWritePackageRestrictionsLocked(int userId) {
+ void scheduleWritePackageRestrictionsLocked(int userId) {
invalidatePackageInfoCache();
final int[] userIds = (userId == UserHandle.USER_ALL)
? mUserManager.getUserIds() : new int[]{userId};
@@ -1531,7 +1450,8 @@
},
new DefaultSystemWrapper(),
LocalServices::getService,
- context::getSystemService);
+ context::getSystemService,
+ (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager()));
if (Build.VERSION.SDK_INT <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1580,15 +1500,15 @@
injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_R_CHANGES,
selinuxChangeListener);
- m.installWhitelistedSystemPackages();
+ m.installAllowlistedSystemPackages();
ServiceManager.addService("package", m);
- final PackageManagerNative pmn = m.new PackageManagerNative();
+ final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);
return m;
}
/** Install/uninstall system packages for all users based on their user-type, as applicable. */
- private void installWhitelistedSystemPackages() {
+ private void installAllowlistedSystemPackages() {
synchronized (mLock) {
final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
isFirstBoot(), isDeviceUpgrading(), mExistingPackages);
@@ -1599,39 +1519,6 @@
}
}
- /**
- * Requests that files preopted on a secondary system partition be copied to the data partition
- * if possible. Note that the actual copying of the files is accomplished by init for security
- * reasons. This simply requests that the copy takes place and awaits confirmation of its
- * completion. See platform/system/extras/cppreopt/ for the implementation of the actual copy.
- */
- private static void requestCopyPreoptedFiles() {
- final int WAIT_TIME_MS = 100;
- final String CP_PREOPT_PROPERTY = "sys.cppreopt";
- if (SystemProperties.getInt("ro.cp_system_other_odex", 0) == 1) {
- SystemProperties.set(CP_PREOPT_PROPERTY, "requested");
- // We will wait for up to 100 seconds.
- final long timeStart = SystemClock.uptimeMillis();
- final long timeEnd = timeStart + 100 * 1000;
- long timeNow = timeStart;
- while (!SystemProperties.get(CP_PREOPT_PROPERTY).equals("finished")) {
- try {
- Thread.sleep(WAIT_TIME_MS);
- } catch (InterruptedException e) {
- // Do nothing
- }
- timeNow = SystemClock.uptimeMillis();
- if (timeNow > timeEnd) {
- SystemProperties.set(CP_PREOPT_PROPERTY, "timed-out");
- Slog.wtf(TAG, "cppreopt did not finish!");
- break;
- }
- }
-
- Slog.i(TAG, "cppreopts took " + (timeNow - timeStart) + " ms");
- }
- }
-
// Link watchables to the class
private void registerObserver() {
mPackages.registerObserver(mWatcher);
@@ -1676,11 +1563,11 @@
mApexManager = testParams.apexManager;
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
+ mBackgroundDexOptService = testParams.backgroundDexOptService;
mDefParseFlags = testParams.defParseFlags;
mDefaultAppProvider = testParams.defaultAppProvider;
mLegacyPermissionManager = testParams.legacyPermissionManagerInternal;
mDexManager = testParams.dexManager;
- mDirsToScanAsSystem = testParams.dirsToScanAsSystem;
mFactoryTest = testParams.factoryTest;
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
@@ -1741,13 +1628,14 @@
mIncrementalVersion = testParams.incrementalVersion;
mDomainVerificationConnection = new DomainVerificationConnection(this);
- mBroadcastHelper = new BroadcastHelper(mInjector);
- mAppDataHelper = new AppDataHelper(this);
- mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this, mRemovePackageHelper,
- mAppDataHelper);
- mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
- mInitAndSystemPackageHelper, mAppDataHelper);
+ mBroadcastHelper = testParams.broadcastHelper;
+ mAppDataHelper = testParams.appDataHelper;
+ mRemovePackageHelper = testParams.removePackageHelper;
+ mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper;
+ mDeletePackageHelper = testParams.deletePackageHelper;
+ mPreferredActivityHelper = testParams.preferredActivityHelper;
+ mResolveIntentHelper = testParams.resolveIntentHelper;
+ mDexOptHelper = testParams.dexOptHelper;
invalidatePackageInfoCache();
}
@@ -1852,6 +1740,7 @@
mPackageDexOptimizer = injector.getPackageDexOptimizer();
mDexManager = injector.getDexManager();
+ mBackgroundDexOptService = injector.getBackgroundDexOptService();
mArtManagerService = injector.getArtManagerService();
mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
mViewCompiler = injector.getViewCompiler();
@@ -1869,22 +1758,8 @@
mApexManager = injector.getApexManager();
mAppsFilter = mInjector.getAppsFilter();
- final List<ScanPartition> scanPartitions = new ArrayList<>();
- final List<ApexManager.ActiveApexInfo> activeApexInfos = mApexManager.getActiveApexInfos();
- for (int i = 0; i < activeApexInfos.size(); i++) {
- final ScanPartition scanPartition = resolveApexToScanPartition(activeApexInfos.get(i));
- if (scanPartition != null) {
- scanPartitions.add(scanPartition);
- }
- }
-
mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager, mPmInternal);
- mDirsToScanAsSystem = new ArrayList<>();
- mDirsToScanAsSystem.addAll(injector.getSystemPartitions());
- mDirsToScanAsSystem.addAll(scanPartitions);
- Slog.d(TAG, "Directories scanned as system partitions: " + mDirsToScanAsSystem);
-
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
mAppLib32InstallDir = getAppLib32InstallDir();
@@ -1899,6 +1774,9 @@
mAppDataHelper);
mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
mInitAndSystemPackageHelper, mAppDataHelper);
+ mPreferredActivityHelper = new PreferredActivityHelper(this);
+ mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
+ mDexOptHelper = new DexOptHelper(this);
synchronized (mLock) {
// Create the computer as soon as the state objects have been installed. The
@@ -1963,7 +1841,7 @@
mPermissionManager.readLegacyPermissionStateTEMP();
if (!mOnlyCore && mFirstBoot) {
- requestCopyPreoptedFiles();
+ DexOptHelper.requestCopyPreoptedFiles();
}
String customResolverActivityName = Resources.getSystem().getString(
@@ -2020,40 +1898,12 @@
}
}
- mCacheDir = preparePackageParserCache(mIsEngBuild);
+ mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
+ mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
- // Set flag to monitor and not change apk file paths when
- // scanning install directories.
- int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
-
- if (mIsUpgrade || mFirstBoot) {
- scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
- }
-
- final int systemParseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
- final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
-
- PackageParser2 packageParser = injector.getScanningCachingPackageParser();
-
- ExecutorService executorService = ParallelPackageParser.makeExecutorService();
- // Prepare apex package info before scanning APKs, these information are needed when
- // scanning apk in apex.
- mApexManager.scanApexPackagesTraced(packageParser, executorService);
-
- mInitAndSystemPackageHelper.scanSystemDirs(mDirsToScanAsSystem, mIsUpgrade,
- packageParser, executorService, mPlatformPackage, mIsPreNMR1Upgrade,
- systemParseFlags, systemScanFlags);
- // Parse overlay configuration files to set default enable state, mutability, and
- // priority of system overlays.
- mOverlayConfig = OverlayConfig.initializeSystemInstance(
- consumer -> mPmInternal.forEachPackage(
- pkg -> consumer.accept(pkg, pkg.isSystem())));
final int[] userIds = mUserManager.getUserIds();
- mInitAndSystemPackageHelper.cleanupSystemPackagesAndInstallStubs(mDirsToScanAsSystem,
- mIsUpgrade, packageParser, executorService, mOnlyCore, packageSettings,
- startTime, mAppInstallDir, mPlatformPackage, mIsPreNMR1Upgrade,
- scanFlags, systemParseFlags, systemScanFlags, userIds);
- packageParser.close();
+ mOverlayConfig = mInitAndSystemPackageHelper.setUpSystemPackages(packageSettings,
+ userIds, startTime);
// Resolve the storage manager.
mStorageManagerPackage = getStorageManagerPackageName();
@@ -2109,7 +1959,7 @@
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
- + ((SystemClock.uptimeMillis()-startTime)/1000f)
+ + ((SystemClock.uptimeMillis() - startTime) / 1000f)
+ " seconds");
// If the build fingerprint has changed since the last time we booted,
@@ -2160,7 +2010,7 @@
// Legacy existing (installed before Q) non-system apps to hide
// their icons in launcher.
if (!mOnlyCore && mIsPreQUpgrade) {
- Slog.i(TAG, "Whitelisting all existing apps to hide their icons");
+ Slog.i(TAG, "Allowlisting all existing apps to hide their icons");
int size = packageSettings.size();
for (int i = 0; i < size; i++) {
final PackageSetting ps = packageSettings.valueAt(i);
@@ -2312,80 +2162,6 @@
setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr());
}
- private @Nullable File preparePackageParserCache(boolean forEngBuild) {
- if (!FORCE_PACKAGE_PARSED_CACHE_ENABLED) {
- if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
- return null;
- }
-
- // Disable package parsing on eng builds to allow for faster incremental development.
- if (forEngBuild) {
- return null;
- }
-
- if (SystemProperties.getBoolean("pm.boot.disable_package_cache", false)) {
- Slog.i(TAG, "Disabling package parser cache due to system property.");
- return null;
- }
- }
-
- // The base directory for the package parser cache lives under /data/system/.
- final File cacheBaseDir = Environment.getPackageCacheDirectory();
- if (!FileUtils.createDir(cacheBaseDir)) {
- return null;
- }
-
- // There are several items that need to be combined together to safely
- // identify cached items. In particular, changing the value of certain
- // feature flags should cause us to invalidate any caches.
- final String cacheName = FORCE_PACKAGE_PARSED_CACHE_ENABLED ? "debug"
- : SystemProperties.digestOf("ro.build.fingerprint");
-
- // Reconcile cache directories, keeping only what we'd actually use.
- for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
- if (Objects.equals(cacheName, cacheDir.getName())) {
- Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
- } else {
- Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
- FileUtils.deleteContentsAndDir(cacheDir);
- }
- }
-
- // Return the versioned package cache directory.
- File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
-
- if (cacheDir == null) {
- // Something went wrong. Attempt to delete everything and return.
- Slog.wtf(TAG, "Cache directory cannot be created - wiping base dir " + cacheBaseDir);
- FileUtils.deleteContentsAndDir(cacheBaseDir);
- return null;
- }
-
- // The following is a workaround to aid development on non-numbered userdebug
- // builds or cases where "adb sync" is used on userdebug builds. If we detect that
- // the system partition is newer.
- //
- // NOTE: When no BUILD_NUMBER is set by the build system, it defaults to a build
- // that starts with "eng." to signify that this is an engineering build and not
- // destined for release.
- if (mIsUserDebugBuild && mIncrementalVersion.startsWith("eng.")) {
- Slog.w(TAG, "Wiping cache directory because the system partition changed.");
-
- // Heuristic: If the /system directory has been modified recently due to an "adb sync"
- // or a regular make, then blow away the cache. Note that mtimes are *NOT* reliable
- // in general and should not be used for production changes. In this specific case,
- // we know that they will work.
- File frameworkDir =
- new File(Environment.getRootDirectory(), "framework");
- if (cacheDir.lastModified() < frameworkDir.lastModified()) {
- FileUtils.deleteContents(cacheBaseDir);
- cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
- }
- }
-
- return cacheDir;
- }
-
@Override
public boolean isFirstBoot() {
// allow instant applications
@@ -2409,9 +2185,10 @@
private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() {
final Intent intent = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
- final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
- MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, Binder.getCallingUid());
+ final List<ResolveInfo> matches =
+ mResolveIntentHelper.queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM, Binder.getCallingUid());
if (matches.size() == 1) {
return matches.get(0).getComponentInfo().packageName;
} else if (matches.size() == 0) {
@@ -2505,9 +2282,10 @@
private @NonNull ComponentName getIntentFilterVerifierComponentNameLPr() {
final Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
- final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
- MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, Binder.getCallingUid());
+ final List<ResolveInfo> matches =
+ mResolveIntentHelper.queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM, Binder.getCallingUid());
ResolveInfo best = null;
final int N = matches.size();
for (int i = 0; i < N; i++) {
@@ -2533,9 +2311,10 @@
@Nullable
private ComponentName getDomainVerificationAgentComponentNameLPr() {
Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
- List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
- MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, Binder.getCallingUid());
+ List<ResolveInfo> matches =
+ mResolveIntentHelper.queryIntentReceiversInternal(intent, null,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM, Binder.getCallingUid());
ResolveInfo best = null;
final int N = matches.size();
for (int i = 0; i < N; i++) {
@@ -3001,7 +2780,6 @@
filterCallingUid, userId);
}
-
@Override
public void deletePreloadsFileCache() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CLEAR_APP_CACHE,
@@ -3257,7 +3035,7 @@
* action and a {@code android.intent.category.BROWSABLE} category</li>
* </ul>
*/
- private int updateFlagsForResolve(int flags, int userId, int callingUid,
+ int updateFlagsForResolve(int flags, int userId, int callingUid,
boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
return mComputer.updateFlagsForResolve(flags, userId, callingUid,
wantInstantApps, isImplicitImageCaptureIntentAndNotSetByDpc);
@@ -3320,8 +3098,9 @@
return false;
}
for (int i=0; i< a.getIntents().size(); i++) {
- if (a.getIntents().get(i).match(intent.getAction(), resolvedType, intent.getScheme(),
- intent.getData(), intent.getCategories(), TAG) >= 0) {
+ if (a.getIntents().get(i).getIntentFilter()
+ .match(intent.getAction(), resolvedType, intent.getScheme(),
+ intent.getData(), intent.getCategories(), TAG) >= 0) {
return true;
}
}
@@ -4033,32 +3812,12 @@
}
}
- /**
- * If the database version for this type of package (internal storage or
- * external storage) is less than the version where package signatures
- * were updated, return true.
- */
- boolean isCompatSignatureUpdateNeeded(AndroidPackage pkg) {
- return isCompatSignatureUpdateNeeded(getSettingsVersionForPackage(pkg));
- }
-
- static boolean isCompatSignatureUpdateNeeded(VersionInfo ver) {
- return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
- }
-
- boolean isRecoverSignatureUpdateNeeded(AndroidPackage pkg) {
- return isRecoverSignatureUpdateNeeded(getSettingsVersionForPackage(pkg));
- }
-
- static boolean isRecoverSignatureUpdateNeeded(VersionInfo ver) {
- return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
- }
-
@Override
public List<String> getAllPackages() {
// Allow iorapd to call this method.
if (Binder.getCallingUid() != Process.IORAPD_UID) {
- enforceSystemOrRootOrShell("getAllPackages is limited to privileged callers");
+ PackageManagerServiceUtils.enforceSystemOrRootOrShell(
+ "getAllPackages is limited to privileged callers");
}
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
@@ -4318,124 +4077,25 @@
@Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
- return resolveIntentInternal(intent, resolvedType, flags, 0 /*privateResolveFlags*/,
- userId, false, Binder.getCallingUid());
- }
-
- /**
- * Normally instant apps can only be resolved when they're visible to the caller.
- * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
- * since we need to allow the system to start any installed application.
- */
- private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,
- @PrivateResolveFlags int privateResolveFlags, int userId, boolean resolveForStart,
- int filterCallingUid) {
- try {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
-
- if (!mUserManager.exists(userId)) return null;
- final int callingUid = Binder.getCallingUid();
- flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
- false /*checkShell*/, "resolve intent");
-
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
- final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
- flags, privateResolveFlags, filterCallingUid, userId, resolveForStart,
- true /*allowDynamicSplits*/);
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-
- final boolean queryMayBeFiltered =
- UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
- && !resolveForStart;
-
- final ResolveInfo bestChoice =
- chooseBestActivity(
- intent, resolvedType, flags, privateResolveFlags, query, userId,
- queryMayBeFiltered);
- final boolean nonBrowserOnly =
- (privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
- if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
- return null;
- }
- return bestChoice;
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
+ return mResolveIntentHelper.resolveIntentInternal(intent, resolvedType, flags,
+ 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
}
@Override
public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
- if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
- throw new SecurityException(
- "findPersistentPreferredActivity can only be run by the system");
- }
- if (!mUserManager.exists(userId)) {
- return null;
- }
- final int callingUid = Binder.getCallingUid();
- intent = PackageManagerServiceUtils.updateIntentForResolve(intent);
- final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
- final int flags = updateFlagsForResolve(
- 0, userId, callingUid, false /*includeInstantApps*/,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, 0));
- final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
- userId);
- synchronized (mLock) {
- return findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
- userId);
- }
+ return mPreferredActivityHelper.findPersistentPreferredActivity(intent, userId);
}
@Override
public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
IntentFilter filter, int match, ComponentName activity) {
- setLastChosenActivity(intent, resolvedType, flags,
+ mPreferredActivityHelper.setLastChosenActivity(intent, resolvedType, flags,
new WatchedIntentFilter(filter), match, activity);
}
- /**
- * Variant that takes a {@link WatchedIntentFilter}
- */
- public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
- WatchedIntentFilter filter, int match, ComponentName activity) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return;
- }
- final int userId = UserHandle.getCallingUserId();
- if (DEBUG_PREFERRED) {
- Log.v(TAG, "setLastChosenActivity intent=" + intent
- + " resolvedType=" + resolvedType
- + " flags=" + flags
- + " filter=" + filter
- + " match=" + match
- + " activity=" + activity);
- filter.dump(new PrintStreamPrinter(System.out), " ");
- }
- intent.setComponent(null);
- final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
- userId);
- // Find any earlier preferred or last chosen entries and nuke them
- findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, false, true, false, userId);
- // Add the new activity as the last chosen for this filter
- addPreferredActivity(filter, match, null, activity, false, userId,
- "Setting last chosen", false);
- }
-
@Override
public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return null;
- }
- final int userId = UserHandle.getCallingUserId();
- if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
- final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
- userId);
- return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, false, false, false, userId);
+ return mPreferredActivityHelper.getLastChosenActivity(intent, resolvedType, flags);
}
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -4450,115 +4110,6 @@
mHandler.sendMessage(msg);
}
- private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
- int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,
- boolean queryMayBeFiltered) {
- if (query != null) {
- final int N = query.size();
- if (N == 1) {
- return query.get(0);
- } else if (N > 1) {
- final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
- // If there is more than one activity with the same priority,
- // then let the user decide between them.
- ResolveInfo r0 = query.get(0);
- ResolveInfo r1 = query.get(1);
- if (DEBUG_INTENT_MATCHING || debug) {
- Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
- + r1.activityInfo.name + "=" + r1.priority);
- }
- // If the first activity has a higher priority, or a different
- // default, then it is always desirable to pick it.
- if (r0.priority != r1.priority
- || r0.preferredOrder != r1.preferredOrder
- || r0.isDefault != r1.isDefault) {
- return query.get(0);
- }
- // If we have saved a preference for a preferred activity for
- // this Intent, use that.
- ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
- flags, query, true, false, debug, userId, queryMayBeFiltered);
- if (ri != null) {
- return ri;
- }
- int browserCount = 0;
- for (int i = 0; i < N; i++) {
- ri = query.get(i);
- if (ri.handleAllWebDataURI) {
- browserCount++;
- }
- // If we have an ephemeral app, use it
- if (ri.activityInfo.applicationInfo.isInstantApp()) {
- final String packageName = ri.activityInfo.packageName;
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
- mDomainVerificationManager, ps, intent, flags, userId)) {
- return ri;
- }
- }
- }
- if ((privateResolveFlags
- & PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
- return null;
- }
- ri = new ResolveInfo(mResolveInfo);
- // if all resolve options are browsers, mark the resolver's info as if it were
- // also a browser.
- ri.handleAllWebDataURI = browserCount == N;
- ri.activityInfo = new ActivityInfo(ri.activityInfo);
- ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
- // If all of the options come from the same package, show the application's
- // label and icon instead of the generic resolver's.
- // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
- // and then throw away the ResolveInfo itself, meaning that the caller loses
- // the resolvePackageName. Therefore the activityInfo.labelRes above provides
- // a fallback for this case; we only set the target package's resources on
- // the ResolveInfo, not the ActivityInfo.
- final String intentPackage = intent.getPackage();
- if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
- final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
- ri.resolvePackageName = intentPackage;
- if (userNeedsBadging(userId)) {
- ri.noResourceId = true;
- } else {
- ri.icon = appi.icon;
- }
- ri.iconResourceId = appi.icon;
- ri.labelRes = appi.labelRes;
- }
- ri.activityInfo.applicationInfo = new ApplicationInfo(
- ri.activityInfo.applicationInfo);
- if (userId != 0) {
- ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
- UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
- }
- // Make sure that the resolver is displayable in car mode
- if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
- ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
- return ri;
- }
- }
- return null;
- }
-
- /**
- * Return true if the given list is not empty and all of its contents have
- * an activityInfo with the given package name.
- */
- private boolean allHavePackage(List<ResolveInfo> list, String packageName) {
- if (ArrayUtils.isEmpty(list)) {
- return false;
- }
- for (int i = 0, N = list.size(); i < N; i++) {
- final ResolveInfo ri = list.get(i);
- final ActivityInfo ai = ri != null ? ri.activityInfo : null;
- if (ai == null || !packageName.equals(ai.packageName)) {
- return false;
- }
- }
- return true;
- }
-
/**
* From Android R, camera intents have to match system apps. The only exception to this is if
* the DPC has set the camera persistent preferred activity. This case was introduced
@@ -4569,14 +4120,14 @@
* activity was not set by the DPC.
*/
@GuardedBy("mLock")
- private boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
+ boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
String resolvedType, int flags) {
return mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
resolvedType, flags);
}
@GuardedBy("mLock")
- private ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ ResolveInfo findPersistentPreferredActivityLP(Intent intent,
String resolvedType,
int flags, List<ResolveInfo> query, boolean debug, int userId) {
return mComputer.findPersistentPreferredActivityLP(intent,
@@ -4591,7 +4142,7 @@
ResolveInfo mPreferredResolveInfo;
}
- private FindPreferredActivityBodyResult findPreferredActivityInternal(
+ FindPreferredActivityBodyResult findPreferredActivityInternal(
Intent intent, String resolvedType, int flags,
List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
@@ -4601,42 +4152,6 @@
removeMatches, debug, userId, queryMayBeFiltered);
}
- private ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType,
- int flags, List<ResolveInfo> query, boolean always, boolean removeMatches,
- boolean debug, int userId) {
- return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, always, removeMatches, debug, userId,
- UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
- }
-
- // TODO: handle preferred activities missing while user has amnesia
- /** <b>must not hold {@link #mLock}</b> */
- private ResolveInfo findPreferredActivityNotLocked(
- Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always,
- boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
- if (Thread.holdsLock(mLock)) {
- Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
- + " is holding mLock", new Throwable());
- }
- if (!mUserManager.exists(userId)) return null;
-
- FindPreferredActivityBodyResult body = findPreferredActivityInternal(
- intent, resolvedType, flags, query, always,
- removeMatches, debug, userId, queryMayBeFiltered);
- if (body.mChanged) {
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
- }
- synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
- if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
- Slog.v(TAG, "No preferred activity to return");
- }
- return body.mPreferredResolveInfo;
- }
-
/*
* Returns if intent can be forwarded from the sourceUserId to the targetUserId
*/
@@ -4701,17 +4216,17 @@
* Returns the package name of the calling Uid if it's an instant app. If it isn't
* instant, returns {@code null}.
*/
- private String getInstantAppPackageName(int callingUid) {
+ String getInstantAppPackageName(int callingUid) {
return mComputer.getInstantAppPackageName(callingUid);
}
- private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
+ @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, int userId) {
return mComputer.queryIntentActivitiesInternal(intent,
resolvedType, flags, userId);
}
- private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
+ @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) {
return mComputer.queryIntentActivitiesInternal(intent,
@@ -4736,7 +4251,7 @@
* @param intent
* @return A filtered list of resolved activities.
*/
- private List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
+ List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
boolean resolveForStart, int userId, Intent intent) {
return mComputer.applyPostResolutionFilter(resolveInfos,
@@ -4748,311 +4263,22 @@
public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
Intent[] specifics, String[] specificTypes, Intent intent,
String resolvedType, int flags, int userId) {
- return new ParceledListSlice<>(queryIntentActivityOptionsInternal(caller, specifics,
- specificTypes, intent, resolvedType, flags, userId));
- }
-
- private @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller,
- Intent[] specifics, String[] specificTypes, Intent intent,
- String resolvedType, int flags, int userId) {
- if (!mUserManager.exists(userId)) return Collections.emptyList();
- final int callingUid = Binder.getCallingUid();
- flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
- false /*checkShell*/, "query intent activity options");
- final String resultsAction = intent.getAction();
-
- final List<ResolveInfo> results = queryIntentActivitiesInternal(intent, resolvedType, flags
- | PackageManager.GET_RESOLVED_FILTER, userId);
-
- if (DEBUG_INTENT_MATCHING) {
- Log.v(TAG, "Query " + intent + ": " + results);
- }
-
- int specificsPos = 0;
- int N;
-
- // todo: note that the algorithm used here is O(N^2). This
- // isn't a problem in our current environment, but if we start running
- // into situations where we have more than 5 or 10 matches then this
- // should probably be changed to something smarter...
-
- // First we go through and resolve each of the specific items
- // that were supplied, taking care of removing any corresponding
- // duplicate items in the generic resolve list.
- if (specifics != null) {
- for (int i=0; i<specifics.length; i++) {
- final Intent sintent = specifics[i];
- if (sintent == null) {
- continue;
- }
-
- if (DEBUG_INTENT_MATCHING) {
- Log.v(TAG, "Specific #" + i + ": " + sintent);
- }
-
- String action = sintent.getAction();
- if (resultsAction != null && resultsAction.equals(action)) {
- // If this action was explicitly requested, then don't
- // remove things that have it.
- action = null;
- }
-
- ResolveInfo ri = null;
- ActivityInfo ai = null;
-
- ComponentName comp = sintent.getComponent();
- if (comp == null) {
- ri = resolveIntent(
- sintent,
- specificTypes != null ? specificTypes[i] : null,
- flags, userId);
- if (ri == null) {
- continue;
- }
- if (ri == mResolveInfo) {
- // ACK! Must do something better with this.
- }
- ai = ri.activityInfo;
- comp = new ComponentName(ai.applicationInfo.packageName,
- ai.name);
- } else {
- ai = getActivityInfo(comp, flags, userId);
- if (ai == null) {
- continue;
- }
- }
-
- // Look for any generic query activities that are duplicates
- // of this specific one, and remove them from the results.
- if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai);
- N = results.size();
- int j;
- for (j=specificsPos; j<N; j++) {
- ResolveInfo sri = results.get(j);
- if ((sri.activityInfo.name.equals(comp.getClassName())
- && sri.activityInfo.applicationInfo.packageName.equals(
- comp.getPackageName()))
- || (action != null && sri.filter.matchAction(action))) {
- results.remove(j);
- if (DEBUG_INTENT_MATCHING) Log.v(
- TAG, "Removing duplicate item from " + j
- + " due to specific " + specificsPos);
- if (ri == null) {
- ri = sri;
- }
- j--;
- N--;
- }
- }
-
- // Add this specific item to its proper place.
- if (ri == null) {
- ri = new ResolveInfo();
- ri.activityInfo = ai;
- }
- results.add(specificsPos, ri);
- ri.specificIndex = i;
- specificsPos++;
- }
- }
-
- // Now we go through the remaining generic results and remove any
- // duplicate actions that are found here.
- N = results.size();
- for (int i=specificsPos; i<N-1; i++) {
- final ResolveInfo rii = results.get(i);
- if (rii.filter == null) {
- continue;
- }
-
- // Iterate over all of the actions of this result's intent
- // filter... typically this should be just one.
- final Iterator<String> it = rii.filter.actionsIterator();
- if (it == null) {
- continue;
- }
- while (it.hasNext()) {
- final String action = it.next();
- if (resultsAction != null && resultsAction.equals(action)) {
- // If this action was explicitly requested, then don't
- // remove things that have it.
- continue;
- }
- for (int j=i+1; j<N; j++) {
- final ResolveInfo rij = results.get(j);
- if (rij.filter != null && rij.filter.hasAction(action)) {
- results.remove(j);
- if (DEBUG_INTENT_MATCHING) Log.v(
- TAG, "Removing duplicate item from " + j
- + " due to action " + action + " at " + i);
- j--;
- N--;
- }
- }
- }
-
- // If the caller didn't request filter information, drop it now
- // so we don't have to marshall/unmarshall it.
- if ((flags&PackageManager.GET_RESOLVED_FILTER) == 0) {
- rii.filter = null;
- }
- }
-
- // Filter out the caller activity if so requested.
- if (caller != null) {
- N = results.size();
- for (int i=0; i<N; i++) {
- ActivityInfo ainfo = results.get(i).activityInfo;
- if (caller.getPackageName().equals(ainfo.applicationInfo.packageName)
- && caller.getClassName().equals(ainfo.name)) {
- results.remove(i);
- break;
- }
- }
- }
-
- // If the caller didn't request filter information,
- // drop them now so we don't have to
- // marshall/unmarshall it.
- if ((flags&PackageManager.GET_RESOLVED_FILTER) == 0) {
- N = results.size();
- for (int i=0; i<N; i++) {
- results.get(i).filter = null;
- }
- }
-
- if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results);
- return results;
+ return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal(
+ caller, specifics, specificTypes, intent, resolvedType, flags, userId));
}
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, int flags, int userId) {
- return new ParceledListSlice<>(queryIntentReceiversInternal(intent, resolvedType,
- flags, userId, Binder.getCallingUid()));
- }
-
- // In this method, we have to know the actual calling UID, but in some cases Binder's
- // call identity is removed, so the UID has to be passed in explicitly.
- private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
- String resolvedType, int flags, int userId, int filterCallingUid) {
- if (!mUserManager.exists(userId)) return Collections.emptyList();
- enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
- false /*checkShell*/, "query intent receivers");
- final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
- flags = updateFlagsForResolve(flags, userId, filterCallingUid, false /*includeInstantApps*/,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- Intent originalIntent = null;
- ComponentName comp = intent.getComponent();
- if (comp == null) {
- if (intent.getSelector() != null) {
- originalIntent = intent;
- intent = intent.getSelector();
- comp = intent.getComponent();
- }
- }
- List<ResolveInfo> list = Collections.emptyList();
- if (comp != null) {
- final ActivityInfo ai = getReceiverInfo(comp, flags, userId);
- if (ai != null) {
- // When specifying an explicit component, we prevent the activity from being
- // used when either 1) the calling package is normal and the activity is within
- // an instant application or 2) the calling package is ephemeral and the
- // activity is not visible to instant applications.
- final boolean matchInstantApp =
- (flags & PackageManager.MATCH_INSTANT) != 0;
- final boolean matchVisibleToInstantAppOnly =
- (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
- final boolean matchExplicitlyVisibleOnly =
- (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
- final boolean isCallerInstantApp =
- instantAppPkgName != null;
- final boolean isTargetSameInstantApp =
- comp.getPackageName().equals(instantAppPkgName);
- final boolean isTargetInstantApp =
- (ai.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
- final boolean isTargetVisibleToInstantApp =
- (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
- final boolean isTargetExplicitlyVisibleToInstantApp =
- isTargetVisibleToInstantApp
- && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
- final boolean isTargetHiddenFromInstantApp =
- !isTargetVisibleToInstantApp
- || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
- final boolean blockResolution =
- !isTargetSameInstantApp
- && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
- || (matchVisibleToInstantAppOnly && isCallerInstantApp
- && isTargetHiddenFromInstantApp));
- if (!blockResolution) {
- ResolveInfo ri = new ResolveInfo();
- ri.activityInfo = ai;
- list = new ArrayList<>(1);
- list.add(ri);
- PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mInjector.getCompatibility(), mComponentResolver,
- list, true, intent, resolvedType, filterCallingUid);
- }
- }
- } else {
- // reader
- synchronized (mLock) {
- String pkgName = intent.getPackage();
- if (pkgName == null) {
- final List<ResolveInfo> result =
- mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
- if (result != null) {
- list = result;
- }
- }
- final AndroidPackage pkg = mPackages.get(pkgName);
- if (pkg != null) {
- final List<ResolveInfo> result = mComponentResolver.queryReceivers(
- intent, resolvedType, flags, pkg.getReceivers(), userId);
- if (result != null) {
- list = result;
- }
- }
- }
- }
-
- if (originalIntent != null) {
- // We also have to ensure all components match the original intent
- PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mInjector.getCompatibility(), mComponentResolver,
- list, true, originalIntent, resolvedType, filterCallingUid);
- }
-
- return applyPostResolutionFilter(
- list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
+ return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal(intent,
+ resolvedType, flags, userId, Binder.getCallingUid()));
}
@Override
public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
final int callingUid = Binder.getCallingUid();
- return resolveServiceInternal(intent, resolvedType, flags, userId, callingUid);
- }
-
- private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
- int userId, int callingUid) {
- if (!mUserManager.exists(userId)) return null;
- flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
- false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
- List<ResolveInfo> query = queryIntentServicesInternal(
- intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
- if (query != null) {
- if (query.size() >= 1) {
- // If there is more than one service with the same priority,
- // just arbitrarily pick the first one.
- return query.get(0);
- }
- }
- return null;
+ return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
+ callingUid);
}
@Override
@@ -5063,7 +4289,7 @@
intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/));
}
- private @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
+ @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
String resolvedType, int flags, int userId, int callingUid,
boolean includeInstantApps) {
return mComputer.queryIntentServicesInternal(intent,
@@ -5074,148 +4300,8 @@
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent,
String resolvedType, int flags, int userId) {
- return new ParceledListSlice<>(
- queryIntentContentProvidersInternal(intent, resolvedType, flags, userId));
- }
-
- private @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
- Intent intent, String resolvedType, int flags, int userId) {
- if (!mUserManager.exists(userId)) return Collections.emptyList();
- final int callingUid = Binder.getCallingUid();
- final String instantAppPkgName = getInstantAppPackageName(callingUid);
- flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
- false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
- ComponentName comp = intent.getComponent();
- if (comp == null) {
- if (intent.getSelector() != null) {
- intent = intent.getSelector();
- comp = intent.getComponent();
- }
- }
- if (comp != null) {
- final List<ResolveInfo> list = new ArrayList<>(1);
- final ProviderInfo pi = getProviderInfo(comp, flags, userId);
- if (pi != null) {
- // When specifying an explicit component, we prevent the provider from being
- // used when either 1) the provider is in an instant application and the
- // caller is not the same instant application or 2) the calling package is an
- // instant application and the provider is not visible to instant applications.
- final boolean matchInstantApp =
- (flags & PackageManager.MATCH_INSTANT) != 0;
- final boolean matchVisibleToInstantAppOnly =
- (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
- final boolean isCallerInstantApp =
- instantAppPkgName != null;
- final boolean isTargetSameInstantApp =
- comp.getPackageName().equals(instantAppPkgName);
- final boolean isTargetInstantApp =
- (pi.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
- final boolean isTargetHiddenFromInstantApp =
- (pi.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
- final boolean blockResolution =
- !isTargetSameInstantApp
- && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
- || (matchVisibleToInstantAppOnly && isCallerInstantApp
- && isTargetHiddenFromInstantApp));
- final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
- && shouldFilterApplicationLocked(
- getPackageSettingInternal(pi.applicationInfo.packageName,
- Process.SYSTEM_UID), callingUid, userId);
- if (!blockResolution && !blockNormalResolution) {
- final ResolveInfo ri = new ResolveInfo();
- ri.providerInfo = pi;
- list.add(ri);
- }
- }
- return list;
- }
-
- // reader
- synchronized (mLock) {
- String pkgName = intent.getPackage();
- if (pkgName == null) {
- final List<ResolveInfo> resolveInfos = mComponentResolver.queryProviders(intent,
- resolvedType, flags, userId);
- if (resolveInfos == null) {
- return Collections.emptyList();
- }
- return applyPostContentProviderResolutionFilter(
- resolveInfos, instantAppPkgName, userId, callingUid);
- }
- final AndroidPackage pkg = mPackages.get(pkgName);
- if (pkg != null) {
- final List<ResolveInfo> resolveInfos = mComponentResolver.queryProviders(intent,
- resolvedType, flags,
- pkg.getProviders(), userId);
- if (resolveInfos == null) {
- return Collections.emptyList();
- }
- return applyPostContentProviderResolutionFilter(
- resolveInfos, instantAppPkgName, userId, callingUid);
- }
- return Collections.emptyList();
- }
- }
-
- private List<ResolveInfo> applyPostContentProviderResolutionFilter(
- List<ResolveInfo> resolveInfos, String instantAppPkgName,
- @UserIdInt int userId, int callingUid) {
- for (int i = resolveInfos.size() - 1; i >= 0; i--) {
- final ResolveInfo info = resolveInfos.get(i);
-
- if (instantAppPkgName == null) {
- SettingBase callingSetting =
- mSettings.getSettingLPr(UserHandle.getAppId(callingUid));
- PackageSetting resolvedSetting =
- getPackageSettingInternal(info.providerInfo.packageName, 0);
- if (!mAppsFilter.shouldFilterApplication(
- callingUid, callingSetting, resolvedSetting, userId)) {
- continue;
- }
- }
-
- final boolean isEphemeralApp = info.providerInfo.applicationInfo.isInstantApp();
- // allow providers that are defined in the provided package
- if (isEphemeralApp && instantAppPkgName.equals(info.providerInfo.packageName)) {
- if (info.providerInfo.splitName != null
- && !ArrayUtils.contains(info.providerInfo.applicationInfo.splitNames,
- info.providerInfo.splitName)) {
- if (mInstantAppInstallerActivity == null) {
- if (DEBUG_INSTANT) {
- Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
- }
- resolveInfos.remove(i);
- continue;
- }
- // requested provider is defined in a split that hasn't been installed yet.
- // add the installer to the resolve list
- if (DEBUG_INSTANT) {
- Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
- }
- final ResolveInfo installerInfo = new ResolveInfo(
- mInstantAppInstallerInfo);
- installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
- null /*failureActivity*/,
- info.providerInfo.packageName,
- info.providerInfo.applicationInfo.longVersionCode,
- info.providerInfo.splitName);
- // add a non-generic filter
- installerInfo.filter = new IntentFilter();
- // load resources from the correct package
- installerInfo.resolvePackageName = info.getComponentInfo().packageName;
- resolveInfos.set(i, installerInfo);
- }
- continue;
- }
- // allow providers that have been explicitly exposed to instant applications
- if (!isEphemeralApp
- && ((info.providerInfo.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
- continue;
- }
- resolveInfos.remove(i);
- }
- return resolveInfos;
+ return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal(
+ intent, resolvedType, flags, userId));
}
@Override
@@ -5459,7 +4545,7 @@
}
}
- private boolean isCallerSameApp(String packageName, int uid) {
+ boolean isCallerSameApp(String packageName, int uid) {
return mComputer.isCallerSameApp(packageName, uid);
}
@@ -5698,34 +4784,6 @@
}
/**
- * Enforces that only the system UID or root's UID can call a method exposed
- * via Binder.
- *
- * @param message used as message if SecurityException is thrown
- * @throws SecurityException if the caller is not system or root
- */
- private static void enforceSystemOrRoot(String message) {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID) {
- throw new SecurityException(message);
- }
- }
-
- /**
- * Enforces that only the system UID or root's UID or shell's UID can call
- * a method exposed via Binder.
- *
- * @param message used as message if SecurityException is thrown
- * @throws SecurityException if the caller is not system or shell
- */
- private static void enforceSystemOrRootOrShell(String message) {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
- throw new SecurityException(message);
- }
- }
-
- /**
* Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
* or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller.
*
@@ -5760,7 +4818,7 @@
@Override
public void performFstrimIfNeeded() {
- enforceSystemOrRoot("Only the system can request fstrim");
+ PackageManagerServiceUtils.enforceSystemOrRoot("Only the system can request fstrim");
// Before everything else, see whether we need to fstrim.
try {
@@ -5782,7 +4840,7 @@
if (doTrim) {
final boolean dexOptDialogShown;
synchronized (mLock) {
- dexOptDialogShown = mDexOptDialogShown;
+ dexOptDialogShown = mDexOptHelper.isDexOptDialogShown();
}
if (!isFirstBoot() && dexOptDialogShown) {
try {
@@ -5804,208 +4862,7 @@
@Override
public void updatePackagesIfNeeded() {
- enforceSystemOrRoot("Only the system can request package update");
-
- // We need to re-extract after an OTA.
- boolean causeUpgrade = isDeviceUpgrading();
-
- // First boot or factory reset.
- // Note: we also handle devices that are upgrading to N right now as if it is their
- // first boot, as they do not have profile data.
- boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
-
- if (!causeUpgrade && !causeFirstBoot) {
- return;
- }
-
- List<PackageSetting> pkgSettings;
- synchronized (mLock) {
- pkgSettings = PackageManagerServiceUtils.getPackagesForDexopt(
- mSettings.getPackagesLocked().values(), this);
- }
-
- List<AndroidPackage> pkgs = new ArrayList<>(pkgSettings.size());
- for (int index = 0; index < pkgSettings.size(); index++) {
- pkgs.add(pkgSettings.get(index).getPkg());
- }
-
- final long startTime = System.nanoTime();
- final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
- causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
- false /* bootComplete */);
-
- final int elapsedTimeSeconds =
- (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
-
- MetricsLogger.histogram(mContext, "opt_dialog_num_dexopted", stats[0]);
- MetricsLogger.histogram(mContext, "opt_dialog_num_skipped", stats[1]);
- MetricsLogger.histogram(mContext, "opt_dialog_num_failed", stats[2]);
- MetricsLogger.histogram(mContext, "opt_dialog_num_total", getOptimizablePackages().size());
- MetricsLogger.histogram(mContext, "opt_dialog_time_s", elapsedTimeSeconds);
- }
-
- /*
- * Return the prebuilt profile path given a package base code path.
- */
- private static String getPrebuildProfilePath(AndroidPackage pkg) {
- return pkg.getBaseApkPath() + ".prof";
- }
-
- /**
- * Performs dexopt on the set of packages in {@code packages} and returns an int array
- * containing statistics about the invocation. The array consists of three elements,
- * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
- * and {@code numberOfPackagesFailed}.
- */
- private int[] performDexOptUpgrade(List<AndroidPackage> pkgs, boolean showDialog,
- final int compilationReason, boolean bootComplete) {
-
- int numberOfPackagesVisited = 0;
- int numberOfPackagesOptimized = 0;
- int numberOfPackagesSkipped = 0;
- int numberOfPackagesFailed = 0;
- final int numberOfPackagesToDexopt = pkgs.size();
-
- for (AndroidPackage pkg : pkgs) {
- numberOfPackagesVisited++;
-
- boolean useProfileForDexopt = false;
-
- if ((isFirstBoot() || isDeviceUpgrading()) && pkg.isSystem()) {
- // Copy over initial preopt profiles since we won't get any JIT samples for methods
- // that are already compiled.
- File profileFile = new File(getPrebuildProfilePath(pkg));
- // Copy profile if it exists.
- if (profileFile.exists()) {
- try {
- // We could also do this lazily before calling dexopt in
- // PackageDexOptimizer to prevent this happening on first boot. The issue
- // is that we don't have a good way to say "do this only once".
- if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.getUid(), pkg.getPackageName(),
- ArtManager.getProfileName(null))) {
- Log.e(TAG, "Installer failed to copy system profile!");
- } else {
- // Disabled as this causes speed-profile compilation during first boot
- // even if things are already compiled.
- // useProfileForDexopt = true;
- }
- } catch (Exception e) {
- Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
- e);
- }
- } else {
- PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(
- pkg.getPackageName());
- // Handle compressed APKs in this path. Only do this for stubs with profiles to
- // minimize the number off apps being speed-profile compiled during first boot.
- // The other paths will not change the filter.
- if (disabledPs != null && disabledPs.getPkg().isStub()) {
- // The package is the stub one, remove the stub suffix to get the normal
- // package and APK names.
- String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg())
- .replace(STUB_SUFFIX, "");
- profileFile = new File(systemProfilePath);
- // If we have a profile for a compressed APK, copy it to the reference
- // location.
- // Note that copying the profile here will cause it to override the
- // reference profile every OTA even though the existing reference profile
- // may have more data. We can't copy during decompression since the
- // directories are not set up at that point.
- if (profileFile.exists()) {
- try {
- // We could also do this lazily before calling dexopt in
- // PackageDexOptimizer to prevent this happening on first boot. The
- // issue is that we don't have a good way to say "do this only
- // once".
- if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.getUid(), pkg.getPackageName(),
- ArtManager.getProfileName(null))) {
- Log.e(TAG, "Failed to copy system profile for stub package!");
- } else {
- useProfileForDexopt = true;
- }
- } catch (Exception e) {
- Log.e(TAG, "Failed to copy profile " +
- profileFile.getAbsolutePath() + " ", e);
- }
- }
- }
- }
- }
-
- if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
- }
- numberOfPackagesSkipped++;
- continue;
- }
-
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of " +
- numberOfPackagesToDexopt + ": " + pkg.getPackageName());
- }
-
- if (showDialog) {
- try {
- ActivityManager.getService().showBootMessage(
- mContext.getResources().getString(R.string.android_upgrading_apk,
- numberOfPackagesVisited, numberOfPackagesToDexopt), true);
- } catch (RemoteException e) {
- }
- synchronized (mLock) {
- mDexOptDialogShown = true;
- }
- }
-
- int pkgCompilationReason = compilationReason;
- if (useProfileForDexopt) {
- // Use background dexopt mode to try and use the profile. Note that this does not
- // guarantee usage of the profile.
- pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- }
-
- if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
- mArtManagerService.compileLayouts(pkg);
- }
-
- // checkProfiles is false to avoid merging profiles during boot which
- // might interfere with background compilation (b/28612421).
- // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
- // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
- // trade-off worth doing to save boot time work.
- int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
- if (compilationReason == REASON_FIRST_BOOT) {
- // TODO: This doesn't cover the upgrade case, we should check for this too.
- dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
- }
- int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
- pkg.getPackageName(),
- pkgCompilationReason,
- dexoptFlags));
-
- switch (primaryDexOptStaus) {
- case PackageDexOptimizer.DEX_OPT_PERFORMED:
- numberOfPackagesOptimized++;
- break;
- case PackageDexOptimizer.DEX_OPT_SKIPPED:
- numberOfPackagesSkipped++;
- break;
- case PackageDexOptimizer.DEX_OPT_CANCELLED:
- // ignore this case
- break;
- case PackageDexOptimizer.DEX_OPT_FAILED:
- numberOfPackagesFailed++;
- break;
- default:
- Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStaus);
- break;
- }
- }
-
- return new int[] { numberOfPackagesOptimized, numberOfPackagesSkipped,
- numberOfPackagesFailed };
+ mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
}
@Override
@@ -6099,13 +4956,8 @@
public boolean performDexOptMode(String packageName,
boolean checkProfiles, String targetCompilerFilter, boolean force,
boolean bootComplete, String splitName) {
- enforceSystemOrRootOrShell("performDexOptMode");
-
- int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) |
- (force ? DexoptOptions.DEXOPT_FORCE : 0) |
- (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
- return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
- targetCompilerFilter, splitName, flags));
+ return mDexOptHelper.performDexOptMode(packageName, checkProfiles, targetCompilerFilter,
+ force, bootComplete, splitName);
}
/**
@@ -6118,143 +4970,7 @@
@Override
public boolean performDexOptSecondary(String packageName, String compilerFilter,
boolean force) {
- int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
- DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
- DexoptOptions.DEXOPT_BOOT_COMPLETE |
- (force ? DexoptOptions.DEXOPT_FORCE : 0);
- return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags));
- }
-
- /*package*/ boolean performDexOpt(DexoptOptions options) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return false;
- } else if (isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
- return false;
- }
-
- if (options.isDexoptOnlySecondaryDex()) {
- return mDexManager.dexoptSecondaryDex(options);
- } else {
- int dexoptStatus = performDexOptWithStatus(options);
- return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
- }
- }
-
- /**
- * Perform dexopt on the given package and return one of following result:
- * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
- * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
- * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
- * {@link PackageDexOptimizer#DEX_OPT_FAILED}
- */
- @PackageDexOptimizer.DexOptResult
- /* package */ int performDexOptWithStatus(DexoptOptions options) {
- return performDexOptTraced(options);
- }
-
- private int performDexOptTraced(DexoptOptions options) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- try {
- return performDexOptInternal(options);
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- }
-
- // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
- // if the package can now be considered up to date for the given filter.
- private int performDexOptInternal(DexoptOptions options) {
- AndroidPackage p;
- PackageSetting pkgSetting;
- synchronized (mLock) {
- p = mPackages.get(options.getPackageName());
- pkgSetting = mSettings.getPackageLPr(options.getPackageName());
- if (p == null || pkgSetting == null) {
- // Package could not be found. Report failure.
- return PackageDexOptimizer.DEX_OPT_FAILED;
- }
- mPackageUsage.maybeWriteAsync(mSettings.getPackagesLocked());
- mCompilerStats.maybeWriteAsync();
- }
- final long callingId = Binder.clearCallingIdentity();
- try {
- synchronized (mInstallLock) {
- return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
- }
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
- public ArraySet<String> getOptimizablePackages() {
- ArraySet<String> pkgs = new ArraySet<>();
- synchronized (mLock) {
- for (AndroidPackage p : mPackages.values()) {
- if (PackageDexOptimizer.canOptimizePackage(p)) {
- pkgs.add(p.getPackageName());
- }
- }
- }
- if (AppHibernationService.isAppHibernationEnabled()) {
- AppHibernationManagerInternal appHibernationManager =
- mInjector.getLocalService(AppHibernationManagerInternal.class);
- pkgs.removeIf(pkgName -> appHibernationManager.isHibernatingGlobally(pkgName));
- }
- return pkgs;
- }
-
- private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
- @NonNull PackageSetting pkgSetting, DexoptOptions options) {
- // System server gets a special path.
- if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
- return mDexManager.dexoptSystemServer(options);
- }
-
- // Select the dex optimizer based on the force parameter.
- // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
- // allocate an object here.
- PackageDexOptimizer pdo = options.isForce()
- ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
- : mPackageDexOptimizer;
-
- // Dexopt all dependencies first. Note: we ignore the return value and march on
- // on errors.
- // Note that we are going to call performDexOpt on those libraries as many times as
- // they are referenced in packages. When we do a batch of performDexOpt (for example
- // at boot, or background job), the passed 'targetCompilerFilter' stays the same,
- // and the first package that uses the library will dexopt it. The
- // others will see that the compiled code for the library is up to date.
- Collection<SharedLibraryInfo> deps = findSharedLibraries(pkgSetting);
- final String[] instructionSets = getAppDexInstructionSets(
- AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting),
- AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting));
- if (!deps.isEmpty()) {
- DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
- options.getCompilationReason(), options.getCompilerFilter(),
- options.getSplitName(),
- options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
- for (SharedLibraryInfo info : deps) {
- AndroidPackage depPackage = null;
- PackageSetting depPackageSetting = null;
- synchronized (mLock) {
- depPackage = mPackages.get(info.getPackageName());
- depPackageSetting = mSettings.getPackageLPr(info.getPackageName());
- }
- if (depPackage != null && depPackageSetting != null) {
- // TODO: Analyze and investigate if we (should) profile libraries.
- pdo.performDexOpt(depPackage, depPackageSetting, instructionSets,
- getOrCreateCompilerPackageStats(depPackage),
- mDexManager.getPackageUseInfoOrDefault(depPackage.getPackageName()),
- libraryOptions);
- } else {
- // TODO(ngeoffray): Support dexopting system shared libraries.
- }
- }
- }
-
- return pdo.performDexOpt(p, pkgSetting, instructionSets,
- getOrCreateCompilerPackageStats(p),
- mDexManager.getPackageUseInfoOrDefault(p.getPackageName()), options);
+ return mDexOptHelper.performDexOptSecondary(packageName, compilerFilter, force);
}
/**
@@ -6276,52 +4992,8 @@
return mDexManager;
}
- /**
- * Execute the background dexopt job immediately.
- */
- @Override
- public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return false;
- }
- enforceSystemOrRootOrShell("runBackgroundDexoptJob");
- final long identity = Binder.clearCallingIdentity();
- try {
- return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private static List<SharedLibraryInfo> findSharedLibraries(PackageSetting pkgSetting) {
- if (!pkgSetting.getPkgState().getUsesLibraryInfos().isEmpty()) {
- ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
- Set<String> collectedNames = new HashSet<>();
- for (SharedLibraryInfo info : pkgSetting.getPkgState().getUsesLibraryInfos()) {
- findSharedLibrariesRecursive(info, retValue, collectedNames);
- }
- return retValue;
- } else {
- return Collections.emptyList();
- }
- }
-
- private static void findSharedLibrariesRecursive(SharedLibraryInfo info,
- ArrayList<SharedLibraryInfo> collected, Set<String> collectedNames) {
- if (!collectedNames.contains(info.getName())) {
- collectedNames.add(info.getName());
- collected.add(info);
-
- if (info.getDependencies() != null) {
- for (SharedLibraryInfo dep : info.getDependencies()) {
- findSharedLibrariesRecursive(dep, collected, collectedNames);
- }
- }
- }
- }
-
List<PackageSetting> findSharedNonSystemLibraries(PackageSetting pkgSetting) {
- List<SharedLibraryInfo> deps = findSharedLibraries(pkgSetting);
+ List<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting);
if (!deps.isEmpty()) {
List<PackageSetting> retValue = new ArrayList<>();
synchronized (mLock) {
@@ -6344,27 +5016,6 @@
return mComputer.getSharedLibraryInfoLPr(name, version);
}
- @Nullable
- public static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
- Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
- @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
- if (newLibraries != null) {
- final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
- SharedLibraryInfo info = null;
- if (versionedLib != null) {
- info = versionedLib.get(version);
- }
- if (info != null) {
- return info;
- }
- }
- final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
- if (versionedLib == null) {
- return null;
- }
- return versionedLib.get(version);
- }
-
SharedLibraryInfo getLatestSharedLibraVersionLPr(AndroidPackage pkg) {
WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
pkg.getStaticSharedLibName());
@@ -6446,33 +5097,7 @@
@Override
public void forceDexOpt(String packageName) {
- enforceSystemOrRoot("forceDexOpt");
-
- AndroidPackage pkg;
- PackageSetting pkgSetting;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkg == null || pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- }
-
- synchronized (mInstallLock) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-
- // Whoever is calling forceDexOpt wants a compiled package.
- // Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, pkgSetting,
- new DexoptOptions(packageName,
- getDefaultCompilerFilter(),
- DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
-
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
- throw new IllegalStateException("Failed to dexopt: " + res);
- }
- }
+ mDexOptHelper.forceDexOpt(packageName);
}
int[] resolveUserIds(int userId) {
@@ -6544,58 +5169,15 @@
@Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting,
Map<String, AndroidPackage> availablePackages)
throws PackageManagerException {
- final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos(
- pkgSetting.getPkg(), availablePackages, mSharedLibraries, null /* newLibraries */,
- mInjector.getCompatibility());
+ final ArrayList<SharedLibraryInfo> sharedLibraryInfos =
+ SharedLibraryHelper.collectSharedLibraryInfos(
+ pkgSetting.getPkg(), availablePackages, mSharedLibraries,
+ null /* newLibraries */, mInjector.getCompatibility());
executeSharedLibrariesUpdateLPr(pkg, pkgSetting, changingLib, changingLibSetting,
sharedLibraryInfos, mUserManager.getUserIds());
}
- private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
- Map<String, AndroidPackage> availablePackages,
- @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
- @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
- PlatformCompat platformCompat) throws PackageManagerException {
- if (pkg == null) {
- return null;
- }
- // The collection used here must maintain the order of addition (so
- // that libraries are searched in the correct order) and must have no
- // duplicates.
- ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
- if (!pkg.getUsesLibraries().isEmpty()) {
- usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
- pkg.getPackageName(), true, pkg.getTargetSdkVersion(), null,
- availablePackages, existingLibraries, newLibraries);
- }
- if (!pkg.getUsesStaticLibraries().isEmpty()) {
- usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
- pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
- pkg.getPackageName(), true, pkg.getTargetSdkVersion(), usesLibraryInfos,
- availablePackages, existingLibraries, newLibraries);
- }
- if (!pkg.getUsesOptionalLibraries().isEmpty()) {
- usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(),
- null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
- }
- if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
- pkg.getPackageName(), pkg.getTargetSdkVersion())) {
- if (!pkg.getUsesNativeLibraries().isEmpty()) {
- usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
- null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
- }
- if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
- usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
- null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
- }
- }
- return usesLibraryInfos;
- }
-
- private void executeSharedLibrariesUpdateLPr(AndroidPackage pkg,
+ void executeSharedLibrariesUpdateLPr(AndroidPackage pkg,
@NonNull PackageSetting pkgSetting, @Nullable AndroidPackage changingLib,
@Nullable PackageSetting changingLibSetting,
ArrayList<SharedLibraryInfo> usesLibraryInfos, int[] allUsers) {
@@ -6643,102 +5225,6 @@
}
}
- @GuardedBy("mLock")
- private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
- @NonNull List<String> requestedLibraries,
- @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
- @NonNull String packageName, boolean required, int targetSdk,
- @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
- @NonNull final Map<String, AndroidPackage> availablePackages,
- @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
- @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
- throws PackageManagerException {
- final int libCount = requestedLibraries.size();
- for (int i = 0; i < libCount; i++) {
- final String libName = requestedLibraries.get(i);
- final long libVersion = requiredVersions != null ? requiredVersions[i]
- : SharedLibraryInfo.VERSION_UNDEFINED;
- final SharedLibraryInfo libraryInfo =
- getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries);
- if (libraryInfo == null) {
- if (required) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires unavailable shared library "
- + libName + "; failing!");
- } else if (DEBUG_SHARED_LIBRARIES) {
- Slog.i(TAG, "Package " + packageName
- + " desires unavailable shared library "
- + libName + "; ignoring!");
- }
- } else {
- if (requiredVersions != null && requiredCertDigests != null) {
- if (libraryInfo.getLongVersion() != requiredVersions[i]) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires unavailable static shared"
- + " library " + libName + " version "
- + libraryInfo.getLongVersion() + "; failing!");
- }
- AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName());
- SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
- if (libPkg == null) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires unavailable static shared"
- + " library; failing!");
- }
- final String[] expectedCertDigests = requiredCertDigests[i];
- if (expectedCertDigests.length > 1) {
- // For apps targeting O MR1 we require explicit enumeration of all certs.
- final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
- ? PackageUtils.computeSignaturesSha256Digests(
- libPkg.getSignatures())
- : PackageUtils.computeSignaturesSha256Digests(
- new Signature[]{libPkg.getSignatures()[0]});
-
- // Take a shortcut if sizes don't match. Note that if an app doesn't
- // target O we don't parse the "additional-certificate" tags similarly
- // how we only consider all certs only for apps targeting O (see above).
- // Therefore, the size check is safe to make.
- if (expectedCertDigests.length != libCertDigests.length) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires differently signed" +
- " static shared library; failing!");
- }
-
- // Use a predictable order as signature order may vary
- Arrays.sort(libCertDigests);
- Arrays.sort(expectedCertDigests);
-
- final int certCount = libCertDigests.length;
- for (int j = 0; j < certCount; j++) {
- if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
- throw new PackageManagerException(
- INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires differently signed" +
- " static shared library; failing!");
- }
- }
- } else {
- // lib signing cert could have rotated beyond the one expected, check to see
- // if the new one has been blessed by the old
- byte[] digestBytes = HexEncoding.decode(
- expectedCertDigests[0], false /* allowSingleChar */);
- if (!libPkg.hasSha256Certificate(digestBytes)) {
- throw new PackageManagerException(
- INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires differently signed" +
- " static shared library; failing!");
- }
- }
- }
- if (outUsedLibraries == null) {
- outUsedLibraries = new ArrayList<>();
- }
- outUsedLibraries.add(libraryInfo);
- }
- }
- return outUsedLibraries;
- }
-
private static boolean hasString(List<String> list, List<String> which) {
if (list == null || which == null) {
return false;
@@ -6754,7 +5240,7 @@
}
@GuardedBy("mLock")
- private ArrayList<AndroidPackage> updateAllSharedLibrariesLocked(
+ ArrayList<AndroidPackage> updateAllSharedLibrariesLocked(
@Nullable AndroidPackage updatedPkg, @Nullable PackageSetting updatedPkgSetting,
Map<String, AndroidPackage> availablePackages) {
ArrayList<AndroidPackage> resultList = null;
@@ -6820,130 +5306,6 @@
return resultList;
}
- /**
- * Commits the package scan and modifies system state.
- * <p><em>WARNING:</em> The method may throw an excpetion in the middle
- * of committing the package, leaving the system in an inconsistent state.
- * This needs to be fixed so, once we get to this point, no errors are
- * possible and the system is not left in an inconsistent state.
- */
- @GuardedBy({"mLock", "mInstallLock"})
- AndroidPackage commitReconciledScanResultLocked(
- @NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
- final ScanResult result = reconciledPkg.mScanResult;
- final ScanRequest request = result.mRequest;
- // TODO(b/135203078): Move this even further away
- ParsedPackage parsedPackage = request.mParsedPackage;
- if ("android".equals(parsedPackage.getPackageName())) {
- // TODO(b/135203078): Move this to initial parse
- parsedPackage.setVersionCode(mSdkVersion)
- .setVersionCodeMajor(0);
- }
-
- final AndroidPackage oldPkg = request.mOldPkg;
- final @ParseFlags int parseFlags = request.mParseFlags;
- final @ScanFlags int scanFlags = request.mScanFlags;
- final PackageSetting oldPkgSetting = request.mOldPkgSetting;
- final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
- final UserHandle user = request.mUser;
- final String realPkgName = request.mRealPkgName;
- final List<String> changedAbiCodePath = result.mChangedAbiCodePath;
- final PackageSetting pkgSetting;
- if (request.mPkgSetting != null && request.mPkgSetting.getSharedUser() != null
- && request.mPkgSetting.getSharedUser() != result.mPkgSetting.getSharedUser()) {
- // shared user changed, remove from old shared user
- request.mPkgSetting.getSharedUser().removePackage(request.mPkgSetting);
- }
- if (result.mExistingSettingCopied) {
- pkgSetting = request.mPkgSetting;
- pkgSetting.updateFrom(result.mPkgSetting);
- } else {
- pkgSetting = result.mPkgSetting;
- if (originalPkgSetting != null) {
- mSettings.addRenamedPackageLPw(
- AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
- originalPkgSetting.getPackageName());
- mTransferredPackages.add(originalPkgSetting.getPackageName());
- } else {
- mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
- }
- }
- if (pkgSetting.getSharedUser() != null) {
- pkgSetting.getSharedUser().addPackage(pkgSetting);
- }
- if (reconciledPkg.mInstallArgs != null
- && reconciledPkg.mInstallArgs.mForceQueryableOverride) {
- pkgSetting.setForceQueryableOverride(true);
- }
-
- // If this is part of a standard install, set the initiating package name, else rely on
- // previous device state.
- if (reconciledPkg.mInstallArgs != null) {
- InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource;
- if (installSource.initiatingPackageName != null) {
- final PackageSetting ips = mSettings.getPackageLPr(
- installSource.initiatingPackageName);
- if (ips != null) {
- installSource = installSource.setInitiatingPackageSignatures(
- ips.getSignatures());
- }
- }
- pkgSetting.setInstallSource(installSource);
- }
-
- // TODO(toddke): Consider a method specifically for modifying the Package object
- // post scan; or, moving this stuff out of the Package object since it has nothing
- // to do with the package on disk.
- // We need to have this here because addUserToSettingLPw() is sometimes responsible
- // for creating the application ID. If we did this earlier, we would be saving the
- // correct ID.
- parsedPackage.setUid(pkgSetting.getAppId());
- final AndroidPackage pkg = parsedPackage.hideAsFinal();
-
- mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting);
-
- if (realPkgName != null) {
- mTransferredPackages.add(pkg.getPackageName());
- }
-
- if (reconciledPkg.mCollectedSharedLibraryInfos != null) {
- executeSharedLibrariesUpdateLPr(pkg, pkgSetting, null, null,
- reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
- }
-
- final KeySetManagerService ksms = mSettings.getKeySetManagerService();
- if (reconciledPkg.mRemoveAppKeySetData) {
- ksms.removeAppKeySetDataLPw(pkg.getPackageName());
- }
- if (reconciledPkg.mSharedUserSignaturesChanged) {
- pkgSetting.getSharedUser().signaturesChanged = Boolean.TRUE;
- pkgSetting.getSharedUser().signatures.mSigningDetails = reconciledPkg.mSigningDetails;
- }
- pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
-
- if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
- for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
- final String codePathString = changedAbiCodePath.get(i);
- try {
- mInstaller.rmdex(codePathString,
- getDexCodeInstructionSet(getPreferredInstructionSet()));
- } catch (InstallerException ignored) {
- }
- }
- }
-
- final int userId = user == null ? 0 : user.getIdentifier();
- // Modify state for the given package setting
- commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
- (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
- if (pkgSetting.getInstantApp(userId)) {
- mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
- }
- pkgSetting.setStatesOnCommit();
-
- return pkg;
- }
-
@GuardedBy("mLock")
private void addBuiltInSharedLibraryLocked(SystemConfig.SharedLibraryEntry entry) {
if (nonStaticSharedLibExistsLocked(entry.name)) {
@@ -6961,17 +5323,12 @@
@GuardedBy("mLock")
private boolean nonStaticSharedLibExistsLocked(String name) {
- return sharedLibExists(name, SharedLibraryInfo.VERSION_UNDEFINED, mSharedLibraries);
- }
-
- private static boolean sharedLibExists(final String name, final long version,
- Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
- WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
- return versionedLib != null && versionedLib.indexOfKey(version) >= 0;
+ return SharedLibraryHelper.sharedLibExists(name, SharedLibraryInfo.VERSION_UNDEFINED,
+ mSharedLibraries);
}
@GuardedBy("mLock")
- private void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) {
+ void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) {
final String name = libraryInfo.getName();
WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
if (versionedLib == null) {
@@ -6985,44 +5342,6 @@
versionedLib.put(libraryInfo.getLongVersion(), libraryInfo);
}
- boolean removeSharedLibraryLPw(String name, long version) {
- WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
- if (versionedLib == null) {
- return false;
- }
- final int libIdx = versionedLib.indexOfKey(version);
- if (libIdx < 0) {
- return false;
- }
- SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
-
- // Remove the shared library overlays from its dependent packages.
- for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
- final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
- libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
- if (dependents == null) {
- continue;
- }
- for (VersionedPackage dependentPackage : dependents) {
- final PackageSetting ps = mSettings.getPackageLPr(
- dependentPackage.getPackageName());
- if (ps != null) {
- ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
- }
- }
- }
-
- versionedLib.remove(version);
- if (versionedLib.size() <= 0) {
- mSharedLibraries.remove(name);
- if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) {
- mStaticLibsByDeclaringPackage.remove(libraryInfo.getDeclaringPackage()
- .getPackageName());
- }
- }
- return true;
- }
-
@Override
public Property getProperty(String propertyName, String packageName, String className) {
Objects.requireNonNull(propertyName);
@@ -7054,201 +5373,6 @@
return new ParceledListSlice<>(result);
}
- /**
- * Adds a scanned package to the system. When this method is finished, the package will
- * be available for query, resolution, etc...
- */
- private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
- @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
- final @ScanFlags int scanFlags, boolean chatty, ReconciledPackage reconciledPkg) {
- final String pkgName = pkg.getPackageName();
- if (mCustomResolverComponentName != null &&
- mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
- setUpCustomResolverActivity(pkg, pkgSetting);
- }
-
- if (pkg.getPackageName().equals("android")) {
- synchronized (mLock) {
- // Set up information for our fall-back user intent resolution activity.
- mPlatformPackage = pkg;
-
- // The instance stored in PackageManagerService is special cased to be non-user
- // specific, so initialize all the needed fields here.
- mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
- PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
-
- if (!mResolverReplaced) {
- mResolveActivity.applicationInfo = mAndroidApplication;
- mResolveActivity.name = ResolverActivity.class.getName();
- mResolveActivity.packageName = mAndroidApplication.packageName;
- mResolveActivity.processName = "system:ui";
- mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
- mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
- mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
- mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
- mResolveActivity.exported = true;
- mResolveActivity.enabled = true;
- mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
- mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
- | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
- | ActivityInfo.CONFIG_SCREEN_LAYOUT
- | ActivityInfo.CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_KEYBOARD
- | ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
- mResolveInfo.activityInfo = mResolveActivity;
- mResolveInfo.priority = 0;
- mResolveInfo.preferredOrder = 0;
- mResolveInfo.match = 0;
- mResolveComponentName = new ComponentName(
- mAndroidApplication.packageName, mResolveActivity.name);
- }
- onChanged();
- }
- }
-
- ArrayList<AndroidPackage> clientLibPkgs = null;
- // writer
- synchronized (mLock) {
- if (!ArrayUtils.isEmpty(reconciledPkg.mAllowedSharedLibraryInfos)) {
- for (SharedLibraryInfo info : reconciledPkg.mAllowedSharedLibraryInfos) {
- commitSharedLibraryInfoLocked(info);
- }
- final Map<String, AndroidPackage> combinedSigningDetails =
- reconciledPkg.getCombinedAvailablePackages();
- try {
- // Shared libraries for the package need to be updated.
- updateSharedLibrariesLocked(pkg, pkgSetting, null, null,
- combinedSigningDetails);
- } catch (PackageManagerException e) {
- Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e);
- }
- // Update all applications that use this library. Skip when booting
- // since this will be done after all packages are scaned.
- if ((scanFlags & SCAN_BOOTING) == 0) {
- clientLibPkgs = updateAllSharedLibrariesLocked(pkg, pkgSetting,
- combinedSigningDetails);
- }
- }
- }
- if (reconciledPkg.mInstallResult != null) {
- reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs;
- }
-
- if ((scanFlags & SCAN_BOOTING) != 0) {
- // No apps can run during boot scan, so they don't need to be frozen
- } else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) {
- // Caller asked to not kill app, so it's probably not frozen
- } else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) {
- // Caller asked us to ignore frozen check for some reason; they
- // probably didn't know the package name
- } else {
- // We're doing major surgery on this package, so it better be frozen
- // right now to keep it from launching
- checkPackageFrozen(pkgName);
- }
-
- // Also need to kill any apps that are dependent on the library.
- if (clientLibPkgs != null) {
- for (int i=0; i<clientLibPkgs.size(); i++) {
- AndroidPackage clientPkg = clientLibPkgs.get(i);
- killApplication(clientPkg.getPackageName(),
- clientPkg.getUid(), "update lib");
- }
- }
-
- // writer
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
-
- synchronized (mLock) {
- // We don't expect installation to fail beyond this point
- // Add the new setting to mSettings
- mSettings.insertPackageSettingLPw(pkgSetting, pkg);
- // Add the new setting to mPackages
- mPackages.put(pkg.getPackageName(), pkg);
- if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
- mApexManager.registerApkInApex(pkg);
- }
-
- // Add the package's KeySets to the global KeySetManagerService
- KeySetManagerService ksms = mSettings.getKeySetManagerService();
- ksms.addScannedPackageLPw(pkg);
-
- mComponentResolver.addAllComponents(pkg, chatty);
- final boolean isReplace =
- reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
- mAppsFilter.addPackage(pkgSetting, isReplace);
- mPackageProperty.addAllProperties(pkg);
-
- if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
- mDomainVerificationManager.addPackage(pkgSetting);
- } else {
- mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
- }
-
- int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
- StringBuilder r = null;
- int i;
- for (i = 0; i < collectionSize; i++) {
- ParsedInstrumentation a = pkg.getInstrumentations().get(i);
- a.setPackageName(pkg.getPackageName());
- mInstrumentation.put(a.getComponentName(), a);
- if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append(a.getName());
- }
- }
- if (r != null) {
- if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Instrumentation: " + r);
- }
-
- final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
- if (!protectedBroadcasts.isEmpty()) {
- synchronized (mProtectedBroadcasts) {
- mProtectedBroadcasts.addAll(protectedBroadcasts);
- }
- }
-
- mPermissionManager.onPackageAdded(pkg, (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
- }
-
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
-
- private void setUpCustomResolverActivity(AndroidPackage pkg, PackageSetting pkgSetting) {
- synchronized (mLock) {
- mResolverReplaced = true;
-
- // The instance created in PackageManagerService is special cased to be non-user
- // specific, so initialize all the needed fields here.
- ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
- PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
-
- // Set up information for custom user intent resolution activity.
- mResolveActivity.applicationInfo = appInfo;
- mResolveActivity.name = mCustomResolverComponentName.getClassName();
- mResolveActivity.packageName = pkg.getPackageName();
- mResolveActivity.processName = pkg.getProcessName();
- mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
- mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
- ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
- mResolveActivity.theme = 0;
- mResolveActivity.exported = true;
- mResolveActivity.enabled = true;
- mResolveInfo.activityInfo = mResolveActivity;
- mResolveInfo.priority = 0;
- mResolveInfo.preferredOrder = 0;
- mResolveInfo.match = 0;
- mResolveComponentName = mCustomResolverComponentName;
- onChanged();
- Slog.i(TAG, "Replacing default ResolverActivity with custom activity: " +
- mResolveComponentName);
- }
- }
-
private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) {
if (installerActivity == null) {
if (DEBUG_INSTANT) {
@@ -7278,7 +5402,7 @@
onChanged();
}
- private void killApplication(String pkgName, @AppIdInt int appId, String reason) {
+ void killApplication(String pkgName, @AppIdInt int appId, String reason) {
killApplication(pkgName, appId, UserHandle.USER_ALL, reason);
}
@@ -7366,7 +5490,7 @@
}
}
- private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
+ void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
int userId, int dataLoaderType) {
final boolean isSystem = PackageManagerServiceUtils.isSystemApp(pkgSetting)
|| PackageManagerServiceUtils.isUpdatedSystemApp(pkgSetting);
@@ -7652,136 +5776,10 @@
@Override
public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
int installReason, List<String> whiteListedPermissions) {
- return installExistingPackageAsUser(packageName, userId, installFlags, installReason,
- whiteListedPermissions, null);
- }
-
- int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
- @PackageManager.InstallFlags int installFlags,
- @PackageManager.InstallReason int installReason,
- @Nullable List<String> allowlistedRestrictedPermissions,
- @Nullable IntentSender intentSender) {
- if (DEBUG_INSTALL) {
- Log.v(TAG, "installExistingPackageAsUser package=" + packageName + " userId=" + userId
- + " installFlags=" + installFlags + " installReason=" + installReason
- + " allowlistedRestrictedPermissions=" + allowlistedRestrictedPermissions);
- }
-
- final int callingUid = Binder.getCallingUid();
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
- != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.INSTALL_EXISTING_PACKAGES)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Neither user " + callingUid + " nor current process has "
- + android.Manifest.permission.INSTALL_PACKAGES + ".");
- }
- PackageSetting pkgSetting;
- enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
- true /* checkShell */, "installExistingPackage for user " + userId);
- if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
- return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
- }
-
- final long callingId = Binder.clearCallingIdentity();
- try {
- boolean installed = false;
- final boolean instantApp =
- (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
- final boolean fullApp =
- (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
-
- // writer
- synchronized (mLock) {
- pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null) {
- return PackageManager.INSTALL_FAILED_INVALID_URI;
- }
- if (!canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
- // only allow the existing package to be used if it's installed as a full
- // application for at least one user
- boolean installAllowed = false;
- for (int checkUserId : mUserManager.getUserIds()) {
- installAllowed = !pkgSetting.getInstantApp(checkUserId);
- if (installAllowed) {
- break;
- }
- }
- if (!installAllowed) {
- return PackageManager.INSTALL_FAILED_INVALID_URI;
- }
- }
- if (!pkgSetting.getInstalled(userId)) {
- pkgSetting.setInstalled(true, userId);
- pkgSetting.setHidden(false, userId);
- pkgSetting.setInstallReason(installReason, userId);
- pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId);
- mSettings.writePackageRestrictionsLPr(userId);
- mSettings.writeKernelMappingLPr(pkgSetting);
- installed = true;
- } else if (fullApp && pkgSetting.getInstantApp(userId)) {
- // upgrade app from instant to full; we don't allow app downgrade
- installed = true;
- }
- setInstantAppForUser(mInjector, pkgSetting, userId, instantApp, fullApp);
- }
-
- if (installed) {
- if (pkgSetting.getPkg() != null) {
- final PermissionManagerServiceInternal.PackageInstalledParams.Builder
- permissionParamsBuilder =
- new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
- if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS)
- != 0) {
- permissionParamsBuilder.setAllowlistedRestrictedPermissions(
- pkgSetting.getPkg().getRequestedPermissions());
- }
- mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
- Process.INVALID_UID /* previousAppId */,
- permissionParamsBuilder.build(), userId);
- }
-
- if (pkgSetting.getPkg() != null) {
- synchronized (mInstallLock) {
- // We don't need to freeze for a brand new install
- mAppDataHelper.prepareAppDataAfterInstallLIF(pkgSetting.getPkg());
- }
- }
- sendPackageAddedForUser(packageName, pkgSetting, userId, DataLoaderType.NONE);
- synchronized (mLock) {
- updateSequenceNumberLP(pkgSetting, new int[]{ userId });
- }
- // start async restore with no post-install since we finish install here
- PackageInstalledInfo res = new PackageInstalledInfo(
- PackageManager.INSTALL_SUCCEEDED);
- res.mPkg = pkgSetting.getPkg();
- res.mNewUsers = new int[]{ userId };
-
- PostInstallData postInstallData =
- new PostInstallData(null, res, () -> {
- restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
- userId);
- if (intentSender != null) {
- onRestoreComplete(res.mReturnCode, mContext, intentSender);
- }
- });
- restoreAndPostInstall(userId, res, postInstallData);
- }
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
-
- return PackageManager.INSTALL_SUCCEEDED;
- }
-
- static void onRestoreComplete(int returnCode, Context context, IntentSender target) {
- Intent fillIn = new Intent();
- fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
- PackageManager.installStatusToPublicStatus(returnCode));
- try {
- target.sendIntent(context, 0, fillIn, null, null);
- } catch (SendIntentException ignored) {
- }
+ final InstallPackageHelper installPackageHelper = new InstallPackageHelper(
+ this, mAppDataHelper);
+ return installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+ installReason, whiteListedPermissions, null);
}
static void setInstantAppForUser(PackageManagerServiceInjector injector,
@@ -8231,7 +6229,7 @@
final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId);
final long callingId = Binder.clearCallingIdentity();
try {
- final String activeLauncherPackageName = mDefaultAppProvider.getDefaultHome(userId);
+ final String activeLauncherPackageName = getActiveLauncherPackageName(userId);
final String dialerPackageName = mDefaultAppProvider.getDefaultDialer(userId);
for (int i = 0; i < packageNames.length; i++) {
canSuspend[i] = false;
@@ -8358,7 +6356,8 @@
@Override
public void finishPackageInstall(int token, boolean didLaunch) {
- enforceSystemOrRoot("Only the system is allowed to finish installs");
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "Only the system is allowed to finish installs");
if (DEBUG_INSTALL) {
Slog.v(TAG, "BM finishing package install for " + token);
@@ -8418,24 +6417,12 @@
ArrayList<IntentFilter> result = new ArrayList<>();
for (int n=0; n<count; n++) {
ParsedActivity activity = pkg.getActivities().get(n);
- if (activity.getIntents() != null && activity.getIntents().size() > 0) {
- result.addAll(activity.getIntents());
+ List<ParsedIntentInfo> intentInfos = activity.getIntents();
+ for (int index = 0; index < intentInfos.size(); index++) {
+ result.add(new IntentFilter(intentInfos.get(index).getIntentFilter()));
}
}
- return new ParceledListSlice<IntentFilter>(result) {
- @Override
- protected void writeElement(IntentFilter parcelable, Parcel dest, int callFlags) {
- parcelable.writeToParcel(dest, callFlags);
- }
-
- @Override
- protected void writeParcelableCreator(IntentFilter parcelable, Parcel dest) {
- // All Parcel#writeParcelableCreator does is serialize the class name to
- // access via reflection to grab its CREATOR. This does that manually, pointing
- // to the parent IntentFilter so that all of the subclass fields are ignored.
- dest.writeString(IntentFilter.class.getName());
- }
- };
+ return new ParceledListSlice<IntentFilter>(result);
}
}
@@ -8570,144 +6557,6 @@
}
}
- /** @param data Post-install is performed only if this is non-null. */
- void restoreAndPostInstall(
- int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
- if (DEBUG_INSTALL) {
- Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg);
- }
-
- // A restore should be requested at this point if (a) the install
- // succeeded, (b) the operation is not an update.
- final boolean update = res.mRemovedInfo != null
- && res.mRemovedInfo.mRemovedPackage != null;
- boolean doRestore = !update && res.mPkg != null;
-
- // Set up the post-install work request bookkeeping. This will be used
- // and cleaned up by the post-install event handling regardless of whether
- // there's a restore pass performed. Token values are >= 1.
- int token;
- if (mNextInstallToken < 0) mNextInstallToken = 1;
- token = mNextInstallToken++;
- if (data != null) {
- mRunningInstalls.put(token, data);
- } else if (DEBUG_INSTALL) {
- Log.v(TAG, "No post-install required for " + token);
- }
-
- if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
-
- if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
- // Pass responsibility to the Backup Manager. It will perform a
- // restore if appropriate, then pass responsibility back to the
- // Package Manager to run the post-install observer callbacks
- // and broadcasts.
- if (res.mFreezer != null) {
- res.mFreezer.close();
- }
- doRestore = performBackupManagerRestore(userId, token, res);
- }
-
- // If this is an update to a package that might be potentially downgraded, then we
- // need to check with the rollback manager whether there's any userdata that might
- // need to be snapshotted or restored for the package.
- //
- // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
- if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
- doRestore = performRollbackManagerRestore(userId, token, res, data);
- }
-
- if (!doRestore) {
- // No restore possible, or the Backup Manager was mysteriously not
- // available -- just fire the post-install work request directly.
- if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
-
- Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
-
- Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
- mHandler.sendMessage(msg);
- }
- }
-
- /**
- * Perform Backup Manager restore for a given {@link PackageInstalledInfo}.
- * Returns whether the restore successfully completed.
- */
- private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- if (bm != null) {
- // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
- // in the BackupManager. USER_ALL is used in compatibility tests.
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- }
- if (DEBUG_INSTALL) {
- Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId);
- }
- Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
- try {
- if (bm.isUserReadyForBackup(userId)) {
- bm.restoreAtInstallForUser(
- userId, res.mPkg.getPackageName(), token);
- } else {
- Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
- + "didn't take place.");
- return false;
- }
- } catch (RemoteException e) {
- // can't happen; the backup manager is local
- } catch (Exception e) {
- Slog.e(TAG, "Exception trying to enqueue restore", e);
- return false;
- }
- } else {
- Slog.e(TAG, "Backup Manager not found!");
- return false;
- }
- return true;
- }
-
- /**
- * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}.
- * Returns whether the restore successfully completed.
- */
- private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
- PostInstallData data) {
- RollbackManagerInternal rm = mInjector.getLocalService(RollbackManagerInternal.class);
-
- final String packageName = res.mPkg.getPackageName();
- final int[] allUsers = mUserManager.getUserIds();
- final int[] installedUsers;
-
- final PackageSetting ps;
- int appId = -1;
- long ceDataInode = -1;
- synchronized (mLock) {
- ps = mSettings.getPackageLPr(packageName);
- if (ps != null) {
- appId = ps.getAppId();
- ceDataInode = ps.getCeDataInode(userId);
- }
-
- // NOTE: We ignore the user specified in the InstallParam because we know this is
- // an update, and hence need to restore data for all installed users.
- installedUsers = ps.queryInstalledUsers(allUsers, true);
- }
-
- boolean doSnapshotOrRestore = data != null && data.args != null
- && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
- || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
-
- if (ps != null && doSnapshotOrRestore) {
- final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
- rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
- appId, ceDataInode, seInfo, token);
- return true;
- }
- return false;
- }
-
/**
* Callback from PackageSettings whenever an app is first transitioned out of the
* 'stopped' state. Normally we just issue the broadcast, but we can't do that if
@@ -8756,343 +6605,7 @@
});
}
- /**
- * Create args that describe an existing installed package. Typically used
- * when cleaning up old installs, or used as a move source.
- */
- InstallArgs createInstallArgsForExisting(String codePath, String[] instructionSets) {
- return new FileInstallArgs(codePath, instructionSets, this);
- }
-
- @GuardedBy("mLock")
- Map<String, ReconciledPackage> reconcilePackagesLocked(
- final ReconcileRequest request, KeySetManagerService ksms,
- PackageManagerServiceInjector injector)
- throws ReconcileFailure {
- final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
-
- final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
-
- // make a copy of the existing set of packages so we can combine them with incoming packages
- final ArrayMap<String, AndroidPackage> combinedPackages =
- new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
-
- combinedPackages.putAll(request.mAllPackages);
-
- final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
- new ArrayMap<>();
-
- for (String installPackageName : scannedPackages.keySet()) {
- final ScanResult scanResult = scannedPackages.get(installPackageName);
-
- // add / replace existing with incoming packages
- combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
- scanResult.mRequest.mParsedPackage);
-
- // in the first pass, we'll build up the set of incoming shared libraries
- final List<SharedLibraryInfo> allowedSharedLibInfos =
- getAllowedSharedLibInfos(scanResult, request.mSharedLibrarySource);
- final SharedLibraryInfo staticLib = scanResult.mStaticSharedLibraryInfo;
- if (allowedSharedLibInfos != null) {
- for (SharedLibraryInfo info : allowedSharedLibInfos) {
- if (!addSharedLibraryToPackageVersionMap(incomingSharedLibraries, info)) {
- throw new ReconcileFailure("Static Shared Library " + staticLib.getName()
- + " is being installed twice in this set!");
- }
- }
- }
-
- // the following may be null if we're just reconciling on boot (and not during install)
- final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
- final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
- final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
- final boolean isInstall = installArgs != null;
- if (isInstall && (res == null || prepareResult == null)) {
- throw new ReconcileFailure("Reconcile arguments are not balanced for "
- + installPackageName + "!");
- }
-
- final DeletePackageAction deletePackageAction;
- // we only want to try to delete for non system apps
- if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
- final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
- final int deleteFlags = PackageManager.DELETE_KEEP_DATA
- | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
- deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
- prepareResult.mOriginalPs, prepareResult.mDisabledPs,
- deleteFlags, null /* all users */);
- if (deletePackageAction == null) {
- throw new ReconcileFailure(
- PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
- "May not delete " + installPackageName + " to replace");
- }
- } else {
- deletePackageAction = null;
- }
-
- final int scanFlags = scanResult.mRequest.mScanFlags;
- final int parseFlags = scanResult.mRequest.mParseFlags;
- final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-
- final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
- final PackageSetting lastStaticSharedLibSetting =
- request.mLastStaticSharedLibSettings.get(installPackageName);
- final PackageSetting signatureCheckPs =
- (prepareResult != null && lastStaticSharedLibSetting != null)
- ? lastStaticSharedLibSetting
- : scanResult.mPkgSetting;
- boolean removeAppKeySetData = false;
- boolean sharedUserSignaturesChanged = false;
- SigningDetails signingDetails = null;
- if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
- if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
- // We just determined the app is signed correctly, so bring
- // over the latest parsed certs.
- } else {
- if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
- throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
- "Package " + parsedPackage.getPackageName()
- + " upgrade keys do not match the previously installed"
- + " version");
- } else {
- String msg = "System package " + parsedPackage.getPackageName()
- + " signature changed; retaining data.";
- reportSettingsProblem(Log.WARN, msg);
- }
- }
- signingDetails = parsedPackage.getSigningDetails();
- } else {
- try {
- final VersionInfo versionInfo = request.mVersionInfos.get(installPackageName);
- final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
- final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
- final boolean isRollback = installArgs != null
- && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
- final boolean compatMatch = verifySignatures(signatureCheckPs,
- disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
- compareRecover, isRollback);
- // The new KeySets will be re-added later in the scanning process.
- if (compatMatch) {
- removeAppKeySetData = true;
- }
- // We just determined the app is signed correctly, so bring
- // over the latest parsed certs.
- signingDetails = parsedPackage.getSigningDetails();
-
- // if this is is a sharedUser, check to see if the new package is signed by a
- // newer
- // signing certificate than the existing one, and if so, copy over the new
- // details
- if (signatureCheckPs.getSharedUser() != null) {
- // Attempt to merge the existing lineage for the shared SigningDetails with
- // the lineage of the new package; if the shared SigningDetails are not
- // returned this indicates the new package added new signers to the lineage
- // and/or changed the capabilities of existing signers in the lineage.
- SigningDetails sharedSigningDetails =
- signatureCheckPs.getSharedUser().signatures.mSigningDetails;
- SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
- signingDetails);
- if (mergedDetails != sharedSigningDetails) {
- signatureCheckPs.getSharedUser().signatures.mSigningDetails =
- mergedDetails;
- }
- if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
- signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
- }
- }
- } catch (PackageManagerException e) {
- if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
- throw new ReconcileFailure(e);
- }
- signingDetails = parsedPackage.getSigningDetails();
-
- // If the system app is part of a shared user we allow that shared user to
- // change
- // signatures as well as part of an OTA. We still need to verify that the
- // signatures
- // are consistent within the shared user for a given boot, so only allow
- // updating
- // the signatures on the first package scanned for the shared user (i.e. if the
- // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
- if (signatureCheckPs.getSharedUser() != null) {
- final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
- .signatures.mSigningDetails.getSignatures();
- if (signatureCheckPs.getSharedUser().signaturesChanged != null
- && compareSignatures(sharedUserSignatures,
- parsedPackage.getSigningDetails().getSignatures())
- != PackageManager.SIGNATURE_MATCH) {
- if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
- // Mismatched signatures is an error and silently skipping system
- // packages will likely break the device in unforeseen ways.
- // However, we allow the device to boot anyway because, prior to Q,
- // vendors were not expecting the platform to crash in this
- // situation.
- // This WILL be a hard failure on any new API levels after Q.
- throw new ReconcileFailure(
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
- "Signature mismatch for shared user: "
- + scanResult.mPkgSetting.getSharedUser());
- } else {
- // Treat mismatched signatures on system packages using a shared
- // UID as
- // fatal for the system overall, rather than just failing to install
- // whichever package happened to be scanned later.
- throw new IllegalStateException(
- "Signature mismatch on system package "
- + parsedPackage.getPackageName()
- + " for shared user "
- + scanResult.mPkgSetting.getSharedUser());
- }
- }
-
- sharedUserSignaturesChanged = true;
- signatureCheckPs.getSharedUser().signatures.mSigningDetails =
- parsedPackage.getSigningDetails();
- signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
- }
- // File a report about this.
- String msg = "System package " + parsedPackage.getPackageName()
- + " signature changed; retaining data.";
- reportSettingsProblem(Log.WARN, msg);
- } catch (IllegalArgumentException e) {
- // should never happen: certs matched when checking, but not when comparing
- // old to new for sharedUser
- throw new RuntimeException(
- "Signing certificates comparison made on incomparable signing details"
- + " but somehow passed verifySignatures!", e);
- }
- }
-
- result.put(installPackageName,
- new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
- res, request.mPreparedPackages.get(installPackageName), scanResult,
- deletePackageAction, allowedSharedLibInfos, signingDetails,
- sharedUserSignaturesChanged, removeAppKeySetData));
- }
-
- for (String installPackageName : scannedPackages.keySet()) {
- // Check all shared libraries and map to their actual file path.
- // We only do this here for apps not on a system dir, because those
- // are the only ones that can fail an install due to this. We
- // will take care of the system apps by updating all of their
- // library paths after the scan is done. Also during the initial
- // scan don't update any libs as we do this wholesale after all
- // apps are scanned to avoid dependency based scanning.
- final ScanResult scanResult = scannedPackages.get(installPackageName);
- if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
- || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
- != 0) {
- continue;
- }
- try {
- result.get(installPackageName).mCollectedSharedLibraryInfos =
- collectSharedLibraryInfos(scanResult.mRequest.mParsedPackage,
- combinedPackages, request.mSharedLibrarySource,
- incomingSharedLibraries, injector.getCompatibility());
-
- } catch (PackageManagerException e) {
- throw new ReconcileFailure(e.error, e.getMessage());
- }
- }
-
- return result;
- }
-
- /**
- * Compare the newly scanned package with current system state to see which of its declared
- * shared libraries should be allowed to be added to the system.
- */
- private static List<SharedLibraryInfo> getAllowedSharedLibInfos(
- ScanResult scanResult,
- Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
- // Let's used the parsed package as scanResult.pkgSetting may be null
- final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
- if (scanResult.mStaticSharedLibraryInfo == null
- && scanResult.mDynamicSharedLibraryInfos == null) {
- return null;
- }
-
- // Any app can add new static shared libraries
- if (scanResult.mStaticSharedLibraryInfo != null) {
- return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
- }
- final boolean hasDynamicLibraries = parsedPackage.isSystem()
- && scanResult.mDynamicSharedLibraryInfos != null;
- if (!hasDynamicLibraries) {
- return null;
- }
- final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
- .isUpdatedSystemApp();
- // We may not yet have disabled the updated package yet, so be sure to grab the
- // current setting if that's the case.
- final PackageSetting updatedSystemPs = isUpdatedSystemApp
- ? scanResult.mRequest.mDisabledPkgSetting == null
- ? scanResult.mRequest.mOldPkgSetting
- : scanResult.mRequest.mDisabledPkgSetting
- : null;
- if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
- || updatedSystemPs.getPkg().getLibraryNames() == null)) {
- Slog.w(TAG, "Package " + parsedPackage.getPackageName()
- + " declares libraries that are not declared on the system image; skipping");
- return null;
- }
- final ArrayList<SharedLibraryInfo> infos =
- new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
- for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
- final String name = info.getName();
- if (isUpdatedSystemApp) {
- // New library entries can only be added through the
- // system image. This is important to get rid of a lot
- // of nasty edge cases: for example if we allowed a non-
- // system update of the app to add a library, then uninstalling
- // the update would make the library go away, and assumptions
- // we made such as through app install filtering would now
- // have allowed apps on the device which aren't compatible
- // with it. Better to just have the restriction here, be
- // conservative, and create many fewer cases that can negatively
- // impact the user experience.
- if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) {
- Slog.w(TAG, "Package " + parsedPackage.getPackageName()
- + " declares library " + name
- + " that is not declared on system image; skipping");
- continue;
- }
- }
- if (sharedLibExists(
- name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
- Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library "
- + name + " that already exists; skipping");
- continue;
- }
- infos.add(info);
- }
- return infos;
- }
-
- /**
- * Returns false if the adding shared library already exists in the map and so could not be
- * added.
- */
- private static boolean addSharedLibraryToPackageVersionMap(
- Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
- SharedLibraryInfo library) {
- final String name = library.getName();
- if (target.containsKey(name)) {
- if (library.getType() != SharedLibraryInfo.TYPE_STATIC) {
- // We've already added this non-version-specific library to the map.
- return false;
- } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) {
- // We've already added this version of a version-specific library to the map.
- return false;
- }
- } else {
- target.put(name, new WatchedLongSparseArray<>());
- }
- target.get(name).put(library.getLongVersion(), library);
- return true;
- }
-
- @Nullable PackageSetting getPackageSettingForUser(String packageName, int callingUid,
+ private @Nullable PackageSetting getPackageSettingForUser(String packageName, int callingUid,
int userId) {
final PackageSetting ps;
synchronized (mLock) {
@@ -9155,34 +6668,8 @@
@Override
public void deleteExistingPackageAsUser(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.DELETE_PACKAGES, null);
- Preconditions.checkNotNull(versionedPackage);
- Preconditions.checkNotNull(observer);
- final String packageName = versionedPackage.getPackageName();
- final long versionCode = versionedPackage.getLongVersionCode();
-
- int installedForUsersCount = 0;
- synchronized (mLock) {
- // Normalize package name to handle renamed packages and static libs
- final String internalPkgName = resolveInternalPackageNameLPr(packageName, versionCode);
- final PackageSetting ps = mSettings.getPackageLPr(internalPkgName);
- if (ps != null) {
- int[] installedUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
- installedForUsersCount = installedUsers.length;
- }
- }
-
- if (installedForUsersCount > 1) {
- mDeletePackageHelper.deletePackageVersionedInternal(
- versionedPackage, observer, userId, 0, true);
- } else {
- try {
- observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
- null);
- } catch (RemoteException re) {
- }
- }
+ mDeletePackageHelper.deleteExistingPackageAsUser(
+ versionedPackage, observer, userId);
}
@Override
@@ -9266,22 +6753,6 @@
return mDevicePolicyManager;
}
- boolean shouldKeepUninstalledPackageLPr(String packageName) {
- return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
- }
-
- private static @Nullable ScanPartition resolveApexToScanPartition(
- ApexManager.ActiveApexInfo apexInfo) {
- for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
- ScanPartition sp = SYSTEM_PARTITIONS.get(i);
- if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
- sp.getFolder().getAbsolutePath())) {
- return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
- }
- }
- return null;
- }
-
@Override
public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall,
int userId) {
@@ -9317,7 +6788,8 @@
@Override
public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp) {
- enforceSystemOrRoot("setRequiredForSystemUser can only be run by the system or root");
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "setRequiredForSystemUser can only be run by the system or root");
synchronized (mLock) {
PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps == null) {
@@ -9336,7 +6808,8 @@
@Override
public void clearApplicationProfileData(String packageName) {
- enforceSystemOrRoot("Only the system can clear all profile data");
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "Only the system can clear all profile data");
final AndroidPackage pkg;
synchronized (mLock) {
@@ -9458,10 +6931,6 @@
return true;
}
- private void resetNetworkPolicies(int userId) {
- mInjector.getLocalService(NetworkPolicyManagerInternal.class).resetUserState(userId);
- }
-
/**
* Remove entries from the keystore daemon. Will only remove it if the
* {@code appId} is valid.
@@ -9561,7 +7030,7 @@
}
@GuardedBy("mLock")
- private int getUidTargetSdkVersionLockedLPr(int uid) {
+ int getUidTargetSdkVersionLockedLPr(int uid) {
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingLPr(appId);
if (obj instanceof SharedUserSetting) {
@@ -9597,57 +7066,11 @@
@Override
public void addPreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity, int userId, boolean removeExisting) {
- addPreferredActivity(new WatchedIntentFilter(filter), match, set, activity, true, userId,
+ mPreferredActivityHelper.addPreferredActivity(
+ new WatchedIntentFilter(filter), match, set, activity, true, userId,
"Adding preferred", removeExisting);
}
- /**
- * Variant that takes a {@link WatchedIntentFilter}
- */
- public void addPreferredActivity(WatchedIntentFilter filter, int match,
- ComponentName[] set, ComponentName activity, boolean always, int userId,
- String opname, boolean removeExisting) {
- // writer
- int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
- false /* checkShell */, "add preferred activity");
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
- != PackageManager.PERMISSION_GRANTED) {
- synchronized (mLock) {
- if (getUidTargetSdkVersionLockedLPr(callingUid)
- < Build.VERSION_CODES.FROYO) {
- Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
- + callingUid);
- return;
- }
- }
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
- }
- if (filter.countActions() == 0) {
- Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
- return;
- }
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, opname + " activity " + activity.flattenToShortString() + " for user "
- + userId + ":");
- filter.dump(new LogPrinter(Log.INFO, TAG), " ");
- }
- synchronized (mLock) {
- final PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId);
- final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
- if (removeExisting && existing != null) {
- Settings.removeFilters(pir, filter, existing);
- }
- pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
- scheduleWritePackageRestrictionsLocked(userId);
- }
- if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
- postPreferredActivityChangedBroadcast(userId);
- }
- }
-
void postPreferredActivityChangedBroadcast(int userId) {
mHandler.post(() -> mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId));
}
@@ -9655,139 +7078,15 @@
@Override
public void replacePreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity, int userId) {
- replacePreferredActivity(new WatchedIntentFilter(filter), match,
+ mPreferredActivityHelper.replacePreferredActivity(new WatchedIntentFilter(filter), match,
set, activity, userId);
}
- /**
- * Variant that takes a {@link WatchedIntentFilter}
- */
- public void replacePreferredActivity(WatchedIntentFilter filter, int match,
- ComponentName[] set, ComponentName activity, int userId) {
- if (filter.countActions() != 1) {
- throw new IllegalArgumentException(
- "replacePreferredActivity expects filter to have only 1 action.");
- }
- if (filter.countDataAuthorities() != 0
- || filter.countDataPaths() != 0
- || filter.countDataSchemes() > 1
- || filter.countDataTypes() != 0) {
- throw new IllegalArgumentException(
- "replacePreferredActivity expects filter to have no data authorities, " +
- "paths, or types; and at most one scheme.");
- }
-
- final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
- false /* checkShell */, "replace preferred activity");
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
- != PackageManager.PERMISSION_GRANTED) {
- synchronized (mLock) {
- if (getUidTargetSdkVersionLockedLPr(callingUid)
- < Build.VERSION_CODES.FROYO) {
- Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
- + Binder.getCallingUid());
- return;
- }
- }
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
- }
-
- synchronized (mLock) {
- final PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
- if (pir != null) {
- // Get all of the existing entries that exactly match this filter.
- final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
- if (existing != null && existing.size() == 1) {
- final PreferredActivity cur = existing.get(0);
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Checking replace of preferred:");
- filter.dump(new LogPrinter(Log.INFO, TAG), " ");
- if (!cur.mPref.mAlways) {
- Slog.i(TAG, " -- CUR; not mAlways!");
- } else {
- Slog.i(TAG, " -- CUR: mMatch=" + cur.mPref.mMatch);
- Slog.i(TAG, " -- CUR: mSet="
- + Arrays.toString(cur.mPref.mSetComponents));
- Slog.i(TAG, " -- CUR: mComponent=" + cur.mPref.mShortComponent);
- Slog.i(TAG, " -- NEW: mMatch="
- + (match&IntentFilter.MATCH_CATEGORY_MASK));
- Slog.i(TAG, " -- CUR: mSet=" + Arrays.toString(set));
- Slog.i(TAG, " -- CUR: mComponent=" + activity.flattenToShortString());
- }
- }
- if (cur.mPref.mAlways && cur.mPref.mComponent.equals(activity)
- && cur.mPref.mMatch == (match&IntentFilter.MATCH_CATEGORY_MASK)
- && cur.mPref.sameSet(set)) {
- // Setting the preferred activity to what it happens to be already
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Replacing with same preferred activity "
- + cur.mPref.mShortComponent + " for user "
- + userId + ":");
- filter.dump(new LogPrinter(Log.INFO, TAG), " ");
- }
- return;
- }
- }
- if (existing != null) {
- Settings.removeFilters(pir, filter, existing);
- }
- }
- }
- addPreferredActivity(filter, match, set, activity, true, userId,
- "Replacing preferred", false);
- }
-
@Override
public void clearPackagePreferredActivities(String packageName) {
- final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return;
- }
- // writer
- synchronized (mLock) {
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg == null || !isCallerSameApp(packageName, callingUid)) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
- != PackageManager.PERMISSION_GRANTED) {
- if (getUidTargetSdkVersionLockedLPr(callingUid)
- < Build.VERSION_CODES.FROYO) {
- Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid "
- + callingUid);
- return;
- }
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
- }
- }
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null
- && shouldFilterApplicationLocked(
- ps, callingUid, UserHandle.getUserId(callingUid))) {
- return;
- }
- }
- int callingUserId = UserHandle.getCallingUserId();
- clearPackagePreferredActivities(packageName, callingUserId);
+ mPreferredActivityHelper.clearPackagePreferredActivities(packageName);
}
- /** This method takes a specific user id as well as UserHandle.USER_ALL. */
- void clearPackagePreferredActivities(String packageName, int userId) {
- final SparseBooleanArray changedUsers = new SparseBooleanArray();
- synchronized (mLock) {
- clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
- }
- if (changedUsers.size() > 0) {
- updateDefaultHomeNotLocked(changedUsers);
- postPreferredActivityChangedBroadcast(userId);
- synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
- }
/** This method takes a specific user id as well as UserHandle.USER_ALL. */
@GuardedBy("mLock")
@@ -9804,185 +7103,31 @@
// Persistent preferred activity might have came into effect due to this
// install.
- updateDefaultHomeNotLocked(userId);
+ mPreferredActivityHelper.updateDefaultHomeNotLocked(userId);
}
@Override
public void resetApplicationPreferences(int userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
- final long identity = Binder.clearCallingIdentity();
- // writer
- try {
- final SparseBooleanArray changedUsers = new SparseBooleanArray();
- synchronized (mLock) {
- clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
- }
- if (changedUsers.size() > 0) {
- postPreferredActivityChangedBroadcast(userId);
- }
- synchronized (mLock) {
- mSettings.applyDefaultPreferredAppsLPw(userId);
- mDomainVerificationManager.clearUser(userId);
- final int numPackages = mPackages.size();
- for (int i = 0; i < numPackages; i++) {
- final AndroidPackage pkg = mPackages.valueAt(i);
- mPermissionManager.resetRuntimePermissions(pkg, userId);
- }
- }
- updateDefaultHomeNotLocked(userId);
- resetNetworkPolicies(userId);
- synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ mPreferredActivityHelper.resetApplicationPreferences(userId);
}
@Override
public int getPreferredActivities(List<IntentFilter> outFilters,
List<ComponentName> outActivities, String packageName) {
- List<WatchedIntentFilter> temp =
- WatchedIntentFilter.toWatchedIntentFilterList(outFilters);
- final int result = getPreferredActivitiesInternal(
- temp, outActivities, packageName);
- outFilters.clear();
- for (int i = 0; i < temp.size(); i++) {
- outFilters.add(temp.get(i).getIntentFilter());
- }
- return result;
- }
-
- /**
- * Variant that takes a {@link WatchedIntentFilter}
- */
- public int getPreferredActivitiesInternal(List<WatchedIntentFilter> outFilters,
- List<ComponentName> outActivities, String packageName) {
- final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return 0;
- }
- int num = 0;
- final int userId = UserHandle.getCallingUserId();
- // reader
- synchronized (mLock) {
- PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
- if (pir != null) {
- final Iterator<PreferredActivity> it = pir.filterIterator();
- while (it.hasNext()) {
- final PreferredActivity pa = it.next();
- final String prefPackageName = pa.mPref.mComponent.getPackageName();
- if (packageName == null
- || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) {
- if (shouldFilterApplicationLocked(
- mSettings.getPackageLPr(prefPackageName), callingUid, userId)) {
- continue;
- }
- if (outFilters != null) {
- outFilters.add(new WatchedIntentFilter(pa.getIntentFilter()));
- }
- if (outActivities != null) {
- outActivities.add(pa.mPref.mComponent);
- }
- }
- }
- }
- }
-
- return num;
+ return mPreferredActivityHelper.getPreferredActivities(outFilters, outActivities,
+ packageName);
}
@Override
public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
int userId) {
- addPersistentPreferredActivity(new WatchedIntentFilter(filter), activity, userId);
- }
-
- /**
- * Variant that takes a {@link WatchedIntentFilter}
- */
- public void addPersistentPreferredActivity(WatchedIntentFilter filter, ComponentName activity,
- int userId) {
- int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID) {
- throw new SecurityException(
- "addPersistentPreferredActivity can only be run by the system");
- }
- if (filter.countActions() == 0) {
- Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
- return;
- }
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Adding persistent preferred activity " + activity
- + " for user " + userId + ":");
- filter.dump(new LogPrinter(Log.INFO, TAG), " ");
- }
- synchronized (mLock) {
- mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
- new PersistentPreferredActivity(filter, activity, true));
- scheduleWritePackageRestrictionsLocked(userId);
- }
- if (isHomeFilter(filter)) {
- updateDefaultHomeNotLocked(userId);
- }
- postPreferredActivityChangedBroadcast(userId);
+ mPreferredActivityHelper.addPersistentPreferredActivity(new WatchedIntentFilter(filter),
+ activity, userId);
}
@Override
public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
- int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID) {
- throw new SecurityException(
- "clearPackagePersistentPreferredActivities can only be run by the system");
- }
- boolean changed = false;
- synchronized (mLock) {
- changed = mSettings.clearPackagePersistentPreferredActivities(packageName, userId);
- }
- if (changed) {
- updateDefaultHomeNotLocked(userId);
- postPreferredActivityChangedBroadcast(userId);
- synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
- }
-
- /**
- * Common machinery for picking apart a restored XML blob and passing
- * it to a caller-supplied functor to be applied to the running system.
- */
- private void restoreFromXml(TypedXmlPullParser parser, int userId,
- String expectedStartTag, BlobXmlRestorer functor)
- throws IOException, XmlPullParserException {
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- }
- if (type != XmlPullParser.START_TAG) {
- // oops didn't find a start tag?!
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Didn't find start tag during restore");
- }
- return;
- }
- // this is supposed to be TAG_PREFERRED_BACKUP
- if (!expectedStartTag.equals(parser.getName())) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Found unexpected tag " + parser.getName());
- }
- return;
- }
-
- // skip interfering stuff, then we're aligned with the backing implementation
- while ((type = parser.next()) == XmlPullParser.TEXT) { }
- functor.apply(parser, userId);
- }
-
- private interface BlobXmlRestorer {
- void apply(TypedXmlPullParser parser, int userId)
- throws IOException, XmlPullParserException;
+ mPreferredActivityHelper.clearPackagePersistentPreferredActivities(packageName, userId);
}
/**
@@ -9992,55 +7137,12 @@
*/
@Override
public byte[] getPreferredActivityBackup(int userId) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system may call getPreferredActivityBackup()");
- }
-
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- try {
- final TypedXmlSerializer serializer = Xml.newFastSerializer();
- serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_PREFERRED_BACKUP);
-
- synchronized (mLock) {
- mSettings.writePreferredActivitiesLPr(serializer, userId, true);
- }
-
- serializer.endTag(null, TAG_PREFERRED_BACKUP);
- serializer.endDocument();
- serializer.flush();
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Unable to write preferred activities for backup", e);
- }
- return null;
- }
-
- return dataStream.toByteArray();
+ return mPreferredActivityHelper.getPreferredActivityBackup(userId);
}
@Override
public void restorePreferredActivities(byte[] backup, int userId) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system may call restorePreferredActivities()");
- }
-
- try {
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
- restoreFromXml(parser, userId, TAG_PREFERRED_BACKUP,
- (readParser, readUserId) -> {
- synchronized (mLock) {
- mSettings.readPreferredActivitiesLPw(readParser, readUserId);
- }
- updateDefaultHomeNotLocked(readUserId);
- });
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
- }
- }
+ mPreferredActivityHelper.restorePreferredActivities(backup, userId);
}
/**
@@ -10050,59 +7152,12 @@
*/
@Override
public byte[] getDefaultAppsBackup(int userId) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system may call getDefaultAppsBackup()");
- }
-
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- try {
- final TypedXmlSerializer serializer = Xml.newFastSerializer();
- serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_DEFAULT_APPS);
-
- synchronized (mLock) {
- mSettings.writeDefaultAppsLPr(serializer, userId);
- }
-
- serializer.endTag(null, TAG_DEFAULT_APPS);
- serializer.endDocument();
- serializer.flush();
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Unable to write default apps for backup", e);
- }
- return null;
- }
-
- return dataStream.toByteArray();
+ return mPreferredActivityHelper.getDefaultAppsBackup(userId);
}
@Override
public void restoreDefaultApps(byte[] backup, int userId) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system may call restoreDefaultApps()");
- }
-
- try {
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
- restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
- (parser1, userId1) -> {
- final String defaultBrowser;
- synchronized (mLock) {
- mSettings.readDefaultAppsLPw(parser1, userId1);
- defaultBrowser = mSettings.removeDefaultBrowserPackageNameLPw(userId1);
- }
- if (defaultBrowser != null) {
- mDefaultAppProvider.setDefaultBrowser(defaultBrowser, false, userId1);
- }
- });
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Exception restoring default apps: " + e.getMessage());
- }
- }
+ mPreferredActivityHelper.restoreDefaultApps(backup, userId);
}
@Override
@@ -10257,114 +7312,19 @@
return mComputer.getDefaultHomeActivity(userId);
}
- private Intent getHomeIntent() {
+ Intent getHomeIntent() {
return mComputer.getHomeIntent();
}
- private WatchedIntentFilter getHomeFilter() {
- WatchedIntentFilter filter = new WatchedIntentFilter(Intent.ACTION_MAIN);
- filter.addCategory(Intent.CATEGORY_HOME);
- filter.addCategory(Intent.CATEGORY_DEFAULT);
- return filter;
- }
-
- private boolean isHomeFilter(@NonNull WatchedIntentFilter filter) {
- return filter.hasAction(Intent.ACTION_MAIN) && filter.hasCategory(Intent.CATEGORY_HOME)
- && filter.hasCategory(CATEGORY_DEFAULT);
- }
-
ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
int userId) {
return mComputer.getHomeActivitiesAsUser(allHomeCandidates,
userId);
}
- /** <b>must not hold {@link #mLock}</b> */
- void updateDefaultHomeNotLocked(SparseBooleanArray userIds) {
- if (Thread.holdsLock(mLock)) {
- Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
- + " is holding mLock", new Throwable());
- }
- for (int i = userIds.size() - 1; i >= 0; --i) {
- final int userId = userIds.keyAt(i);
- updateDefaultHomeNotLocked(userId);
- }
- }
-
- /**
- * <b>must not hold {@link #mLock}</b>
- *
- * @return Whether the ACTION_PREFERRED_ACTIVITY_CHANGED broadcast has been scheduled.
- */
- private boolean updateDefaultHomeNotLocked(int userId) {
- if (Thread.holdsLock(mLock)) {
- Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
- + " is holding mLock", new Throwable());
- }
- if (!mSystemReady) {
- // We might get called before system is ready because of package changes etc, but
- // finding preferred activity depends on settings provider, so we ignore the update
- // before that.
- return false;
- }
- final Intent intent = getHomeIntent();
- final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
- final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
- intent, null, 0, resolveInfos, true, false, false, userId);
- final String packageName = preferredResolveInfo != null
- && preferredResolveInfo.activityInfo != null
- ? preferredResolveInfo.activityInfo.packageName : null;
- final String currentPackageName = mDefaultAppProvider.getDefaultHome(userId);
- if (TextUtils.equals(currentPackageName, packageName)) {
- return false;
- }
- final String[] callingPackages = getPackagesForUid(Binder.getCallingUid());
- if (callingPackages != null && ArrayUtils.contains(callingPackages,
- mRequiredPermissionControllerPackage)) {
- // PermissionController manages default home directly.
- return false;
- }
-
- if (packageName == null) {
- // Keep the default home package in RoleManager.
- return false;
- }
- return mDefaultAppProvider.setDefaultHome(packageName, userId, mContext.getMainExecutor(),
- successful -> {
- if (successful) {
- postPreferredActivityChangedBroadcast(userId);
- }
- });
- }
-
@Override
public void setHomeActivity(ComponentName comp, int userId) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return;
- }
- ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
- getHomeActivitiesAsUser(homeActivities, userId);
-
- boolean found = false;
-
- final int size = homeActivities.size();
- final ComponentName[] set = new ComponentName[size];
- for (int i = 0; i < size; i++) {
- final ResolveInfo candidate = homeActivities.get(i);
- final ActivityInfo info = candidate.activityInfo;
- final ComponentName activityName = new ComponentName(info.packageName, info.name);
- set[i] = activityName;
- if (!found && activityName.equals(comp)) {
- found = true;
- }
- }
- if (!found) {
- throw new IllegalArgumentException("Component " + comp + " cannot be home on user "
- + userId);
- }
- replacePreferredActivity(getHomeFilter(), IntentFilter.MATCH_CATEGORY_EMPTY,
- set, comp, userId);
+ mPreferredActivityHelper.setHomeActivity(comp, userId);
}
private @Nullable String getSetupWizardPackageNameImpl() {
@@ -10847,8 +7807,7 @@
if (isSystemStub
&& (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
- if (!mInitAndSystemPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting,
- mDefParseFlags, mDirsToScanAsSystem)) {
+ if (!mInitAndSystemPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) {
Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable "
+ "commpressed package " + setting.getPackageName());
updateAllowed[i] = false;
@@ -11304,7 +8263,8 @@
@Override
public void enterSafeMode() {
- enforceSystemOrRoot("Only the system can request entering safe mode");
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "Only the system can request entering safe mode");
if (!mSystemReady) {
mSafeMode = true;
@@ -11313,7 +8273,8 @@
@Override
public void systemReady() {
- enforceSystemOrRoot("Only the system can claim the system is ready");
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "Only the system can claim the system is ready");
final ContentResolver resolver = mContext.getContentResolver();
if (mReleaseOnSystemReady != null) {
@@ -11468,8 +8429,13 @@
mPerUidReadTimeoutsCache = null;
}
});
+
+ mBackgroundDexOptService.systemReady();
}
+ /**
+ * Used by SystemServer
+ */
public void waitForAppDataPrepared() {
if (mPrepareAppDataFuture == null) {
return;
@@ -11502,632 +8468,22 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+ new DumpHelper(this).doDump(fd, pw, args);
+ }
- DumpState dumpState = new DumpState();
- ArraySet<String> permissionNames = null;
-
- int opti = 0;
- while (opti < args.length) {
- String opt = args[opti];
- if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
- break;
- }
- opti++;
-
- if ("-a".equals(opt)) {
- // Right now we only know how to print all.
- } else if ("-h".equals(opt)) {
- pw.println("Package manager dump options:");
- pw.println(" [-h] [-f] [--checkin] [--all-components] [cmd] ...");
- pw.println(" --checkin: dump for a checkin");
- pw.println(" -f: print details of intent filters");
- pw.println(" -h: print this help");
- pw.println(" --all-components: include all component names in package dump");
- pw.println(" cmd may be one of:");
- pw.println(" apex: list active APEXes and APEX session state");
- pw.println(" l[ibraries]: list known shared libraries");
- pw.println(" f[eatures]: list device features");
- pw.println(" k[eysets]: print known keysets");
- pw.println(" r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
- pw.println(" perm[issions]: dump permissions");
- pw.println(" permission [name ...]: dump declaration and use of given permission");
- pw.println(" pref[erred]: print preferred package settings");
- pw.println(" preferred-xml [--full]: print preferred package settings as xml");
- pw.println(" prov[iders]: dump content providers");
- pw.println(" p[ackages]: dump installed packages");
- pw.println(" q[ueries]: dump app queryability calculations");
- pw.println(" s[hared-users]: dump shared user IDs");
- pw.println(" m[essages]: print collected runtime messages");
- pw.println(" v[erifiers]: print package verifier info");
- pw.println(" d[omain-preferred-apps]: print domains preferred apps");
- pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
- pw.println(" t[imeouts]: print read timeouts for known digesters");
- pw.println(" version: print database version info");
- pw.println(" write: write current settings now");
- pw.println(" installs: details about install sessions");
- pw.println(" check-permission <permission> <package> [<user>]: does pkg hold perm?");
- pw.println(" dexopt: dump dexopt state");
- pw.println(" compiler-stats: dump compiler statistics");
- pw.println(" service-permissions: dump permissions required by services");
- pw.println(" snapshot: dump snapshot statistics");
- pw.println(" protected-broadcasts: print list of protected broadcast actions");
- pw.println(" known-packages: dump known packages");
- pw.println(" <package.name>: info about given package");
- return;
- } else if ("--checkin".equals(opt)) {
- dumpState.setCheckIn(true);
- } else if ("--all-components".equals(opt)) {
- dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
- } else if ("-f".equals(opt)) {
- dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
- } else if ("--proto".equals(opt)) {
- dumpProto(fd);
- return;
- } else {
- pw.println("Unknown argument: " + opt + "; use -h for help");
- }
- }
-
- // Is the caller requesting to dump a particular piece of data?
- if (opti < args.length) {
- String cmd = args[opti];
- opti++;
- // Is this a package name?
- if ("android".equals(cmd) || cmd.contains(".")) {
- dumpState.setTargetPackageName(cmd);
- // When dumping a single package, we always dump all of its
- // filter information since the amount of data will be reasonable.
- dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
- } else if ("check-permission".equals(cmd)) {
- if (opti >= args.length) {
- pw.println("Error: check-permission missing permission argument");
- return;
- }
- String perm = args[opti];
- opti++;
- if (opti >= args.length) {
- pw.println("Error: check-permission missing package argument");
- return;
- }
-
- String pkg = args[opti];
- opti++;
- int user = UserHandle.getUserId(Binder.getCallingUid());
- if (opti < args.length) {
- try {
- user = Integer.parseInt(args[opti]);
- } catch (NumberFormatException e) {
- pw.println("Error: check-permission user argument is not a number: "
- + args[opti]);
- return;
- }
- }
-
- // Normalize package name to handle renamed packages and static libs
- pkg = resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
-
- pw.println(checkPermission(perm, pkg, user));
- return;
- } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_LIBS);
- } else if ("f".equals(cmd) || "features".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_FEATURES);
- } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
- if (opti >= args.length) {
- dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
- | DumpState.DUMP_SERVICE_RESOLVERS
- | DumpState.DUMP_RECEIVER_RESOLVERS
- | DumpState.DUMP_CONTENT_RESOLVERS);
- } else {
- while (opti < args.length) {
- String name = args[opti];
- if ("a".equals(name) || "activity".equals(name)) {
- dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
- } else if ("s".equals(name) || "service".equals(name)) {
- dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
- } else if ("r".equals(name) || "receiver".equals(name)) {
- dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
- } else if ("c".equals(name) || "content".equals(name)) {
- dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
- } else {
- pw.println("Error: unknown resolver table type: " + name);
- return;
- }
- opti++;
- }
- }
- } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PERMISSIONS);
- } else if ("permission".equals(cmd)) {
- if (opti >= args.length) {
- pw.println("Error: permission requires permission name");
- return;
- }
- permissionNames = new ArraySet<>();
- while (opti < args.length) {
- permissionNames.add(args[opti]);
- opti++;
- }
- dumpState.setDump(DumpState.DUMP_PERMISSIONS
- | DumpState.DUMP_PACKAGES | DumpState.DUMP_SHARED_USERS);
- } else if ("pref".equals(cmd) || "preferred".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PREFERRED);
- } else if ("preferred-xml".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
- if (opti < args.length && "--full".equals(args[opti])) {
- dumpState.setFullPreferred(true);
- opti++;
- }
- } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED);
- } else if ("p".equals(cmd) || "packages".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PACKAGES);
- } else if ("q".equals(cmd) || "queries".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_QUERIES);
- } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_SHARED_USERS);
- if (opti < args.length && "noperm".equals(args[opti])) {
- dumpState.setOptionEnabled(DumpState.OPTION_SKIP_PERMISSIONS);
- }
- } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PROVIDERS);
- } else if ("m".equals(cmd) || "messages".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_MESSAGES);
- } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_VERIFIERS);
- } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
- } else if ("version".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_VERSION);
- } else if ("k".equals(cmd) || "keysets".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_KEYSETS);
- } else if ("installs".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_INSTALLS);
- } else if ("frozen".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_FROZEN);
- } else if ("volumes".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_VOLUMES);
- } else if ("dexopt".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_DEXOPT);
- } else if ("compiler-stats".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
- } else if ("changes".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_CHANGES);
- } else if ("service-permissions".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
- } else if ("known-packages".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
- } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
- } else if ("snapshot".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
- if (opti < args.length) {
- if ("--full".equals(args[opti])) {
- dumpState.setBrief(false);
- opti++;
- } else if ("--brief".equals(args[opti])) {
- dumpState.setBrief(true);
- opti++;
- }
- }
- } else if ("protected-broadcasts".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_PROTECTED_BROADCASTS);
- } else if ("write".equals(cmd)) {
- synchronized (mLock) {
- writeSettingsLPrTEMP();
- pw.println("Settings written.");
- return;
+ void dumpSnapshotStats(PrintWriter pw, boolean isBrief) {
+ if (!mSnapshotEnabled) {
+ pw.println(" Snapshots disabled");
+ } else {
+ int hits = 0;
+ int level = sSnapshotCorked.get();
+ synchronized (mSnapshotLock) {
+ if (mSnapshotComputer != null) {
+ hits = mSnapshotComputer.getUsed();
}
}
- }
-
- final String packageName = dumpState.getTargetPackageName();
- final boolean checkin = dumpState.isCheckIn();
-
- // Return if the package doesn't exist.
- if (packageName != null
- && getPackageSetting(packageName) == null
- && !mApexManager.isApexPackage(packageName)) {
- pw.println("Unable to find package: " + packageName);
- return;
- }
-
- if (checkin) {
- pw.println("vers,1");
- }
-
- // reader
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_VERSION)
- && packageName == null) {
- dump(DumpState.DUMP_VERSION, fd, pw, dumpState);
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
- ipw.println("Known Packages:");
- ipw.increaseIndent();
- for (int i = 0; i <= LAST_KNOWN_PACKAGE; i++) {
- final String knownPackage = PackageManagerInternal.knownPackageToString(i);
- ipw.print(knownPackage);
- ipw.println(":");
- final String[] pkgNames = mPmInternal.getKnownPackageNames(i,
- UserHandle.USER_SYSTEM);
- ipw.increaseIndent();
- if (ArrayUtils.isEmpty(pkgNames)) {
- ipw.println("none");
- } else {
- for (String name : pkgNames) {
- ipw.println(name);
- }
- }
- ipw.decreaseIndent();
- }
- ipw.decreaseIndent();
- }
-
- if (dumpState.isDumping(DumpState.DUMP_VERIFIERS)
- && packageName == null) {
- final String requiredVerifierPackage = mRequiredVerifierPackage;
- if (!checkin) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- pw.println("Verifiers:");
- pw.print(" Required: ");
- pw.print(requiredVerifierPackage);
- pw.print(" (uid=");
- pw.print(getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.USER_SYSTEM));
- pw.println(")");
- } else if (requiredVerifierPackage != null) {
- pw.print("vrfy,"); pw.print(requiredVerifierPackage);
- pw.print(",");
- pw.println(getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.USER_SYSTEM));
- }
- }
-
- if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER)
- && packageName == null) {
- final DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
- final ComponentName verifierComponent = proxy.getComponentName();
- if (verifierComponent != null) {
- String verifierPackageName = verifierComponent.getPackageName();
- if (!checkin) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- pw.println("Domain Verifier:");
- pw.print(" Using: ");
- pw.print(verifierPackageName);
- pw.print(" (uid=");
- pw.print(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.USER_SYSTEM));
- pw.println(")");
- } else if (verifierPackageName != null) {
- pw.print("dv,"); pw.print(verifierPackageName);
- pw.print(",");
- pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.USER_SYSTEM));
- }
- } else {
- pw.println();
- pw.println("No Domain Verifier available!");
- }
- }
-
- if (dumpState.isDumping(DumpState.DUMP_LIBS)
- && packageName == null) {
- dump(DumpState.DUMP_LIBS, fd, pw, dumpState);
- }
-
- if (dumpState.isDumping(DumpState.DUMP_FEATURES)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- if (!checkin) {
- pw.println("Features:");
- }
-
- synchronized (mAvailableFeatures) {
- for (FeatureInfo feat : mAvailableFeatures.values()) {
- if (!checkin) {
- pw.print(" ");
- pw.print(feat.name);
- if (feat.version > 0) {
- pw.print(" version=");
- pw.print(feat.version);
- }
- pw.println();
- } else {
- pw.print("feat,");
- pw.print(feat.name);
- pw.print(",");
- pw.println(feat.version);
- }
- }
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
- synchronized (mLock) {
- mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
- }
- }
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
- synchronized (mLock) {
- mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
- }
- }
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
- synchronized (mLock) {
- mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
- }
- }
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
- synchronized (mLock) {
- mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
- dump(DumpState.DUMP_PREFERRED, fd, pw, dumpState);
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)
- && packageName == null) {
- dump(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState);
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
- dump(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState);
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
- synchronized (mLock) {
- mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
- synchronized (mLock) {
- mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
- synchronized (mLock) {
- mSettings.getKeySetManagerService().dumpLPr(pw, packageName, dumpState);
- }
- }
-
- if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
- // This cannot be moved to ComputerEngine since some variables of the collections
- // in PackageUserState such as suspendParams, disabledComponents and enabledComponents
- // do not have a copy.
- synchronized (mLock) {
- mSettings.dumpPackagesLPr(pw, packageName, permissionNames, dumpState, checkin);
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_QUERIES)) {
- dump(DumpState.DUMP_QUERIES, fd, pw, dumpState);
- }
-
- if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
- // This cannot be moved to ComputerEngine since the set of packages in the
- // SharedUserSetting do not have a copy.
- synchronized (mLock) {
- mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState, checkin);
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_CHANGES)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- pw.println("Package Changes:");
- synchronized (mLock) {
- pw.print(" Sequence number="); pw.println(mChangedPackagesSequenceNumber);
- final int K = mChangedPackages.size();
- for (int i = 0; i < K; i++) {
- final SparseArray<String> changes = mChangedPackages.valueAt(i);
- pw.print(" User "); pw.print(mChangedPackages.keyAt(i)); pw.println(":");
- final int N = changes.size();
- if (N == 0) {
- pw.print(" "); pw.println("No packages changed");
- } else {
- for (int j = 0; j < N; j++) {
- final String pkgName = changes.valueAt(j);
- final int sequenceNumber = changes.keyAt(j);
- pw.print(" ");
- pw.print("seq=");
- pw.print(sequenceNumber);
- pw.print(", package=");
- pw.println(pkgName);
- }
- }
- }
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_FROZEN)
- && packageName == null) {
- // XXX should handle packageName != null by dumping only install data that
- // the given package is involved with.
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
- ipw.println();
- ipw.println("Frozen packages:");
- ipw.increaseIndent();
- synchronized (mLock) {
- if (mFrozenPackages.size() == 0) {
- ipw.println("(none)");
- } else {
- for (int i = 0; i < mFrozenPackages.size(); i++) {
- ipw.println(mFrozenPackages.valueAt(i));
- }
- }
- }
- ipw.decreaseIndent();
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_VOLUMES)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
- ipw.println();
- ipw.println("Loaded volumes:");
- ipw.increaseIndent();
- synchronized (mLoadedVolumes) {
- if (mLoadedVolumes.size() == 0) {
- ipw.println("(none)");
- } else {
- for (int i = 0; i < mLoadedVolumes.size(); i++) {
- ipw.println(mLoadedVolumes.valueAt(i));
- }
- }
- }
- ipw.decreaseIndent();
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS)
- && packageName == null) {
- synchronized (mLock) {
- mComponentResolver.dumpServicePermissions(pw, dumpState);
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_DEXOPT)) {
- dump(DumpState.DUMP_DEXOPT, fd, pw, dumpState);
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) {
- dump(DumpState.DUMP_COMPILER_STATS, fd, pw, dumpState);
- }
-
- if (dumpState.isDumping(DumpState.DUMP_MESSAGES)
- && packageName == null) {
- if (!checkin) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- synchronized (mLock) {
- mSettings.dumpReadMessagesLPr(pw, dumpState);
- }
- pw.println();
- pw.println("Package warning messages:");
- dumpCriticalInfo(pw, null);
- } else {
- dumpCriticalInfo(pw, "msg,");
- }
- }
-
- // PackageInstaller should be called outside of mPackages lock
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_INSTALLS)
- && packageName == null) {
- // XXX should handle packageName != null by dumping only install data that
- // the given package is involved with.
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- mInstallerService.dump(new IndentingPrintWriter(pw, " ", 120));
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_APEX)
- && (packageName == null || mApexManager.isApexPackage(packageName))) {
- mApexManager.dump(pw, packageName);
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PER_UID_READ_TIMEOUTS)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- pw.println("Per UID read timeouts:");
- pw.println(" Default timeouts flag: " + getDefaultTimeouts());
- pw.println(" Known digesters list flag: " + getKnownDigestersList());
-
- PerUidReadTimeouts[] items = getPerUidReadTimeouts();
- pw.println(" Timeouts (" + items.length + "):");
- for (PerUidReadTimeouts item : items) {
- pw.print(" (");
- pw.print("uid=" + item.uid + ", ");
- pw.print("minTimeUs=" + item.minTimeUs + ", ");
- pw.print("minPendingTimeUs=" + item.minPendingTimeUs + ", ");
- pw.print("maxPendingTimeUs=" + item.maxPendingTimeUs);
- pw.println(")");
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- pw.println("Snapshot statistics");
- if (!mSnapshotEnabled) {
- pw.println(" Snapshots disabled");
- } else {
- int hits = 0;
- int level = sSnapshotCorked.get();
- synchronized (mSnapshotLock) {
- if (mSnapshotComputer != null) {
- hits = mSnapshotComputer.getUsed();
- }
- }
- final long now = SystemClock.currentTimeMicro();
- mSnapshotStatistics.dump(pw, " ", now, hits, level, dumpState.isBrief());
- }
- }
-
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PROTECTED_BROADCASTS)
- && packageName == null) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
- }
- pw.println("Protected broadcast actions:");
- synchronized (mProtectedBroadcasts) {
- for (int i = 0; i < mProtectedBroadcasts.size(); i++) {
- pw.print(" ");
- pw.println(mProtectedBroadcasts.valueAt(i));
- }
- }
-
+ final long now = SystemClock.currentTimeMicro();
+ mSnapshotStatistics.dump(pw, " ", now, hits, level, isBrief);
}
}
@@ -12135,7 +8491,7 @@
* Dump package manager states to the file according to a given dumping type of
* {@link DumpState}.
*/
- private void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) {
+ void dumpComputer(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) {
mComputer.dump(type, fd, pw, dumpState);
}
@@ -12160,83 +8516,6 @@
}
}
- private void dumpProto(FileDescriptor fd) {
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
- synchronized (mLock) {
- final long requiredVerifierPackageToken =
- proto.start(PackageServiceDumpProto.REQUIRED_VERIFIER_PACKAGE);
- proto.write(PackageServiceDumpProto.PackageShortProto.NAME, mRequiredVerifierPackage);
- proto.write(
- PackageServiceDumpProto.PackageShortProto.UID,
- getPackageUid(
- mRequiredVerifierPackage,
- MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.USER_SYSTEM));
- proto.end(requiredVerifierPackageToken);
-
- DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
- ComponentName verifierComponent = proxy.getComponentName();
- if (verifierComponent != null) {
- String verifierPackageName = verifierComponent.getPackageName();
- final long verifierPackageToken =
- proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
- proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
- proto.write(
- PackageServiceDumpProto.PackageShortProto.UID,
- getPackageUid(
- verifierPackageName,
- MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.USER_SYSTEM));
- proto.end(verifierPackageToken);
- }
-
- dumpSharedLibrariesProto(proto);
- dumpFeaturesProto(proto);
- mSettings.dumpPackagesProto(proto);
- mSettings.dumpSharedUsersProto(proto);
- dumpCriticalInfo(proto);
- }
- proto.flush();
- }
-
- private void dumpFeaturesProto(ProtoOutputStream proto) {
- synchronized (mAvailableFeatures) {
- final int count = mAvailableFeatures.size();
- for (int i = 0; i < count; i++) {
- mAvailableFeatures.valueAt(i).dumpDebug(proto, PackageServiceDumpProto.FEATURES);
- }
- }
- }
-
- private void dumpSharedLibrariesProto(ProtoOutputStream proto) {
- final int count = mSharedLibraries.size();
- for (int i = 0; i < count; i++) {
- final String libName = mSharedLibraries.keyAt(i);
- WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
- if (versionedLib == null) {
- continue;
- }
- final int versionCount = versionedLib.size();
- for (int j = 0; j < versionCount; j++) {
- final SharedLibraryInfo libraryInfo = versionedLib.valueAt(j);
- final long sharedLibraryToken =
- proto.start(PackageServiceDumpProto.SHARED_LIBRARIES);
- proto.write(PackageServiceDumpProto.SharedLibraryProto.NAME, libraryInfo.getName());
- final boolean isJar = (libraryInfo.getPath() != null);
- proto.write(PackageServiceDumpProto.SharedLibraryProto.IS_JAR, isJar);
- if (isJar) {
- proto.write(PackageServiceDumpProto.SharedLibraryProto.PATH,
- libraryInfo.getPath());
- } else {
- proto.write(PackageServiceDumpProto.SharedLibraryProto.APK,
- libraryInfo.getPackageName());
- }
- proto.end(sharedLibraryToken);
- }
- }
- }
-
public PackageFreezer freezePackage(String packageName, String killReason) {
return freezePackage(packageName, UserHandle.USER_ALL, killReason);
}
@@ -12262,7 +8541,7 @@
/**
* Verify that given package is currently frozen.
*/
- private void checkPackageFrozen(String packageName) {
+ void checkPackageFrozen(String packageName) {
synchronized (mLock) {
if (!mFrozenPackages.contains(packageName)) {
Slog.wtf(TAG, "Expected " + packageName + " to be frozen!", new Throwable());
@@ -12370,60 +8649,8 @@
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
mInstantAppRegistry.onUserRemovedLPw(userId);
- removeUnusedPackagesLPw(userManager, userId);
- }
- }
-
- /**
- * We're removing userId and would like to remove any downloaded packages
- * that are no longer in use by any other user.
- * @param userId the user being removed
- */
- @GuardedBy("mLock")
- private void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
- final boolean DEBUG_CLEAN_APKS = false;
- int [] users = userManager.getUserIds();
- final int numPackages = mSettings.getPackagesLocked().size();
- for (int index = 0; index < numPackages; index++) {
- final PackageSetting ps = mSettings.getPackagesLocked().valueAt(index);
- if (ps.getPkg() == null) {
- continue;
- }
- final String packageName = ps.getPkg().getPackageName();
- // Skip over if system app or static shared library
- if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
- || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())) {
- continue;
- }
- if (DEBUG_CLEAN_APKS) {
- Slog.i(TAG, "Checking package " + packageName);
- }
- boolean keep = shouldKeepUninstalledPackageLPr(packageName);
- if (keep) {
- if (DEBUG_CLEAN_APKS) {
- Slog.i(TAG, " Keeping package " + packageName + " - requested by DO");
- }
- } else {
- for (int i = 0; i < users.length; i++) {
- if (users[i] != userId && ps.getInstalled(users[i])) {
- keep = true;
- if (DEBUG_CLEAN_APKS) {
- Slog.i(TAG, " Keeping package " + packageName + " for user "
- + users[i]);
- }
- break;
- }
- }
- }
- if (!keep) {
- if (DEBUG_CLEAN_APKS) {
- Slog.i(TAG, " Removing package " + packageName);
- }
- //end run
- mHandler.post(() -> mDeletePackageHelper.deletePackageX(
- packageName, PackageManager.VERSION_CODE_HIGHEST,
- userId, 0, true /*removedBySystem*/));
- }
+ mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
+ mAppsFilter.onUserDeleted(userId);
}
}
@@ -12445,7 +8672,7 @@
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
scheduleWritePackageListLocked(userId);
- mAppsFilter.onUsersChanged();
+ mAppsFilter.onUserCreated(userId);
}
}
@@ -12461,7 +8688,7 @@
}
}
- boolean readPermissionStateForUser(@UserIdInt int userId) {
+ private boolean readPermissionStateForUser(@UserIdInt int userId) {
synchronized (mLock) {
mPermissionManager.writeLegacyPermissionStateTEMP();
mSettings.readPermissionStateForUserSyncLPr(userId);
@@ -12511,7 +8738,7 @@
return mArtManagerService;
}
- private boolean userNeedsBadging(int userId) {
+ boolean userNeedsBadging(int userId) {
int index = mUserNeedsBadging.indexOfKey(userId);
if (index < 0) {
final UserInfo userInfo;
@@ -12640,200 +8867,6 @@
}
}
- private final class PackageChangeObserverDeathRecipient implements IBinder.DeathRecipient {
- private final IPackageChangeObserver mObserver;
-
- PackageChangeObserverDeathRecipient(IPackageChangeObserver observer) {
- mObserver = observer;
- }
-
- @Override
- public void binderDied() {
- synchronized (mPackageChangeObservers) {
- mPackageChangeObservers.remove(mObserver);
- Log.d(TAG, "Size of mPackageChangeObservers after removing dead observer is "
- + mPackageChangeObservers.size());
- }
- }
- }
-
- private class PackageManagerNative extends IPackageManagerNative.Stub {
- @Override
- public void registerPackageChangeObserver(@NonNull IPackageChangeObserver observer) {
- synchronized (mPackageChangeObservers) {
- try {
- observer.asBinder().linkToDeath(
- new PackageChangeObserverDeathRecipient(observer), 0);
- } catch (RemoteException e) {
- Log.e(TAG, e.getMessage());
- }
- mPackageChangeObservers.add(observer);
- Log.d(TAG, "Size of mPackageChangeObservers after registry is "
- + mPackageChangeObservers.size());
- }
- }
-
- @Override
- public void unregisterPackageChangeObserver(@NonNull IPackageChangeObserver observer) {
- synchronized (mPackageChangeObservers) {
- mPackageChangeObservers.remove(observer);
- Log.d(TAG, "Size of mPackageChangeObservers after unregistry is "
- + mPackageChangeObservers.size());
- }
- }
-
- @Override
- public String[] getAllPackages() {
- return PackageManagerService.this.getAllPackages().toArray(new String[0]);
- }
-
- @Override
- public String[] getNamesForUids(int[] uids) throws RemoteException {
- String[] names = null;
- String[] results = null;
- try {
- if (uids == null || uids.length == 0) {
- return null;
- }
- names = PackageManagerService.this.getNamesForUids(uids);
- results = (names != null) ? names : new String[uids.length];
- // massage results so they can be parsed by the native binder
- for (int i = results.length - 1; i >= 0; --i) {
- if (results[i] == null) {
- results[i] = "";
- }
- }
- return results;
- } catch (Throwable t) {
- // STOPSHIP(186558987): revert addition of try/catch/log
- Slog.e(TAG, "uids: " + Arrays.toString(uids));
- Slog.e(TAG, "names: " + Arrays.toString(names));
- Slog.e(TAG, "results: " + Arrays.toString(results));
- Slog.e(TAG, "throwing exception", t);
- throw t;
- }
- }
-
- // NB: this differentiates between preloads and sideloads
- @Override
- public String getInstallerForPackage(String packageName) throws RemoteException {
- final String installerName = getInstallerPackageName(packageName);
- if (!TextUtils.isEmpty(installerName)) {
- return installerName;
- }
- // differentiate between preload and sideload
- int callingUser = UserHandle.getUserId(Binder.getCallingUid());
- ApplicationInfo appInfo = getApplicationInfo(packageName,
- /*flags*/ 0,
- /*userId*/ callingUser);
- if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return "preload";
- }
- return "";
- }
-
- @Override
- public long getVersionCodeForPackage(String packageName) throws RemoteException {
- try {
- int callingUser = UserHandle.getUserId(Binder.getCallingUid());
- PackageInfo pInfo = getPackageInfo(packageName, 0, callingUser);
- if (pInfo != null) {
- return pInfo.getLongVersionCode();
- }
- } catch (Exception e) {
- }
- return 0;
- }
-
- @Override
- public int getTargetSdkVersionForPackage(String packageName) throws RemoteException {
- int targetSdk = getTargetSdkVersion(packageName);
- if (targetSdk != -1) {
- return targetSdk;
- }
-
- throw new RemoteException("Couldn't get targetSdkVersion for package " + packageName);
- }
-
- @Override
- public boolean isPackageDebuggable(String packageName) throws RemoteException {
- int callingUser = UserHandle.getCallingUserId();
- ApplicationInfo appInfo = getApplicationInfo(packageName, 0, callingUser);
- if (appInfo != null) {
- return (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
- }
-
- throw new RemoteException("Couldn't get debug flag for package " + packageName);
- }
-
- @Override
- public boolean[] isAudioPlaybackCaptureAllowed(String[] packageNames)
- throws RemoteException {
- int callingUser = UserHandle.getUserId(Binder.getCallingUid());
- boolean[] results = new boolean[packageNames.length];
- for (int i = results.length - 1; i >= 0; --i) {
- ApplicationInfo appInfo = getApplicationInfo(packageNames[i], 0, callingUser);
- results[i] = appInfo != null && appInfo.isAudioPlaybackCaptureAllowed();
- }
- return results;
- }
-
- @Override
- public int getLocationFlags(String packageName) throws RemoteException {
- int callingUser = UserHandle.getUserId(Binder.getCallingUid());
- ApplicationInfo appInfo = getApplicationInfo(packageName,
- /*flags*/ 0,
- /*userId*/ callingUser);
- if (appInfo == null) {
- throw new RemoteException(
- "Couldn't get ApplicationInfo for package " + packageName);
- }
- return ((appInfo.isSystemApp() ? IPackageManagerNative.LOCATION_SYSTEM : 0)
- | (appInfo.isVendor() ? IPackageManagerNative.LOCATION_VENDOR : 0)
- | (appInfo.isProduct() ? IPackageManagerNative.LOCATION_PRODUCT : 0));
- }
-
- @Override
- public String getModuleMetadataPackageName() throws RemoteException {
- return PackageManagerService.this.mModuleInfoProvider.getPackageName();
- }
-
- @Override
- public boolean hasSha256SigningCertificate(String packageName, byte[] certificate)
- throws RemoteException {
- return PackageManagerService.this.hasSigningCertificate(
- packageName, certificate, CERT_INPUT_SHA256);
- }
-
- @Override
- public boolean hasSystemFeature(String featureName, int version) {
- return PackageManagerService.this.hasSystemFeature(featureName, version);
- }
-
- @Override
- public void registerStagedApexObserver(IStagedApexObserver observer) {
- mInstallerService.getStagingManager().registerStagedApexObserver(observer);
- }
-
- @Override
- public void unregisterStagedApexObserver(IStagedApexObserver observer) {
- mInstallerService.getStagingManager().unregisterStagedApexObserver(observer);
- }
-
- @Override
- public String[] getStagedApexModuleNames() {
- return mInstallerService.getStagingManager()
- .getStagedApexModuleNames().toArray(new String[0]);
- }
-
- @Override
- @Nullable
- public StagedApexInfo getStagedApexInfo(String moduleName) {
- return mInstallerService.getStagingManager().getStagedApexInfo(moduleName);
- }
-
- }
-
private AndroidPackage getPackage(String packageName) {
return mComputer.getPackage(packageName);
}
@@ -13107,85 +9140,9 @@
return disabledPkg == null ? null : disabledPkg.getPackageName();
}
- /**
- * Only keep package names that refer to {@link AndroidPackage#isSystem system} packages.
- *
- * @param pkgNames The packages to filter
- *
- * @return The filtered packages
- */
- private @NonNull String[] filterOnlySystemPackages(@Nullable String... pkgNames) {
- if (pkgNames == null) {
- return ArrayUtils.emptyArray(String.class);
- }
-
- ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
-
- for (String pkgName: pkgNames) {
- synchronized (mLock) {
- if (pkgName == null) {
- continue;
- }
-
- AndroidPackage pkg = getPackage(pkgName);
- if (pkg == null) {
- Log.w(TAG, "Could not find package " + pkgName);
- continue;
- }
-
- if (!pkg.isSystem()) {
- Log.w(TAG, pkgName + " is not system");
- continue;
- }
-
- systemPackageNames.add(pkgName);
- }
- }
-
- return systemPackageNames.toArray(new String[]{});
- }
-
@Override
public @NonNull String[] getKnownPackageNames(int knownPackage, int userId) {
- return getKnownPackageNamesInternal(knownPackage, userId);
- }
-
- private String[] getKnownPackageNamesInternal(int knownPackage, int userId) {
- switch (knownPackage) {
- case PackageManagerInternal.PACKAGE_BROWSER:
- return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) };
- case PackageManagerInternal.PACKAGE_INSTALLER:
- return filterOnlySystemPackages(mRequiredInstallerPackage);
- case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
- return filterOnlySystemPackages(mSetupWizardPackage);
- case PackageManagerInternal.PACKAGE_SYSTEM:
- return new String[]{"android"};
- case PackageManagerInternal.PACKAGE_VERIFIER:
- return filterOnlySystemPackages(mRequiredVerifierPackage);
- case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
- return filterOnlySystemPackages(
- mDefaultTextClassifierPackage, mSystemTextClassifierPackageName);
- case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
- return filterOnlySystemPackages(mRequiredPermissionControllerPackage);
- case PackageManagerInternal.PACKAGE_CONFIGURATOR:
- return filterOnlySystemPackages(mConfiguratorPackage);
- case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
- return filterOnlySystemPackages(mIncidentReportApproverPackage);
- case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
- return filterOnlySystemPackages(mAppPredictionServicePackage);
- case PackageManagerInternal.PACKAGE_COMPANION:
- return filterOnlySystemPackages(COMPANION_PACKAGE_NAME);
- case PackageManagerInternal.PACKAGE_RETAIL_DEMO:
- return TextUtils.isEmpty(mRetailDemoPackage)
- ? ArrayUtils.emptyArray(String.class)
- : new String[] {mRetailDemoPackage};
- case PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE:
- return filterOnlySystemPackages(getOverlayConfigSignaturePackageName());
- case PackageManagerInternal.PACKAGE_RECENTS:
- return filterOnlySystemPackages(mRecentsPackage);
- default:
- return ArrayUtils.emptyArray(String.class);
- }
+ return PackageManagerService.this.getKnownPackageNamesInternal(knownPackage, userId);
}
@Override
@@ -13367,8 +9324,8 @@
@Override
public List<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, int flags, int filterCallingUid, int userId) {
- return PackageManagerService.this.queryIntentReceiversInternal(intent, resolvedType,
- flags, userId, filterCallingUid);
+ return PackageManagerService.this.mResolveIntentHelper.queryIntentReceiversInternal(
+ intent, resolvedType, flags, userId, filterCallingUid);
}
@Override
@@ -13403,17 +9360,6 @@
SparseArray<String> profileOwnerPackages) {
mProtectedPackages.setDeviceAndProfileOwnerPackages(
deviceOwnerUserId, deviceOwnerPackage, profileOwnerPackages);
-
- final ArraySet<Integer> usersWithPoOrDo = new ArraySet<>();
- if (deviceOwnerPackage != null) {
- usersWithPoOrDo.add(deviceOwnerUserId);
- }
- final int sz = profileOwnerPackages.size();
- for (int i = 0; i < sz; i++) {
- if (profileOwnerPackages.valueAt(i) != null) {
- usersWithPoOrDo.add(profileOwnerPackages.keyAt(i));
- }
- }
}
@Override
@@ -13661,7 +9607,7 @@
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int privateResolveFlags, int userId, boolean resolveForStart,
int filterCallingUid) {
- return resolveIntentInternal(
+ return mResolveIntentHelper.resolveIntentInternal(
intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart,
filterCallingUid);
}
@@ -13669,7 +9615,8 @@
@Override
public ResolveInfo resolveService(Intent intent, String resolvedType,
int flags, int userId, int callingUid) {
- return resolveServiceInternal(intent, resolvedType, flags, userId, callingUid);
+ return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
+ callingUid);
}
@Override
@@ -14613,7 +10560,8 @@
private void applyMimeGroupChanges(String packageName, String mimeGroup) {
if (mComponentResolver.updateMimeGroup(packageName, mimeGroup)) {
Binder.withCleanCallingIdentity(() ->
- clearPackagePreferredActivities(packageName, UserHandle.USER_ALL));
+ mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
+ UserHandle.USER_ALL));
}
mPmInternal.writeSettings(false);
@@ -14664,7 +10612,7 @@
* Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's
* writeLegacyPermissionsTEMP() beforehand.
*
- * TODO(zhanghai): This should be removed once we finish migration of permission storage.
+ * TODO(b/182523293): This should be removed once we finish migration of permission storage.
*/
void writeSettingsLPrTEMP() {
mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
@@ -14710,7 +10658,7 @@
}
}
- private static String getDefaultTimeouts() {
+ static String getDefaultTimeouts() {
final long token = Binder.clearCallingIdentity();
try {
return DeviceConfig.getString(NAMESPACE_PACKAGE_MANAGER_SERVICE,
@@ -14720,7 +10668,7 @@
}
}
- private static String getKnownDigestersList() {
+ static String getKnownDigestersList() {
final long token = Binder.clearCallingIdentity();
try {
return DeviceConfig.getString(NAMESPACE_PACKAGE_MANAGER_SERVICE,
@@ -14847,54 +10795,15 @@
}
}
+ boolean shouldKeepUninstalledPackageLPr(String packageName) {
+ return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+ }
+
@Override
public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
String featureId, int userId) throws RemoteException {
- Objects.requireNonNull(packageName);
- final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
- false /* checkShell */, "get launch intent sender for package");
- final int packageUid = getPackageUid(callingPackage, 0 /* flags */, userId);
- if (!UserHandle.isSameApp(callingUid, packageUid)) {
- throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
- + callingUid + " does not own package: " + callingPackage);
- }
-
- // Using the same implementation with the #getLaunchIntentForPackage to get the ResolveInfo.
- // Pass the resolveForStart as true in queryIntentActivities to skip the app filtering.
- final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
- intentToResolve.addCategory(Intent.CATEGORY_INFO);
- intentToResolve.setPackage(packageName);
- String resolvedType = intentToResolve.resolveTypeIfNeeded(mContext.getContentResolver());
- List<ResolveInfo> ris = queryIntentActivitiesInternal(intentToResolve, resolvedType,
- 0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
- true /* resolveForStart */, false /* allowDynamicSplits */);
- if (ris == null || ris.size() <= 0) {
- intentToResolve.removeCategory(Intent.CATEGORY_INFO);
- intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
- intentToResolve.setPackage(packageName);
- resolvedType = intentToResolve.resolveTypeIfNeeded(mContext.getContentResolver());
- ris = queryIntentActivitiesInternal(intentToResolve, resolvedType,
- 0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
- true /* resolveForStart */, false /* allowDynamicSplits */);
- }
-
- final Intent intent = new Intent(intentToResolve);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // For the case of empty result, no component name is assigned into the intent. A
- // non-launchable IntentSender which contains the failed intent is created. The
- // SendIntentException is thrown if the IntentSender#sendIntent is invoked.
- if (ris != null && !ris.isEmpty()) {
- intent.setClassName(ris.get(0).activityInfo.packageName,
- ris.get(0).activityInfo.name);
- }
- final IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
- ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
- featureId, null /* token */, null /* resultWho */,
- 1 /* requestCode */, new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null,
- PendingIntent.FLAG_IMMUTABLE, null /* bOptions */, userId);
- return new IntentSender(target);
+ return mResolveIntentHelper.getLaunchIntentSenderForPackage(packageName, callingPackage,
+ featureId, userId);
}
@Override
@@ -14928,31 +10837,255 @@
}
}
- public boolean getSafeMode() {
+ boolean getSafeMode() {
return mSafeMode;
}
- public ComponentName getResolveComponentName() {
+ ComponentName getResolveComponentName() {
return mResolveComponentName;
}
- public DefaultAppProvider getDefaultAppProvider() {
+ DefaultAppProvider getDefaultAppProvider() {
return mDefaultAppProvider;
}
- public File getCacheDir() {
+ File getCacheDir() {
return mCacheDir;
}
- public List<ScanPartition> getDirsToScanAsSystem() {
- return mDirsToScanAsSystem;
- }
-
- public PackageProperty getPackageProperty() {
+ PackageProperty getPackageProperty() {
return mPackageProperty;
}
- public WatchedArrayMap<ComponentName, ParsedInstrumentation> getInstrumentation() {
+ WatchedArrayMap<ComponentName, ParsedInstrumentation> getInstrumentation() {
return mInstrumentation;
}
+
+ int getSdkVersion() {
+ return mSdkVersion;
+ }
+
+ void addAllPackageProperties(@NonNull AndroidPackage pkg) {
+ mPackageProperty.addAllProperties(pkg);
+ }
+
+ void addInstrumentation(ComponentName name, ParsedInstrumentation instrumentation) {
+ mInstrumentation.put(name, instrumentation);
+ }
+
+ String[] getKnownPackageNamesInternal(int knownPackage, int userId) {
+ switch (knownPackage) {
+ case PackageManagerInternal.PACKAGE_BROWSER:
+ return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) };
+ case PackageManagerInternal.PACKAGE_INSTALLER:
+ return filterOnlySystemPackages(mRequiredInstallerPackage);
+ case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
+ return filterOnlySystemPackages(mSetupWizardPackage);
+ case PackageManagerInternal.PACKAGE_SYSTEM:
+ return new String[]{"android"};
+ case PackageManagerInternal.PACKAGE_VERIFIER:
+ return filterOnlySystemPackages(mRequiredVerifierPackage);
+ case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
+ return filterOnlySystemPackages(
+ mDefaultTextClassifierPackage, mSystemTextClassifierPackageName);
+ case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
+ return filterOnlySystemPackages(mRequiredPermissionControllerPackage);
+ case PackageManagerInternal.PACKAGE_CONFIGURATOR:
+ return filterOnlySystemPackages(mConfiguratorPackage);
+ case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
+ return filterOnlySystemPackages(mIncidentReportApproverPackage);
+ case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
+ return filterOnlySystemPackages(mAppPredictionServicePackage);
+ case PackageManagerInternal.PACKAGE_COMPANION:
+ return filterOnlySystemPackages(COMPANION_PACKAGE_NAME);
+ case PackageManagerInternal.PACKAGE_RETAIL_DEMO:
+ return TextUtils.isEmpty(mRetailDemoPackage)
+ ? ArrayUtils.emptyArray(String.class)
+ : new String[] {mRetailDemoPackage};
+ case PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE:
+ return filterOnlySystemPackages(getOverlayConfigSignaturePackageName());
+ case PackageManagerInternal.PACKAGE_RECENTS:
+ return filterOnlySystemPackages(mRecentsPackage);
+ default:
+ return ArrayUtils.emptyArray(String.class);
+ }
+ }
+
+ /**
+ * Only keep package names that refer to {@link AndroidPackage#isSystem system} packages.
+ *
+ * @param pkgNames The packages to filter
+ *
+ * @return The filtered packages
+ */
+ private @NonNull String[] filterOnlySystemPackages(@Nullable String... pkgNames) {
+ if (pkgNames == null) {
+ return ArrayUtils.emptyArray(String.class);
+ }
+
+ ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
+
+ for (String pkgName: pkgNames) {
+ synchronized (mLock) {
+ if (pkgName == null) {
+ continue;
+ }
+
+ AndroidPackage pkg = getPackage(pkgName);
+ if (pkg == null) {
+ Log.w(TAG, "Could not find package " + pkgName);
+ continue;
+ }
+
+ if (!pkg.isSystem()) {
+ Log.w(TAG, pkgName + " is not system");
+ continue;
+ }
+
+ systemPackageNames.add(pkgName);
+ }
+ }
+
+ return systemPackageNames.toArray(new String[]{});
+ }
+
+ String getActiveLauncherPackageName(int userId) {
+ return mDefaultAppProvider.getDefaultHome(userId);
+ }
+
+ boolean setActiveLauncherPackage(@NonNull String packageName, @UserIdInt int userId,
+ @NonNull Consumer<Boolean> callback) {
+ return mDefaultAppProvider.setDefaultHome(packageName, userId, mContext.getMainExecutor(),
+ callback);
+ }
+
+ void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) {
+ mDefaultAppProvider.setDefaultBrowser(packageName, async, userId);
+ }
+
+ ResolveInfo getInstantAppInstallerInfo() {
+ return mInstantAppInstallerInfo;
+ }
+
+ PackageUsage getPackageUsage() {
+ return mPackageUsage;
+ }
+
+ String getModuleMetadataPackageName() {
+ return mModuleInfoProvider.getPackageName();
+ }
+
+ File getAppInstallDir() {
+ return mAppInstallDir;
+ }
+
+ boolean isExpectingBetter(String packageName) {
+ return mInitAndSystemPackageHelper.isExpectingBetter(packageName);
+ }
+
+ int getDefParseFlags() {
+ return mDefParseFlags;
+ }
+
+ void setUpCustomResolverActivity(AndroidPackage pkg, PackageSetting pkgSetting) {
+ synchronized (mLock) {
+ mResolverReplaced = true;
+
+ // The instance created in PackageManagerService is special cased to be non-user
+ // specific, so initialize all the needed fields here.
+ ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
+ PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+
+ // Set up information for custom user intent resolution activity.
+ mResolveActivity.applicationInfo = appInfo;
+ mResolveActivity.name = mCustomResolverComponentName.getClassName();
+ mResolveActivity.packageName = pkg.getPackageName();
+ mResolveActivity.processName = pkg.getProcessName();
+ mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+ | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+ mResolveActivity.theme = 0;
+ mResolveActivity.exported = true;
+ mResolveActivity.enabled = true;
+ mResolveInfo.activityInfo = mResolveActivity;
+ mResolveInfo.priority = 0;
+ mResolveInfo.preferredOrder = 0;
+ mResolveInfo.match = 0;
+ mResolveComponentName = mCustomResolverComponentName;
+ PackageManagerService.onChanged();
+ Slog.i(TAG, "Replacing default ResolverActivity with custom activity: "
+ + mResolveComponentName);
+ }
+ }
+
+ void setPlatformPackage(AndroidPackage pkg, PackageSetting pkgSetting) {
+ synchronized (mLock) {
+ // Set up information for our fall-back user intent resolution activity.
+ mPlatformPackage = pkg;
+
+ // The instance stored in PackageManagerService is special cased to be non-user
+ // specific, so initialize all the needed fields here.
+ mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
+ PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+
+ if (!mResolverReplaced) {
+ mResolveActivity.applicationInfo = mAndroidApplication;
+ mResolveActivity.name = ResolverActivity.class.getName();
+ mResolveActivity.packageName = mAndroidApplication.packageName;
+ mResolveActivity.processName = "system:ui";
+ mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
+ mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
+ mResolveActivity.exported = true;
+ mResolveActivity.enabled = true;
+ mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_KEYBOARD
+ | ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ mResolveInfo.activityInfo = mResolveActivity;
+ mResolveInfo.priority = 0;
+ mResolveInfo.preferredOrder = 0;
+ mResolveInfo.match = 0;
+ mResolveComponentName = new ComponentName(
+ mAndroidApplication.packageName, mResolveActivity.name);
+ }
+ PackageManagerService.onChanged();
+ }
+ }
+
+ ResolveInfo getResolveInfo() {
+ return mResolveInfo;
+ }
+
+ ApplicationInfo getCoreAndroidApplication() {
+ return mAndroidApplication;
+ }
+
+ boolean isSystemReady() {
+ return mSystemReady;
+ }
+
+ AndroidPackage getPlatformPackage() {
+ return mPlatformPackage;
+ }
+
+ boolean isPreNUpgrade() {
+ return mIsPreNUpgrade;
+ }
+
+ boolean isPreNMR1Upgrade() {
+ return mIsPreNMR1Upgrade;
+ }
+
+ InitAndSystemPackageHelper getInitAndSystemPackageHelper() {
+ return mInitAndSystemPackageHelper;
+ }
+
+ boolean isOverlayMutable(String packageName) {
+ return mOverlayConfig.isMutable(packageName);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index a63cc36..97a09ff 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -134,6 +134,7 @@
private final Singleton<DomainVerificationManagerInternal>
mDomainVerificationManagerInternalProducer;
private final Singleton<Handler> mHandlerProducer;
+ private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -168,7 +169,8 @@
Producer<Handler> handlerProducer,
SystemWrapper systemWrapper,
ServiceProducer getLocalServiceProducer,
- ServiceProducer getSystemServiceProducer) {
+ ServiceProducer getSystemServiceProducer,
+ Producer<BackgroundDexOptService> backgroundDexOptService) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -217,6 +219,7 @@
new Singleton<>(
domainVerificationManagerInternalProducer);
mHandlerProducer = new Singleton<>(handlerProducer);
+ mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
}
/**
@@ -377,6 +380,10 @@
return getLocalService(ActivityManagerInternal.class);
}
+ public BackgroundDexOptService getBackgroundDexOptService() {
+ return mBackgroundDexOptService.get(this, mPackageManager);
+ }
+
/** Provides an abstraction to static access to system state. */
public interface SystemWrapper {
void disablePackageCaches();
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 2870c45..9327c5f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -100,5 +100,14 @@
public boolean isEngBuild;
public boolean isUserDebugBuild;
public int sdkInt = Build.VERSION.SDK_INT;
+ public BackgroundDexOptService backgroundDexOptService;
public final String incrementalVersion = Build.VERSION.INCREMENTAL;
+ public BroadcastHelper broadcastHelper;
+ public AppDataHelper appDataHelper;
+ public RemovePackageHelper removePackageHelper;
+ public InitAndSystemPackageHelper initAndSystemPackageHelper;
+ public DeletePackageHelper deletePackageHelper;
+ public PreferredActivityHelper preferredActivityHelper;
+ public ResolveIntentHelper resolveIntentHelper;
+ public DexOptHelper dexOptHelper;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 144b2b0..e124e04 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -23,7 +23,6 @@
import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
-import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
@@ -33,7 +32,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.AppGlobals;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.Context;
@@ -45,7 +43,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -53,14 +50,13 @@
import android.content.pm.parsing.component.ParsedMainComponent;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Binder;
import android.os.Build;
import android.os.Debug;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Process;
-import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
import android.os.incremental.V4Signature;
import android.os.incremental.V4Signature.HashingInfo;
@@ -86,11 +82,9 @@
import com.android.server.EventLogTags;
import com.android.server.IntentResolver;
import com.android.server.compat.PlatformCompat;
-import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
-import com.android.server.utils.WatchedLongSparseArray;
import dalvik.system.VMRuntime;
@@ -112,14 +106,10 @@
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
+import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
@@ -130,7 +120,6 @@
* {@hide}
*/
public class PackageManagerServiceUtils {
- private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 3 * 1000 * 1000; // 3MB
public final static Predicate<PackageSetting> REMOVE_IF_NULL_PKG =
@@ -150,151 +139,18 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
- private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
- List<ResolveInfo> ris = null;
- try {
- ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
- .getList();
- } catch (RemoteException e) {
- }
- ArraySet<String> pkgNames = new ArraySet<String>();
- if (ris != null) {
- for (ResolveInfo ri : ris) {
- pkgNames.add(ri.activityInfo.packageName);
- }
- }
- return pkgNames;
- }
+ /**
+ * The initial enabled state of the cache before other checks are done.
+ */
+ private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true;
- // Sort a list of apps by their last usage, most recently used apps first. The order of
- // packages without usage data is undefined (but they will be sorted after the packages
- // that do have usage data).
- public static void sortPackagesByUsageDate(List<PackageSetting> pkgSettings,
- PackageManagerService packageManagerService) {
- if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
- return;
- }
-
- Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) ->
- Long.compare(
- pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills(),
- pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills())
- );
- }
-
- // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
- // package will be removed from {@code packages} and added to {@code result} with its
- // dependencies. If usage data is available, the positive packages will be sorted by usage
- // data (with {@code sortTemp} as temporary storage).
- private static void applyPackageFilter(
- Predicate<PackageSetting> filter,
- Collection<PackageSetting> result,
- Collection<PackageSetting> packages,
- @NonNull List<PackageSetting> sortTemp,
- PackageManagerService packageManagerService) {
- for (PackageSetting pkgSetting : packages) {
- if (filter.test(pkgSetting)) {
- sortTemp.add(pkgSetting);
- }
- }
-
- sortPackagesByUsageDate(sortTemp, packageManagerService);
- packages.removeAll(sortTemp);
-
- for (PackageSetting pkgSetting : sortTemp) {
- result.add(pkgSetting);
-
- List<PackageSetting> deps =
- packageManagerService.findSharedNonSystemLibraries(pkgSetting);
- if (!deps.isEmpty()) {
- deps.removeAll(result);
- result.addAll(deps);
- packages.removeAll(deps);
- }
- }
-
- sortTemp.clear();
- }
-
- // Sort apps by importance for dexopt ordering. Important apps are given
- // more priority in case the device runs out of space.
- public static List<PackageSetting> getPackagesForDexopt(
- Collection<PackageSetting> packages,
- PackageManagerService packageManagerService) {
- return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT);
- }
-
- public static List<PackageSetting> getPackagesForDexopt(
- Collection<PackageSetting> pkgSettings,
- PackageManagerService packageManagerService,
- boolean debug) {
- List<PackageSetting> result = new LinkedList<>();
- ArrayList<PackageSetting> remainingPkgSettings = new ArrayList<>(pkgSettings);
-
- // First, remove all settings without available packages
- remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG);
-
- ArrayList<PackageSetting> sortTemp = new ArrayList<>(remainingPkgSettings.size());
-
- // Give priority to core apps.
- applyPackageFilter(pkgSetting -> pkgSetting.getPkg().isCoreApp(), result,
- remainingPkgSettings, sortTemp, packageManagerService);
-
- // Give priority to system apps that listen for pre boot complete.
- Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
- final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
- applyPackageFilter(pkgSetting -> pkgNames.contains(pkgSetting.getPackageName()), result,
- remainingPkgSettings, sortTemp, packageManagerService);
-
- // Give priority to apps used by other apps.
- DexManager dexManager = packageManagerService.getDexManager();
- applyPackageFilter(pkgSetting ->
- dexManager.getPackageUseInfoOrDefault(pkgSetting.getPackageName())
- .isAnyCodePathUsedByOtherApps(),
- result, remainingPkgSettings, sortTemp, packageManagerService);
-
- // Filter out packages that aren't recently used, add all remaining apps.
- // TODO: add a property to control this?
- Predicate<PackageSetting> remainingPredicate;
- if (!remainingPkgSettings.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
- if (debug) {
- Log.i(TAG, "Looking at historical package use");
- }
- // Get the package that was used last.
- PackageSetting lastUsed = Collections.max(remainingPkgSettings,
- (pkgSetting1, pkgSetting2) -> Long.compare(
- pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills(),
- pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills()));
- if (debug) {
- Log.i(TAG, "Taking package " + lastUsed.getPackageName()
- + " as reference in time use");
- }
- long estimatedPreviousSystemUseTime = lastUsed.getPkgState()
- .getLatestForegroundPackageUseTimeInMills();
- // Be defensive if for some reason package usage has bogus data.
- if (estimatedPreviousSystemUseTime != 0) {
- final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
- remainingPredicate = pkgSetting -> pkgSetting.getPkgState()
- .getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
- } else {
- // No meaningful historical info. Take all.
- remainingPredicate = pkgSetting -> true;
- }
- sortPackagesByUsageDate(remainingPkgSettings, packageManagerService);
- } else {
- // No historical info. Take all.
- remainingPredicate = pkgSetting -> true;
- }
- applyPackageFilter(remainingPredicate, result, remainingPkgSettings, sortTemp,
- packageManagerService);
-
- if (debug) {
- Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
- Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings));
- }
-
- return result;
- }
+ /**
+ * Whether to skip all other checks and force the cache to be enabled.
+ *
+ * Setting this to true will cause the cache to be named "debug" to avoid eviction from
+ * build fingerprint changes.
+ */
+ private static final boolean FORCE_PACKAGE_PARSED_CACHE_ENABLED = false;
/**
* Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
@@ -343,17 +199,6 @@
}
}
- public static String packagesToString(List<PackageSetting> pkgSettings) {
- StringBuilder sb = new StringBuilder();
- for (int index = 0; index < pkgSettings.size(); index++) {
- if (sb.length() > 0) {
- sb.append(", ");
- }
- sb.append(pkgSettings.get(index).getPackageName());
- }
- return sb.toString();
- }
-
/**
* Verifies that the given string {@code isa} is a valid supported isa on
* the running device.
@@ -1169,13 +1014,14 @@
}
final boolean match = comp.getIntents().stream().anyMatch(
- f -> IntentResolver.intentMatchesFilter(f, intent, resolvedType));
+ f -> IntentResolver.intentMatchesFilter(f.getIntentFilter(), intent,
+ resolvedType));
if (!match) {
Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
Slog.w(TAG, "Access blocked: " + comp.getComponentName());
if (DEBUG_INTENT_MATCHING) {
Slog.v(TAG, "Component intent filters:");
- comp.getIntents().forEach(f -> f.dump(logPrinter, " "));
+ comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, " "));
Slog.v(TAG, "-----------------------------");
}
resolveInfos.remove(i);
@@ -1276,4 +1122,107 @@
}
return StorageEnums.UNKNOWN;
}
+
+ /**
+ * Enforces that only the system UID or root's UID or shell's UID can call
+ * a method exposed via Binder.
+ *
+ * @param message used as message if SecurityException is thrown
+ * @throws SecurityException if the caller is not system or shell
+ */
+ public static void enforceSystemOrRootOrShell(String message) {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+ throw new SecurityException(message);
+ }
+ }
+
+ /**
+ * Enforces that only the system UID or root's UID can call a method exposed
+ * via Binder.
+ *
+ * @param message used as message if SecurityException is thrown
+ * @throws SecurityException if the caller is not system or root
+ */
+ public static void enforceSystemOrRoot(String message) {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID) {
+ throw new SecurityException(message);
+ }
+ }
+
+ public static @Nullable File preparePackageParserCache(boolean forEngBuild,
+ boolean isUserDebugBuild, String incrementalVersion) {
+ if (!FORCE_PACKAGE_PARSED_CACHE_ENABLED) {
+ if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
+ return null;
+ }
+
+ // Disable package parsing on eng builds to allow for faster incremental development.
+ if (forEngBuild) {
+ return null;
+ }
+
+ if (SystemProperties.getBoolean("pm.boot.disable_package_cache", false)) {
+ Slog.i(TAG, "Disabling package parser cache due to system property.");
+ return null;
+ }
+ }
+
+ // The base directory for the package parser cache lives under /data/system/.
+ final File cacheBaseDir = Environment.getPackageCacheDirectory();
+ if (!FileUtils.createDir(cacheBaseDir)) {
+ return null;
+ }
+
+ // There are several items that need to be combined together to safely
+ // identify cached items. In particular, changing the value of certain
+ // feature flags should cause us to invalidate any caches.
+ final String cacheName = FORCE_PACKAGE_PARSED_CACHE_ENABLED ? "debug"
+ : SystemProperties.digestOf("ro.build.fingerprint");
+
+ // Reconcile cache directories, keeping only what we'd actually use.
+ for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
+ if (Objects.equals(cacheName, cacheDir.getName())) {
+ Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
+ } else {
+ Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
+ FileUtils.deleteContentsAndDir(cacheDir);
+ }
+ }
+
+ // Return the versioned package cache directory.
+ File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
+
+ if (cacheDir == null) {
+ // Something went wrong. Attempt to delete everything and return.
+ Slog.wtf(TAG, "Cache directory cannot be created - wiping base dir " + cacheBaseDir);
+ FileUtils.deleteContentsAndDir(cacheBaseDir);
+ return null;
+ }
+
+ // The following is a workaround to aid development on non-numbered userdebug
+ // builds or cases where "adb sync" is used on userdebug builds. If we detect that
+ // the system partition is newer.
+ //
+ // NOTE: When no BUILD_NUMBER is set by the build system, it defaults to a build
+ // that starts with "eng." to signify that this is an engineering build and not
+ // destined for release.
+ if (isUserDebugBuild && incrementalVersion.startsWith("eng.")) {
+ Slog.w(TAG, "Wiping cache directory because the system partition changed.");
+
+ // Heuristic: If the /system directory has been modified recently due to an "adb sync"
+ // or a regular make, then blow away the cache. Note that mtimes are *NOT* reliable
+ // in general and should not be used for production changes. In this specific case,
+ // we know that they will work.
+ File frameworkDir =
+ new File(Environment.getRootDirectory(), "framework");
+ if (cacheDir.lastModified() < frameworkDir.lastModified()) {
+ FileUtils.deleteContents(cacheBaseDir);
+ cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
+ }
+ }
+
+ return cacheDir;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e4f6398..3554cc0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -226,6 +226,8 @@
return runForceDexOpt();
case "bg-dexopt-job":
return runDexoptJob();
+ case "cancel-bg-dexopt-job":
+ return cancelBgDexOptJob();
case "dump-profiles":
return runDumpProfiles();
case "snapshot-profile":
@@ -1863,12 +1865,18 @@
while ((arg = getNextArg()) != null) {
packageNames.add(arg);
}
- boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
- packageNames);
+ boolean result = BackgroundDexOptService.getService().runBackgroundDexoptJob(
+ packageNames.isEmpty() ? null : packageNames);
getOutPrintWriter().println(result ? "Success" : "Failure");
return result ? 0 : -1;
}
+ private int cancelBgDexOptJob() throws RemoteException {
+ BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
+ getOutPrintWriter().println("Success");
+ return 0;
+ }
+
private int runDumpProfiles() throws RemoteException {
String packageName = getNextArg();
mInterface.dumpProfiles(packageName);
@@ -3940,6 +3948,11 @@
pw.println(" overlap with the actual job but the job scheduler will not be able to");
pw.println(" cancel it. It will also run even if the device is not in the idle");
pw.println(" maintenance mode.");
+ pw.println(" cancel-bg-dexopt-job");
+ pw.println(" Cancels currently running background optimizations immediately.");
+ pw.println(" This cancels optimizations run from bg-dexopt-job or from JobScjeduler.");
+ pw.println(" Note that cancelling currently running bg-dexopt-job command requires");
+ pw.println(" running this command from separate adb shell.");
pw.println("");
pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE");
pw.println(" Reconciles the package secondary dex files with the generated oat files.");
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 74e1050..a60d2c8 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -32,7 +32,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
-public final class PackageRemovedInfo {
+final class PackageRemovedInfo {
final PackageSender mPackageSender;
String mRemovedPackage;
String mInstallerPackageName;
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
new file mode 100644
index 0000000..68e880b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.Intent.CATEGORY_DEFAULT;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
+import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.PrintStreamPrinter;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+final class PreferredActivityHelper {
+ // XML tags for backup/restore of various bits of state
+ private static final String TAG_PREFERRED_BACKUP = "pa";
+ private static final String TAG_DEFAULT_APPS = "da";
+
+ private final PackageManagerService mPm;
+
+ // TODO(b/198166813): remove PMS dependency
+ PreferredActivityHelper(PackageManagerService pm) {
+ mPm = pm;
+ }
+
+ private ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType,
+ int flags, List<ResolveInfo> query, boolean always, boolean removeMatches,
+ boolean debug, int userId) {
+ return findPreferredActivityNotLocked(
+ intent, resolvedType, flags, query, always, removeMatches, debug, userId,
+ UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
+ }
+
+ // TODO: handle preferred activities missing while user has amnesia
+ /** <b>must not hold {@link PackageManagerService.mLock}</b> */
+ public ResolveInfo findPreferredActivityNotLocked(
+ Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ if (Thread.holdsLock(mPm.mLock)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ + " is holding mLock", new Throwable());
+ }
+ if (!mPm.mUserManager.exists(userId)) return null;
+
+ PackageManagerService.FindPreferredActivityBodyResult body =
+ mPm.findPreferredActivityInternal(
+ intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ if (body.mChanged) {
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
+ }
+ synchronized (mPm.mLock) {
+ mPm.scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+ if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
+ Slog.v(TAG, "No preferred activity to return");
+ }
+ return body.mPreferredResolveInfo;
+ }
+
+ /** This method takes a specific user id as well as UserHandle.USER_ALL. */
+ public void clearPackagePreferredActivities(String packageName, int userId) {
+ final SparseBooleanArray changedUsers = new SparseBooleanArray();
+ synchronized (mPm.mLock) {
+ mPm.clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
+ }
+ if (changedUsers.size() > 0) {
+ updateDefaultHomeNotLocked(changedUsers);
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ synchronized (mPm.mLock) {
+ mPm.scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+ }
+
+ /**
+ * <b>must not hold {@link PackageManagerService.mLock}</b>
+ *
+ * @return Whether the ACTION_PREFERRED_ACTIVITY_CHANGED broadcast has been scheduled.
+ */
+ public boolean updateDefaultHomeNotLocked(int userId) {
+ if (Thread.holdsLock(mPm.mLock)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ + " is holding mLock", new Throwable());
+ }
+ if (!mPm.isSystemReady()) {
+ // We might get called before system is ready because of package changes etc, but
+ // finding preferred activity depends on settings provider, so we ignore the update
+ // before that.
+ return false;
+ }
+ final Intent intent = mPm.getHomeIntent();
+ final List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesInternal(intent, null,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+ final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
+ intent, null, 0, resolveInfos, true, false, false, userId);
+ final String packageName = preferredResolveInfo != null
+ && preferredResolveInfo.activityInfo != null
+ ? preferredResolveInfo.activityInfo.packageName : null;
+ final String currentPackageName = mPm.getActiveLauncherPackageName(userId);
+ if (TextUtils.equals(currentPackageName, packageName)) {
+ return false;
+ }
+ final String[] callingPackages = mPm.getPackagesForUid(Binder.getCallingUid());
+ if (callingPackages != null && ArrayUtils.contains(callingPackages,
+ mPm.mRequiredPermissionControllerPackage)) {
+ // PermissionController manages default home directly.
+ return false;
+ }
+
+ if (packageName == null) {
+ // Keep the default home package in RoleManager.
+ return false;
+ }
+ return mPm.setActiveLauncherPackage(packageName, userId,
+ successful -> {
+ if (successful) {
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ }
+ });
+ }
+
+ /**
+ * Variant that takes a {@link WatchedIntentFilter}
+ */
+ public void addPreferredActivity(WatchedIntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity, boolean always, int userId,
+ String opname, boolean removeExisting) {
+ // writer
+ int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+ false /* checkShell */, "add preferred activity");
+ if (mPm.mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ synchronized (mPm.mLock) {
+ if (mPm.getUidTargetSdkVersionLockedLPr(callingUid)
+ < Build.VERSION_CODES.FROYO) {
+ Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
+ + callingUid);
+ return;
+ }
+ }
+ mPm.mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ }
+ if (filter.countActions() == 0) {
+ Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+ return;
+ }
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, opname + " activity " + activity.flattenToShortString() + " for user "
+ + userId + ":");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ }
+ synchronized (mPm.mLock) {
+ final PreferredIntentResolver pir = mPm.mSettings.editPreferredActivitiesLPw(userId);
+ final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
+ if (removeExisting && existing != null) {
+ Settings.removeFilters(pir, filter, existing);
+ }
+ pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
+ mPm.scheduleWritePackageRestrictionsLocked(userId);
+ }
+ if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ }
+ }
+
+ /**
+ * Variant that takes a {@link WatchedIntentFilter}
+ */
+ public void replacePreferredActivity(WatchedIntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity, int userId) {
+ if (filter.countActions() != 1) {
+ throw new IllegalArgumentException(
+ "replacePreferredActivity expects filter to have only 1 action.");
+ }
+ if (filter.countDataAuthorities() != 0
+ || filter.countDataPaths() != 0
+ || filter.countDataSchemes() > 1
+ || filter.countDataTypes() != 0) {
+ throw new IllegalArgumentException(
+ "replacePreferredActivity expects filter to have no data authorities, "
+ + "paths, or types; and at most one scheme.");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+ false /* checkShell */, "replace preferred activity");
+ if (mPm.mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ synchronized (mPm.mLock) {
+ if (mPm.getUidTargetSdkVersionLockedLPr(callingUid)
+ < Build.VERSION_CODES.FROYO) {
+ Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
+ + Binder.getCallingUid());
+ return;
+ }
+ }
+ mPm.mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ }
+
+ synchronized (mPm.mLock) {
+ final PreferredIntentResolver pir = mPm.mSettings.getPreferredActivities(userId);
+ if (pir != null) {
+ // Get all of the existing entries that exactly match this filter.
+ final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
+ if (existing != null && existing.size() == 1) {
+ final PreferredActivity cur = existing.get(0);
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Checking replace of preferred:");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ if (!cur.mPref.mAlways) {
+ Slog.i(TAG, " -- CUR; not mAlways!");
+ } else {
+ Slog.i(TAG, " -- CUR: mMatch=" + cur.mPref.mMatch);
+ Slog.i(TAG, " -- CUR: mSet="
+ + Arrays.toString(cur.mPref.mSetComponents));
+ Slog.i(TAG, " -- CUR: mComponent=" + cur.mPref.mShortComponent);
+ Slog.i(TAG, " -- NEW: mMatch="
+ + (match & IntentFilter.MATCH_CATEGORY_MASK));
+ Slog.i(TAG, " -- CUR: mSet=" + Arrays.toString(set));
+ Slog.i(TAG, " -- CUR: mComponent=" + activity.flattenToShortString());
+ }
+ }
+ if (cur.mPref.mAlways && cur.mPref.mComponent.equals(activity)
+ && cur.mPref.mMatch == (match & IntentFilter.MATCH_CATEGORY_MASK)
+ && cur.mPref.sameSet(set)) {
+ // Setting the preferred activity to what it happens to be already
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Replacing with same preferred activity "
+ + cur.mPref.mShortComponent + " for user "
+ + userId + ":");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ }
+ return;
+ }
+ }
+ if (existing != null) {
+ Settings.removeFilters(pir, filter, existing);
+ }
+ }
+ }
+ addPreferredActivity(filter, match, set, activity, true, userId,
+ "Replacing preferred", false);
+ }
+
+ public void clearPackagePreferredActivities(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ if (mPm.getInstantAppPackageName(callingUid) != null) {
+ return;
+ }
+ // writer
+ synchronized (mPm.mLock) {
+ AndroidPackage pkg = mPm.mPackages.get(packageName);
+ if (pkg == null || !mPm.isCallerSameApp(packageName, callingUid)) {
+ if (mPm.mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ if (mPm.getUidTargetSdkVersionLockedLPr(callingUid)
+ < Build.VERSION_CODES.FROYO) {
+ Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid "
+ + callingUid);
+ return;
+ }
+ mPm.mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ }
+ }
+ final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps != null
+ && mPm.shouldFilterApplicationLocked(
+ ps, callingUid, UserHandle.getUserId(callingUid))) {
+ return;
+ }
+ }
+ int callingUserId = UserHandle.getCallingUserId();
+ clearPackagePreferredActivities(packageName, callingUserId);
+ }
+
+ /** <b>must not hold {@link #PackageManagerService.mLock}</b> */
+ void updateDefaultHomeNotLocked(SparseBooleanArray userIds) {
+ if (Thread.holdsLock(mPm.mLock)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ + " is holding mLock", new Throwable());
+ }
+ for (int i = userIds.size() - 1; i >= 0; --i) {
+ final int userId = userIds.keyAt(i);
+ updateDefaultHomeNotLocked(userId);
+ }
+ }
+
+ public void setHomeActivity(ComponentName comp, int userId) {
+ if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ return;
+ }
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+ mPm.getHomeActivitiesAsUser(homeActivities, userId);
+
+ boolean found = false;
+
+ final int size = homeActivities.size();
+ final ComponentName[] set = new ComponentName[size];
+ for (int i = 0; i < size; i++) {
+ final ResolveInfo candidate = homeActivities.get(i);
+ final ActivityInfo info = candidate.activityInfo;
+ final ComponentName activityName = new ComponentName(info.packageName, info.name);
+ set[i] = activityName;
+ if (!found && activityName.equals(comp)) {
+ found = true;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException("Component " + comp + " cannot be home on user "
+ + userId);
+ }
+ replacePreferredActivity(getHomeFilter(), IntentFilter.MATCH_CATEGORY_EMPTY,
+ set, comp, userId);
+ }
+
+ private WatchedIntentFilter getHomeFilter() {
+ WatchedIntentFilter filter = new WatchedIntentFilter(Intent.ACTION_MAIN);
+ filter.addCategory(Intent.CATEGORY_HOME);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+ return filter;
+ }
+
+ /**
+ * Variant that takes a {@link WatchedIntentFilter}
+ */
+ public void addPersistentPreferredActivity(WatchedIntentFilter filter, ComponentName activity,
+ int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "addPersistentPreferredActivity can only be run by the system");
+ }
+ if (filter.countActions() == 0) {
+ Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+ return;
+ }
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Adding persistent preferred activity " + activity
+ + " for user " + userId + ":");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ }
+ synchronized (mPm.mLock) {
+ mPm.mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
+ new PersistentPreferredActivity(filter, activity, true));
+ mPm.scheduleWritePackageRestrictionsLocked(userId);
+ }
+ if (isHomeFilter(filter)) {
+ updateDefaultHomeNotLocked(userId);
+ }
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ }
+
+ public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "clearPackagePersistentPreferredActivities can only be run by the system");
+ }
+ boolean changed = false;
+ synchronized (mPm.mLock) {
+ changed = mPm.mSettings.clearPackagePersistentPreferredActivities(packageName, userId);
+ }
+ if (changed) {
+ updateDefaultHomeNotLocked(userId);
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ synchronized (mPm.mLock) {
+ mPm.scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+ }
+
+ private boolean isHomeFilter(@NonNull WatchedIntentFilter filter) {
+ return filter.hasAction(Intent.ACTION_MAIN) && filter.hasCategory(Intent.CATEGORY_HOME)
+ && filter.hasCategory(CATEGORY_DEFAULT);
+ }
+
+ /**
+ * Common machinery for picking apart a restored XML blob and passing
+ * it to a caller-supplied functor to be applied to the running system.
+ */
+ private void restoreFromXml(TypedXmlPullParser parser, int userId,
+ String expectedStartTag, BlobXmlRestorer functor)
+ throws IOException, XmlPullParserException {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ // oops didn't find a start tag?!
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Didn't find start tag during restore");
+ }
+ return;
+ }
+ // this is supposed to be TAG_PREFERRED_BACKUP
+ if (!expectedStartTag.equals(parser.getName())) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Found unexpected tag " + parser.getName());
+ }
+ return;
+ }
+
+ // skip interfering stuff, then we're aligned with the backing implementation
+ while ((type = parser.next()) == XmlPullParser.TEXT) { }
+ functor.apply(parser, userId);
+ }
+
+ private interface BlobXmlRestorer {
+ void apply(TypedXmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException;
+ }
+
+ public byte[] getPreferredActivityBackup(int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call getPreferredActivityBackup()");
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ try {
+ final TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PREFERRED_BACKUP);
+
+ synchronized (mPm.mLock) {
+ mPm.mSettings.writePreferredActivitiesLPr(serializer, userId, true);
+ }
+
+ serializer.endTag(null, TAG_PREFERRED_BACKUP);
+ serializer.endDocument();
+ serializer.flush();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write preferred activities for backup", e);
+ }
+ return null;
+ }
+
+ return dataStream.toByteArray();
+ }
+
+ public void restorePreferredActivities(byte[] backup, int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call restorePreferredActivities()");
+ }
+
+ try {
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+ restoreFromXml(parser, userId, TAG_PREFERRED_BACKUP,
+ (readParser, readUserId) -> {
+ synchronized (mPm.mLock) {
+ mPm.mSettings.readPreferredActivitiesLPw(readParser, readUserId);
+ }
+ updateDefaultHomeNotLocked(readUserId);
+ });
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Non-Binder method, support for the backup/restore mechanism: write the
+ * default browser (etc) settings in its canonical XML format. Returns the default
+ * browser XML representation as a byte array, or null if there is none.
+ */
+ public byte[] getDefaultAppsBackup(int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call getDefaultAppsBackup()");
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ try {
+ final TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_DEFAULT_APPS);
+
+ synchronized (mPm.mLock) {
+ mPm.mSettings.writeDefaultAppsLPr(serializer, userId);
+ }
+
+ serializer.endTag(null, TAG_DEFAULT_APPS);
+ serializer.endDocument();
+ serializer.flush();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write default apps for backup", e);
+ }
+ return null;
+ }
+
+ return dataStream.toByteArray();
+ }
+
+ public void restoreDefaultApps(byte[] backup, int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call restoreDefaultApps()");
+ }
+
+ try {
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+ restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
+ (parser1, userId1) -> {
+ final String defaultBrowser;
+ synchronized (mPm.mLock) {
+ mPm.mSettings.readDefaultAppsLPw(parser1, userId1);
+ defaultBrowser = mPm.mSettings.removeDefaultBrowserPackageNameLPw(
+ userId1);
+ }
+ if (defaultBrowser != null) {
+ mPm.setDefaultBrowser(defaultBrowser, false, userId1);
+ }
+ });
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring default apps: " + e.getMessage());
+ }
+ }
+ }
+
+ public void resetApplicationPreferences(int userId) {
+ mPm.mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ final long identity = Binder.clearCallingIdentity();
+ // writer
+ try {
+ final SparseBooleanArray changedUsers = new SparseBooleanArray();
+ synchronized (mPm.mLock) {
+ mPm.clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
+ }
+ if (changedUsers.size() > 0) {
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ }
+ synchronized (mPm.mLock) {
+ mPm.mSettings.applyDefaultPreferredAppsLPw(userId);
+ mPm.mDomainVerificationManager.clearUser(userId);
+ final int numPackages = mPm.mPackages.size();
+ for (int i = 0; i < numPackages; i++) {
+ final AndroidPackage pkg = mPm.mPackages.valueAt(i);
+ mPm.mPermissionManager.resetRuntimePermissions(pkg, userId);
+ }
+ }
+ updateDefaultHomeNotLocked(userId);
+ resetNetworkPolicies(userId);
+ synchronized (mPm.mLock) {
+ mPm.scheduleWritePackageRestrictionsLocked(userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void resetNetworkPolicies(int userId) {
+ mPm.mInjector.getLocalService(NetworkPolicyManagerInternal.class).resetUserState(userId);
+ }
+
+ public int getPreferredActivities(List<IntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName) {
+ List<WatchedIntentFilter> temp =
+ WatchedIntentFilter.toWatchedIntentFilterList(outFilters);
+ final int result = getPreferredActivitiesInternal(
+ temp, outActivities, packageName);
+ outFilters.clear();
+ for (int i = 0; i < temp.size(); i++) {
+ outFilters.add(temp.get(i).getIntentFilter());
+ }
+ return result;
+ }
+
+ /**
+ * Variant that takes a {@link WatchedIntentFilter}
+ */
+ private int getPreferredActivitiesInternal(List<WatchedIntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ if (mPm.getInstantAppPackageName(callingUid) != null) {
+ return 0;
+ }
+ int num = 0;
+ final int userId = UserHandle.getCallingUserId();
+ // reader
+ synchronized (mPm.mLock) {
+ PreferredIntentResolver pir = mPm.mSettings.getPreferredActivities(userId);
+ if (pir != null) {
+ final Iterator<PreferredActivity> it = pir.filterIterator();
+ while (it.hasNext()) {
+ final PreferredActivity pa = it.next();
+ final String prefPackageName = pa.mPref.mComponent.getPackageName();
+ if (packageName == null
+ || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) {
+ if (mPm.shouldFilterApplicationLocked(
+ mPm.mSettings.getPackageLPr(prefPackageName), callingUid, userId)) {
+ continue;
+ }
+ if (outFilters != null) {
+ outFilters.add(new WatchedIntentFilter(pa.getIntentFilter()));
+ }
+ if (outActivities != null) {
+ outActivities.add(pa.mPref.mComponent);
+ }
+ }
+ }
+ }
+ }
+
+ return num;
+ }
+
+ public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
+ if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
+ throw new SecurityException(
+ "findPersistentPreferredActivity can only be run by the system");
+ }
+ if (!mPm.mUserManager.exists(userId)) {
+ return null;
+ }
+ final int callingUid = Binder.getCallingUid();
+ intent = PackageManagerServiceUtils.updateIntentForResolve(intent);
+ final String resolvedType = intent.resolveTypeIfNeeded(mPm.mContext.getContentResolver());
+ final int flags = mPm.updateFlagsForResolve(
+ 0, userId, callingUid, false /*includeInstantApps*/,
+ mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+ 0));
+ final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+ flags, userId);
+ synchronized (mPm.mLock) {
+ return mPm.findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
+ userId);
+ }
+ }
+
+ /**
+ * Variant that takes a {@link WatchedIntentFilter}
+ */
+ public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+ WatchedIntentFilter filter, int match, ComponentName activity) {
+ if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ return;
+ }
+ final int userId = UserHandle.getCallingUserId();
+ if (DEBUG_PREFERRED) {
+ Log.v(TAG, "setLastChosenActivity intent=" + intent
+ + " resolvedType=" + resolvedType
+ + " flags=" + flags
+ + " filter=" + filter
+ + " match=" + match
+ + " activity=" + activity);
+ filter.dump(new PrintStreamPrinter(System.out), " ");
+ }
+ intent.setComponent(null);
+ final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+ flags, userId);
+ // Find any earlier preferred or last chosen entries and nuke them
+ findPreferredActivityNotLocked(
+ intent, resolvedType, flags, query, false, true, false, userId);
+ // Add the new activity as the last chosen for this filter
+ addPreferredActivity(filter, match, null, activity, false, userId,
+ "Setting last chosen", false);
+ }
+
+ public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) {
+ if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ return null;
+ }
+ final int userId = UserHandle.getCallingUserId();
+ if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
+ final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+ flags, userId);
+ return findPreferredActivityNotLocked(
+ intent, resolvedType, flags, query, false, false, false, userId);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7596cdf..bb3ffb4 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -29,7 +29,10 @@
import android.annotation.NonNull;
import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.os.Process;
import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
import android.util.Log;
@@ -42,6 +45,7 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.File;
import java.util.Collections;
@@ -168,7 +172,7 @@
final int libraryNamesSize = pkg.getLibraryNames().size();
for (i = 0; i < libraryNamesSize; i++) {
String name = pkg.getLibraryNames().get(i);
- if (mPm.removeSharedLibraryLPw(name, 0)) {
+ if (removeSharedLibraryLPw(name, 0)) {
if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
@@ -185,7 +189,7 @@
// Any package can hold static shared libraries.
if (pkg.getStaticSharedLibName() != null) {
- if (mPm.removeSharedLibraryLPw(pkg.getStaticSharedLibName(),
+ if (removeSharedLibraryLPw(pkg.getStaticSharedLibName(),
pkg.getStaticSharedLibVersion())) {
if (DEBUG_REMOVE && chatty) {
if (r == null) {
@@ -203,6 +207,44 @@
}
}
+ private boolean removeSharedLibraryLPw(String name, long version) {
+ WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(name);
+ if (versionedLib == null) {
+ return false;
+ }
+ final int libIdx = versionedLib.indexOfKey(version);
+ if (libIdx < 0) {
+ return false;
+ }
+ SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+ // Remove the shared library overlays from its dependent packages.
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ final List<VersionedPackage> dependents = mPm.getPackagesUsingSharedLibraryLPr(
+ libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependentPackage : dependents) {
+ final PackageSetting ps = mPm.mSettings.getPackageLPr(
+ dependentPackage.getPackageName());
+ if (ps != null) {
+ ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+ }
+ }
+ }
+
+ versionedLib.remove(version);
+ if (versionedLib.size() <= 0) {
+ mPm.mSharedLibraries.remove(name);
+ if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) {
+ mPm.mStaticLibsByDeclaringPackage.remove(libraryInfo.getDeclaringPackage()
+ .getPackageName());
+ }
+ }
+ return true;
+ }
+
/*
* This method deletes the package from internal data structures. If the DELETE_KEEP_DATA
* flag is not set, the data directory is removed as well.
@@ -220,9 +262,8 @@
outInfo.mInstallerPackageName = deletedPs.getInstallSource().installerPackageName;
outInfo.mIsStaticSharedLib = deletedPkg != null
&& deletedPkg.getStaticSharedLibName() != null;
- outInfo.populateUsers(
- deletedPs == null ? null : deletedPs.queryInstalledUsers(
- mUserManagerInternal.getUserIds(), true), deletedPs);
+ outInfo.populateUsers(deletedPs.queryInstalledUsers(
+ mUserManagerInternal.getUserIds(), true), deletedPs);
}
removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0);
@@ -249,58 +290,55 @@
// writer
boolean installedStateChanged = false;
- if (deletedPs != null) {
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
- final SparseBooleanArray changedUsers = new SparseBooleanArray();
- synchronized (mPm.mLock) {
- mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName());
- mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
- mPm.mAppsFilter.removePackage(mPm.getPackageSetting(packageName),
- false /* isReplace */);
- removedAppId = mPm.mSettings.removePackageLPw(packageName);
- if (outInfo != null) {
- outInfo.mRemovedAppId = removedAppId;
- }
- if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
- // If we don't have a disabled system package to reinstall, the package is
- // really gone and its permission state should be removed.
- final SharedUserSetting sus = deletedPs.getSharedUser();
- List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages()
- : null;
- if (sharedUserPkgs == null) {
- sharedUserPkgs = Collections.emptyList();
- }
- mPermissionManager.onPackageUninstalled(packageName, deletedPs.getAppId(),
- deletedPs.getPkg(), sharedUserPkgs, UserHandle.USER_ALL);
- }
- mPm.clearPackagePreferredActivitiesLPw(
- deletedPs.getPackageName(), changedUsers, UserHandle.USER_ALL);
+ if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+ final SparseBooleanArray changedUsers = new SparseBooleanArray();
+ synchronized (mPm.mLock) {
+ mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName());
+ mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
+ mPm.mAppsFilter.removePackage(mPm.getPackageSetting(packageName),
+ false /* isReplace */);
+ removedAppId = mPm.mSettings.removePackageLPw(packageName);
+ if (outInfo != null) {
+ outInfo.mRemovedAppId = removedAppId;
+ }
+ if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
+ // If we don't have a disabled system package to reinstall, the package is
+ // really gone and its permission state should be removed.
+ final SharedUserSetting sus = deletedPs.getSharedUser();
+ List<AndroidPackage> sharedUserPkgs =
+ sus != null ? sus.getPackages() : Collections.emptyList();
+ mPermissionManager.onPackageUninstalled(packageName, deletedPs.getAppId(),
+ deletedPs.getPkg(), sharedUserPkgs, UserHandle.USER_ALL);
+ }
+ mPm.clearPackagePreferredActivitiesLPw(
+ deletedPs.getPackageName(), changedUsers, UserHandle.USER_ALL);
- mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
- }
- if (changedUsers.size() > 0) {
- mPm.updateDefaultHomeNotLocked(changedUsers);
- mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
- }
+ mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
}
- // make sure to preserve per-user disabled state if this removal was just
- // a downgrade of a system app to the factory package
- if (outInfo != null && outInfo.mOrigUsers != null) {
+ if (changedUsers.size() > 0) {
+ final PreferredActivityHelper preferredActivityHelper =
+ new PreferredActivityHelper(mPm);
+ preferredActivityHelper.updateDefaultHomeNotLocked(changedUsers);
+ mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
+ }
+ }
+ // make sure to preserve per-user disabled state if this removal was just
+ // a downgrade of a system app to the factory package
+ if (outInfo != null && outInfo.mOrigUsers != null) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Propagating install state across downgrade");
+ }
+ for (int userId : allUserHandles) {
+ final boolean installed = ArrayUtils.contains(outInfo.mOrigUsers, userId);
if (DEBUG_REMOVE) {
- Slog.d(TAG, "Propagating install state across downgrade");
+ Slog.d(TAG, " user " + userId + " => " + installed);
}
- for (int userId : allUserHandles) {
- final boolean installed = ArrayUtils.contains(outInfo.mOrigUsers, userId);
- if (DEBUG_REMOVE) {
- Slog.d(TAG, " user " + userId + " => " + installed);
- }
- if (installed != deletedPs.getInstalled(userId)) {
- installedStateChanged = true;
- }
- deletedPs.setInstalled(installed, userId);
- if (installed) {
- deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
- }
+ if (installed != deletedPs.getInstalled(userId)) {
+ installedStateChanged = true;
+ }
+ deletedPs.setInstalled(installed, userId);
+ if (installed) {
+ deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
}
}
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
new file mode 100644
index 0000000..f5010e4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
+import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.AuxiliaryResolveInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+final class ResolveIntentHelper {
+ private final PackageManagerService mPm;
+ private final PreferredActivityHelper mPreferredActivityHelper;
+
+ // TODO(b/198166813): remove PMS dependency
+ ResolveIntentHelper(PackageManagerService pm, PreferredActivityHelper preferredActivityHelper) {
+ mPm = pm;
+ mPreferredActivityHelper = preferredActivityHelper;
+ }
+
+ /**
+ * Normally instant apps can only be resolved when they're visible to the caller.
+ * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+ * since we need to allow the system to start any installed application.
+ */
+ public ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,
+ @PackageManagerInternal.PrivateResolveFlags int privateResolveFlags, int userId,
+ boolean resolveForStart, int filterCallingUid) {
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
+
+ if (!mPm.mUserManager.exists(userId)) return null;
+ final int callingUid = Binder.getCallingUid();
+ flags = mPm.updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
+ mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ resolvedType, flags));
+ mPm.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+ false /*checkShell*/, "resolve intent");
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
+ final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+ flags, privateResolveFlags, filterCallingUid, userId, resolveForStart,
+ true /*allowDynamicSplits*/);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+ final boolean queryMayBeFiltered =
+ UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
+ && !resolveForStart;
+
+ final ResolveInfo bestChoice =
+ chooseBestActivity(
+ intent, resolvedType, flags, privateResolveFlags, query, userId,
+ queryMayBeFiltered);
+ final boolean nonBrowserOnly =
+ (privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
+ if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
+ return null;
+ }
+ return bestChoice;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
+ int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,
+ boolean queryMayBeFiltered) {
+ if (query != null) {
+ final int n = query.size();
+ if (n == 1) {
+ return query.get(0);
+ } else if (n > 1) {
+ final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+ // If there is more than one activity with the same priority,
+ // then let the user decide between them.
+ ResolveInfo r0 = query.get(0);
+ ResolveInfo r1 = query.get(1);
+ if (DEBUG_INTENT_MATCHING || debug) {
+ Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
+ + r1.activityInfo.name + "=" + r1.priority);
+ }
+ // If the first activity has a higher priority, or a different
+ // default, then it is always desirable to pick it.
+ if (r0.priority != r1.priority
+ || r0.preferredOrder != r1.preferredOrder
+ || r0.isDefault != r1.isDefault) {
+ return query.get(0);
+ }
+ // If we have saved a preference for a preferred activity for
+ // this Intent, use that.
+ ResolveInfo ri = mPreferredActivityHelper.findPreferredActivityNotLocked(intent,
+ resolvedType, flags, query, true, false, debug, userId,
+ queryMayBeFiltered);
+ if (ri != null) {
+ return ri;
+ }
+ int browserCount = 0;
+ for (int i = 0; i < n; i++) {
+ ri = query.get(i);
+ if (ri.handleAllWebDataURI) {
+ browserCount++;
+ }
+ // If we have an ephemeral app, use it
+ if (ri.activityInfo.applicationInfo.isInstantApp()) {
+ final String packageName = ri.activityInfo.packageName;
+ final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
+ mPm.mDomainVerificationManager, ps, intent, flags, userId)) {
+ return ri;
+ }
+ }
+ }
+ if ((privateResolveFlags
+ & PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
+ return null;
+ }
+ ri = new ResolveInfo(mPm.getResolveInfo());
+ // if all resolve options are browsers, mark the resolver's info as if it were
+ // also a browser.
+ ri.handleAllWebDataURI = browserCount == n;
+ ri.activityInfo = new ActivityInfo(ri.activityInfo);
+ ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
+ // If all of the options come from the same package, show the application's
+ // label and icon instead of the generic resolver's.
+ // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
+ // and then throw away the ResolveInfo itself, meaning that the caller loses
+ // the resolvePackageName. Therefore the activityInfo.labelRes above provides
+ // a fallback for this case; we only set the target package's resources on
+ // the ResolveInfo, not the ActivityInfo.
+ final String intentPackage = intent.getPackage();
+ if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
+ final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
+ ri.resolvePackageName = intentPackage;
+ if (mPm.userNeedsBadging(userId)) {
+ ri.noResourceId = true;
+ } else {
+ ri.icon = appi.icon;
+ }
+ ri.iconResourceId = appi.icon;
+ ri.labelRes = appi.labelRes;
+ }
+ ri.activityInfo.applicationInfo = new ApplicationInfo(
+ ri.activityInfo.applicationInfo);
+ if (userId != 0) {
+ ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+ UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+ }
+ // Make sure that the resolver is displayable in car mode
+ if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
+ ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
+ return ri;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return true if the given list is not empty and all of its contents have
+ * an activityInfo with the given package name.
+ */
+ private boolean allHavePackage(List<ResolveInfo> list, String packageName) {
+ if (ArrayUtils.isEmpty(list)) {
+ return false;
+ }
+ for (int i = 0, n = list.size(); i < n; i++) {
+ final ResolveInfo ri = list.get(i);
+ final ActivityInfo ai = ri != null ? ri.activityInfo : null;
+ if (ai == null || !packageName.equals(ai.packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
+ String featureId, int userId) throws RemoteException {
+ Objects.requireNonNull(packageName);
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+ false /* checkShell */, "get launch intent sender for package");
+ final int packageUid = mPm.getPackageUid(callingPackage, 0 /* flags */, userId);
+ if (!UserHandle.isSameApp(callingUid, packageUid)) {
+ throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
+ + callingUid + " does not own package: " + callingPackage);
+ }
+
+ // Using the same implementation with the #getLaunchIntentForPackage to get the ResolveInfo.
+ // Pass the resolveForStart as true in queryIntentActivities to skip the app filtering.
+ final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+ intentToResolve.addCategory(Intent.CATEGORY_INFO);
+ intentToResolve.setPackage(packageName);
+ String resolvedType = intentToResolve.resolveTypeIfNeeded(
+ mPm.mContext.getContentResolver());
+ List<ResolveInfo> ris = mPm.queryIntentActivitiesInternal(intentToResolve, resolvedType,
+ 0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
+ true /* resolveForStart */, false /* allowDynamicSplits */);
+ if (ris == null || ris.size() <= 0) {
+ intentToResolve.removeCategory(Intent.CATEGORY_INFO);
+ intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+ intentToResolve.setPackage(packageName);
+ resolvedType = intentToResolve.resolveTypeIfNeeded(mPm.mContext.getContentResolver());
+ ris = mPm.queryIntentActivitiesInternal(intentToResolve, resolvedType,
+ 0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
+ true /* resolveForStart */, false /* allowDynamicSplits */);
+ }
+
+ final Intent intent = new Intent(intentToResolve);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // For the case of empty result, no component name is assigned into the intent. A
+ // non-launchable IntentSender which contains the failed intent is created. The
+ // SendIntentException is thrown if the IntentSender#sendIntent is invoked.
+ if (ris != null && !ris.isEmpty()) {
+ intent.setClassName(ris.get(0).activityInfo.packageName,
+ ris.get(0).activityInfo.name);
+ }
+ final IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
+ ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+ featureId, null /* token */, null /* resultWho */,
+ 1 /* requestCode */, new Intent[]{intent},
+ resolvedType != null ? new String[]{resolvedType} : null,
+ PendingIntent.FLAG_IMMUTABLE, null /* bOptions */, userId);
+ return new IntentSender(target);
+ }
+
+ // In this method, we have to know the actual calling UID, but in some cases Binder's
+ // call identity is removed, so the UID has to be passed in explicitly.
+ public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
+ String resolvedType, int flags, int userId, int filterCallingUid) {
+ if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+ mPm.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
+ false /*checkShell*/, "query intent receivers");
+ final String instantAppPkgName = mPm.getInstantAppPackageName(filterCallingUid);
+ flags = mPm.updateFlagsForResolve(
+ flags, userId, filterCallingUid, false /*includeInstantApps*/,
+ mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+ flags));
+ Intent originalIntent = null;
+ ComponentName comp = intent.getComponent();
+ if (comp == null) {
+ if (intent.getSelector() != null) {
+ originalIntent = intent;
+ intent = intent.getSelector();
+ comp = intent.getComponent();
+ }
+ }
+ List<ResolveInfo> list = Collections.emptyList();
+ if (comp != null) {
+ final ActivityInfo ai = mPm.getReceiverInfo(comp, flags, userId);
+ if (ai != null) {
+ // When specifying an explicit component, we prevent the activity from being
+ // used when either 1) the calling package is normal and the activity is within
+ // an instant application or 2) the calling package is ephemeral and the
+ // activity is not visible to instant applications.
+ final boolean matchInstantApp =
+ (flags & PackageManager.MATCH_INSTANT) != 0;
+ final boolean matchVisibleToInstantAppOnly =
+ (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+ final boolean matchExplicitlyVisibleOnly =
+ (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
+ final boolean isCallerInstantApp =
+ instantAppPkgName != null;
+ final boolean isTargetSameInstantApp =
+ comp.getPackageName().equals(instantAppPkgName);
+ final boolean isTargetInstantApp =
+ (ai.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ final boolean isTargetVisibleToInstantApp =
+ (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+ final boolean isTargetExplicitlyVisibleToInstantApp = isTargetVisibleToInstantApp
+ && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
+ final boolean isTargetHiddenFromInstantApp = !isTargetVisibleToInstantApp
+ || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
+ final boolean blockResolution =
+ !isTargetSameInstantApp
+ && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+ || (matchVisibleToInstantAppOnly && isCallerInstantApp
+ && isTargetHiddenFromInstantApp));
+ if (!blockResolution) {
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = ai;
+ list = new ArrayList<>(1);
+ list.add(ri);
+ PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
+ mPm.mInjector.getCompatibility(), mPm.mComponentResolver,
+ list, true, intent, resolvedType, filterCallingUid);
+ }
+ }
+ } else {
+ // reader
+ synchronized (mPm.mLock) {
+ String pkgName = intent.getPackage();
+ if (pkgName == null) {
+ final List<ResolveInfo> result = mPm.mComponentResolver.queryReceivers(
+ intent, resolvedType, flags, userId);
+ if (result != null) {
+ list = result;
+ }
+ }
+ final AndroidPackage pkg = mPm.mPackages.get(pkgName);
+ if (pkg != null) {
+ final List<ResolveInfo> result = mPm.mComponentResolver.queryReceivers(
+ intent, resolvedType, flags, pkg.getReceivers(), userId);
+ if (result != null) {
+ list = result;
+ }
+ }
+ }
+ }
+
+ if (originalIntent != null) {
+ // We also have to ensure all components match the original intent
+ PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
+ mPm.mInjector.getCompatibility(), mPm.mComponentResolver,
+ list, true, originalIntent, resolvedType, filterCallingUid);
+ }
+
+ return mPm.applyPostResolutionFilter(
+ list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
+ }
+
+
+ public ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
+ int userId, int callingUid) {
+ if (!mPm.mUserManager.exists(userId)) return null;
+ flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+ false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+ List<ResolveInfo> query = mPm.queryIntentServicesInternal(
+ intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
+ if (query != null) {
+ if (query.size() >= 1) {
+ // If there is more than one service with the same priority,
+ // just arbitrarily pick the first one.
+ return query.get(0);
+ }
+ }
+ return null;
+ }
+
+ public @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
+ Intent intent, String resolvedType, int flags, int userId) {
+ if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+ final int callingUid = Binder.getCallingUid();
+ final String instantAppPkgName = mPm.getInstantAppPackageName(callingUid);
+ flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+ false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+ ComponentName comp = intent.getComponent();
+ if (comp == null) {
+ if (intent.getSelector() != null) {
+ intent = intent.getSelector();
+ comp = intent.getComponent();
+ }
+ }
+ if (comp != null) {
+ final List<ResolveInfo> list = new ArrayList<>(1);
+ final ProviderInfo pi = mPm.getProviderInfo(comp, flags, userId);
+ if (pi != null) {
+ // When specifying an explicit component, we prevent the provider from being
+ // used when either 1) the provider is in an instant application and the
+ // caller is not the same instant application or 2) the calling package is an
+ // instant application and the provider is not visible to instant applications.
+ final boolean matchInstantApp =
+ (flags & PackageManager.MATCH_INSTANT) != 0;
+ final boolean matchVisibleToInstantAppOnly =
+ (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+ final boolean isCallerInstantApp =
+ instantAppPkgName != null;
+ final boolean isTargetSameInstantApp =
+ comp.getPackageName().equals(instantAppPkgName);
+ final boolean isTargetInstantApp =
+ (pi.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ final boolean isTargetHiddenFromInstantApp =
+ (pi.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
+ final boolean blockResolution =
+ !isTargetSameInstantApp
+ && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+ || (matchVisibleToInstantAppOnly && isCallerInstantApp
+ && isTargetHiddenFromInstantApp));
+ final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
+ && mPm.shouldFilterApplicationLocked(
+ mPm.getPackageSettingInternal(pi.applicationInfo.packageName,
+ Process.SYSTEM_UID), callingUid, userId);
+ if (!blockResolution && !blockNormalResolution) {
+ final ResolveInfo ri = new ResolveInfo();
+ ri.providerInfo = pi;
+ list.add(ri);
+ }
+ }
+ return list;
+ }
+
+ // reader
+ synchronized (mPm.mLock) {
+ String pkgName = intent.getPackage();
+ if (pkgName == null) {
+ final List<ResolveInfo> resolveInfos = mPm.mComponentResolver.queryProviders(intent,
+ resolvedType, flags, userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
+ return applyPostContentProviderResolutionFilter(
+ resolveInfos, instantAppPkgName, userId, callingUid);
+ }
+ final AndroidPackage pkg = mPm.mPackages.get(pkgName);
+ if (pkg != null) {
+ final List<ResolveInfo> resolveInfos = mPm.mComponentResolver.queryProviders(intent,
+ resolvedType, flags,
+ pkg.getProviders(), userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
+ return applyPostContentProviderResolutionFilter(
+ resolveInfos, instantAppPkgName, userId, callingUid);
+ }
+ return Collections.emptyList();
+ }
+ }
+
+ private List<ResolveInfo> applyPostContentProviderResolutionFilter(
+ List<ResolveInfo> resolveInfos, String instantAppPkgName,
+ @UserIdInt int userId, int callingUid) {
+ for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+ final ResolveInfo info = resolveInfos.get(i);
+
+ if (instantAppPkgName == null) {
+ SettingBase callingSetting =
+ mPm.mSettings.getSettingLPr(UserHandle.getAppId(callingUid));
+ PackageSetting resolvedSetting =
+ mPm.getPackageSettingInternal(info.providerInfo.packageName, 0);
+ if (!mPm.mAppsFilter.shouldFilterApplication(
+ callingUid, callingSetting, resolvedSetting, userId)) {
+ continue;
+ }
+ }
+
+ final boolean isEphemeralApp = info.providerInfo.applicationInfo.isInstantApp();
+ // allow providers that are defined in the provided package
+ if (isEphemeralApp && instantAppPkgName.equals(info.providerInfo.packageName)) {
+ if (info.providerInfo.splitName != null
+ && !ArrayUtils.contains(info.providerInfo.applicationInfo.splitNames,
+ info.providerInfo.splitName)) {
+ if (mPm.mInstantAppInstallerActivity == null) {
+ if (DEBUG_INSTANT) {
+ Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
+ }
+ resolveInfos.remove(i);
+ continue;
+ }
+ // requested provider is defined in a split that hasn't been installed yet.
+ // add the installer to the resolve list
+ if (DEBUG_INSTANT) {
+ Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
+ }
+ final ResolveInfo installerInfo = new ResolveInfo(
+ mPm.getInstantAppInstallerInfo());
+ installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
+ null /*failureActivity*/,
+ info.providerInfo.packageName,
+ info.providerInfo.applicationInfo.longVersionCode,
+ info.providerInfo.splitName);
+ // add a non-generic filter
+ installerInfo.filter = new IntentFilter();
+ // load resources from the correct package
+ installerInfo.resolvePackageName = info.getComponentInfo().packageName;
+ resolveInfos.set(i, installerInfo);
+ }
+ continue;
+ }
+ // allow providers that have been explicitly exposed to instant applications
+ if (!isEphemeralApp && (
+ (info.providerInfo.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
+ continue;
+ }
+ resolveInfos.remove(i);
+ }
+ return resolveInfos;
+ }
+
+ public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller,
+ Intent[] specifics, String[] specificTypes, Intent intent,
+ String resolvedType, int flags, int userId) {
+ if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+ final int callingUid = Binder.getCallingUid();
+ flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+ mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+ flags));
+ mPm.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+ false /*checkShell*/, "query intent activity options");
+ final String resultsAction = intent.getAction();
+
+ final List<ResolveInfo> results = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+ flags | PackageManager.GET_RESOLVED_FILTER, userId);
+
+ if (DEBUG_INTENT_MATCHING) {
+ Log.v(TAG, "Query " + intent + ": " + results);
+ }
+
+ int specificsPos = 0;
+ int N;
+
+ // todo: note that the algorithm used here is O(N^2). This
+ // isn't a problem in our current environment, but if we start running
+ // into situations where we have more than 5 or 10 matches then this
+ // should probably be changed to something smarter...
+
+ // First we go through and resolve each of the specific items
+ // that were supplied, taking care of removing any corresponding
+ // duplicate items in the generic resolve list.
+ if (specifics != null) {
+ for (int i = 0; i < specifics.length; i++) {
+ final Intent sintent = specifics[i];
+ if (sintent == null) {
+ continue;
+ }
+
+ if (DEBUG_INTENT_MATCHING) {
+ Log.v(TAG, "Specific #" + i + ": " + sintent);
+ }
+
+ String action = sintent.getAction();
+ if (resultsAction != null && resultsAction.equals(action)) {
+ // If this action was explicitly requested, then don't
+ // remove things that have it.
+ action = null;
+ }
+
+ ResolveInfo ri = null;
+ ActivityInfo ai = null;
+
+ ComponentName comp = sintent.getComponent();
+ if (comp == null) {
+ ri = mPm.resolveIntent(
+ sintent,
+ specificTypes != null ? specificTypes[i] : null,
+ flags, userId);
+ if (ri == null) {
+ continue;
+ }
+ if (ri == mPm.getResolveInfo()) {
+ // ACK! Must do something better with this.
+ }
+ ai = ri.activityInfo;
+ comp = new ComponentName(ai.applicationInfo.packageName,
+ ai.name);
+ } else {
+ ai = mPm.getActivityInfo(comp, flags, userId);
+ if (ai == null) {
+ continue;
+ }
+ }
+
+ // Look for any generic query activities that are duplicates
+ // of this specific one, and remove them from the results.
+ if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai);
+ N = results.size();
+ int j;
+ for (j = specificsPos; j < N; j++) {
+ ResolveInfo sri = results.get(j);
+ if ((sri.activityInfo.name.equals(comp.getClassName())
+ && sri.activityInfo.applicationInfo.packageName.equals(
+ comp.getPackageName()))
+ || (action != null && sri.filter.matchAction(action))) {
+ results.remove(j);
+ if (DEBUG_INTENT_MATCHING) {
+ Log.v(
+ TAG, "Removing duplicate item from " + j
+ + " due to specific " + specificsPos);
+ }
+ if (ri == null) {
+ ri = sri;
+ }
+ j--;
+ N--;
+ }
+ }
+
+ // Add this specific item to its proper place.
+ if (ri == null) {
+ ri = new ResolveInfo();
+ ri.activityInfo = ai;
+ }
+ results.add(specificsPos, ri);
+ ri.specificIndex = i;
+ specificsPos++;
+ }
+ }
+
+ // Now we go through the remaining generic results and remove any
+ // duplicate actions that are found here.
+ N = results.size();
+ for (int i = specificsPos; i < N - 1; i++) {
+ final ResolveInfo rii = results.get(i);
+ if (rii.filter == null) {
+ continue;
+ }
+
+ // Iterate over all of the actions of this result's intent
+ // filter... typically this should be just one.
+ final Iterator<String> it = rii.filter.actionsIterator();
+ if (it == null) {
+ continue;
+ }
+ while (it.hasNext()) {
+ final String action = it.next();
+ if (resultsAction != null && resultsAction.equals(action)) {
+ // If this action was explicitly requested, then don't
+ // remove things that have it.
+ continue;
+ }
+ for (int j = i + 1; j < N; j++) {
+ final ResolveInfo rij = results.get(j);
+ if (rij.filter != null && rij.filter.hasAction(action)) {
+ results.remove(j);
+ if (DEBUG_INTENT_MATCHING) {
+ Log.v(
+ TAG, "Removing duplicate item from " + j
+ + " due to action " + action + " at " + i);
+ }
+ j--;
+ N--;
+ }
+ }
+ }
+
+ // If the caller didn't request filter information, drop it now
+ // so we don't have to marshall/unmarshall it.
+ if ((flags & PackageManager.GET_RESOLVED_FILTER) == 0) {
+ rii.filter = null;
+ }
+ }
+
+ // Filter out the caller activity if so requested.
+ if (caller != null) {
+ N = results.size();
+ for (int i = 0; i < N; i++) {
+ ActivityInfo ainfo = results.get(i).activityInfo;
+ if (caller.getPackageName().equals(ainfo.applicationInfo.packageName)
+ && caller.getClassName().equals(ainfo.name)) {
+ results.remove(i);
+ break;
+ }
+ }
+ }
+
+ // If the caller didn't request filter information,
+ // drop them now so we don't have to
+ // marshall/unmarshall it.
+ if ((flags & PackageManager.GET_RESOLVED_FILTER) == 0) {
+ N = results.size();
+ for (int i = 0; i < N; i++) {
+ results.get(i).filter = null;
+ }
+ }
+
+ if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results);
+ return results;
+ }
+
+}
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index ef06c58..8018011 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -65,6 +65,7 @@
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.content.pm.parsing.component.ParsedProcess;
@@ -115,12 +116,22 @@
/**
* Helper class that handles package scanning logic
*/
-public final class ScanPackageHelper {
+final class ScanPackageHelper {
final PackageManagerService mPm;
+ final InstallPackageHelper mInstallPackageHelper;
+ final PackageManagerServiceInjector mInjector;
// TODO(b/198166813): remove PMS dependency
public ScanPackageHelper(PackageManagerService pm) {
mPm = pm;
+ mInjector = pm.mInjector;
+ mInstallPackageHelper = new InstallPackageHelper(mPm, mInjector);
+ }
+
+ ScanPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
+ mPm = pm;
+ mInjector = injector;
+ mInstallPackageHelper = new InstallPackageHelper(mPm, injector);
}
/**
@@ -197,7 +208,7 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
- try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
+ try (PackageParser2 pp = mInjector.getScanningPackageParser()) {
parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -208,8 +219,7 @@
PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
}
- return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
- mPm.mPlatformPackage, mPm.mIsUpgrade, mPm.mIsPreNMR1Upgrade);
+ return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user);
}
// TODO(b/199937291): scanPackageNewLI() and scanPackageOnlyLI() should be merged.
@@ -248,7 +258,7 @@
} else {
isUpdatedSystemApp = disabledPkgSetting != null;
}
- applyPolicy(parsedPackage, scanFlags, mPm.mPlatformPackage, isUpdatedSystemApp);
+ applyPolicy(parsedPackage, scanFlags, mPm.getPlatformPackage(), isUpdatedSystemApp);
assertPackageIsValid(parsedPackage, parseFlags, scanFlags);
SharedUserSetting sharedUserSetting = null;
@@ -264,14 +274,14 @@
}
}
}
- String platformPackageName = mPm.mPlatformPackage == null
- ? null : mPm.mPlatformPackage.getPackageName();
+ String platformPackageName = mPm.getPlatformPackage() == null
+ ? null : mPm.getPlatformPackage().getPackageName();
final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting,
pkgSetting == null ? null : pkgSetting.getPkg(), pkgSetting, disabledPkgSetting,
originalPkgSetting, realPkgName, parseFlags, scanFlags,
Objects.equals(parsedPackage.getPackageName(), platformPackageName), user,
cpuAbiOverride);
- return scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest, currentTime);
+ return scanPackageOnlyLI(request, mInjector, mPm.mFactoryTest, currentTime);
}
}
@@ -756,8 +766,7 @@
public AndroidPackage addForInitLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags, long currentTime,
- @Nullable UserHandle user, AndroidPackage platformPackage, boolean isUpgrade,
- boolean isPreNMR1Upgrade) throws PackageManagerException {
+ @Nullable UserHandle user) throws PackageManagerException {
final boolean scanSystemPartition =
(parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
final String renamedPkgName;
@@ -765,8 +774,11 @@
final boolean isSystemPkgUpdated;
final boolean pkgAlreadyExists;
PackageSetting pkgSetting;
+ AndroidPackage platformPackage;
+ final boolean isUpgrade = mPm.isDeviceUpgrading();
synchronized (mPm.mLock) {
+ platformPackage = mPm.getPlatformPackage();
renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
final String realPkgName = getRealPackageName(parsedPackage,
@@ -815,8 +827,8 @@
if (isSystemPkgUpdated) {
// we're updating the disabled package, so, scan it as the package setting
boolean isPlatformPackage = platformPackage != null
- && Objects.equals(platformPackage.getPackageName(),
- parsedPackage.getPackageName());
+ && platformPackage.getPackageName().equals(
+ parsedPackage.getPackageName());
final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting,
null, disabledPkgSetting /* pkgSetting */,
null /* disabledPkgSetting */, null /* originalPkgSetting */,
@@ -824,7 +836,7 @@
applyPolicy(parsedPackage, scanFlags,
platformPackage, true);
final ScanResult scanResult =
- scanPackageOnlyLI(request, mPm.mInjector,
+ scanPackageOnlyLI(request, mInjector,
mPm.mFactoryTest, -1L);
if (scanResult.mExistingSettingCopied
&& scanResult.mRequest.mPkgSetting != null) {
@@ -858,9 +870,9 @@
+ "; " + pkgSetting.getPathString()
+ " --> " + parsedPackage.getPath());
- final InstallArgs args = mPm.createInstallArgsForExisting(
+ final InstallArgs args = new FileInstallArgs(
pkgSetting.getPathString(), getAppDexInstructionSets(
- pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
+ pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm);
args.cleanUpResourcesLI();
synchronized (mPm.mLock) {
mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
@@ -900,8 +912,7 @@
// TODO(b/136132412): skip for Incremental installation
final boolean skipVerify = scanSystemPartition
|| (forceCollect && canSkipForcedPackageVerification(parsedPackage));
- collectCertificatesLI(pkgSetting, parsedPackage, forceCollect, skipVerify,
- isPreNMR1Upgrade);
+ collectCertificatesLI(pkgSetting, parsedPackage, forceCollect, skipVerify);
// Reset profile if the application version is changed
maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage);
@@ -944,9 +955,10 @@
+ parsedPackage.getLongVersionCode()
+ "; " + pkgSetting.getPathString() + " --> "
+ parsedPackage.getPath());
- InstallArgs args = mPm.createInstallArgsForExisting(
+ InstallArgs args = new FileInstallArgs(
pkgSetting.getPathString(), getAppDexInstructionSets(
- pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
+ pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()),
+ mPm);
synchronized (mPm.mInstallLock) {
args.cleanUpResourcesLI();
}
@@ -973,7 +985,7 @@
try {
final String pkgName = scanResult.mPkgSetting.getPackageName();
final Map<String, ReconciledPackage> reconcileResult =
- mPm.reconcilePackagesLocked(
+ mInstallPackageHelper.reconcilePackagesLocked(
new ReconcileRequest(
Collections.singletonMap(pkgName, scanResult),
mPm.mSharedLibraries,
@@ -985,9 +997,9 @@
Collections.singletonMap(pkgName,
mPm.getSharedLibLatestVersionSetting(
scanResult))),
- mPm.mSettings.getKeySetManagerService(), mPm.mInjector);
+ mPm.mSettings.getKeySetManagerService(), mInjector);
appIdCreated = optimisticallyRegisterAppId(scanResult);
- mPm.commitReconciledScanResultLocked(
+ mInstallPackageHelper.commitReconciledScanResultLocked(
reconcileResult.get(pkgName), mPm.mUserManager.getUserIds());
} catch (PackageManagerException e) {
if (appIdCreated) {
@@ -1063,11 +1075,10 @@
}
private void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
- boolean forceCollect, boolean skipVerify, boolean mIsPreNMR1Upgrade)
- throws PackageManagerException {
+ boolean forceCollect, boolean skipVerify) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
// directory and not the APK file.
- final long lastModifiedTime = mIsPreNMR1Upgrade
+ final long lastModifiedTime = mPm.isPreNMR1Upgrade()
? new File(parsedPackage.getPath()).lastModified()
: getLastModifiedTime(parsedPackage);
final Settings.VersionInfo settingsVersionForPackage =
@@ -1075,9 +1086,9 @@
if (ps != null && !forceCollect
&& ps.getPathString().equals(parsedPackage.getPath())
&& ps.getLastModifiedTime() == lastModifiedTime
- && !PackageManagerService.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
- && !PackageManagerService.isRecoverSignatureUpdateNeeded(
- settingsVersionForPackage)) {
+ && !InstallPackageHelper.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
+ && !InstallPackageHelper.isRecoverSignatureUpdateNeeded(
+ settingsVersionForPackage)) {
if (ps.getSigningDetails().getSignatures() != null
&& ps.getSigningDetails().getSignatures().length != 0
&& ps.getSigningDetails().getSignatureSchemeVersion()
@@ -1238,7 +1249,7 @@
synchronized (mPm.mLock) {
// The special "android" package can only be defined once
if (pkg.getPackageName().equals("android")) {
- if (mPm.mAndroidApplication != null) {
+ if (mPm.getCoreAndroidApplication() != null) {
Slog.w(TAG, "*************************************************");
Slog.w(TAG, "Core android package being redefined. Skipping.");
Slog.w(TAG, " codePath=" + pkg.getPath());
@@ -1389,7 +1400,7 @@
// to the user-installed location. If we don't allow this change, any newer,
// user-installed version of the application will be ignored.
if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
- if (mPm.mExpectingBetter.containsKey(pkg.getPackageName())) {
+ if (mPm.isExpectingBetter(pkg.getPackageName())) {
Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package "
+ pkg.getPackageName());
} else {
@@ -1470,9 +1481,7 @@
if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
// This must be an update to a system overlay. Immutable overlays cannot be
// upgraded.
- Objects.requireNonNull(mPm.mOverlayConfig,
- "Parsing non-system dir before overlay configs are initialized");
- if (!mPm.mOverlayConfig.isMutable(pkg.getPackageName())) {
+ if (!mPm.isOverlayMutable(pkg.getPackageName())) {
throw new PackageManagerException("Overlay "
+ pkg.getPackageName()
+ " is static and cannot be upgraded.");
@@ -1786,7 +1795,7 @@
final ParsedActivity component = pkg.getActivities().get(i);
final Boolean enabled = componentsEnabledStates.get(component.getName());
if (enabled != null) {
- component.setEnabled(enabled);
+ ComponentMutateUtils.setEnabled(component, enabled);
}
}
@@ -1794,7 +1803,7 @@
final ParsedActivity component = pkg.getReceivers().get(i);
final Boolean enabled = componentsEnabledStates.get(component.getName());
if (enabled != null) {
- component.setEnabled(enabled);
+ ComponentMutateUtils.setEnabled(component, enabled);
}
}
@@ -1802,7 +1811,7 @@
final ParsedProvider component = pkg.getProviders().get(i);
final Boolean enabled = componentsEnabledStates.get(component.getName());
if (enabled != null) {
- component.setEnabled(enabled);
+ ComponentMutateUtils.setEnabled(component, enabled);
}
}
@@ -1810,7 +1819,7 @@
final ParsedService component = pkg.getServices().get(i);
final Boolean enabled = componentsEnabledStates.get(component.getName());
if (enabled != null) {
- component.setEnabled(enabled);
+ ComponentMutateUtils.setEnabled(component, enabled);
}
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 85adaa0..f89d9eb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -802,7 +802,9 @@
disabled = p;
}
mDisabledSysPackages.put(name, disabled);
-
+ if (disabled.getSharedUser() != null) {
+ disabled.getSharedUser().mDisabledPackages.add(disabled);
+ }
return true;
}
return false;
@@ -814,6 +816,9 @@
Log.w(PackageManagerService.TAG, "Package " + name + " is not disabled");
return null;
}
+ if (p.getSharedUser() != null) {
+ p.getSharedUser().mDisabledPackages.remove(p);
+ }
p.getPkgState().setUpdatedSystemApp(false);
PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbi(),
@@ -833,7 +838,11 @@
}
void removeDisabledSystemPackageLPw(String name) {
- mDisabledSysPackages.remove(name);
+ final PackageSetting p = mDisabledSysPackages.remove(name);
+ if (p != null && p.getSharedUser() != null) {
+ p.getSharedUser().mDisabledPackages.remove(p);
+ checkAndPruneSharedUserLPw(p.getSharedUser(), false);
+ }
}
PackageSetting addPackageLPw(String name, String realName, File codePath,
@@ -883,27 +892,24 @@
}
void pruneSharedUsersLPw() {
- ArrayList<String> removeStage = new ArrayList<String>();
- for (Map.Entry<String,SharedUserSetting> entry : mSharedUsers.entrySet()) {
+ List<String> removeKeys = new ArrayList<>();
+ List<SharedUserSetting> removeValues = new ArrayList<>();
+ for (Map.Entry<String, SharedUserSetting> entry : mSharedUsers.entrySet()) {
final SharedUserSetting sus = entry.getValue();
if (sus == null) {
- removeStage.add(entry.getKey());
+ removeKeys.add(entry.getKey());
continue;
}
// remove packages that are no longer installed
- for (Iterator<PackageSetting> iter = sus.packages.iterator(); iter.hasNext();) {
- PackageSetting ps = iter.next();
- if (mPackages.get(ps.getPackageName()) == null) {
- iter.remove();
- }
- }
- if (sus.packages.size() == 0) {
- removeStage.add(entry.getKey());
+ sus.packages.removeIf(ps -> mPackages.get(ps.getPackageName()) == null);
+ sus.mDisabledPackages.removeIf(
+ ps -> mDisabledSysPackages.get(ps.getPackageName()) == null);
+ if (sus.packages.isEmpty() && sus.mDisabledPackages.isEmpty()) {
+ removeValues.add(sus);
}
}
- for (int i = 0; i < removeStage.size(); i++) {
- mSharedUsers.remove(removeStage.get(i));
- }
+ removeKeys.forEach(mSharedUsers::remove);
+ removeValues.forEach(sus -> checkAndPruneSharedUserLPw(sus, true));
}
/**
@@ -1233,18 +1239,20 @@
}
}
+ private void checkAndPruneSharedUserLPw(SharedUserSetting s, boolean skipCheck) {
+ if (skipCheck || (s.packages.isEmpty() && s.mDisabledPackages.isEmpty())) {
+ mSharedUsers.remove(s.name);
+ removeAppIdLPw(s.userId);
+ }
+ }
+
int removePackageLPw(String name) {
- final PackageSetting p = mPackages.get(name);
+ final PackageSetting p = mPackages.remove(name);
if (p != null) {
- mPackages.remove(name);
removeInstallerPackageStatus(name);
if (p.getSharedUser() != null) {
p.getSharedUser().removePackage(p);
- if (p.getSharedUser().packages.size() == 0) {
- mSharedUsers.remove(p.getSharedUser().name);
- removeAppIdLPw(p.getSharedUser().userId);
- return p.getSharedUser().userId;
- }
+ checkAndPruneSharedUserLPw(p.getSharedUser(), false);
} else {
removeAppIdLPw(p.getAppId());
return p.getAppId();
@@ -2994,17 +3002,6 @@
}
}
- // If the build is setup to drop runtime permissions
- // on update drop the files before loading them.
- if (PackageManagerService.CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE) {
- final VersionInfo internal = getInternalVersion();
- if (!Build.FINGERPRINT.equals(internal.fingerprint)) {
- for (UserInfo user : users) {
- mRuntimePermissionsPersistence.deleteUserRuntimePermissionsFile(user.id);
- }
- }
- }
-
final int N = mPendingPackages.size();
for (int i = 0; i < N; i++) {
@@ -3052,17 +3049,16 @@
* Make sure all the updated system packages have their shared users
* associated with them.
*/
- final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
- while (disabledIt.hasNext()) {
- final PackageSetting disabledPs = disabledIt.next();
+ for (PackageSetting disabledPs : mDisabledSysPackages.values()) {
final Object id = getSettingLPr(disabledPs.getAppId());
- if (id != null && id instanceof SharedUserSetting) {
+ if (id instanceof SharedUserSetting) {
disabledPs.setSharedUser((SharedUserSetting) id);
+ disabledPs.getSharedUser().mDisabledPackages.add(disabledPs);
}
}
- mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "
- + mSharedUsers.size() + " shared uids\n");
+ mReadMessages.append("Read completed successfully: ").append(mPackages.size())
+ .append(" packages, ").append(mSharedUsers.size()).append(" shared uids\n");
writeKernelMappingLPr();
@@ -3085,7 +3081,7 @@
for (int i=0; i<intents.size(); i++) {
Pair<String, ParsedIntentInfo> pair = intents.get(i);
applyDefaultPreferredActivityLPw(pmInternal,
- new WatchedIntentFilter(pair.second),
+ pair.second.getIntentFilter(),
new ComponentName(ps.getPackageName(), pair.first), userId);
}
}
@@ -3171,7 +3167,7 @@
}
private void applyDefaultPreferredActivityLPw(PackageManagerInternal pmInternal,
- WatchedIntentFilter tmpPa, ComponentName cn, int userId) {
+ IntentFilter tmpPa, ComponentName cn, int userId) {
// The initial preferences only specify the target activity
// component and intent-filter, not the set of matches. So we
// now need to query for the matches to build the correct
@@ -3415,7 +3411,7 @@
PreferredActivity tmpPa = new PreferredActivity(parser);
if (tmpPa.mPref.getParseError() == null) {
applyDefaultPreferredActivityLPw(
- pmInternal, tmpPa, tmpPa.mPref.mComponent, userId);
+ pmInternal, tmpPa.getIntentFilter(), tmpPa.mPref.mComponent, userId);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <preferred-activity> "
diff --git a/services/core/java/com/android/server/pm/SharedLibraryHelper.java b/services/core/java/com/android/server/pm/SharedLibraryHelper.java
new file mode 100644
index 0000000..2582316
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SharedLibraryHelper.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.os.Build;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import libcore.util.HexEncoding;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+final class SharedLibraryHelper {
+ private static final boolean DEBUG_SHARED_LIBRARIES = false;
+
+ /**
+ * Apps targeting Android S and above need to declare dependencies to the public native
+ * shared libraries that are defined by the device maker using {@code uses-native-library} tag
+ * in its {@code AndroidManifest.xml}.
+ *
+ * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist,
+ * the package manager rejects to install the app. The dependency can be specified as optional
+ * using {@code android:required} attribute in the tag, in which case failing to satisfy the
+ * dependency doesn't stop the installation.
+ * <p>Once installed, an app is provided with only the native shared libraries that are
+ * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear
+ * in the app manifest will fail even if it actually exists on the device.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
+
+ /**
+ * Compare the newly scanned package with current system state to see which of its declared
+ * shared libraries should be allowed to be added to the system.
+ */
+ public static List<SharedLibraryInfo> getAllowedSharedLibInfos(
+ ScanResult scanResult,
+ Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
+ // Let's used the parsed package as scanResult.pkgSetting may be null
+ final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
+ if (scanResult.mStaticSharedLibraryInfo == null
+ && scanResult.mDynamicSharedLibraryInfos == null) {
+ return null;
+ }
+
+ // Any app can add new static shared libraries
+ if (scanResult.mStaticSharedLibraryInfo != null) {
+ return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
+ }
+ final boolean hasDynamicLibraries = parsedPackage.isSystem()
+ && scanResult.mDynamicSharedLibraryInfos != null;
+ if (!hasDynamicLibraries) {
+ return null;
+ }
+ final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
+ .isUpdatedSystemApp();
+ // We may not yet have disabled the updated package yet, so be sure to grab the
+ // current setting if that's the case.
+ final PackageSetting updatedSystemPs = isUpdatedSystemApp
+ ? scanResult.mRequest.mDisabledPkgSetting == null
+ ? scanResult.mRequest.mOldPkgSetting
+ : scanResult.mRequest.mDisabledPkgSetting
+ : null;
+ if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
+ || updatedSystemPs.getPkg().getLibraryNames() == null)) {
+ Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+ + " declares libraries that are not declared on the system image; skipping");
+ return null;
+ }
+ final ArrayList<SharedLibraryInfo> infos =
+ new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
+ for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
+ final String name = info.getName();
+ if (isUpdatedSystemApp) {
+ // New library entries can only be added through the
+ // system image. This is important to get rid of a lot
+ // of nasty edge cases: for example if we allowed a non-
+ // system update of the app to add a library, then uninstalling
+ // the update would make the library go away, and assumptions
+ // we made such as through app install filtering would now
+ // have allowed apps on the device which aren't compatible
+ // with it. Better to just have the restriction here, be
+ // conservative, and create many fewer cases that can negatively
+ // impact the user experience.
+ if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) {
+ Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+ + " declares library " + name
+ + " that is not declared on system image; skipping");
+ continue;
+ }
+ }
+ if (sharedLibExists(
+ name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
+ Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library "
+ + name + " that already exists; skipping");
+ continue;
+ }
+ infos.add(info);
+ }
+ return infos;
+ }
+
+ public static boolean sharedLibExists(final String name, final long version,
+ Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
+ WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
+ return versionedLib != null && versionedLib.indexOfKey(version) >= 0;
+ }
+
+ /**
+ * Returns false if the adding shared library already exists in the map and so could not be
+ * added.
+ */
+ public static boolean addSharedLibraryToPackageVersionMap(
+ Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
+ SharedLibraryInfo library) {
+ final String name = library.getName();
+ if (target.containsKey(name)) {
+ if (library.getType() != SharedLibraryInfo.TYPE_STATIC) {
+ // We've already added this non-version-specific library to the map.
+ return false;
+ } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) {
+ // We've already added this version of a version-specific library to the map.
+ return false;
+ }
+ } else {
+ target.put(name, new WatchedLongSparseArray<>());
+ }
+ target.get(name).put(library.getLongVersion(), library);
+ return true;
+ }
+
+ public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
+ Map<String, AndroidPackage> availablePackages,
+ @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+ @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+ PlatformCompat platformCompat) throws PackageManagerException {
+ if (pkg == null) {
+ return null;
+ }
+ // The collection used here must maintain the order of addition (so
+ // that libraries are searched in the correct order) and must have no
+ // duplicates.
+ ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
+ if (!pkg.getUsesLibraries().isEmpty()) {
+ usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
+ pkg.getPackageName(), true, pkg.getTargetSdkVersion(), null,
+ availablePackages, existingLibraries, newLibraries);
+ }
+ if (!pkg.getUsesStaticLibraries().isEmpty()) {
+ usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
+ pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
+ pkg.getPackageName(), true, pkg.getTargetSdkVersion(), usesLibraryInfos,
+ availablePackages, existingLibraries, newLibraries);
+ }
+ if (!pkg.getUsesOptionalLibraries().isEmpty()) {
+ usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(),
+ null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
+ usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+ }
+ if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
+ pkg.getPackageName(), pkg.getTargetSdkVersion())) {
+ if (!pkg.getUsesNativeLibraries().isEmpty()) {
+ usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
+ null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(),
+ usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+ }
+ if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
+ usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
+ null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
+ usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+ }
+ }
+ return usesLibraryInfos;
+ }
+
+ public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
+ @NonNull List<String> requestedLibraries,
+ @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
+ @NonNull String packageName, boolean required, int targetSdk,
+ @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
+ @NonNull final Map<String, AndroidPackage> availablePackages,
+ @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+ @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+ throws PackageManagerException {
+ final int libCount = requestedLibraries.size();
+ for (int i = 0; i < libCount; i++) {
+ final String libName = requestedLibraries.get(i);
+ final long libVersion = requiredVersions != null ? requiredVersions[i]
+ : SharedLibraryInfo.VERSION_UNDEFINED;
+ final SharedLibraryInfo libraryInfo =
+ getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries);
+ if (libraryInfo == null) {
+ if (required) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable shared library "
+ + libName + "; failing!");
+ } else if (DEBUG_SHARED_LIBRARIES) {
+ Slog.i(TAG, "Package " + packageName
+ + " desires unavailable shared library "
+ + libName + "; ignoring!");
+ }
+ } else {
+ if (requiredVersions != null && requiredCertDigests != null) {
+ if (libraryInfo.getLongVersion() != requiredVersions[i]) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable static shared"
+ + " library " + libName + " version "
+ + libraryInfo.getLongVersion() + "; failing!");
+ }
+ AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName());
+ SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
+ if (libPkg == null) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable static shared"
+ + " library; failing!");
+ }
+ final String[] expectedCertDigests = requiredCertDigests[i];
+ if (expectedCertDigests.length > 1) {
+ // For apps targeting O MR1 we require explicit enumeration of all certs.
+ final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
+ ? PackageUtils.computeSignaturesSha256Digests(
+ libPkg.getSignatures())
+ : PackageUtils.computeSignaturesSha256Digests(
+ new Signature[]{libPkg.getSignatures()[0]});
+
+ // Take a shortcut if sizes don't match. Note that if an app doesn't
+ // target O we don't parse the "additional-certificate" tags similarly
+ // how we only consider all certs only for apps targeting O (see above).
+ // Therefore, the size check is safe to make.
+ if (expectedCertDigests.length != libCertDigests.length) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires differently signed"
+ + " static shared library; failing!");
+ }
+
+ // Use a predictable order as signature order may vary
+ Arrays.sort(libCertDigests);
+ Arrays.sort(expectedCertDigests);
+
+ final int certCount = libCertDigests.length;
+ for (int j = 0; j < certCount; j++) {
+ if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires differently signed"
+ + " static shared library; failing!");
+ }
+ }
+ } else {
+ // lib signing cert could have rotated beyond the one expected, check to see
+ // if the new one has been blessed by the old
+ byte[] digestBytes = HexEncoding.decode(
+ expectedCertDigests[0], false /* allowSingleChar */);
+ if (!libPkg.hasSha256Certificate(digestBytes)) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires differently signed"
+ + " static shared library; failing!");
+ }
+ }
+ }
+ if (outUsedLibraries == null) {
+ outUsedLibraries = new ArrayList<>();
+ }
+ outUsedLibraries.add(libraryInfo);
+ }
+ }
+ return outUsedLibraries;
+ }
+
+ @Nullable
+ public static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
+ Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+ @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
+ if (newLibraries != null) {
+ final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
+ SharedLibraryInfo info = null;
+ if (versionedLib != null) {
+ info = versionedLib.get(version);
+ }
+ if (info != null) {
+ return info;
+ }
+ }
+ final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
+ if (versionedLib == null) {
+ return null;
+ }
+ return versionedLib.get(version);
+ }
+
+ public static List<SharedLibraryInfo> findSharedLibraries(PackageSetting pkgSetting) {
+ if (!pkgSetting.getPkgState().getUsesLibraryInfos().isEmpty()) {
+ ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
+ Set<String> collectedNames = new HashSet<>();
+ for (SharedLibraryInfo info : pkgSetting.getPkgState().getUsesLibraryInfos()) {
+ findSharedLibrariesRecursive(info, retValue, collectedNames);
+ }
+ return retValue;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ private static void findSharedLibrariesRecursive(SharedLibraryInfo info,
+ ArrayList<SharedLibraryInfo> collected, Set<String> collectedNames) {
+ if (!collectedNames.contains(info.getName())) {
+ collectedNames.add(info.getName());
+ collected.add(info);
+
+ if (info.getDependencies() != null) {
+ for (SharedLibraryInfo dep : info.getDependencies()) {
+ findSharedLibrariesRecursive(dep, collected, collectedNames);
+ }
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 15df249..3387737 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -16,9 +16,11 @@
package com.android.server.pm;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProcessImpl;
import android.service.pm.PackageServiceDumpProto;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -31,6 +33,7 @@
import libcore.util.EmptyArray;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -52,6 +55,11 @@
final ArraySet<PackageSetting> packages;
+ // It is possible for a system app to leave shared user ID by an update.
+ // We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
+ // the update and revert the system app back into the original shared user ID.
+ final ArraySet<PackageSetting> mDisabledPackages;
+
final PackageSignatures signatures = new PackageSignatures();
Boolean signaturesChanged;
@@ -77,6 +85,7 @@
name = _name;
seInfoTargetSdkVersion = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
packages = new ArraySet<>();
+ mDisabledPackages = new ArraySet<>();
processes = new ArrayMap<>();
mSnapshot = makeCache();
}
@@ -87,13 +96,14 @@
name = orig.name;
uidFlags = orig.uidFlags;
uidPrivateFlags = orig.uidPrivateFlags;
- packages = new ArraySet(orig.packages);
+ packages = new ArraySet<>(orig.packages);
+ mDisabledPackages = new ArraySet<>(orig.mDisabledPackages);
// A SigningDetails seems to consist solely of final attributes, so
// it is safe to copy the reference.
signatures.mSigningDetails = orig.signatures.mSigningDetails;
signaturesChanged = orig.signaturesChanged;
- processes = new ArrayMap(orig.processes);
- mSnapshot = new SnapshotCache.Sealed();
+ processes = new ArrayMap<>(orig.processes);
+ mSnapshot = new SnapshotCache.Sealed<>();
}
/**
@@ -118,15 +128,14 @@
void addProcesses(Map<String, ParsedProcess> newProcs) {
if (newProcs != null) {
- final int numProcs = newProcs.size();
for (String key : newProcs.keySet()) {
ParsedProcess newProc = newProcs.get(key);
ParsedProcess proc = processes.get(newProc.getName());
if (proc == null) {
- proc = new ParsedProcess(newProc);
+ proc = new ParsedProcessImpl(newProc);
processes.put(newProc.getName(), proc);
} else {
- proc.addStateFrom(newProc);
+ ComponentMutateUtils.addStateFrom(proc, newProc);
}
}
onChanged();
@@ -174,9 +183,12 @@
}
}
- public @Nullable List<AndroidPackage> getPackages() {
+ /**
+ * @return the list of packages that uses this shared UID
+ */
+ public @NonNull List<AndroidPackage> getPackages() {
if (packages == null || packages.size() == 0) {
- return null;
+ return Collections.emptyList();
}
final ArrayList<AndroidPackage> pkgList = new ArrayList<>(packages.size());
for (PackageSetting ps : packages) {
@@ -269,8 +281,7 @@
this.processes.clear();
this.processes.ensureCapacity(numProcs);
for (int i = 0; i < numProcs; i++) {
- ParsedProcess proc =
- new ParsedProcess(sharedUser.processes.valueAt(i));
+ ParsedProcess proc = new ParsedProcessImpl(sharedUser.processes.valueAt(i));
this.processes.put(proc.getName(), proc);
}
} else {
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index a82cb9e..b49c8da 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -132,7 +132,7 @@
final AppDataHelper appDataHelper = new AppDataHelper(mPm);
final ArrayList<PackageFreezer> freezers = new ArrayList<>();
final ArrayList<AndroidPackage> loaded = new ArrayList<>();
- final int parseFlags = mPm.mDefParseFlags | ParsingPackageUtils.PARSE_EXTERNAL_STORAGE;
+ final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_EXTERNAL_STORAGE;
final Settings.VersionInfo ver;
final List<PackageSetting> packages;
diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java
index 3d2ffe1..c5569cd 100644
--- a/services/core/java/com/android/server/pm/VerificationParams.java
+++ b/services/core/java/com/android/server/pm/VerificationParams.java
@@ -65,7 +65,7 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -78,7 +78,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
final class VerificationParams extends HandlerParams {
/**
@@ -688,7 +688,7 @@
private void sendVerificationCompleteNotification() {
if (mParentVerificationParams != null) {
- mParentVerificationParams.trySendVerificationCompleteNotification(this, mRet);
+ mParentVerificationParams.trySendVerificationCompleteNotification(this);
} else {
try {
mObserver.onPackageInstalled(null, mRet, mErrorMessage,
@@ -717,7 +717,7 @@
static final class MultiPackageVerificationParams extends HandlerParams {
private final IPackageInstallObserver2 mObserver;
private final List<VerificationParams> mChildParams;
- private final Map<VerificationParams, Integer> mVerificationState;
+ private final Set<VerificationParams> mVerificationState;
MultiPackageVerificationParams(VerificationParams parent, List<VerificationParams> children,
PackageManagerService pm) throws PackageManagerException {
@@ -731,7 +731,7 @@
final VerificationParams childParams = children.get(i);
childParams.mParentVerificationParams = this;
}
- mVerificationState = new ArrayMap<>(mChildParams.size());
+ mVerificationState = new ArraySet<>(mChildParams.size());
mObserver = parent.mObserver;
}
@@ -749,18 +749,16 @@
}
}
- void trySendVerificationCompleteNotification(VerificationParams child, int currentStatus) {
- mVerificationState.put(child, currentStatus);
+ void trySendVerificationCompleteNotification(VerificationParams child) {
+ mVerificationState.add(child);
if (mVerificationState.size() != mChildParams.size()) {
return;
}
int completeStatus = PackageManager.INSTALL_SUCCEEDED;
String errorMsg = null;
- for (VerificationParams params : mVerificationState.keySet()) {
+ for (VerificationParams params : mVerificationState) {
int status = params.mRet;
- if (status == PackageManager.INSTALL_UNKNOWN) {
- return;
- } else if (status != PackageManager.INSTALL_SUCCEEDED) {
+ if (status != PackageManager.INSTALL_SUCCEEDED) {
completeStatus = status;
errorMsg = params.mErrorMessage;
break;
diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
index 5a4431e..052a4ca 100644
--- a/services/core/java/com/android/server/pm/dex/OWNERS
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -1,2 +1,3 @@
-calin@google.com
+alanstokes@google.com
+jiakaiz@google.com
ngeoffray@google.com
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 7598423..6846ac5 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -26,6 +26,7 @@
import android.content.pm.SigningDetails;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageImpl;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedProvider;
import android.content.pm.parsing.component.ParsedService;
@@ -314,37 +315,37 @@
int permissionsSize = permissions.size();
for (int index = 0; index < permissionsSize; index++) {
- permissions.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(permissions.get(index), this.packageName);
}
int permissionGroupsSize = permissionGroups.size();
for (int index = 0; index < permissionGroupsSize; index++) {
- permissionGroups.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(permissionGroups.get(index), this.packageName);
}
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
- activities.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(activities.get(index), this.packageName);
}
int receiversSize = receivers.size();
for (int index = 0; index < receiversSize; index++) {
- receivers.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(receivers.get(index), this.packageName);
}
int providersSize = providers.size();
for (int index = 0; index < providersSize; index++) {
- providers.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(providers.get(index), this.packageName);
}
int servicesSize = services.size();
for (int index = 0; index < servicesSize; index++) {
- services.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(services.get(index), this.packageName);
}
int instrumentationsSize = instrumentations.size();
for (int index = 0; index < instrumentationsSize; index++) {
- instrumentations.get(index).setPackageName(this.packageName);
+ ComponentMutateUtils.setPackageName(instrumentations.get(index), this.packageName);
}
return this;
@@ -354,22 +355,26 @@
public PackageImpl setAllComponentsDirectBootAware(boolean allComponentsDirectBootAware) {
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
- activities.get(index).setDirectBootAware(allComponentsDirectBootAware);
+ ComponentMutateUtils.setDirectBootAware(activities.get(index),
+ allComponentsDirectBootAware);
}
int receiversSize = receivers.size();
for (int index = 0; index < receiversSize; index++) {
- receivers.get(index).setDirectBootAware(allComponentsDirectBootAware);
+ ComponentMutateUtils.setDirectBootAware(receivers.get(index),
+ allComponentsDirectBootAware);
}
int providersSize = providers.size();
for (int index = 0; index < providersSize; index++) {
- providers.get(index).setDirectBootAware(allComponentsDirectBootAware);
+ ComponentMutateUtils.setDirectBootAware(providers.get(index),
+ allComponentsDirectBootAware);
}
int servicesSize = services.size();
for (int index = 0; index < servicesSize; index++) {
- services.get(index).setDirectBootAware(allComponentsDirectBootAware);
+ ComponentMutateUtils.setDirectBootAware(services.get(index),
+ allComponentsDirectBootAware);
}
return this;
@@ -434,7 +439,7 @@
int size = permissionGroups.size();
for (int index = size - 1; index >= 0; --index) {
// TODO(b/135203078): Builder/immutability
- permissionGroups.get(index).setPriority(0);
+ ComponentMutateUtils.setPriority(permissionGroups.get(index), 0);
}
return this;
}
@@ -446,7 +451,7 @@
for (int index = 0; index < receiversSize; index++) {
ParsedActivity receiver = receivers.get(index);
if ((receiver.getFlags() & ActivityInfo.FLAG_SINGLE_USER) != 0) {
- receiver.setExported(false);
+ ComponentMutateUtils.setExported(receiver, false);
}
}
@@ -455,7 +460,7 @@
for (int index = 0; index < servicesSize; index++) {
ParsedService service = services.get(index);
if ((service.getFlags() & ActivityInfo.FLAG_SINGLE_USER) != 0) {
- service.setExported(false);
+ ComponentMutateUtils.setExported(service, false);
}
}
@@ -464,7 +469,7 @@
for (int index = 0; index < providersSize; index++) {
ParsedProvider provider = providers.get(index);
if ((provider.getFlags() & ActivityInfo.FLAG_SINGLE_USER) != 0) {
- provider.setExported(false);
+ ComponentMutateUtils.setExported(provider, false);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 0e939ef..ece0a62b 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -213,6 +213,7 @@
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.UWB_RANGING);
+ NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.NEARBY_WIFI_DEVICES);
}
private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
@@ -915,6 +916,11 @@
}
grantPermissionsToSystemPackage(pm, dialerPackage, userId,
CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS);
+ boolean isAndroidAutomotive =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
+ if (isAndroidAutomotive) {
+ grantPermissionsToSystemPackage(pm, dialerPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
}
private void grantDefaultPermissionsToDefaultSystemSmsApp(PackageManagerWrapper pm,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d54c571..e62988dd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -91,8 +91,10 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.SigningDetails;
+import android.content.pm.parsing.component.ComponentMutateUtils;
import android.content.pm.parsing.component.ParsedPermission;
import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedPermissionUtils;
import android.content.pm.permission.CompatibilityPermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.metrics.LogMaker;
@@ -2287,7 +2289,7 @@
newPermissionNum++) {
final ParsedPermission newPermission =
newPackage.getPermissions().get(newPermissionNum);
- final int newProtection = newPermission.getProtection();
+ final int newProtection = ParsedPermissionUtils.getProtection(newPermission);
if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
final String permissionName = newPermission.getName();
@@ -2403,7 +2405,7 @@
ParsedPermission p = pkg.getPermissions().get(i);
// Assume by default that we did not install this permission into the system.
- p.setFlags(p.getFlags() & ~PermissionInfo.FLAG_INSTALLED);
+ ComponentMutateUtils.setExactFlags(p, p.getFlags() & ~PermissionInfo.FLAG_INSTALLED);
final PermissionInfo permissionInfo;
final Permission oldPermission;
@@ -2413,7 +2415,8 @@
// permissions for one app being granted to someone just because they happen
// to be in a group defined by another app (before this had no implications).
if (pkg.getTargetSdkVersion() > Build.VERSION_CODES.LOLLIPOP_MR1) {
- p.setParsedPermissionGroup(mRegistry.getPermissionGroup(p.getGroup()));
+ ComponentMutateUtils.setParsedPermissionGroup(p,
+ mRegistry.getPermissionGroup(p.getGroup()));
// Warn for a permission in an unknown group.
if (DEBUG_PERMISSIONS
&& p.getGroup() != null && p.getParsedPermissionGroup() == null) {
@@ -2441,7 +2444,8 @@
mRegistry.addPermission(permission);
}
if (permission.isInstalled()) {
- p.setFlags(p.getFlags() | PermissionInfo.FLAG_INSTALLED);
+ ComponentMutateUtils.setExactFlags(p,
+ p.getFlags() | PermissionInfo.FLAG_INSTALLED);
}
if (permission.isDefinitionChanged()) {
definitionChangedPermissions.add(p.getName());
@@ -2516,7 +2520,7 @@
r.append(p.getName());
}
}
- if (p.isAppOp()) {
+ if (ParsedPermissionUtils.isAppOp(p)) {
// TODO(zhanghai): Should we just remove the entry for this permission directly?
mRegistry.removeAppOpPermissionPackage(p.getName(), pkg.getPackageName());
}
@@ -3451,7 +3455,7 @@
for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) {
final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i];
if (info.getName().equals(perm)
- && pkg.getTargetSdkVersion() < info.sdkVersion) {
+ && pkg.getTargetSdkVersion() < info.getSdkVersion()) {
allowed = true;
Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+ pkg.getPackageName());
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index a5ba82f..d47f510 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -187,7 +187,7 @@
for (int intentIndex = 0; intentIndex < intentsSize && !needsAutoVerify;
intentIndex++) {
ParsedIntentInfo intent = intents.get(intentIndex);
- needsAutoVerify = intent.needsVerification();
+ needsAutoVerify = intent.getIntentFilter().needsVerification();
}
}
@@ -205,10 +205,11 @@
int intentsSize = intents.size();
for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) {
ParsedIntentInfo intent = intents.get(intentIndex);
- if (intent.handlesWebUris(false)) {
- int authorityCount = intent.countDataAuthorities();
+ IntentFilter intentFilter = intent.getIntentFilter();
+ if (intentFilter.handlesWebUris(false)) {
+ int authorityCount = intentFilter.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
- String host = intent.getDataAuthority(index).getHost();
+ String host = intentFilter.getDataAuthority(index).getHost();
if (isValidHost(host) == valid) {
totalSize += byteSizeOf(host);
underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
@@ -248,12 +249,13 @@
int intentsSize = intents.size();
for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) {
ParsedIntentInfo intent = intents.get(intentIndex);
- if (checkAutoVerify && !intent.getAutoVerify()) {
+ IntentFilter intentFilter = intent.getIntentFilter();
+ if (checkAutoVerify && !intentFilter.getAutoVerify()) {
continue;
}
- if (!intent.hasCategory(Intent.CATEGORY_DEFAULT)
- || !intent.handlesWebUris(checkAutoVerify)) {
+ if (!intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)
+ || !intentFilter.handlesWebUris(checkAutoVerify)) {
continue;
}
@@ -271,9 +273,9 @@
// app will probably fail. This can be re-configured to work properly by the
// app developer by declaring a separate intent-filter. This may not be worth
// fixing.
- int authorityCount = intent.countDataAuthorities();
+ int authorityCount = intentFilter.countDataAuthorities();
for (int index = 0; index < authorityCount && underMaxSize; index++) {
- String host = intent.getDataAuthority(index).getHost();
+ String host = intentFilter.getDataAuthority(index).getHost();
if (isValidHost(host) == valid) {
totalSize += byteSizeOf(host);
underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 12e6086d..61076ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,7 +47,6 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -94,8 +93,10 @@
import static com.android.server.wm.WindowManagerPolicyProto.SCREEN_ON_FULLY;
import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
@@ -106,6 +107,7 @@
import android.app.UiModeManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -114,6 +116,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -192,6 +195,7 @@
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.app.AssistUtils;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.logging.MetricsLogger;
@@ -231,6 +235,7 @@
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* WindowManagerPolicy implementation for the Android phone UI. This
@@ -309,6 +314,22 @@
static final int PENDING_KEY_NULL = -1;
+ // Must match: config_shortPressOnStemPrimaryBehavior in config.xml
+ static final int SHORT_PRESS_PRIMARY_NOTHING = 0;
+ static final int SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS = 1;
+
+ // Must match: config_longPressOnStemPrimaryBehavior in config.xml
+ static final int LONG_PRESS_PRIMARY_NOTHING = 0;
+ static final int LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT = 1;
+
+ // Must match: config_doublePressOnStemPrimaryBehavior in config.xml
+ static final int DOUBLE_PRESS_PRIMARY_NOTHING = 0;
+ static final int DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP = 1;
+
+ // Must match: config_triplePressOnStemPrimaryBehavior in config.xml
+ static final int TRIPLE_PRESS_PRIMARY_NOTHING = 0;
+ static final int TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY = 1;
+
static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -316,6 +337,8 @@
static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
+ private static final String TALKBACK_LABEL = "TalkBack";
+
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -401,7 +424,6 @@
private AccessibilityShortcutController mAccessibilityShortcutController;
boolean mSafeMode;
- private WindowState mKeyguardCandidate = null;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
// This is for car dock and this is updated from resource.
@@ -488,6 +510,10 @@
int mShortPressOnSleepBehavior;
int mShortPressOnWindowBehavior;
int mPowerVolUpBehavior;
+ int mShortPressOnStemPrimaryBehavior;
+ int mDoublePressOnStemPrimaryBehavior;
+ int mTriplePressOnStemPrimaryBehavior;
+ int mLongPressOnStemPrimaryBehavior;
boolean mHasSoftInput = false;
boolean mHapticTextHandleEnabled;
boolean mUseTvRouting;
@@ -1193,6 +1219,170 @@
return mLongPressOnPowerBehavior;
}
+ private void stemPrimaryPress(int count) {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "stemPrimaryPress: " + count);
+ }
+ if (count == 3) {
+ stemPrimaryTriplePressAction(mTriplePressOnStemPrimaryBehavior);
+ } else if (count == 2) {
+ stemPrimaryDoublePressAction(mDoublePressOnStemPrimaryBehavior);
+ } else if (count == 1) {
+ stemPrimarySinglePressAction(mShortPressOnStemPrimaryBehavior);
+ }
+ }
+
+ private void stemPrimarySinglePressAction(int behavior) {
+ switch (behavior) {
+ case SHORT_PRESS_PRIMARY_NOTHING:
+ break;
+ case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS:
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Executing stem primary short press action behavior.");
+ }
+ final boolean keyguardActive =
+ mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_ALL_APPS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+ break;
+ }
+ }
+
+ private void stemPrimaryDoublePressAction(int behavior) {
+ switch (behavior) {
+ case DOUBLE_PRESS_PRIMARY_NOTHING:
+ break;
+ case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Executing stem primary double press action behavior.");
+ }
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ switchRecentTask();
+ }
+ break;
+ }
+ }
+
+ private void stemPrimaryTriplePressAction(int behavior) {
+ switch (behavior) {
+ case TRIPLE_PRESS_PRIMARY_NOTHING:
+ break;
+ case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Executing stem primary triple press action behavior.");
+ }
+ toggleTalkBack();
+ break;
+ }
+ }
+
+ private void stemPrimaryLongPress() {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Executing stem primary long press action behavior.");
+ }
+
+ switch (mLongPressOnStemPrimaryBehavior) {
+ case LONG_PRESS_PRIMARY_NOTHING:
+ break;
+ case LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT:
+ launchVoiceAssist(/* allowDuringSetup= */false);
+ break;
+ }
+ }
+
+ private void toggleTalkBack() {
+ final ComponentName componentName = getTalkbackComponent();
+ if (componentName == null) {
+ return;
+ }
+
+ final Set<ComponentName> enabledServices =
+ AccessibilityUtils.getEnabledServicesFromSettings(mContext, mCurrentUserId);
+
+ AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
+ !enabledServices.contains(componentName));
+ }
+
+ private ComponentName getTalkbackComponent() {
+ AccessibilityManager accessibilityManager = mContext.getSystemService(
+ AccessibilityManager.class);
+ List<AccessibilityServiceInfo> serviceInfos =
+ accessibilityManager.getInstalledAccessibilityServiceList();
+
+ for (AccessibilityServiceInfo service : serviceInfos) {
+ final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ if (isTalkback(serviceInfo)) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+ return null;
+ }
+
+ private boolean isTalkback(ServiceInfo info) {
+ String label = info.loadLabel(mPackageManager).toString();
+ return label.equals(TALKBACK_LABEL);
+ }
+
+ /**
+ * Load most recent task (expect current task) and bring it to the front.
+ */
+ private void switchRecentTask() {
+ RecentTaskInfo targetTask = mActivityTaskManagerInternal.getMostRecentTaskFromBackground();
+ if (targetTask == null) {
+ if (DEBUG_INPUT) {
+ Slog.w(TAG, "No recent task available! Show watch face.");
+ }
+ goHome();
+ return;
+ }
+
+ if (DEBUG_INPUT) {
+ Slog.d(
+ TAG,
+ "Starting task from recents. id="
+ + targetTask.id
+ + ", persistentId="
+ + targetTask.persistentId
+ + ", topActivity="
+ + targetTask.topActivity
+ + ", baseIntent="
+ + targetTask.baseIntent);
+ }
+ try {
+ ActivityManager.getService().startActivityFromRecents(targetTask.persistentId, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to start task " + targetTask.persistentId + " from recents", e);
+ }
+ }
+
+ private int getMaxMultiPressStemPrimaryCount() {
+ switch (mTriplePressOnStemPrimaryBehavior) {
+ case TRIPLE_PRESS_PRIMARY_NOTHING:
+ break;
+ case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
+ if (Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
+ /* def= */ 0,
+ UserHandle.USER_CURRENT)
+ == 1) {
+ return 3;
+ }
+ break;
+ }
+ if (mDoublePressOnStemPrimaryBehavior != DOUBLE_PRESS_PRIMARY_NOTHING) {
+ return 2;
+ }
+ return 1;
+ }
+
private boolean hasLongPressOnPowerBehavior() {
return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
}
@@ -1205,6 +1395,16 @@
return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
}
+ private boolean hasLongPressOnStemPrimaryBehavior() {
+ return mLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING;
+ }
+
+ private boolean hasStemPrimaryBehavior() {
+ return getMaxMultiPressStemPrimaryCount() > 1
+ || hasLongPressOnStemPrimaryBehavior()
+ || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
+ }
+
private void interceptScreenshotChord() {
mHandler.removeCallbacks(mScreenshotRunnable);
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
@@ -1814,18 +2014,22 @@
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
// When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
// receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
// need to call IKeyguardService#keyguardGoingAway here.
return handleStartTransitionForKeyguardLw(keyguardGoingAway
- && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation, duration);
+ && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation,
+ keyguardOccluding, duration);
}
@Override
public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
- handleStartTransitionForKeyguardLw(keyguardGoingAway, 0 /* duration */);
+ handleStartTransitionForKeyguardLw(
+ keyguardGoingAway, false /* keyguardOccludingStarted */,
+ 0 /* duration */);
}
});
@@ -2034,6 +2238,35 @@
}
}
+ /**
+ * Rule for single stem primary key gesture.
+ */
+ private final class StemPrimaryKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
+ StemPrimaryKeyRule(int gestures) {
+ super(mContext, KeyEvent.KEYCODE_STEM_PRIMARY, gestures);
+ }
+
+ @Override
+ int getMaxMultiPressCount() {
+ return getMaxMultiPressStemPrimaryCount();
+ }
+
+ @Override
+ void onPress(long downTime) {
+ stemPrimaryPress(1 /*count*/);
+ }
+
+ @Override
+ void onLongPress(long eventTime) {
+ stemPrimaryLongPress();
+ }
+
+ @Override
+ void onMultiPress(long downTime, int count) {
+ stemPrimaryPress(count);
+ }
+ }
+
private void initSingleKeyGestureRules() {
mSingleKeyGestureDetector = new SingleKeyGestureDetector();
@@ -2049,6 +2282,13 @@
if (hasLongPressOnBackBehavior()) {
mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS));
}
+ if (hasStemPrimaryBehavior()) {
+ int stemPrimaryKeyGestures = 0;
+ if (hasLongPressOnStemPrimaryBehavior()) {
+ stemPrimaryKeyGestures |= KEY_LONGPRESS;
+ }
+ mSingleKeyGestureDetector.addRule(new StemPrimaryKeyRule(stemPrimaryKeyGestures));
+ }
}
/**
@@ -2077,6 +2317,14 @@
if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
}
+ mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior);
+ mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior);
+ mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior);
+ mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior);
}
public void updateSettings() {
@@ -3050,26 +3298,28 @@
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
- setKeyguardOccludedLw(occluded, false /* force */);
+ setKeyguardOccludedLw(occluded, false /* force */,
+ false /* transitionStarted */);
}
}
@Override
- public int applyKeyguardOcclusionChange() {
+ public int applyKeyguardOcclusionChange(boolean transitionStarted) {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
- mKeyguardOccludedChanged = false;
- if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */)) {
+ if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */,
+ transitionStarted)) {
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
}
}
return 0;
}
- private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) {
- final int res = applyKeyguardOcclusionChange();
- if (res != 0) return res;
+ private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration) {
+ final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding);
+ if (redoLayout != 0) return redoLayout;
if (keyguardGoingAway) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
@@ -3267,42 +3517,32 @@
mNavBarVirtualKeyHapticFeedbackEnabled = enabled;
}
- /** {@inheritDoc} */
- @Override
- public void setKeyguardCandidateLw(WindowState win) {
- mKeyguardCandidate = win;
- setKeyguardOccludedLw(isKeyguardOccluded(), true /* force */);
- }
-
/**
* Updates the occluded state of the Keyguard.
*
* @param isOccluded Whether the Keyguard is occluded by another window.
* @param force notify the occluded status to KeyguardService and update flags even though
* occlude status doesn't change.
+ * @param transitionStarted {@code true} if keyguard (un)occluded transition started.
* @return Whether the flags have changed and we have to redo the layout.
*/
- private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force) {
+ private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
+ boolean transitionStarted) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
+ mKeyguardOccludedChanged = false;
if (isKeyguardOccluded() == isOccluded && !force) {
return false;
}
final boolean showing = mKeyguardDelegate.isShowing();
final boolean animate = showing && !isOccluded;
- mKeyguardDelegate.setOccluded(isOccluded, animate);
-
- if (!showing) {
- return false;
- }
- if (mKeyguardCandidate != null) {
- if (isOccluded) {
- mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
- } else if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
- mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
- }
- }
- return true;
+ // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService
+ // uses remote animation start as a signal to update its occlusion status ,so we don't need
+ // to notify here.
+ final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
+ || !transitionStarted;
+ mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
+ return showing;
}
/** {@inheritDoc} */
@@ -5378,6 +5618,22 @@
pw.print("mShortPressOnWindowBehavior=");
pw.println(shortPressOnWindowBehaviorToString(mShortPressOnWindowBehavior));
pw.print(prefix);
+ pw.print("mShortPressOnStemPrimaryBehavior=");
+ pw.println(shortPressOnStemPrimaryBehaviorToString(
+ mShortPressOnStemPrimaryBehavior));
+ pw.print(prefix);
+ pw.print("mDoublePressOnStemPrimaryBehavior=");
+ pw.println(doublePressOnStemPrimaryBehaviorToString(
+ mDoublePressOnStemPrimaryBehavior));
+ pw.print(prefix);
+ pw.print("mTriplePressOnStemPrimaryBehavior=");
+ pw.println(triplePressOnStemPrimaryBehaviorToString(
+ mTriplePressOnStemPrimaryBehavior));
+ pw.print(prefix);
+ pw.print("mLongPressOnStemPrimaryBehavior=");
+ pw.println(longPressOnStemPrimaryBehaviorToString(
+ mLongPressOnStemPrimaryBehavior));
+ pw.print(prefix);
pw.print("mAllowStartActivityForLongPressOnPowerDuringSetup=");
pw.println(mAllowStartActivityForLongPressOnPowerDuringSetup);
pw.print(prefix);
@@ -5593,6 +5849,50 @@
}
}
+ private static String shortPressOnStemPrimaryBehaviorToString(int behavior) {
+ switch (behavior) {
+ case SHORT_PRESS_PRIMARY_NOTHING:
+ return "SHORT_PRESS_PRIMARY_NOTHING";
+ case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS:
+ return "SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String doublePressOnStemPrimaryBehaviorToString(int behavior) {
+ switch (behavior) {
+ case DOUBLE_PRESS_PRIMARY_NOTHING:
+ return "DOUBLE_PRESS_PRIMARY_NOTHING";
+ case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
+ return "DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String triplePressOnStemPrimaryBehaviorToString(int behavior) {
+ switch (behavior) {
+ case TRIPLE_PRESS_PRIMARY_NOTHING:
+ return "TRIPLE_PRESS_PRIMARY_NOTHING";
+ case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
+ return "TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String longPressOnStemPrimaryBehaviorToString(int behavior) {
+ switch (behavior) {
+ case LONG_PRESS_PRIMARY_NOTHING:
+ return "LONG_PRESS_PRIMARY_NOTHING";
+ case LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT:
+ return "LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
private static String lidBehaviorToString(int behavior) {
switch (behavior) {
case LID_BEHAVIOR_LOCK:
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 1d4943f..b7fb6e5 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -53,6 +53,7 @@
// Key code of current key down event, reset when key up.
private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
private volatile boolean mHandledByLongPress = false;
+ private volatile boolean mHandledByMultiPress = false;
private final Handler mHandler;
private long mLastDownTime = 0;
private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout();
@@ -271,6 +272,7 @@
mKeyPressCounter + 1, mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
+ mHandledByMultiPress = true;
mKeyPressCounter = 0;
}
}
@@ -284,8 +286,9 @@
return false;
}
- if (mHandledByLongPress) {
+ if (mHandledByLongPress || mHandledByMultiPress) {
mHandledByLongPress = false;
+ mHandledByMultiPress = false;
mKeyPressCounter = 0;
return true;
}
@@ -339,6 +342,7 @@
}
mHandledByLongPress = false;
+ mHandledByMultiPress = false;
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 78b03b2..81dd9c5 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -27,6 +27,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -78,7 +79,6 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.IApplicationToken;
import android.view.IDisplayFoldListener;
import android.view.IWindowManager;
import android.view.KeyEvent;
@@ -173,8 +173,11 @@
*/
void onKeyguardOccludedChangedLw(boolean occluded);
- /** Applies a keyguard occlusion change if one happened. */
- int applyKeyguardOcclusionChange();
+ /**
+ * Applies a keyguard occlusion change if one happened.
+ * @param transitionStarted Whether keyguard (un)occlude transition is starting or not.
+ */
+ int applyKeyguardOcclusionChange(boolean transitionStarted);
/**
* Interface to the Window Manager state associated with a particular
@@ -203,14 +206,6 @@
public int getBaseType();
/**
- * Return the token for the application (actually activity) that owns
- * this window. May return null for system windows.
- *
- * @return An IApplicationToken identifying the owning activity.
- */
- public IApplicationToken getAppToken();
-
- /**
* Return true if this window (or a window it is attached to, but not
* considering its app token) is currently animating.
*/
@@ -676,7 +671,7 @@
*/
default boolean canBeHiddenByKeyguardLw(WindowState win) {
// Keyguard visibility of window from activities are determined over activity visibility.
- if (win.getAppToken() != null) {
+ if (win.getBaseType() == TYPE_BASE_APPLICATION) {
return false;
}
switch (win.getAttrs().type) {
@@ -719,13 +714,6 @@
int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId);
/**
- * Set or clear a window which can behave as the keyguard.
- *
- * @param win The window which can behave as the keyguard.
- */
- void setKeyguardCandidateLw(@Nullable WindowState win);
-
- /**
* Create and return an animation to re-display a window that was force hidden by Keyguard.
*/
public Animation createHiddenByKeyguardExit(boolean onWallpaper,
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 86ff33e..0080ec6 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -29,7 +29,6 @@
import com.android.internal.policy.IKeyguardService;
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
-import com.android.server.wm.WindowManagerService;
import java.io.PrintWriter;
@@ -235,13 +234,6 @@
return false;
}
- public boolean hasLockscreenWallpaper() {
- if (mKeyguardService != null) {
- return mKeyguardService.hasLockscreenWallpaper();
- }
- return false;
- }
-
public boolean hasKeyguard() {
return mKeyguardState.deviceHasKeyguard;
}
@@ -259,13 +251,8 @@
}
}
- /**
- * @deprecated Notify occlude status change via remote animation.
- */
- @Deprecated
- public void setOccluded(boolean isOccluded, boolean animate) {
- if (!WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
- && mKeyguardService != null) {
+ public void setOccluded(boolean isOccluded, boolean animate, boolean notify) {
+ if (mKeyguardService != null && notify) {
if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
mKeyguardService.setOccluded(isOccluded, animate);
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index c356fec..2029f86 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -267,10 +267,6 @@
return mKeyguardStateMonitor.isTrusted();
}
- public boolean hasLockscreenWallpaper() {
- return mKeyguardStateMonitor.hasLockscreenWallpaper();
- }
-
public boolean isSecure(int userId) {
return mKeyguardStateMonitor.isSecure(userId);
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index f0f62ed..c0aa8ae 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -44,7 +44,6 @@
private volatile boolean mSimSecure = true;
private volatile boolean mInputRestricted = true;
private volatile boolean mTrusted = false;
- private volatile boolean mHasLockscreenWallpaper = false;
private int mCurrentUserId;
@@ -79,10 +78,6 @@
return mTrusted;
}
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
public int getCurrentUser() {
return mCurrentUserId;
}
@@ -116,11 +111,6 @@
mCallback.onTrustedChanged();
}
- @Override // Binder interface
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- }
-
public interface StateCallback {
void onTrustedChanged();
void onShowingChanged();
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 0bfb55c..f45bda7 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -47,11 +47,13 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
+import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
@@ -302,49 +304,53 @@
public String computePackageStateHash(@UserIdInt int userId) {
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
- final MessageDigestUtils md = new MessageDigestUtils();
+ final MessageDigestOutputStream mdos = new MessageDigestOutputStream();
+ DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(mdos));
packageManagerInternal.forEachInstalledPackage(pkg -> {
- md.writeString(pkg.getPackageName());
- md.writeLong(pkg.getLongVersionCode());
- md.writeInt(packageManagerInternal.getApplicationEnabledState(pkg.getPackageName(),
- userId));
+ try {
+ dataOutputStream.writeUTF(pkg.getPackageName());
+ dataOutputStream.writeLong(pkg.getLongVersionCode());
+ dataOutputStream.writeInt(packageManagerInternal.getApplicationEnabledState(
+ pkg.getPackageName(), userId));
- final List<String> requestedPermissions = pkg.getRequestedPermissions();
- final int requestedPermissionsSize = requestedPermissions.size();
- md.writeInt(requestedPermissionsSize);
- for (int i = 0; i < requestedPermissionsSize; i++) {
- md.writeString(requestedPermissions.get(i));
- }
+ final List<String> requestedPermissions = pkg.getRequestedPermissions();
+ final int requestedPermissionsSize = requestedPermissions.size();
+ dataOutputStream.writeInt(requestedPermissionsSize);
+ for (int i = 0; i < requestedPermissionsSize; i++) {
+ dataOutputStream.writeUTF(requestedPermissions.get(i));
+ }
- final ArraySet<String> enabledComponents = packageManagerInternal.getEnabledComponents(
- pkg.getPackageName(), userId);
- final int enabledComponentsSize = CollectionUtils.size(enabledComponents);
- md.writeInt(enabledComponentsSize);
- for (int i = 0; i < enabledComponentsSize; i++) {
- md.writeString(enabledComponents.valueAt(i));
- }
+ final ArraySet<String> enabledComponents =
+ packageManagerInternal.getEnabledComponents(pkg.getPackageName(), userId);
+ final int enabledComponentsSize = CollectionUtils.size(enabledComponents);
+ dataOutputStream.writeInt(enabledComponentsSize);
+ for (int i = 0; i < enabledComponentsSize; i++) {
+ dataOutputStream.writeUTF(enabledComponents.valueAt(i));
+ }
- final ArraySet<String> disabledComponents =
- packageManagerInternal.getDisabledComponents(pkg.getPackageName(), userId);
- final int disabledComponentsSize = CollectionUtils.size(disabledComponents);
- for (int i = 0; i < disabledComponentsSize; i++) {
- md.writeString(disabledComponents.valueAt(i));
- }
+ final ArraySet<String> disabledComponents =
+ packageManagerInternal.getDisabledComponents(pkg.getPackageName(), userId);
+ final int disabledComponentsSize = CollectionUtils.size(disabledComponents);
+ for (int i = 0; i < disabledComponentsSize; i++) {
+ dataOutputStream.writeUTF(disabledComponents.valueAt(i));
+ }
- for (final Signature signature : pkg.getSigningDetails().getSignatures()) {
- md.writeBytes(signature.toByteArray());
+ for (final Signature signature : pkg.getSigningDetails().getSignatures()) {
+ dataOutputStream.write(signature.toByteArray());
+ }
+ } catch (IOException e) {
+ // Never happens for MessageDigestOutputStream and DataOutputStream.
+ throw new AssertionError(e);
}
}, userId);
-
- return md.getDigestAsString();
+ return mdos.getDigestAsString();
}
- private static class MessageDigestUtils {
- private final byte[] mBuffer = new byte[8];
+ private static class MessageDigestOutputStream extends OutputStream {
private final MessageDigest mMessageDigest;
- MessageDigestUtils() {
+ MessageDigestOutputStream() {
try {
mMessageDigest = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
@@ -358,32 +364,19 @@
return HexEncoding.encodeToString(mMessageDigest.digest(), true /* uppercase */);
}
- void writeBytes(@NonNull byte[] bytes) {
- mMessageDigest.update(bytes);
+ @Override
+ public void write(int b) throws IOException {
+ mMessageDigest.update((byte) b);
}
- void writeString(@NonNull String s) {
- mMessageDigest.update(s.getBytes(StandardCharsets.UTF_8));
+ @Override
+ public void write(byte[] b) throws IOException {
+ mMessageDigest.update(b);
}
- void writeLong(long v) {
- mBuffer[0] = (byte) (v >>> 56);
- mBuffer[1] = (byte) (v >>> 48);
- mBuffer[2] = (byte) (v >>> 40);
- mBuffer[3] = (byte) (v >>> 32);
- mBuffer[4] = (byte) (v >>> 24);
- mBuffer[5] = (byte) (v >>> 16);
- mBuffer[6] = (byte) (v >>> 8);
- mBuffer[7] = (byte) (v >>> 0);
- mMessageDigest.update(mBuffer, 0, 8);
- }
-
- void writeInt(int v) {
- mBuffer[0] = (byte) (v >>> 24);
- mBuffer[1] = (byte) (v >>> 16);
- mBuffer[2] = (byte) (v >>> 8);
- mBuffer[3] = (byte) (v >>> 0);
- mMessageDigest.update(mBuffer, 0, 4);
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ mMessageDigest.update(b, off, len);
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9943b99..2cc9c86 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2701,7 +2701,7 @@
mHandler.removeMessages(MSG_ATTENTIVE_TIMEOUT);
- if (isBeingKeptFromShowingInattentiveSleepWarningLocked()) {
+ if (isBeingKeptFromInattentiveSleepLocked()) {
return;
}
@@ -2717,10 +2717,6 @@
}
mInattentiveSleepWarningOverlayController.show();
nextTimeout = goToSleepTime;
- } else {
- if (DEBUG && getWakefulnessLocked() != WAKEFULNESS_ASLEEP) {
- Slog.i(TAG, "Going to sleep now due to long user inactivity");
- }
}
if (nextTimeout >= 0) {
@@ -2739,7 +2735,7 @@
if (getWakefulnessLocked() != WAKEFULNESS_AWAKE) {
mInattentiveSleepWarningOverlayController.dismiss(false);
return true;
- } else if (attentiveTimeout < 0 || isBeingKeptFromShowingInattentiveSleepWarningLocked()
+ } else if (attentiveTimeout < 0 || isBeingKeptFromInattentiveSleepLocked()
|| now < showWarningTime) {
mInattentiveSleepWarningOverlayController.dismiss(true);
return true;
@@ -2861,8 +2857,11 @@
Slog.d(TAG, "updateWakefulnessLocked: Bed time for group " + id);
}
if (isAttentiveTimeoutExpired(id, time)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Going to sleep now due to long user inactivity");
+ }
changed = sleepDisplayGroupNoUpdateLocked(id, time,
- PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+ PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
} else if (shouldNapAtBedTimeLocked()) {
changed = dreamDisplayGroupNoUpdateLocked(id, time, Process.SYSTEM_UID);
@@ -2898,7 +2897,7 @@
long now = mClock.uptimeMillis();
if (isAttentiveTimeoutExpired(groupId, now)) {
- return !isBeingKeptFromInattentiveSleepLocked(groupId);
+ return !isBeingKeptFromInattentiveSleepLocked();
} else {
return !isBeingKeptAwakeLocked(groupId);
}
@@ -2928,13 +2927,7 @@
* a phone call. This function only controls whether the device will go to sleep which is
* independent of whether it will be allowed to suspend.
*/
- private boolean isBeingKeptFromInattentiveSleepLocked(int groupId) {
- return mStayOn || mScreenBrightnessBoostInProgress || mProximityPositive
- || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & (
- USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0;
- }
-
- private boolean isBeingKeptFromShowingInattentiveSleepWarningLocked() {
+ private boolean isBeingKeptFromInattentiveSleepLocked() {
return mStayOn || mScreenBrightnessBoostInProgress || mProximityPositive || !mBootCompleted;
}
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index db408488..96823c8a 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -1088,6 +1088,10 @@
@GuardedBy("mSamples")
private long mLastForecastCallTimeMillis = 0;
+ private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
+ @VisibleForTesting
+ long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
+
void updateSevereThresholds() {
synchronized (mSamples) {
List<TemperatureThreshold> thresholds =
@@ -1107,13 +1111,12 @@
}
}
- private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
private static final int RING_BUFFER_SIZE = 30;
private void updateTemperature() {
synchronized (mSamples) {
if (SystemClock.elapsedRealtime() - mLastForecastCallTimeMillis
- < INACTIVITY_THRESHOLD_MILLIS) {
+ < mInactivityThresholdMillis) {
// Trigger this again after a second as long as forecast has been called more
// recently than the inactivity timeout
mHandler.postDelayed(this::updateTemperature, 1000);
@@ -1199,7 +1202,7 @@
float getForecast(int forecastSeconds) {
synchronized (mSamples) {
- mLastForecastCallTimeMillis = System.currentTimeMillis();
+ mLastForecastCallTimeMillis = SystemClock.elapsedRealtime();
if (mSamples.isEmpty()) {
updateTemperature();
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index ca24ed9..720c773 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -843,6 +843,11 @@
final String packageName = newPackage.getPackageName();
final int rollbackDataPolicy = computeRollbackDataPolicy(
session.rollbackDataPolicy, newPackage.getRollbackDataPolicy());
+ if (!session.isStaged() && (installFlags & PackageManager.INSTALL_APEX) != 0
+ && rollbackDataPolicy != PackageManager.ROLLBACK_DATA_POLICY_RETAIN) {
+ Slog.e(TAG, "Only RETAIN is supported for rebootless APEX: " + packageName);
+ return false;
+ }
Slog.i(TAG, "Enabling rollback for install of " + packageName
+ ", session:" + session.sessionId
+ ", rollbackDataPolicy=" + rollbackDataPolicy);
@@ -1157,6 +1162,7 @@
assertInWorkerThread();
Slog.i(TAG, "makeRollbackAvailable id=" + rollback.info.getRollbackId());
rollback.makeAvailable();
+ mPackageHealthObserver.notifyRollbackAvailable(rollback.info);
// TODO(zezeozue): Provide API to explicitly start observing instead
// of doing this for all rollbacks. If we do this for all rollbacks,
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 1295b70..1856786 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -51,6 +51,7 @@
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
@@ -75,8 +76,11 @@
private final Handler mHandler;
private final ApexManager mApexManager;
private final File mLastStagedRollbackIdsFile;
+ private final File mTwoPhaseRollbackEnabledFile;
// Staged rollback ids that have been committed but their session is not yet ready
private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
+ // True if needing to roll back only rebootless apexes when native crash happens
+ private boolean mTwoPhaseRollbackEnabled;
RollbackPackageHealthObserver(Context context) {
mContext = context;
@@ -86,8 +90,19 @@
File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
+ mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
mApexManager = ApexManager.getInstance();
+
+ if (SystemProperties.getBoolean("sys.boot_completed", false)) {
+ // Load the value from the file if system server has crashed and restarted
+ mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile);
+ } else {
+ // Disable two-phase rollback for a normal reboot. We assume the rebootless apex
+ // installed before reboot is stable if native crash didn't happen.
+ mTwoPhaseRollbackEnabled = false;
+ writeBoolean(mTwoPhaseRollbackEnabledFile, false);
+ }
}
@Override
@@ -144,6 +159,31 @@
PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
}
+ @AnyThread
+ void notifyRollbackAvailable(RollbackInfo rollback) {
+ mHandler.post(() -> {
+ // Enable two-phase rollback when a rebootless apex rollback is made available.
+ // We assume the rebootless apex is stable and is less likely to be the cause
+ // if native crash doesn't happen before reboot. So we will clear the flag and disable
+ // two-phase rollback after reboot.
+ if (isRebootlessApex(rollback)) {
+ mTwoPhaseRollbackEnabled = true;
+ writeBoolean(mTwoPhaseRollbackEnabledFile, true);
+ }
+ });
+ }
+
+ private static boolean isRebootlessApex(RollbackInfo rollback) {
+ if (!rollback.isStaged()) {
+ for (PackageRollbackInfo info : rollback.getPackages()) {
+ if (info.isApex()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
* to check for native crashes and mitigate them if needed.
*/
@@ -155,6 +195,7 @@
@WorkerThread
private void onBootCompleted() {
assertInWorkerThread();
+
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
// TODO(gavincorkery): Call into Package Watchdog from outside the observer
@@ -277,6 +318,23 @@
return mPendingStagedRollbackIds.isEmpty();
}
+ private static boolean readBoolean(File file) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return fis.read() == 1;
+ } catch (IOException ignore) {
+ return false;
+ }
+ }
+
+ private static void writeBoolean(File file, boolean value) {
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(value ? 1 : 0);
+ fos.flush();
+ FileUtils.sync(fos);
+ } catch (IOException ignore) {
+ }
+ }
+
@WorkerThread
private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) {
assertInWorkerThread();
@@ -420,13 +478,44 @@
Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
}
+ /**
+ * Two-phase rollback:
+ * 1. roll back rebootless apexes first
+ * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
+ *
+ * This approach gives us a better chance to correctly attribute native crash to rebootless
+ * apex update without rolling back Mainline updates which might contains critical security
+ * fixes.
+ */
+ @WorkerThread
+ private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) {
+ assertInWorkerThread();
+ if (!mTwoPhaseRollbackEnabled) {
+ return false;
+ }
+
+ Slog.i(TAG, "Rolling back all rebootless APEX rollbacks");
+ boolean found = false;
+ for (RollbackInfo rollback : rollbacks) {
+ if (isRebootlessApex(rollback)) {
+ VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ found = true;
+ }
+ }
+ return found;
+ }
+
@WorkerThread
private void rollbackAll() {
assertInWorkerThread();
- Slog.i(TAG, "Rolling back all available rollbacks");
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+ if (useTwoPhaseRollback(rollbacks)) {
+ return;
+ }
+ Slog.i(TAG, "Rolling back all available rollbacks");
// Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
// pending staged rollbacks are handled.
for (RollbackInfo rollback : rollbacks) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 47bd72a..9f211db 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -310,6 +310,7 @@
for (int i = 0; i < aidlEvent.data.length; ++i) {
aidlEvent.data[i] = hidlEvent.data.get(i);
}
+ aidlEvent.recognitionStillActive = aidlEvent.status == RecognitionStatus.FORCED;
return aidlEvent;
}
@@ -441,15 +442,7 @@
private static @NonNull
HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) {
if (dataSize > 0) {
- // Extract a dup of the underlying FileDescriptor out of data.
- FileDescriptor fd = new FileDescriptor();
- try {
- ParcelFileDescriptor dup = data.dup();
- fd.setInt$(dup.detachFd());
- return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ return HidlMemoryUtil.fileDescriptorToHidlMemory(data.getFileDescriptor(), dataSize);
} else {
return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0);
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index e3ce719..990b21c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -226,8 +226,7 @@
public void recognitionCallback(int modelHandle, RecognitionEvent event) {
// A recognition event must be the last one for its model, unless it is a forced one
// (those leave the model active).
- mCallbackThread.pushWithDedupe(modelHandle,
- event.status != RecognitionStatus.FORCED,
+ mCallbackThread.pushWithDedupe(modelHandle, !event.recognitionStillActive,
() -> mDelegateCallback.recognitionCallback(modelHandle, event));
}
@@ -235,8 +234,7 @@
public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
// A recognition event must be the last one for its model, unless it is a forced one
// (those leave the model active).
- mCallbackThread.pushWithDedupe(modelHandle,
- event.common.status != RecognitionStatus.FORCED,
+ mCallbackThread.pushWithDedupe(modelHandle, !event.common.recognitionStillActive,
() -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index 6870f4f..235d10f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -55,8 +55,7 @@
private final ISoundTriggerHal mUnderlying;
private final Map<Integer, ModelState> mModelStates = new HashMap<>();
- public SoundTriggerHalEnforcer(
- ISoundTriggerHal underlying) {
+ public SoundTriggerHalEnforcer(ISoundTriggerHal underlying) {
mUnderlying = underlying;
}
@@ -239,15 +238,12 @@
private class ModelCallbackEnforcer implements ModelCallback {
private final ModelCallback mUnderlying;
- private ModelCallbackEnforcer(
- ModelCallback underlying) {
+ private ModelCallbackEnforcer(ModelCallback underlying) {
mUnderlying = underlying;
}
@Override
public void recognitionCallback(int model, RecognitionEvent event) {
- int status = event.status;
-
synchronized (mModelStates) {
ModelState state = mModelStates.get(model);
if (state == null || state == ModelState.INACTIVE) {
@@ -255,7 +251,15 @@
reboot();
return;
}
- if (status != RecognitionStatus.FORCED) {
+ if (event.recognitionStillActive && event.status != RecognitionStatus.SUCCESS
+ && event.status != RecognitionStatus.FORCED) {
+ Log.wtfStack(TAG,
+ "recognitionStillActive is only allowed when the recognition status "
+ + "is SUCCESS");
+ reboot();
+ return;
+ }
+ if (!event.recognitionStillActive) {
mModelStates.replace(model, ModelState.INACTIVE);
}
}
@@ -265,7 +269,6 @@
@Override
public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
- int status = event.common.status;
synchronized (mModelStates) {
ModelState state = mModelStates.get(model);
if (state == null || state == ModelState.INACTIVE) {
@@ -273,7 +276,16 @@
reboot();
return;
}
- if (status != RecognitionStatus.FORCED) {
+ if (event.common.recognitionStillActive
+ && event.common.status != RecognitionStatus.SUCCESS
+ && event.common.status != RecognitionStatus.FORCED) {
+ Log.wtfStack(TAG,
+ "recognitionStillActive is only allowed when the recognition status "
+ + "is SUCCESS");
+ reboot();
+ return;
+ }
+ if (!event.common.recognitionStillActive) {
mModelStates.replace(model, ModelState.INACTIVE);
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index f564756..0a085ba 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -26,6 +26,7 @@
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
import android.os.IBinder;
@@ -221,11 +222,15 @@
@Override
public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+ // A FORCED status implies that recognition is still active after the event.
+ event.common.recognitionStillActive |= event.common.status == RecognitionStatus.FORCED;
mDelegate.phraseRecognitionCallback(model, event);
}
@Override
public void recognitionCallback(int model, RecognitionEvent event) {
+ // A FORCED status implies that recognition is still active after the event.
+ event.recognitionStillActive |= event.status == RecognitionStatus.FORCED;
mDelegate.recognitionCallback(model, event);
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 7b31946..4243fc7 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -731,7 +731,7 @@
int captureSession) {
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (event.status != RecognitionStatus.FORCED) {
+ if (!event.recognitionStillActive) {
modelState.activityState = ModelState.Activity.LOADED;
}
}
@@ -760,7 +760,7 @@
@NonNull PhraseRecognitionEvent event, int captureSession) {
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (event.common.status != RecognitionStatus.FORCED) {
+ if (!event.common.recognitionStillActive) {
modelState.activityState = ModelState.Activity.LOADED;
}
}
@@ -772,7 +772,7 @@
Log.w(TAG, "Client callback exception.", e);
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (event.common.status != RecognitionStatus.FORCED) {
+ if (!event.common.recognitionStillActive) {
modelState.activityState = ModelState.Activity.INTERCEPTED;
// If we failed to deliver an actual event to the client, they would
// never know to restart it whenever circumstances change. Thus, we
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index f211158..934b0e4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -24,7 +24,6 @@
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
@@ -461,7 +460,7 @@
@NonNull RecognitionEvent recognitionEvent) {
ISoundTriggerCallback callback;
synchronized (SoundTriggerModule.this) {
- if (recognitionEvent.status != RecognitionStatus.FORCED) {
+ if (!recognitionEvent.recognitionStillActive) {
setState(ModelState.LOADED);
}
callback = mCallback;
@@ -482,7 +481,7 @@
@NonNull PhraseRecognitionEvent phraseRecognitionEvent) {
ISoundTriggerCallback callback;
synchronized (SoundTriggerModule.this) {
- if (phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
+ if (!phraseRecognitionEvent.common.recognitionStillActive) {
setState(ModelState.LOADED);
}
callback = mCallback;
diff --git a/services/core/java/com/android/server/timedetector/OWNERS b/services/core/java/com/android/server/timedetector/OWNERS
index 8f80897..67fc9d6 100644
--- a/services/core/java/com/android/server/timedetector/OWNERS
+++ b/services/core/java/com/android/server/timedetector/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# This code is maintained by the same OWNERS as timezonedetector.
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index ac2d76e..477ebf6 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -55,10 +55,11 @@
*/
@StringDef(prefix = "KEY_", value = {
KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
- KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE,
- KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE,
- KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
- KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
+ KEY_PRIMARY_LTZP_MODE_OVERRIDE,
+ KEY_SECONDARY_LTZP_MODE_OVERRIDE,
+ KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
+ KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
@@ -82,16 +83,14 @@
* The key for the server flag that can override the device config for whether the primary
* location time zone provider is enabled, disabled, or (for testing) in simulation mode.
*/
- public static final @DeviceConfigKey String
- KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE =
+ public static final @DeviceConfigKey String KEY_PRIMARY_LTZP_MODE_OVERRIDE =
"primary_location_time_zone_provider_mode_override";
/**
* The key for the server flag that can override the device config for whether the secondary
* location time zone provider is enabled or disabled, or (for testing) in simulation mode.
*/
- public static final @DeviceConfigKey String
- KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE =
+ public static final @DeviceConfigKey String KEY_SECONDARY_LTZP_MODE_OVERRIDE =
"secondary_location_time_zone_provider_mode_override";
/**
@@ -106,18 +105,20 @@
* The key for the timeout passed to a location time zone provider that tells it how long it has
* to provide an explicit first suggestion without being declared uncertain.
*/
- public static final @DeviceConfigKey String
- KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS =
- "ltpz_init_timeout_millis";
+ public static final @DeviceConfigKey String KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS =
+ "ltzp_init_timeout_millis";
/**
* The key for the extra time added to {@link
- * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone
+ * #KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone
* manager before the location time zone provider will actually be declared uncertain.
*/
- public static final @DeviceConfigKey String
- KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
- "ltpz_init_timeout_fuzz_millis";
+ public static final @DeviceConfigKey String KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
+ "ltzp_init_timeout_fuzz_millis";
+
+ /** The key for the setting that controls rate limiting of provider events. */
+ public static final @DeviceConfigKey String KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS =
+ "ltzp_event_filtering_age_threshold_millis";
/**
* The key for the server flag that can override location time zone detection being enabled for
diff --git a/services/core/java/com/android/server/timezone/OWNERS b/services/core/java/com/android/server/timezone/OWNERS
index 8f80897..2d36574 100644
--- a/services/core/java/com/android/server/timezone/OWNERS
+++ b/services/core/java/com/android/server/timezone/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+include platform/libcore:/OWNERS
diff --git a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
index 1867ee2..3b32549 100644
--- a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
+++ b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
@@ -16,9 +16,11 @@
package com.android.server.timezonedetector;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ShellCommand;
+import android.os.SystemClock;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -29,27 +31,39 @@
import java.util.StringTokenizer;
/**
- * A time zone suggestion from a geolocation source.
+ * A time zone suggestion from the location_time_zone_manager service to the time_zone_detector
+ * service.
*
- * <p> Geolocation-based suggestions have the following properties:
+ * <p>Geolocation-based suggestions have the following properties:
*
* <ul>
+ * <li>{@code effectiveFromElapsedMillis}: The time according to the elapsed realtime clock
+ * after which the suggestion should be considered in effect. For example, when a location fix
+ * used to establish the time zone is old, then the suggestion
+ * {@code effectiveFromElapsedMillis} should reflect this and indicates the time zone that was
+ * detected / correct at that time. The time_zone_detector is only expected to use the latest
+ * suggestion it has received, and so later suggestions always counteract previous suggestions.
+ * The inclusion of this information means that the time_zone_detector can take into account
+ * ordering when comparing suggestions from different sources.
+ * <br />Note: Because the times can be back-dated, time_zone_detector can be sent a sequence of
+ * suggestions where the {@code effectiveFromElapsedMillis} of later suggestions is before
+ * the {@code effectiveFromElapsedMillis} of an earlier one.</li>
* <li>{@code zoneIds}. When not {@code null}, {@code zoneIds} contains a list of suggested time
* zone IDs, e.g. ["America/Phoenix", "America/Denver"]. Usually there will be a single zoneId.
* When there are multiple, this indicates multiple answers are possible for the current
- * location / accuracy, i.e. if there is a nearby time zone border. The detection logic
- * receiving the suggestion is expected to use the first element in the absence of other
- * information, but one of the others may be used if there is supporting evidence / preferences
- * such as a device setting or corroborating signals from another source.
+ * location / accuracy, e.g. if there is a nearby time zone border. The time_zone_detector is
+ * expected to use the first element in the absence of other information, but one of the other
+ * zone IDs may be used if there is supporting evidence / preferences such as a device setting
+ * or corroborating signals from another source.
* <br />{@code zoneIds} can be empty if the current location has been determined to have no
* time zone. For example, oceans or disputed areas. This is considered a strong signal and the
- * received need not look for time zone from other sources.
- * <br />{@code zoneIds} can be {@code null} to indicate that the geolocation source has entered
- * an "un-opinionated" state and any previous suggestion is being withdrawn. This indicates the
- * source cannot provide a valid suggestion due to technical limitations. For example, a
- * geolocation source may become un-opinionated if the device's location is no longer known with
- * sufficient accuracy, or if the location is known but no time zone can be determined because
- * no time zone mapping information is available.</li>
+ * time_zone_detector need not look for time zone from other sources.
+ * <br />{@code zoneIds} can be {@code null} to indicate that the location_time_zone_manager has
+ * entered an "uncertain" state and any previous suggestion is being withdrawn. This indicates
+ * the location_time_zone_manager cannot provide a valid suggestion. For example, the
+ * location_time_zone_manager may become uncertain if components further downstream cannot
+ * determine the device's location with sufficient accuracy, or if the location is known but no
+ * time zone can be determined because no time zone mapping information is available.</li>
* <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
* used to record why the suggestion exists and how it was obtained. This information exists
* only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
@@ -61,10 +75,14 @@
*/
public final class GeolocationTimeZoneSuggestion {
+ @ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis;
@Nullable private final List<String> mZoneIds;
@Nullable private ArrayList<String> mDebugInfo;
- public GeolocationTimeZoneSuggestion(@Nullable List<String> zoneIds) {
+ private GeolocationTimeZoneSuggestion(
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis,
+ @Nullable List<String> zoneIds) {
+ mEffectiveFromElapsedMillis = effectiveFromElapsedMillis;
if (zoneIds == null) {
// Unopinionated
mZoneIds = null;
@@ -74,6 +92,34 @@
}
/**
+ * Creates a "uncertain" suggestion instance.
+ */
+ @NonNull
+ public static GeolocationTimeZoneSuggestion createUncertainSuggestion(
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis) {
+ return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, null);
+ }
+
+ /**
+ * Creates a "certain" suggestion instance.
+ */
+ @NonNull
+ public static GeolocationTimeZoneSuggestion createCertainSuggestion(
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis,
+ @NonNull List<String> zoneIds) {
+ return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds);
+ }
+
+ /**
+ * Returns the "effective from" time associated with the suggestion. See {@link
+ * GeolocationTimeZoneSuggestion} for details.
+ */
+ @ElapsedRealtimeLong
+ public long getEffectiveFromElapsedMillis() {
+ return mEffectiveFromElapsedMillis;
+ }
+
+ /**
* Returns the zone Ids being suggested. See {@link GeolocationTimeZoneSuggestion} for details.
*/
@Nullable
@@ -110,18 +156,20 @@
}
GeolocationTimeZoneSuggestion
that = (GeolocationTimeZoneSuggestion) o;
- return Objects.equals(mZoneIds, that.mZoneIds);
+ return mEffectiveFromElapsedMillis == that.mEffectiveFromElapsedMillis
+ && Objects.equals(mZoneIds, that.mZoneIds);
}
@Override
public int hashCode() {
- return Objects.hash(mZoneIds);
+ return Objects.hash(mEffectiveFromElapsedMillis, mZoneIds);
}
@Override
public String toString() {
return "GeolocationTimeZoneSuggestion{"
- + "mZoneIds=" + mZoneIds
+ + "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis
+ + ", mZoneIds=" + mZoneIds
+ ", mDebugInfo=" + mDebugInfo
+ '}';
}
@@ -141,8 +189,11 @@
}
}
}
+
+ long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
- GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
+ GeolocationTimeZoneSuggestion suggestion =
+ new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds);
suggestion.addDebugInfo("Command line injection");
return suggestion;
}
diff --git a/services/core/java/com/android/server/timezonedetector/OWNERS b/services/core/java/com/android/server/timezonedetector/OWNERS
index 8f80897..0293242 100644
--- a/services/core/java/com/android/server/timezonedetector/OWNERS
+++ b/services/core/java/com/android/server/timezonedetector/OWNERS
@@ -1,3 +1,7 @@
# Bug component: 847766
+# This is the main list for platform time / time zone detection maintainers, for this dir and
+# ultimately referenced by other OWNERS files for components maintained by the same team.
+nfuller@google.com
+jmorace@google.com
mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+narayan@google.com
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 5828130..20f4fa1 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -72,17 +72,18 @@
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
- ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE,
- ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE,
- ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
- ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
+ ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
}));
- private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
- private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
- Duration.ofMinutes(1);
- private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+ private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+ private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
+ private static final Duration DEFAULT_LTZP_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+ private static final Duration DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD = Duration.ofMinutes(1);
private static final Object SLOCK = new Object();
@@ -326,8 +327,7 @@
// In test mode: use the test setting value.
return mTestPrimaryLocationTimeZoneProviderMode;
}
- return mServerFlags.getOptionalString(
- ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE)
+ return mServerFlags.getOptionalString(ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE)
.orElse(getPrimaryLocationTimeZoneProviderModeFromConfig());
}
@@ -346,8 +346,7 @@
// In test mode: use the test setting value.
return mTestSecondaryLocationTimeZoneProviderMode;
}
- return mServerFlags.getOptionalString(
- ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE)
+ return mServerFlags.getOptionalString(ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE)
.orElse(getSecondaryLocationTimeZoneProviderModeFromConfig());
}
@@ -385,8 +384,8 @@
@NonNull
public Duration getLocationTimeZoneProviderInitializationTimeout() {
return mServerFlags.getDurationFromMillis(
- ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
- DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT);
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
+ DEFAULT_LTZP_INITIALIZATION_TIMEOUT);
}
/**
@@ -396,8 +395,8 @@
@NonNull
public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() {
return mServerFlags.getDurationFromMillis(
- ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
- DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ);
}
/**
@@ -408,7 +407,18 @@
public Duration getLocationTimeZoneUncertaintyDelay() {
return mServerFlags.getDurationFromMillis(
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
- DEFAULT_PROVIDER_UNCERTAINTY_DELAY);
+ DEFAULT_LTZP_UNCERTAINTY_DELAY);
+ }
+
+ /**
+ * Returns the time between equivalent events before the provider process will send the event
+ * to the system server.
+ */
+ @NonNull
+ public Duration getLocationTimeZoneProviderEventFilteringAgeThreshold() {
+ return mServerFlags.getDurationFromMillis(
+ ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+ DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD);
}
/** Clears all in-memory test config. */
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index 9d340e4..a1de294 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import java.time.Duration;
@@ -111,11 +112,12 @@
}
@Override
- void onStartUpdates(@NonNull Duration initializationTimeout) {
+ void onStartUpdates(@NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold) {
// Set a request on the proxy - it will be sent immediately if the service is bound,
// or will be sent as soon as the service becomes bound.
- TimeZoneProviderRequest request =
- TimeZoneProviderRequest.createStartUpdatesRequest(initializationTimeout);
+ TimeZoneProviderRequest request = TimeZoneProviderRequest.createStartUpdatesRequest(
+ initializationTimeout, eventFilteringAgeThreshold);
mProxy.setRequest(request);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
index 98e984d..20fb61d 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
@@ -16,7 +16,9 @@
package com.android.server.timezonedetector.location;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
+import android.os.SystemClock;
import com.android.server.LocalServices;
import com.android.server.timezonedetector.ConfigurationChangeListener;
@@ -78,4 +80,14 @@
Duration getUncertaintyDelay() {
return mServiceConfigAccessor.getLocationTimeZoneUncertaintyDelay();
}
+
+ @Override
+ Duration getProviderEventFilteringAgeThreshold() {
+ return mServiceConfigAccessor.getLocationTimeZoneProviderEventFilteringAgeThreshold();
+ }
+
+ @Override
+ @ElapsedRealtimeLong long elapsedRealtimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
index 76ef958..466a039 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
@@ -25,14 +29,13 @@
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
-import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
-import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
import android.annotation.DurationMillisLong;
-import android.annotation.IntRange;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -41,7 +44,6 @@
import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
import java.time.Duration;
-import java.util.List;
import java.util.Objects;
/**
@@ -182,7 +184,7 @@
// re-started).
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- "Providers are stopping");
+ mEnvironment.elapsedRealtimeMillis(), "Providers are stopping");
makeSuggestion(suggestion);
}
}
@@ -270,6 +272,7 @@
// If both providers are {perm failed} then the controller immediately
// becomes uncertain.
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ mEnvironment.elapsedRealtimeMillis(),
"Providers are failed:"
+ " primary=" + mPrimaryProvider.getCurrentState()
+ " secondary=" + mPrimaryProvider.getCurrentState());
@@ -281,6 +284,7 @@
}
}
+ @GuardedBy("mSharedLock")
private void tryStartProvider(@NonNull LocationTimeZoneProvider provider,
@NonNull ConfigurationInternal configuration) {
ProviderState providerState = provider.getCurrentState();
@@ -289,7 +293,8 @@
debugLog("Enabling " + provider);
provider.startUpdates(configuration,
mEnvironment.getProviderInitializationTimeout(),
- mEnvironment.getProviderInitializationTimeoutFuzz());
+ mEnvironment.getProviderInitializationTimeoutFuzz(),
+ mEnvironment.getProviderEventFilteringAgeThreshold());
break;
}
case PROVIDER_STATE_STARTED_INITIALIZING:
@@ -406,6 +411,7 @@
// If both providers are now terminated, then a suggestion must be sent informing the
// time zone detector that there are no further updates coming in future.
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ mEnvironment.elapsedRealtimeMillis(),
"Both providers are terminated:"
+ " primary=" + primaryCurrentState.provider
+ ", secondary=" + secondaryCurrentState.provider);
@@ -428,8 +434,9 @@
// the loss of a binder-based provider, or initialization took too long. This is treated
// the same as explicit uncertainty, i.e. where the provider has explicitly told this
// process it is uncertain.
- handleProviderUncertainty(provider, "provider=" + provider
- + ", implicit uncertainty, event=null");
+ long uncertaintyStartedElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+ handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
+ "provider=" + provider + ", implicit uncertainty, event=null");
return;
}
@@ -444,18 +451,17 @@
switch (event.getType()) {
case EVENT_TYPE_PERMANENT_FAILURE: {
// This shouldn't happen. A provider cannot be started and have this event type.
- warnLog("Provider=" + provider
- + " is started, but event suggests it shouldn't be");
+ warnLog("Provider=" + provider + " is started, but event suggests it shouldn't be");
break;
}
case EVENT_TYPE_UNCERTAIN: {
- handleProviderUncertainty(provider, "provider=" + provider
- + ", explicit uncertainty. event=" + event);
+ long uncertaintyStartedElapsedMillis = event.getCreationElapsedMillis();
+ handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
+ "provider=" + provider + ", explicit uncertainty. event=" + event);
break;
}
case EVENT_TYPE_SUGGESTION: {
- handleProviderSuggestion(provider, event.getSuggestion().getTimeZoneIds(),
- "Event received provider=" + provider + ", event=" + event);
+ handleProviderSuggestion(provider, event);
break;
}
default: {
@@ -471,8 +477,8 @@
@GuardedBy("mSharedLock")
private void handleProviderSuggestion(
@NonNull LocationTimeZoneProvider provider,
- @Nullable List<String> timeZoneIds,
- @NonNull String reason) {
+ @NonNull TimeZoneProviderEvent providerEvent) {
+
// By definition, the controller is now certain.
cancelUncertaintyTimeout();
@@ -480,10 +486,25 @@
stopProviderIfStarted(mSecondaryProvider);
}
- GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(timeZoneIds);
- suggestion.addDebugInfo(reason);
- // Rely on the receiver to dedupe suggestions. It is better to over-communicate.
- makeSuggestion(suggestion);
+ TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
+
+ // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
+ // suggestion (which indicates the time when the provider detected the location used to
+ // establish the time zone).
+ //
+ // An alternative would be to use the current time or the providerEvent creation time, but
+ // this would hinder the ability for the time_zone_detector to judge which suggestions are
+ // based on newer information when comparing suggestions between different sources.
+ long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
+ GeolocationTimeZoneSuggestion geoSuggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
+
+ String debugInfo = "Event received provider=" + provider
+ + ", providerEvent=" + providerEvent
+ + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
+ geoSuggestion.addDebugInfo(debugInfo);
+ makeSuggestion(geoSuggestion);
}
@Override
@@ -547,7 +568,9 @@
*/
@GuardedBy("mSharedLock")
void handleProviderUncertainty(
- @NonNull LocationTimeZoneProvider provider, @NonNull String reason) {
+ @NonNull LocationTimeZoneProvider provider,
+ @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
+ @NonNull String reason) {
Objects.requireNonNull(provider);
// Start the uncertainty timeout if needed to ensure the controller will eventually make an
@@ -555,9 +578,11 @@
if (!mUncertaintyTimeoutQueue.hasQueued()) {
debugLog("Starting uncertainty timeout: reason=" + reason);
- Duration delay = mEnvironment.getUncertaintyDelay();
- mUncertaintyTimeoutQueue.runDelayed(() -> onProviderUncertaintyTimeout(provider),
- delay.toMillis());
+ Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
+ mUncertaintyTimeoutQueue.runDelayed(
+ () -> onProviderUncertaintyTimeout(
+ provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
+ uncertaintyDelay.toMillis());
}
if (provider == mPrimaryProvider) {
@@ -569,21 +594,45 @@
}
}
- private void onProviderUncertaintyTimeout(@NonNull LocationTimeZoneProvider provider) {
+ private void onProviderUncertaintyTimeout(
+ @NonNull LocationTimeZoneProvider provider,
+ @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
+ @NonNull Duration uncertaintyDelay) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
+ long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+
+ // For the effectiveFromElapsedMillis suggestion property, use the
+ // uncertaintyStartedElapsedMillis. This is the time when the provider first reported
+ // uncertainty, i.e. before the uncertainty timeout.
+ //
+ // afterUncertaintyTimeoutElapsedMillis could be used instead, which is the time when
+ // the location_time_zone_manager finally confirms that the time zone was uncertain,
+ // but the suggestion property allows the information to be back-dated, which should
+ // help when comparing suggestions from different sources.
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ uncertaintyStartedElapsedMillis,
"Uncertainty timeout triggered for " + provider.getName() + ":"
+ " primary=" + mPrimaryProvider
- + ", secondary=" + mSecondaryProvider);
+ + ", secondary=" + mSecondaryProvider
+ + ", uncertaintyStarted="
+ + Duration.ofMillis(uncertaintyStartedElapsedMillis)
+ + ", afterUncertaintyTimeout="
+ + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+ + ", uncertaintyDelay=" + uncertaintyDelay
+ );
makeSuggestion(suggestion);
}
}
@NonNull
- private static GeolocationTimeZoneSuggestion createUncertainSuggestion(@NonNull String reason) {
- GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
+ private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis,
+ @NonNull String reason) {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ effectiveFromElapsedMillis);
suggestion.addDebugInfo(reason);
return suggestion;
}
@@ -618,19 +667,4 @@
return builder.build();
}
}
-
- @Nullable
- private LocationTimeZoneProvider getLocationTimeZoneProvider(
- @IntRange(from = 0, to = 1) int providerIndex) {
- LocationTimeZoneProvider targetProvider;
- if (providerIndex == 0) {
- targetProvider = mPrimaryProvider;
- } else if (providerIndex == 1) {
- targetProvider = mSecondaryProvider;
- } else {
- warnLog("Bad providerIndex=" + providerIndex);
- targetProvider = null;
- }
- return targetProvider;
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 8dbc520..c5c59ce 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -28,6 +28,7 @@
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderService;
import android.util.IndentingPrintWriter;
import android.util.Log;
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
index 3488956..6c9e174 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
@@ -26,10 +26,11 @@
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS;
-import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS;
-import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS;
-import static com.android.server.timedetector.ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-import static com.android.server.timedetector.ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
+import static com.android.server.timedetector.ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS;
+import static com.android.server.timedetector.ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS;
+import static com.android.server.timedetector.ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS;
+import static com.android.server.timedetector.ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE;
+import static com.android.server.timedetector.ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_DISABLED;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_ENABLED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -126,20 +127,23 @@
pw.println();
pw.printf("This service is also affected by the following device_config flags in the"
+ " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
- pw.printf(" %s\n", KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE);
+ pw.printf(" %s\n", KEY_PRIMARY_LTZP_MODE_OVERRIDE);
pw.printf(" Overrides the mode of the primary provider. Values=%s|%s\n",
PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED);
- pw.printf(" %s\n", KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE);
+ pw.printf(" %s\n", KEY_SECONDARY_LTZP_MODE_OVERRIDE);
pw.printf(" Overrides the mode of the secondary provider. Values=%s|%s\n",
PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED);
pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS);
pw.printf(" Sets the amount of time the service waits when uncertain before making an"
+ " 'uncertain' suggestion to the time zone detector.\n");
- pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS);
+ pw.printf(" %s\n", KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS);
pw.printf(" Sets the initialization time passed to the providers.\n");
- pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS);
+ pw.printf(" %s\n", KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS);
pw.printf(" Sets the amount of extra time added to the providers' initialization time."
+ "\n");
+ pw.printf(" %s\n", KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS);
+ pw.printf(" Sets the amount of time that must pass between equivalent LTZP events before"
+ + " they will be reported to the system server.\n");
pw.println();
pw.printf("Typically, use '%s' to stop the service before setting individual"
+ " flags and '%s' after to restart it.\n",
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index ee85ee4..90540b0 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -24,9 +28,6 @@
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
-import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
-import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
@@ -34,6 +35,7 @@
import android.annotation.Nullable;
import android.os.Handler;
import android.os.SystemClock;
+import android.service.timezone.TimeZoneProviderEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -527,7 +529,8 @@
* called using the handler thread from the {@link ThreadingDomain}.
*/
final void startUpdates(@NonNull ConfigurationInternal currentUserConfiguration,
- @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) {
+ @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz,
+ @NonNull Duration eventFilteringAgeThreshold) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -542,7 +545,7 @@
mInitializationTimeoutQueue.runDelayed(
this::handleInitializationTimeout, delay.toMillis());
- onStartUpdates(initializationTimeout);
+ onStartUpdates(initializationTimeout, eventFilteringAgeThreshold);
}
}
@@ -568,9 +571,11 @@
* Implemented by subclasses to do work during {@link #startUpdates}. This is where the logic
* to start the real provider should be implemented.
*
- * @param initializationTimeout the initialization timeout to pass to the real provider
+ * @param initializationTimeout the initialization timeout to pass to the provider
+ * @param eventFilteringAgeThreshold the event filtering age threshold to pass to the provider
*/
- abstract void onStartUpdates(@NonNull Duration initializationTimeout);
+ abstract void onStartUpdates(@NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold);
/**
* Stops the provider. It is an error to call this method except when the {@link
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index b4aff3e..fdb9c14 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -17,6 +17,7 @@
package com.android.server.timezonedetector.location;
import android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.os.Handler;
@@ -130,10 +131,22 @@
abstract Duration getProviderInitializationTimeoutFuzz();
/**
+ * Returns the value passed to LocationTimeZoneProviders to control rate limiting of
+ * equivalent events.
+ */
+ abstract Duration getProviderEventFilteringAgeThreshold();
+
+ /**
* Returns the delay allowed after receiving uncertainty from a provider before it should be
* passed on.
*/
abstract Duration getUncertaintyDelay();
+
+ /**
+ * Returns the elapsed realtime as millis, the same as {@link
+ * android.os.SystemClock#elapsedRealtime()}.
+ */
+ abstract @ElapsedRealtimeLong long elapsedRealtimeMillis();
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java
index 7b1a77c..f187db1 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
+import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
diff --git a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
index 4ef819f..9cb1813 100644
--- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.SystemClock;
+import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
/**
@@ -58,7 +60,7 @@
void setRequest(@NonNull TimeZoneProviderRequest request) {
if (request.sendUpdates()) {
TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
- "Provider is disabled");
+ SystemClock.elapsedRealtime(), "Provider is disabled");
handleTimeZoneProviderEvent(event);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index fcac3e8..f54fe48 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -26,7 +26,7 @@
import android.os.IBinder;
import android.service.timezone.ITimeZoneProvider;
import android.service.timezone.ITimeZoneProviderManager;
-import android.service.timezone.TimeZoneProviderSuggestion;
+import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -152,7 +152,9 @@
mServiceWatcher.runOnBinder(binder -> {
ITimeZoneProvider service = ITimeZoneProvider.Stub.asInterface(binder);
if (request.sendUpdates()) {
- service.startUpdates(managerProxy, request.getInitializationTimeout().toMillis());
+ service.startUpdates(managerProxy,
+ request.getInitializationTimeout().toMillis(),
+ request.getEventFilteringAgeThreshold().toMillis());
} else {
service.stopUpdates();
}
@@ -177,25 +179,7 @@
// executed on binder thread
@Override
- public void onTimeZoneProviderSuggestion(TimeZoneProviderSuggestion suggestion) {
- onTimeZoneProviderEvent(TimeZoneProviderEvent.createSuggestionEvent(suggestion));
- }
-
- // executed on binder thread
- @Override
- public void onTimeZoneProviderUncertain() {
- onTimeZoneProviderEvent(TimeZoneProviderEvent.createUncertainEvent());
-
- }
-
- // executed on binder thread
- @Override
- public void onTimeZoneProviderPermanentFailure(String failureReason) {
- onTimeZoneProviderEvent(
- TimeZoneProviderEvent.createPermanentFailureEvent(failureReason));
- }
-
- private void onTimeZoneProviderEvent(TimeZoneProviderEvent event) {
+ public void onTimeZoneProviderEvent(TimeZoneProviderEvent event) {
synchronized (mSharedLock) {
if (mManagerProxy != this) {
// Ignore incoming calls if this instance is no longer the current
diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java
deleted file mode 100644
index 7648795..0000000
--- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timezonedetector.location;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.service.timezone.TimeZoneProviderService;
-import android.service.timezone.TimeZoneProviderSuggestion;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.Objects;
-
-/**
- * An event from a {@link TimeZoneProviderService}.
- */
-final class TimeZoneProviderEvent {
-
- @IntDef(prefix = "EVENT_TYPE_",
- value = { EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUGGESTION, EVENT_TYPE_UNCERTAIN })
- @Retention(RetentionPolicy.SOURCE)
- @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
- public @interface EventType {}
-
- /**
- * The provider failed permanently. See {@link
- * TimeZoneProviderService#reportPermanentFailure(Throwable)}
- */
- public static final @EventType int EVENT_TYPE_PERMANENT_FAILURE = 1;
-
- /**
- * The provider made a suggestion. See {@link
- * TimeZoneProviderService#reportSuggestion(TimeZoneProviderSuggestion)}
- */
- public static final @EventType int EVENT_TYPE_SUGGESTION = 2;
-
- /**
- * The provider was uncertain about the time zone. See {@link
- * TimeZoneProviderService#reportUncertain()}
- */
- public static final @EventType int EVENT_TYPE_UNCERTAIN = 3;
-
- private static final TimeZoneProviderEvent UNCERTAIN_EVENT =
- new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, null, null);
-
- private final @EventType int mType;
-
- @Nullable
- private final TimeZoneProviderSuggestion mSuggestion;
-
- @Nullable
- private final String mFailureCause;
-
- private TimeZoneProviderEvent(@EventType int type,
- @Nullable TimeZoneProviderSuggestion suggestion,
- @Nullable String failureCause) {
- mType = type;
- mSuggestion = suggestion;
- mFailureCause = failureCause;
- }
-
- /** Returns a event of type {@link #EVENT_TYPE_SUGGESTION}. */
- public static TimeZoneProviderEvent createSuggestionEvent(
- @NonNull TimeZoneProviderSuggestion suggestion) {
- return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION,
- Objects.requireNonNull(suggestion), null);
- }
-
- /** Returns a event of type {@link #EVENT_TYPE_UNCERTAIN}. */
- public static TimeZoneProviderEvent createUncertainEvent() {
- return UNCERTAIN_EVENT;
- }
-
- /** Returns a event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
- public static TimeZoneProviderEvent createPermanentFailureEvent(@NonNull String cause) {
- return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, null,
- Objects.requireNonNull(cause));
- }
-
- /**
- * Returns the event type.
- */
- public @EventType int getType() {
- return mType;
- }
-
- /**
- * Returns the suggestion. Populated when {@link #getType()} is {@link #EVENT_TYPE_SUGGESTION}.
- */
- @Nullable
- public TimeZoneProviderSuggestion getSuggestion() {
- return mSuggestion;
- }
-
- /**
- * Returns the failure cauese. Populated when {@link #getType()} is {@link
- * #EVENT_TYPE_PERMANENT_FAILURE}.
- */
- @Nullable
- public String getFailureCause() {
- return mFailureCause;
- }
-
- @Override
- public String toString() {
- return "TimeZoneProviderEvent{"
- + "mType=" + mType
- + ", mSuggestion=" + mSuggestion
- + ", mFailureCause=" + mFailureCause
- + '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- TimeZoneProviderEvent that = (TimeZoneProviderEvent) o;
- return mType == that.mType
- && Objects.equals(mSuggestion, that.mSuggestion)
- && Objects.equals(mFailureCause, that.mFailureCause);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mType, mSuggestion, mFailureCause);
- }
-}
diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
index 951e9d0..dda7291 100644
--- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
@@ -17,6 +17,7 @@
package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
+import android.service.timezone.TimeZoneProviderEvent;
/**
* Used by {@link LocationTimeZoneProvider} to ensure that all time zone IDs are understood by the
diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java
index e8386bc..a9d94dd 100644
--- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java
@@ -31,23 +31,32 @@
private static final TimeZoneProviderRequest STOP_UPDATES =
new TimeZoneProviderRequest(
false /* sendUpdates */,
- null /* initializationTimeout */);
+ null /* initializationTimeout */,
+ null /* eventFilteringAgeThreshold */);
private final boolean mSendUpdates;
@Nullable
private final Duration mInitializationTimeout;
+ @Nullable
+ private final Duration mEventFilteringAgeThreshold;
+
private TimeZoneProviderRequest(
- boolean sendUpdates, @Nullable Duration initializationTimeout) {
+ boolean sendUpdates, @Nullable Duration initializationTimeout,
+ @Nullable Duration eventFilteringAgeThreshold) {
mSendUpdates = sendUpdates;
mInitializationTimeout = initializationTimeout;
+ mEventFilteringAgeThreshold = eventFilteringAgeThreshold;
}
/** Creates a request to start updates with the specified timeout. */
public static TimeZoneProviderRequest createStartUpdatesRequest(
- @NonNull Duration initializationTimeout) {
- return new TimeZoneProviderRequest(true, Objects.requireNonNull(initializationTimeout));
+ @NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold) {
+ return new TimeZoneProviderRequest(true,
+ Objects.requireNonNull(initializationTimeout),
+ Objects.requireNonNull(eventFilteringAgeThreshold));
}
/** Creates a request to stop updates. */
@@ -74,6 +83,17 @@
return mInitializationTimeout;
}
+ /**
+ * Returns the threshold the remote process is to use to filter equivalent events. Only valid
+ * when {@link #sendUpdates()} is {@code true}.
+ *
+ * <p>Guaranteed to be set when {@link #sendUpdates()} returns {@code true}.
+ */
+ @NonNull
+ public Duration getEventFilteringAgeThreshold() {
+ return mEventFilteringAgeThreshold;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -82,15 +102,15 @@
if (o == null || getClass() != o.getClass()) {
return false;
}
- TimeZoneProviderRequest
- that = (TimeZoneProviderRequest) o;
+ TimeZoneProviderRequest that = (TimeZoneProviderRequest) o;
return mSendUpdates == that.mSendUpdates
- && mInitializationTimeout == that.mInitializationTimeout;
+ && Objects.equals(mInitializationTimeout, that.mInitializationTimeout)
+ && Objects.equals(mEventFilteringAgeThreshold, that.mEventFilteringAgeThreshold);
}
@Override
public int hashCode() {
- return Objects.hash(mSendUpdates, mInitializationTimeout);
+ return Objects.hash(mSendUpdates, mInitializationTimeout, mEventFilteringAgeThreshold);
}
@Override
@@ -98,6 +118,7 @@
return "TimeZoneProviderRequest{"
+ "mSendUpdates=" + mSendUpdates
+ ", mInitializationTimeout=" + mInitializationTimeout
+ + ", mEventFilteringAgeThreshold=" + mEventFilteringAgeThreshold
+ "}";
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index 0f4367d..ff0529f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -19,6 +19,7 @@
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import android.annotation.NonNull;
+import android.service.timezone.TimeZoneProviderEvent;
import com.android.i18n.timezone.ZoneInfoDb;
@@ -52,7 +53,7 @@
// enables immediate failover to a secondary provider, one that might provide valid IDs for
// the same location, which should provide better behavior than just ignoring the event.
if (hasInvalidZones(event)) {
- return TimeZoneProviderEvent.createUncertainEvent();
+ return TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis());
}
return event;
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
new file mode 100644
index 0000000..c4e5660
--- /dev/null
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.tv.interactive;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.media.tv.interactive.ITvIAppClient;
+import android.media.tv.interactive.ITvIAppManager;
+import android.media.tv.interactive.ITvIAppService;
+import android.media.tv.interactive.ITvIAppServiceCallback;
+import android.media.tv.interactive.ITvIAppSession;
+import android.media.tv.interactive.ITvIAppSessionCallback;
+import android.media.tv.interactive.TvIAppService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.SystemService;
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class provides a system service that manages interactive TV applications.
+ */
+public class TvIAppManagerService extends SystemService {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvIAppManagerService";
+ // A global lock.
+ private final Object mLock = new Object();
+ private final Context mContext;
+ // ID of the current user.
+ @GuardedBy("mLock")
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
+ // IDs of the running profiles. Their parent user ID should be mCurrentUserId.
+ @GuardedBy("mLock")
+ private final Set<Integer> mRunningProfiles = new HashSet<>();
+ // A map from user id to UserState.
+ @GuardedBy("mLock")
+ private final SparseArray<UserState> mUserStates = new SparseArray<>();
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ */
+ public TvIAppManagerService(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ if (DEBUG) {
+ Slogf.d(TAG, "onStart");
+ }
+ // TODO: make service name a constant in Context
+ publishBinderService("tv_interactive_app", new BinderService());
+ }
+
+ private SessionState getSessionState(IBinder sessionToken) {
+ // TODO: implement user state and get session from it.
+ return null;
+ }
+
+ private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
+ String methodName) {
+ return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
+ false, methodName, null);
+ }
+
+ @GuardedBy("mLock")
+ private UserState getOrCreateUserStateLocked(int userId) {
+ UserState userState = getUserStateLocked(userId);
+ if (userState == null) {
+ userState = new UserState(userId);
+ mUserStates.put(userId, userState);
+ }
+ return userState;
+ }
+
+ @GuardedBy("mLock")
+ private UserState getUserStateLocked(int userId) {
+ return mUserStates.get(userId);
+ }
+
+ @GuardedBy("mLock")
+ private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ return getSessionStateLocked(sessionToken, callingUid, userState);
+ }
+
+ @GuardedBy("mLock")
+ private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid,
+ UserState userState) {
+ SessionState sessionState = userState.mSessionStateMap.get(sessionToken);
+ if (sessionState == null) {
+ throw new SessionNotFoundException("Session state not found for token " + sessionToken);
+ }
+ // Only the application that requested this session or the system can access it.
+ if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+ throw new SecurityException("Illegal access to the session with token " + sessionToken
+ + " from uid " + callingUid);
+ }
+ return sessionState;
+ }
+
+ private final class BinderService extends ITvIAppManager.Stub {
+
+ @Override
+ public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
+ int seq, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "createSession");
+ final long identity = Binder.clearCallingIdentity();
+
+ try {
+ synchronized (mLock) {
+ if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
+ // Only current user and its running profiles can create sessions.
+ // Let the client get onConnectionFailed callback for this case.
+ sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+ return;
+ }
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId);
+ if (iAppState == null) {
+ Slogf.w(TAG, "Failed to find state for iAppServiceId=" + iAppServiceId);
+ sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+ return;
+ }
+ ServiceState serviceState =
+ userState.mServiceStateMap.get(iAppState.mComponentName);
+ if (serviceState == null) {
+ int tiasUid = PackageManager.getApplicationInfoAsUserCached(
+ iAppState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
+ serviceState = new ServiceState(iAppState.mComponentName, resolvedUserId);
+ userState.mServiceStateMap.put(iAppState.mComponentName, serviceState);
+ }
+ // Send a null token immediately while reconnecting.
+ if (serviceState.mReconnecting) {
+ sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+ return;
+ }
+
+ // Create a new session token and a session state.
+ IBinder sessionToken = new Binder();
+ SessionState sessionState = new SessionState(sessionToken, iAppServiceId, type,
+ iAppState.mComponentName, client, seq, callingUid,
+ callingPid, resolvedUserId);
+
+ // Add them to the global session state map of the current user.
+ userState.mSessionStateMap.put(sessionToken, sessionState);
+
+ // Also, add them to the session state map of the current service.
+ serviceState.mSessionTokens.add(sessionToken);
+
+ if (serviceState.mService != null) {
+ if (!createSessionInternalLocked(serviceState.mService, sessionToken,
+ resolvedUserId)) {
+ removeSessionStateLocked(sessionToken, resolvedUserId);
+ }
+ } else {
+ updateServiceConnectionLocked(iAppState.mComponentName, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void releaseSession(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "releaseSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void startIApp(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
+ }
+ try {
+ SessionState sessionState = getSessionState(sessionToken);
+ if (sessionState != null && sessionState.mSession != null) {
+ sessionState.mSession.startIApp();
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in start", e);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void sendSessionTokenToClientLocked(ITvIAppClient client, String iAppServiceId,
+ IBinder sessionToken, int seq) {
+ try {
+ client.onSessionCreated(iAppServiceId, sessionToken, seq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onSessionCreated", e);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean createSessionInternalLocked(ITvIAppService service, IBinder sessionToken,
+ int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ SessionState sessionState = userState.mSessionStateMap.get(sessionToken);
+ if (DEBUG) {
+ Slogf.d(TAG, "createSessionInternalLocked(iAppServiceId="
+ + sessionState.mIAppServiceId + ")");
+ }
+
+ // Set up a callback to send the session token.
+ ITvIAppSessionCallback callback = new SessionCallback(sessionState);
+
+ boolean created = true;
+ // Create a session. When failed, send a null token immediately.
+ try {
+ service.createSession(callback, sessionState.mIAppServiceId, sessionState.mType);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in createSession", e);
+ sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mIAppServiceId, null,
+ sessionState.mSeq);
+ created = false;
+ }
+ return created;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ SessionState sessionState = null;
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+ UserState userState = getOrCreateUserStateLocked(userId);
+ if (sessionState.mSession != null) {
+ sessionState.mSession.asBinder().unlinkToDeath(sessionState, 0);
+ sessionState.mSession.release();
+ }
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in releaseSession", e);
+ } finally {
+ if (sessionState != null) {
+ sessionState.mSession = null;
+ }
+ }
+ removeSessionStateLocked(sessionToken, userId);
+ return sessionState;
+ }
+
+ @GuardedBy("mLock")
+ private void removeSessionStateLocked(IBinder sessionToken, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+
+ // Remove the session state from the global session state map of the current user.
+ SessionState sessionState = userState.mSessionStateMap.remove(sessionToken);
+
+ if (sessionState == null) {
+ Slogf.e(TAG, "sessionState null, no more remove session action!");
+ return;
+ }
+
+ // Also remove the session token from the session token list of the current client and
+ // service.
+ ClientState clientState = userState.mClientStateMap.get(sessionState.mClient.asBinder());
+ if (clientState != null) {
+ clientState.mSessionTokens.remove(sessionToken);
+ if (clientState.isEmpty()) {
+ userState.mClientStateMap.remove(sessionState.mClient.asBinder());
+ sessionState.mClient.asBinder().unlinkToDeath(clientState, 0);
+ }
+ }
+
+ ServiceState serviceState = userState.mServiceStateMap.get(sessionState.mComponent);
+ if (serviceState != null) {
+ serviceState.mSessionTokens.remove(sessionToken);
+ }
+ updateServiceConnectionLocked(sessionState.mComponent, userId);
+ }
+
+ @GuardedBy("mLock")
+ private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
+ String iAppServiceId, int userId) {
+ // Let clients know the create session requests are failed.
+ UserState userState = getOrCreateUserStateLocked(userId);
+ List<SessionState> sessionsToAbort = new ArrayList<>();
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ SessionState sessionState = userState.mSessionStateMap.get(sessionToken);
+ if (sessionState.mSession == null
+ && (iAppServiceId == null
+ || sessionState.mIAppServiceId.equals(iAppServiceId))) {
+ sessionsToAbort.add(sessionState);
+ }
+ }
+ for (SessionState sessionState : sessionsToAbort) {
+ removeSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
+ sendSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mIAppServiceId, null, sessionState.mSeq);
+ }
+ updateServiceConnectionLocked(serviceState.mComponent, userId);
+ }
+
+ @GuardedBy("mLock")
+ private void updateServiceConnectionLocked(ComponentName component, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ ServiceState serviceState = userState.mServiceStateMap.get(component);
+ if (serviceState == null) {
+ return;
+ }
+ if (serviceState.mReconnecting) {
+ if (!serviceState.mSessionTokens.isEmpty()) {
+ // wait until all the sessions are removed.
+ return;
+ }
+ serviceState.mReconnecting = false;
+ }
+
+ boolean shouldBind = !serviceState.mSessionTokens.isEmpty();
+
+ if (serviceState.mService == null && shouldBind) {
+ // This means that the service is not yet connected but its state indicates that we
+ // have pending requests. Then, connect the service.
+ if (serviceState.mBound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ return;
+ }
+ if (DEBUG) {
+ Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
+ }
+
+ Intent i = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+ serviceState.mBound = mContext.bindServiceAsUser(
+ i, serviceState.mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ new UserHandle(userId));
+ } else if (serviceState.mService != null && !shouldBind) {
+ // This means that the service is already connected but its state indicates that we have
+ // nothing to do with it. Then, disconnect the service.
+ if (DEBUG) {
+ Slogf.d(TAG, "unbindService(service=" + component + ")");
+ }
+ mContext.unbindService(serviceState.mConnection);
+ userState.mServiceStateMap.remove(component);
+ }
+ }
+
+ private static final class UserState {
+ private final int mUserId;
+ // A mapping from the TV IApp ID to its TvIAppState.
+ private Map<String, TvIAppState> mIAppMap = new HashMap<>();
+ // A mapping from the token of a client to its state.
+ private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
+ // A mapping from the name of a TV IApp service to its state.
+ private final Map<ComponentName, ServiceState> mServiceStateMap = new HashMap<>();
+ // A mapping from the token of a TV IApp session to its state.
+ private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>();
+
+ private UserState(int userId) {
+ mUserId = userId;
+ }
+ }
+
+ private static final class TvIAppState {
+ private final String mIAppServiceId;
+ private final ComponentName mComponentName;
+
+ TvIAppState(String id, ComponentName componentName) {
+ mIAppServiceId = id;
+ mComponentName = componentName;
+ }
+ }
+
+ private final class SessionState implements IBinder.DeathRecipient {
+ private final IBinder mSessionToken;
+ private ITvIAppSession mSession;
+ private final String mIAppServiceId;
+ private final int mType;
+ private final ITvIAppClient mClient;
+ private final int mSeq;
+ private final ComponentName mComponent;
+
+ // The UID of the application that created the session.
+ // The application is usually the TV app.
+ private final int mCallingUid;
+
+ // The PID of the application that created the session.
+ // The application is usually the TV app.
+ private final int mCallingPid;
+
+ private final int mUserId;
+
+ private SessionState(IBinder sessionToken, String iAppServiceId, int type,
+ ComponentName componentName, ITvIAppClient client, int seq, int callingUid,
+ int callingPid, int userId) {
+ mSessionToken = sessionToken;
+ mIAppServiceId = iAppServiceId;
+ mComponent = componentName;
+ mType = type;
+ mClient = client;
+ mSeq = seq;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ mUserId = userId;
+ }
+
+ @Override
+ public void binderDied() {
+ }
+ }
+
+ private final class ClientState implements IBinder.DeathRecipient {
+ private final List<IBinder> mSessionTokens = new ArrayList<>();
+
+ private IBinder mClientToken;
+ private final int mUserId;
+
+ ClientState(IBinder clientToken, int userId) {
+ mClientToken = clientToken;
+ mUserId = userId;
+ }
+
+ public boolean isEmpty() {
+ return mSessionTokens.isEmpty();
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(mUserId);
+ // DO NOT remove the client state of clientStateMap in this method. It will be
+ // removed in releaseSessionLocked().
+ ClientState clientState = userState.mClientStateMap.get(mClientToken);
+ if (clientState != null) {
+ while (clientState.mSessionTokens.size() > 0) {
+ IBinder sessionToken = clientState.mSessionTokens.get(0);
+ releaseSessionLocked(
+ sessionToken, Process.SYSTEM_UID, mUserId);
+ // the releaseSessionLocked function may return before the sessionToken
+ // is removed if the related sessionState is null. So need to check again
+ // to avoid death circulation.
+ if (clientState.mSessionTokens.contains(sessionToken)) {
+ Slogf.d(TAG, "remove sessionToken " + sessionToken + " for "
+ + mClientToken);
+ clientState.mSessionTokens.remove(sessionToken);
+ }
+ }
+ }
+ mClientToken = null;
+ }
+ }
+ }
+
+ private final class ServiceState {
+ private final List<IBinder> mSessionTokens = new ArrayList<>();
+ private final ServiceConnection mConnection;
+ private final ComponentName mComponent;
+
+ private ITvIAppService mService;
+ private ServiceCallback mCallback;
+ private boolean mBound;
+ private boolean mReconnecting;
+
+ private ServiceState(ComponentName component, int userId) {
+ mComponent = component;
+ mConnection = new IAppServiceConnection(component, userId);
+ }
+ }
+
+ private final class IAppServiceConnection implements ServiceConnection {
+ private final ComponentName mComponent;
+ private final int mUserId;
+
+ private IAppServiceConnection(ComponentName component, int userId) {
+ mComponent = component;
+ mUserId = userId;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onServiceConnected(component=" + component + ")");
+ }
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mUserId);
+ if (userState == null) {
+ // The user was removed while connecting.
+ mContext.unbindService(this);
+ return;
+ }
+ ServiceState serviceState = userState.mServiceStateMap.get(mComponent);
+ serviceState.mService = ITvIAppService.Stub.asInterface(service);
+
+ List<IBinder> tokensToBeRemoved = new ArrayList<>();
+
+ // And create sessions, if any.
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ if (!createSessionInternalLocked(
+ serviceState.mService, sessionToken, mUserId)) {
+ tokensToBeRemoved.add(sessionToken);
+ }
+ }
+
+ for (IBinder sessionToken : tokensToBeRemoved) {
+ removeSessionStateLocked(sessionToken, mUserId);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onServiceDisconnected(component=" + component + ")");
+ }
+ if (!mComponent.equals(component)) {
+ throw new IllegalArgumentException("Mismatched ComponentName: "
+ + mComponent + " (expected), " + component + " (actual).");
+ }
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(mUserId);
+ ServiceState serviceState = userState.mServiceStateMap.get(mComponent);
+ if (serviceState != null) {
+ serviceState.mReconnecting = true;
+ serviceState.mBound = false;
+ serviceState.mService = null;
+ serviceState.mCallback = null;
+
+ abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
+ }
+ }
+ }
+ }
+
+ private final class ServiceCallback extends ITvIAppServiceCallback.Stub {
+ private final ComponentName mComponent;
+ private final int mUserId;
+
+ ServiceCallback(ComponentName component, int userId) {
+ mComponent = component;
+ mUserId = userId;
+ }
+ }
+
+ private final class SessionCallback extends ITvIAppSessionCallback.Stub {
+ private final SessionState mSessionState;
+
+ SessionCallback(SessionState sessionState) {
+ mSessionState = sessionState;
+ }
+
+ @Override
+ public void onSessionCreated(ITvIAppSession session) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onSessionCreated(iAppServiceId="
+ + mSessionState.mIAppServiceId + ")");
+ }
+ synchronized (mLock) {
+ mSessionState.mSession = session;
+ if (session != null && addSessionTokenToClientStateLocked(session)) {
+ sendSessionTokenToClientLocked(
+ mSessionState.mClient,
+ mSessionState.mIAppServiceId,
+ mSessionState.mSessionToken,
+ mSessionState.mSeq);
+ } else {
+ removeSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
+ sendSessionTokenToClientLocked(mSessionState.mClient,
+ mSessionState.mIAppServiceId, null, mSessionState.mSeq);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) {
+ try {
+ session.asBinder().linkToDeath(mSessionState, 0);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "session process has already died", e);
+ return false;
+ }
+
+ IBinder clientToken = mSessionState.mClient.asBinder();
+ UserState userState = getOrCreateUserStateLocked(mSessionState.mUserId);
+ ClientState clientState = userState.mClientStateMap.get(clientToken);
+ if (clientState == null) {
+ clientState = new ClientState(clientToken, mSessionState.mUserId);
+ try {
+ clientToken.linkToDeath(clientState, 0);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "client process has already died", e);
+ return false;
+ }
+ userState.mClientStateMap.put(clientToken, clientState);
+ }
+ clientState.mSessionTokens.add(mSessionState.mSessionToken);
+ return true;
+ }
+ }
+
+ private static class SessionNotFoundException extends IllegalArgumentException {
+ SessionNotFoundException(String name) {
+ super(name);
+ }
+ }
+
+ private static class ClientPidNotFoundException extends IllegalArgumentException {
+ ClientPidNotFoundException(String name) {
+ super(name);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 8fb81fa..3177fac 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -37,6 +37,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
@@ -44,6 +45,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -68,6 +71,8 @@
// Map of the current available frontend resources
private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
+ // Backup Map of the current available frontend resources
+ private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
// Map of the current available lnb resources
private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
// Map of the current available Cas resources
@@ -174,6 +179,27 @@
}
@Override
+ public boolean hasUnusedFrontend(int frontendType) {
+ enforceTrmAccessPermission("hasUnusedFrontend");
+ synchronized (mLock) {
+ return hasUnusedFrontendInternal(frontendType);
+ }
+ }
+
+ @Override
+ public boolean isLowestPriority(int clientId, int frontendType)
+ throws RemoteException {
+ enforceTrmAccessPermission("isLowestPriority");
+ synchronized (mLock) {
+ if (!checkClientExists(clientId)) {
+ throw new RemoteException("isLowestPriority called from unregistered client: "
+ + clientId);
+ }
+ return isLowestPriorityInternal(clientId, frontendType);
+ }
+ }
+
+ @Override
public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
enforceTrmAccessPermission("setFrontendInfoList");
if (infos == null) {
@@ -458,6 +484,104 @@
return isHigherPriorityInternal(challengerProfile, holderProfile);
}
}
+
+ @Override
+ public void storeResourceMap(int resourceType) {
+ enforceTrmAccessPermission("storeResourceMap");
+ synchronized (mLock) {
+ storeResourceMapInternal(resourceType);
+ }
+ }
+
+ @Override
+ public void clearResourceMap(int resourceType) {
+ enforceTrmAccessPermission("clearResourceMap");
+ synchronized (mLock) {
+ clearResourceMapInternal(resourceType);
+ }
+ }
+
+ @Override
+ public void restoreResourceMap(int resourceType) {
+ enforceTrmAccessPermission("restoreResourceMap");
+ synchronized (mLock) {
+ restoreResourceMapInternal(resourceType);
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
+ synchronized (mLock) {
+ if (mClientProfiles != null) {
+ pw.println("ClientProfiles:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, ClientProfile> entry : mClientProfiles.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ if (mFrontendResources != null) {
+ pw.println("FrontendResources:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, FrontendResource> entry
+ : mFrontendResources.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ if (mFrontendResourcesBackup != null) {
+ pw.println("FrontendResourcesBackUp:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, FrontendResource> entry
+ : mFrontendResourcesBackup.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ if (mLnbResources != null) {
+ pw.println("LnbResources:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, LnbResource> entry : mLnbResources.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ if (mCasResources != null) {
+ pw.println("CasResources:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, CasResource> entry : mCasResources.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ if (mCiCamResources != null) {
+ pw.println("CiCamResources:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, CiCamResource> entry : mCiCamResources.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+
+ if (mListeners != null) {
+ pw.println("Listners:");
+ pw.increaseIndent();
+ for (Map.Entry<Integer, ResourcesReclaimListenerRecord> entry
+ : mListeners.entrySet()) {
+ pw.println(entry.getKey() + " : " + entry.getValue());
+ }
+ pw.decreaseIndent();
+ }
+ }
+ }
+
}
/**
@@ -552,6 +676,83 @@
return true;
}
+
+ protected boolean hasUnusedFrontendInternal(int frontendType) {
+ for (FrontendResource fr : getFrontendResources().values()) {
+ if (fr.getType() == frontendType && !fr.isInUse()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean isLowestPriorityInternal(int clientId, int frontendType)
+ throws RemoteException {
+ // Update the client priority
+ ClientProfile requestClient = getClientProfile(clientId);
+ if (requestClient == null) {
+ return true;
+ }
+ clientPriorityUpdateOnRequest(requestClient);
+ int clientPriority = requestClient.getPriority();
+
+ // Check if there is another holder with lower priority
+ for (FrontendResource fr : getFrontendResources().values()) {
+ if (fr.getType() == frontendType && fr.isInUse()) {
+ int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
+ // Returns false only when the clientPriority is strictly greater
+ // because false means that there is another reclaimable resource
+ if (clientPriority > priority) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ protected void storeResourceMapInternal(int resourceType) {
+ switch (resourceType) {
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+ if (mFrontendResources != null && mFrontendResources.size() > 0) {
+ mFrontendResourcesBackup.putAll(mFrontendResources);
+ mFrontendResources.clear();
+ }
+ break;
+ // TODO: implement for other resource type when needed
+ default:
+ break;
+ }
+ }
+
+ protected void clearResourceMapInternal(int resourceType) {
+ switch (resourceType) {
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+ if (mFrontendResources != null) {
+ mFrontendResources.clear();
+ }
+ break;
+ // TODO: implement for other resource type when needed
+ default:
+ break;
+ }
+ }
+
+ protected void restoreResourceMapInternal(int resourceType) {
+ switch (resourceType) {
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+ if (mFrontendResourcesBackup != null
+ && mFrontendResourcesBackup.size() > 0) {
+ mFrontendResources.clear();
+ mFrontendResources.putAll(mFrontendResourcesBackup);
+ mFrontendResourcesBackup.clear();
+ }
+ break;
+ // TODO: implement for other resource type when needed
+ default:
+ break;
+ }
+ }
+
@VisibleForTesting
protected void setFrontendInfoListInternal(TunerFrontendInfo[] infos) {
if (DEBUG) {
@@ -692,7 +893,7 @@
} else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
// Record the frontend id with the lowest client priority among all the
// in use frontends when no available frontend has been found.
- int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
+ int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityFrHandle = fr.getHandle();
currentLowestPriority = priority;
@@ -1168,6 +1369,33 @@
return profile.getPriority();
}
+ /**
+ * Update the owner and sharee clients' priority and get the highest priority
+ * for frontend resource
+ *
+ * @param clientId the owner client id.
+ * @return the highest priority among all the clients holding the same frontend resource.
+ */
+ private int getFrontendHighestClientPriority(int clientId) {
+ // Check if the owner profile exists
+ ClientProfile ownerClient = getClientProfile(clientId);
+ if (ownerClient == null) {
+ return 0;
+ }
+
+ // Update and get the priority of the owner client
+ int highestPriority = updateAndGetOwnerClientPriority(clientId);
+
+ // Update and get all the client IDs of frontend resource holders
+ for (int shareeId : ownerClient.getShareFeClientIds()) {
+ int priority = updateAndGetOwnerClientPriority(shareeId);
+ if (priority > highestPriority) {
+ highestPriority = priority;
+ }
+ }
+ return highestPriority;
+ }
+
@VisibleForTesting
@Nullable
protected FrontendResource getFrontendResource(int frontendHandle) {
@@ -1345,6 +1573,9 @@
}
private void clearAllResourcesAndClientMapping(ClientProfile profile) {
+ if (profile == null) {
+ return;
+ }
// Clear Lnb
for (Integer lnbHandle : profile.getInUseLnbHandles()) {
getLnbResource(lnbHandle).removeOwner();
diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
index 9b0ef15..25ae000 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
@@ -263,6 +263,33 @@
}
/**
+ * Removes all of the mappings whose index is between {@code fromIndex}, inclusive, and
+ * {@code toIndex}, exclusive. The matrix does not shrink.
+ */
+ public void removeRange(int fromIndex, int toIndex) {
+ if (toIndex < fromIndex) {
+ throw new ArrayIndexOutOfBoundsException("toIndex < fromIndex");
+ }
+ final int num = toIndex - fromIndex;
+ if (num == 0) {
+ return;
+ }
+ validateIndex(fromIndex);
+ validateIndex(toIndex - 1);
+ for (int i = fromIndex; i < toIndex; i++) {
+ mInUse[mMap[i]] = false;
+ }
+ System.arraycopy(mKeys, toIndex, mKeys, fromIndex, mSize - toIndex);
+ System.arraycopy(mMap, toIndex, mMap, fromIndex, mSize - toIndex);
+ for (int i = mSize - num; i < mSize; i++) {
+ mKeys[i] = 0;
+ mMap[i] = 0;
+ }
+ mSize -= num;
+ onChanged();
+ }
+
+ /**
* Returns the number of key-value mappings that this WatchedSparseBooleanMatrix
* currently stores.
*/
@@ -371,7 +398,7 @@
// Preemptively grow the matrix, which also grows the free list.
growMatrix();
}
- int newIndex = nextFree();
+ int newIndex = nextFree(true /* acquire */);
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex);
mSize++;
@@ -447,12 +474,12 @@
}
/**
- * Find an unused storage index, mark it in-use, and return it.
+ * Find an unused storage index, and return it. Mark it in-use if the {@code acquire} is true.
*/
- private int nextFree() {
+ private int nextFree(boolean acquire) {
for (int i = 0; i < mInUse.length; i++) {
if (!mInUse[i]) {
- mInUse[i] = true;
+ mInUse[i] = acquire;
return i;
}
}
@@ -488,7 +515,8 @@
}
// dst and src are identify raw (row, col) in mValues. srcIndex is the index (as
// in the result of keyAt()) of the key being relocated.
- for (int dst = nextFree(); dst < mSize; dst = nextFree()) {
+ for (int dst = nextFree(false); dst < mSize; dst = nextFree(false)) {
+ mInUse[dst] = true;
int srcIndex = lastInuse();
int src = mMap[srcIndex];
mInUse[src] = false;
@@ -539,6 +567,20 @@
}
/**
+ * Set capacity to enlarge the size of the 2D matrix. Capacity less than the {@link #capacity()}
+ * is not supported.
+ */
+ public void setCapacity(int capacity) {
+ if (capacity <= mOrder) {
+ return;
+ }
+ if (capacity % STEP != 0) {
+ capacity = ((capacity / STEP) + 1) * STEP;
+ }
+ resizeMatrix(capacity);
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS
index 7e7335d..08f0a90 100644
--- a/services/core/java/com/android/server/vibrator/OWNERS
+++ b/services/core/java/com/android/server/vibrator/OWNERS
@@ -1 +1,3 @@
+lsandrade@google.com
michaelwr@google.com
+sbowden@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vibrator/RampDownAdapter.java b/services/core/java/com/android/server/vibrator/RampDownAdapter.java
index d5cd344..e97ed4c 100644
--- a/services/core/java/com/android/server/vibrator/RampDownAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampDownAdapter.java
@@ -77,9 +77,8 @@
*/
private int addRampDownToZeroAmplitudeSegments(List<VibrationEffectSegment> segments,
int repeatIndex) {
- int newRepeatIndex = repeatIndex;
- int newSegmentCount = segments.size();
- for (int i = 1; i < newSegmentCount; i++) {
+ int segmentCount = segments.size();
+ for (int i = 1; i < segmentCount; i++) {
VibrationEffectSegment previousSegment = segments.get(i - 1);
if (!isOffSegment(segments.get(i))
|| !endsWithNonZeroAmplitude(previousSegment)) {
@@ -116,16 +115,23 @@
if (replacementSegments != null) {
int segmentsAdded = replacementSegments.size() - 1;
- segments.remove(i);
+ VibrationEffectSegment originalOffSegment = segments.remove(i);
segments.addAll(i, replacementSegments);
- if (repeatIndex > i) {
- newRepeatIndex += segmentsAdded;
+ if (repeatIndex >= i) {
+ if (repeatIndex == i) {
+ // This effect is repeating to the removed off segment: add it back at the
+ // end of the vibration so the loop timings are preserved, and skip it.
+ segments.add(originalOffSegment);
+ repeatIndex++;
+ segmentCount++;
+ }
+ repeatIndex += segmentsAdded;
}
i += segmentsAdded;
- newSegmentCount += segmentsAdded;
+ segmentCount += segmentsAdded;
}
}
- return newRepeatIndex;
+ return repeatIndex;
}
/**
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 25321c1..a327382 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -1077,7 +1077,7 @@
newSegments.remove(segmentIndex);
newSegments.addAll(segmentIndex, fallback.getSegments());
if (segmentIndex < effect.getRepeatIndex()) {
- newRepeatIndex += fallback.getSegments().size();
+ newRepeatIndex += fallback.getSegments().size() - 1;
}
return new VibrationEffect.Composed(newSegments, newRepeatIndex);
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index efccd57..4b7fd90 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -41,12 +41,11 @@
private final Object mLock = new Object();
private final NativeWrapper mNativeWrapper;
- private final VibratorInfo.Builder mVibratorInfoBuilder;
@GuardedBy("mLock")
private VibratorInfo mVibratorInfo;
@GuardedBy("mLock")
- private boolean mVibratorInfoLoaded;
+ private boolean mVibratorInfoLoadSuccessful;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
new RemoteCallbackList<>();
@@ -73,10 +72,16 @@
NativeWrapper nativeWrapper) {
mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener);
- mVibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
- mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
- mVibratorInfoBuilder);
- mVibratorInfo = mVibratorInfoBuilder.build();
+ VibratorInfo.Builder vibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
+ mVibratorInfoLoadSuccessful = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
+ vibratorInfoBuilder);
+ mVibratorInfo = vibratorInfoBuilder.build();
+
+ if (!mVibratorInfoLoadSuccessful) {
+ Slog.e(TAG,
+ "Vibrator controller initialization failed to load some HAL info for vibrator "
+ + vibratorId);
+ }
}
/** Register state listener for this vibrator. */
@@ -108,15 +113,33 @@
}
}
+ /** Reruns the query to the vibrator to load the {@link VibratorInfo}, if not yet successful. */
+ public void reloadVibratorInfoIfNeeded() {
+ synchronized (mLock) {
+ if (mVibratorInfoLoadSuccessful) {
+ return;
+ }
+ int vibratorId = mVibratorInfo.getId();
+ VibratorInfo.Builder vibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
+ mVibratorInfoLoadSuccessful = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
+ vibratorInfoBuilder);
+ mVibratorInfo = vibratorInfoBuilder.build();
+ if (!mVibratorInfoLoadSuccessful) {
+ Slog.e(TAG, "Failed retry of HAL getInfo for vibrator " + vibratorId);
+ }
+ }
+ }
+
+ /** Checks if the {@link VibratorInfo} was loaded from the vibrator hardware successfully. */
+ boolean isVibratorInfoLoadSuccessful() {
+ synchronized (mLock) {
+ return mVibratorInfoLoadSuccessful;
+ }
+ }
+
/** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
public VibratorInfo getVibratorInfo() {
synchronized (mLock) {
- if (!mVibratorInfoLoaded) {
- // Try to load the vibrator metadata that has failed in the last attempt.
- mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
- mVibratorInfoBuilder);
- mVibratorInfo = mVibratorInfoBuilder.build();
- }
return mVibratorInfo;
}
}
@@ -164,7 +187,9 @@
* @return true if this vibrator has this capability, false otherwise
*/
public boolean hasCapability(long capability) {
- return mVibratorInfo.hasCapability(capability);
+ synchronized (mLock) {
+ return mVibratorInfo.hasCapability(capability);
+ }
}
/** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
@@ -178,10 +203,10 @@
* <p>This will affect the state of {@link #isUnderExternalControl()}.
*/
public void setExternalControl(boolean externalControl) {
- if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
- return;
- }
synchronized (mLock) {
+ if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+ return;
+ }
mIsUnderExternalControl = externalControl;
mNativeWrapper.setExternalControl(externalControl);
}
@@ -192,10 +217,10 @@
* if given {@code effect} is {@code null}.
*/
public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
- if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
- return;
- }
synchronized (mLock) {
+ if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+ return;
+ }
if (prebaked == null) {
mNativeWrapper.alwaysOnDisable(id);
} else {
@@ -268,10 +293,10 @@
* do not support the input or a negative number if the operation failed.
*/
public long on(PrimitiveSegment[] primitives, long vibrationId) {
- if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
- return 0;
- }
synchronized (mLock) {
+ if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ return 0;
+ }
long duration = mNativeWrapper.compose(primitives, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
@@ -290,10 +315,10 @@
* @return The duration of the effect playing, or 0 if unsupported.
*/
public long on(RampSegment[] primitives, long vibrationId) {
- if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
- return 0;
- }
synchronized (mLock) {
+ if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+ return 0;
+ }
int braking = mVibratorInfo.getDefaultBraking();
long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
if (duration > 0) {
@@ -327,6 +352,7 @@
synchronized (mLock) {
return "VibratorController{"
+ "mVibratorInfo=" + mVibratorInfo
+ + ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful
+ ", mIsVibrating=" + mIsVibrating
+ ", mCurrentAmplitude=" + mCurrentAmplitude
+ ", mIsUnderExternalControl=" + mIsUnderExternalControl
@@ -393,10 +419,15 @@
* allocated and returned by {@link #nativeInit(int, OnVibrationCompleteListener)}.
*/
private static native long getNativeFinalizer();
+
private static native boolean isAvailable(long nativePtr);
+
private static native long on(long nativePtr, long milliseconds, long vibrationId);
+
private static native void off(long nativePtr);
+
private static native void setAmplitude(long nativePtr, float amplitude);
+
private static native long performEffect(long nativePtr, long effect, long strength,
long vibrationId);
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 567463f..3a3ce5b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -128,6 +128,8 @@
private VibrationThread mNextVibration;
@GuardedBy("mLock")
private ExternalVibrationHolder mCurrentExternalVibration;
+ @GuardedBy("mLock")
+ private boolean mServiceReady;
private final VibrationSettings mVibrationSettings;
private final VibrationScaler mVibrationScaler;
@@ -201,6 +203,9 @@
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
+ // Load vibrator hardware info. The vibrator ids and manager capabilities are loaded only
+ // once and assumed unchanged for the lifecycle of this service. Each individual vibrator
+ // can still retry loading each individual vibrator hardware spec once more at systemReady.
mCapabilities = mNativeWrapper.getCapabilities();
int[] vibratorIds = mNativeWrapper.getVibratorIds();
if (vibratorIds == null) {
@@ -235,6 +240,11 @@
Slog.v(TAG, "Initializing VibratorManager service...");
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "systemReady");
try {
+ // Will retry to load each vibrator's info, if any request have failed.
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mVibrators.valueAt(i).reloadVibratorInfoIfNeeded();
+ }
+
mVibrationSettings.onSystemReady();
mInputDeviceDelegate.onSystemReady();
@@ -243,6 +253,9 @@
// Will update settings and input devices.
updateServiceState();
} finally {
+ synchronized (mLock) {
+ mServiceReady = true;
+ }
Slog.v(TAG, "VibratorManager service initialized");
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -256,8 +269,19 @@
@Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
- VibratorController controller = mVibrators.get(vibratorId);
- return controller == null ? null : controller.getVibratorInfo();
+ final VibratorController controller = mVibrators.get(vibratorId);
+ if (controller == null) {
+ return null;
+ }
+ final VibratorInfo info = controller.getVibratorInfo();
+ synchronized (mLock) {
+ if (mServiceReady) {
+ return info;
+ }
+ }
+ // If the service is not ready and the load was unsuccessful then return null while waiting
+ // for the service to be ready. It will retry to load the complete info from the HAL.
+ return controller.isVibratorInfoLoadSuccessful() ? info : null;
}
@Override // Binder call
@@ -1344,24 +1368,18 @@
return IExternalVibratorService.SCALE_MUTE;
}
- int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
- vib.getVibrationAttributes());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
- vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
- }
- return vibHolder.scale;
- }
-
ExternalVibrationHolder cancelingExternalVibration = null;
VibrationThread cancelingVibration = null;
int scale;
synchronized (mLock) {
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+ vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+ if (ignoreStatus != null) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ endVibrationLocked(vibHolder, ignoreStatus);
+ return vibHolder.scale;
+ }
if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.externalVibration.equals(vib)) {
// We are already playing this external vibration, so we can return the same
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c9685702..e190b1e 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2260,17 +2260,8 @@
IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId) {
final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
if (!hasPrivilege) {
- try {
- mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
- Binder.getCallingPid(), Binder.getCallingUid(), callingPkg,
- callingFeatureId);
- } catch (Exception e) {
- // If the calling package name does not match a package installed on the system,
- // an exception is thrown. Don't allow that exception to be thrown, otherwise,
- // there is a difference in control flow that allows calling apps to determine
- // if a package is installed on the device.
- return null;
- }
+ mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
+ Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
}
wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/wm/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
index 054044b..e1e7ee4 100644
--- a/services/core/java/com/android/server/wm/ActivityAssistInfo.java
+++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
@@ -30,7 +30,7 @@
private final int mTaskId;
public ActivityAssistInfo(ActivityRecord activityRecord) {
- this.mActivityToken = activityRecord.appToken;
+ this.mActivityToken = activityRecord.token;
this.mAssistToken = activityRecord.assistToken;
this.mTaskId = activityRecord.getTask().mTaskId;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ee72fc8..6c9f1e5 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -558,7 +558,7 @@
final ActivityRecord below = ar.getTask().getActivity((r) -> !r.finishing,
ar, false /*includeBoundary*/, true /*traverseTopToBottom*/);
if (below != null && below.getUid() == ar.getUid()) {
- return below.appToken.asBinder();
+ return below.token;
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 1c2333a..00e1f55 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -44,6 +44,7 @@
*/
@IntDef(suffix = { "_ORDERED_ID" }, value = {
FIRST_ORDERED_ID,
+ COMMUNAL_MODE_ORDERED_ID,
LAST_ORDERED_ID // Update this when adding new ids
})
@Retention(RetentionPolicy.SOURCE)
@@ -55,10 +56,15 @@
static final int FIRST_ORDERED_ID = 0;
/**
+ * The identifier for {@link com.android.server.communal.CommunalManagerService} interceptor.
+ */
+ public static final int COMMUNAL_MODE_ORDERED_ID = 1;
+
+ /**
* The final id, used by the framework to determine the valid range of ids. Update this when
* adding new ids.
*/
- static final int LAST_ORDERED_ID = FIRST_ORDERED_ID;
+ static final int LAST_ORDERED_ID = COMMUNAL_MODE_ORDERED_ID;
/**
* Data class for storing the various arguments needed for activity interception.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8ef973d..ab61a4b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -154,6 +154,7 @@
import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING;
+import static com.android.server.wm.ActivityRecordProto.MIN_ASPECT_RATIO;
import static com.android.server.wm.ActivityRecordProto.NAME;
import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS;
import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS;
@@ -300,7 +301,6 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.IApplicationToken;
import android.view.InputApplicationHandle;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -329,7 +329,6 @@
import com.android.internal.os.TransferPipe;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.XmlUtils;
import com.android.server.LocalServices;
import com.android.server.am.AppTimeTracker;
@@ -359,7 +358,6 @@
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
-import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -424,8 +422,6 @@
final ActivityTaskManagerService mAtmService;
final ActivityInfo info; // activity info provided by developer in AndroidManifest
- // TODO: rename to mActivityToken
- final ActivityRecord.Token appToken;
// Which user is this running for?
final int mUserId;
// The package implementing intent's component
@@ -1223,7 +1219,7 @@
TransferPipe tp = new TransferPipe();
try {
r.app.getThread().dumpActivity(
- tp.getWriteFd(), r.appToken, innerPrefix, args);
+ tp.getWriteFd(), r.token, innerPrefix, args);
// Short timeout, since blocking here can deadlock with the application.
tp.go(fd, 2000);
} finally {
@@ -1290,7 +1286,7 @@
+ "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
config);
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
MoveToDisplayItem.obtain(displayId, config));
} catch (RemoteException e) {
// If process died, whatever.
@@ -1307,7 +1303,7 @@
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
+ "config: %s", this, config);
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
ActivityConfigurationChangeItem.obtain(config));
} catch (RemoteException e) {
// If process died, whatever.
@@ -1325,7 +1321,7 @@
ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
this, onTop);
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
TopResumedActivityChangeItem.obtain(onTop));
} catch (RemoteException e) {
// If process died, whatever.
@@ -1444,8 +1440,6 @@
if (getDisplayContent() != null) {
getDisplayContent().mClosingApps.remove(this);
}
- } else if (oldTask != null && oldTask.getRootTask() != null) {
- task.getRootTask().mExitingActivities.remove(this);
}
final Task rootTask = getRootTask();
@@ -1575,60 +1569,27 @@
return mLetterboxUiController.isFullyTransparentBarAllowed(rect);
}
- static class Token extends IApplicationToken.Stub {
- private WeakReference<ActivityRecord> weakActivity;
- private final String name;
- private final String tokenString;
-
- Token(Intent intent) {
- name = intent.getComponent().flattenToShortString();
- tokenString = "Token{" + Integer.toHexString(System.identityHashCode(this)) + "}";
- }
-
- private void attach(ActivityRecord activity) {
- if (weakActivity != null) {
- throw new IllegalStateException("Already attached..." + this);
- }
- weakActivity = new WeakReference<>(activity);
- }
-
- private static @Nullable ActivityRecord tokenToActivityRecordLocked(Token token) {
- if (token == null) {
- return null;
- }
- ActivityRecord r = token.weakActivity.get();
- if (r == null || r.getRootTask() == null) {
- return null;
- }
- return r;
- }
+ private static class Token extends Binder {
+ @NonNull WeakReference<ActivityRecord> mActivityRef;
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Token{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(' ');
- if (weakActivity != null) {
- sb.append(weakActivity.get());
- }
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public String getName() {
- return name;
+ return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+ + mActivityRef.get() + "}";
}
}
static @Nullable ActivityRecord forTokenLocked(IBinder token) {
+ if (token == null) return null;
+ final Token activityToken;
try {
- return Token.tokenToActivityRecordLocked((Token)token);
+ activityToken = (Token) token;
} catch (ClassCastException e) {
Slog.w(TAG, "Bad activity token: " + token, e);
return null;
}
+ final ActivityRecord r = activityToken.mActivityRef.get();
+ return r == null || r.getRootTask() == null ? null : r;
}
static boolean isResolverActivity(String className) {
@@ -1660,11 +1621,11 @@
boolean _rootVoiceInteraction, ActivityTaskSupervisor supervisor,
ActivityOptions options, ActivityRecord sourceRecord, PersistableBundle persistentState,
TaskDescription _taskDescription, long _createTime) {
- super(_service.mWindowManager, new Token(_intent).asBinder(), TYPE_APPLICATION, true,
+ super(_service.mWindowManager, new Token(), TYPE_APPLICATION, true,
null /* displayContent */, false /* ownerCanManageAppTokens */);
mAtmService = _service;
- appToken = (Token) token;
+ ((Token) token).mActivityRef = new WeakReference<>(this);
info = aInfo;
mUserId = UserHandle.getUserId(info.applicationInfo.uid);
packageName = info.applicationInfo.packageName;
@@ -1728,8 +1689,6 @@
cds.attachColorTransformController(packageName, mUserId,
new WeakReference<>(mColorTransformController));
- appToken.attach(this);
-
mRootWindowContainer = _service.mRootWindowContainer;
launchedFromPid = _launchedFromPid;
launchedFromUid = _launchedFromUid;
@@ -1867,13 +1826,13 @@
@NonNull InputApplicationHandle getInputApplicationHandle(boolean update) {
if (mInputApplicationHandle == null) {
- mInputApplicationHandle = new InputApplicationHandle(appToken, toString(),
+ mInputApplicationHandle = new InputApplicationHandle(token, toString(),
mInputDispatchingTimeoutMillis);
} else if (update) {
final String name = toString();
if (mInputDispatchingTimeoutMillis != mInputApplicationHandle.dispatchingTimeoutMillis
|| !name.equals(mInputApplicationHandle.name)) {
- mInputApplicationHandle = new InputApplicationHandle(appToken, name,
+ mInputApplicationHandle = new InputApplicationHandle(token, name,
mInputDispatchingTimeoutMillis);
}
}
@@ -2039,7 +1998,8 @@
boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
ActivityRecord from, boolean newTask, boolean taskSwitch, boolean processRunning,
- boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty) {
+ boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty,
+ boolean activityAllDrawn) {
// If the display is frozen, we won't do anything until the actual window is
// displayed so there is no reason to put in the starting window.
if (!okToDisplay()) {
@@ -2060,7 +2020,7 @@
mWmService.mTaskSnapshotController.getSnapshot(task.mTaskId, task.mUserId,
false /* restoreFromDisk */, false /* isLowResolution */);
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
- allowTaskSnapshot, activityCreated, snapshot);
+ allowTaskSnapshot, activityCreated, activityAllDrawn, snapshot);
//TODO(191787740) Remove for T
final boolean useLegacy = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN
@@ -2074,7 +2034,7 @@
final int typeParameter = mWmService.mStartingSurfaceController
.makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
- allowTaskSnapshot, activityCreated, useEmpty, useLegacy);
+ allowTaskSnapshot, activityCreated, useEmpty, useLegacy, activityAllDrawn);
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
if (isActivityTypeHome()) {
@@ -2209,10 +2169,10 @@
private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
- boolean allowTaskSnapshot, boolean activityCreated,
+ boolean allowTaskSnapshot, boolean activityCreated, boolean activityAllDrawn,
TaskSnapshot snapshot) {
- if ((newTask || !processRunning || (taskSwitch && !activityCreated))
- && !isActivityTypeHome()) {
+ if ((newTask || !processRunning || (taskSwitch && !activityCreated)
+ || (taskSwitch && !activityAllDrawn)) && !isActivityTypeHome()) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
if (isSnapshotCompatible(snapshot)) {
@@ -2334,7 +2294,7 @@
.applyStartingWindowAnimation(mStartingWindow);
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
TransferSplashScreenViewStateItem.obtain(parcelable,
windowAnimationLeash));
scheduleTransferSplashScreenTimeout();
@@ -2476,7 +2436,7 @@
*/
void reparent(TaskFragment newTaskFrag, int position, String reason) {
if (getParent() == null) {
- Slog.w(TAG, "reparent: Attempted to reparent non-existing app token: " + appToken);
+ Slog.w(TAG, "reparent: Attempted to reparent non-existing app token: " + token);
return;
}
final TaskFragment prevTaskFrag = getTaskFragment();
@@ -3432,7 +3392,7 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
DestroyActivityItem.obtain(finishing, configChangeFlags));
} catch (Exception e) {
// We can just ignore exceptions here... if the process has crashed, our death
@@ -3512,7 +3472,7 @@
detachFromProcess();
// Resume key dispatching if it is currently paused before we remove the container.
resumeKeyDispatchingLocked();
- mDisplayContent.removeAppToken(appToken);
+ mDisplayContent.removeAppToken(token);
cleanUpActivityServices();
removeUriPermissionsLocked();
@@ -3832,22 +3792,15 @@
getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
}
- final Task rootTask = getRootTask();
if (delayed && !isEmpty()) {
// set the token aside because it has an active animation to be finished
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"removeAppToken make exiting: %s", this);
- if (rootTask != null) {
- rootTask.mExitingActivities.add(this);
- }
mIsExiting = true;
} else {
// Make sure there is no animation running on this token, so any windows associated
// with it will be removed as soon as their animations are complete
cancelAnimation();
- if (rootTask != null) {
- rootTask.mExitingActivities.remove(this);
- }
removeIfPossible();
}
@@ -4205,26 +4158,8 @@
}
@Override
- boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
- // For legacy reasons we process the TaskStack.mExitingActivities first in DisplayContent
- // before the non-exiting app tokens. So, we skip the exiting app tokens here.
- // TODO: Investigate if we need to continue to do this or if we can just process them
- // in-order.
- if (mIsExiting && !forAllWindowsUnchecked(WindowState::waitingForReplacement, true)) {
- return false;
- }
- return forAllWindowsUnchecked(callback, traverseTopToBottom);
- }
-
- boolean forAllWindowsUnchecked(ToBooleanFunction<WindowState> callback,
- boolean traverseTopToBottom) {
- return super.forAllWindows(callback, traverseTopToBottom);
- }
-
- @Override
- boolean forAllActivities(
- Function<ActivityRecord, Boolean> callback, boolean traverseTopToBottom) {
- return callback.apply(this);
+ boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ return callback.test(this);
}
@Override
@@ -4300,7 +4235,7 @@
try {
final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(resultWho, requestCode, resultCode, data));
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
ActivityResultItem.obtain(list));
return;
} catch (Exception e) {
@@ -4348,7 +4283,7 @@
// Making sure the client state is RESUMED after transaction completed and doing
// so only if activity is currently RESUMED. Otherwise, client may have extra
// life-cycle calls to RESUMED (and PAUSED later).
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
NewIntentItem.obtain(ar, mState == RESUMED));
unsent = false;
} catch (RemoteException e) {
@@ -4694,6 +4629,7 @@
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
+ logAppCompatState();
}
/**
@@ -4709,8 +4645,7 @@
*/
void setVisibility(boolean visible) {
if (getParent() == null) {
- Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: "
- + appToken);
+ Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
return;
}
if (visible) {
@@ -4721,7 +4656,6 @@
ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
mTaskSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
- logAppCompatState();
}
@VisibleForTesting
@@ -4748,7 +4682,7 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
- appToken, visible, appTransition, isVisible(), mVisibleRequested,
+ token, visible, appTransition, isVisible(), mVisibleRequested,
Debug.getCallers(6));
// Before setting mVisibleRequested so we can track changes.
@@ -4961,7 +4895,7 @@
*/
private void postApplyAnimation(boolean visible, boolean fromTransition) {
final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
- final boolean delayed = isAnimating(TRANSITION | PARENTS | CHILDREN,
+ final boolean delayed = isAnimating(PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
if (!delayed && !usingShellTransitions) {
@@ -5117,7 +5051,7 @@
if (state == STOPPING && !isSleeping()) {
if (getParent() == null) {
Slog.w(TAG_WM, "Attempted to notify stopping on non-existing app token: "
- + appToken);
+ + token);
return;
}
}
@@ -5248,8 +5182,7 @@
void notifyAppResumed(boolean wasStopped) {
if (getParent() == null) {
- Slog.w(TAG_WM, "Attempted to notify resumed of non-existing app token: "
- + appToken);
+ Slog.w(TAG_WM, "Attempted to notify resumed of non-existing app token: " + token);
return;
}
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppResumed: wasStopped=%b %s",
@@ -5465,7 +5398,7 @@
// the move to {@link PAUSED}.
setState(PAUSING, "makeActiveIfNeeded");
try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
PauseActivityItem.obtain(finishing, false /* userLeaving */,
configChangeFlags, false /* dontReport */));
} catch (Exception e) {
@@ -5478,7 +5411,7 @@
setState(STARTED, "makeActiveIfNeeded");
try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
StartActivityItem.obtain(takeOptions()));
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
@@ -5586,7 +5519,7 @@
stopFreezingScreenLocked(false);
try {
if (returningOptions != null) {
- app.getThread().scheduleOnNewActivityOptions(appToken, returningOptions.toBundle());
+ app.getThread().scheduleOnNewActivityOptions(token, returningOptions.toBundle());
}
} catch(RemoteException e) {
}
@@ -5665,7 +5598,7 @@
}
void activityPaused(boolean timeout) {
- ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", appToken,
+ ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", token,
timeout);
final TaskFragment taskFragment = getTaskFragment();
@@ -5765,7 +5698,7 @@
}
EventLogTags.writeWmStopActivity(
mUserId, System.identityHashCode(this), shortComponentName);
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
StopActivityItem.obtain(configChangeFlags));
mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
@@ -5913,14 +5846,14 @@
if (mayFreezeScreenLocked(app)) {
if (getParent() == null) {
Slog.w(TAG_WM,
- "Attempted to freeze screen with non-existing app token: " + appToken);
+ "Attempted to freeze screen with non-existing app token: " + token);
return;
}
// Window configuration changes only effect windows, so don't require a screen freeze.
int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
if (freezableConfigChanges == 0 && okToDisplay()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", appToken);
+ ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
return;
}
@@ -5938,7 +5871,7 @@
}
ProtoLog.i(WM_DEBUG_ORIENTATION,
"Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
- appToken, isVisible(), mFreezingScreen, mVisibleRequested,
+ token, isVisible(), mFreezingScreen, mVisibleRequested,
new RuntimeException().fillInStackTrace());
if (!mVisibleRequested) {
return;
@@ -5994,7 +5927,7 @@
return;
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Clear freezing of %s: visible=%b freezing=%b", appToken,
+ "Clear freezing of %s: visible=%b freezing=%b", token,
isVisible(), isFreezingScreen());
stopFreezingScreen(true, force);
}
@@ -6118,7 +6051,7 @@
/** Called when the windows associated app window container are visible. */
void onWindowsVisible() {
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting visible in " + appToken);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting visible in " + token);
mTaskSupervisor.stopWaitingForActivityVisible(this);
if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsVisibleLocked(): " + this);
if (!nowVisible) {
@@ -6144,7 +6077,7 @@
/** Called when the windows associated app window container are no longer visible. */
void onWindowsGone() {
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting gone in " + appToken);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting gone in " + token);
if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsGone(): " + this);
nowVisible = false;
}
@@ -6629,7 +6562,7 @@
final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev, newTask || newSingleActivity, taskSwitch, isProcessRunning(),
- allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty);
+ allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty, allDrawn);
if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
Slog.d(TAG, "Scheduled starting window for " + this);
}
@@ -7511,7 +7444,7 @@
/**
* Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity
* requested in the config or via an ADB command. For more context see {@link
- * WindowManagerService#getLetterboxHorizontalPositionMultiplier}.
+ * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)}.
*/
private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
@@ -7848,7 +7781,7 @@
// Below figure is an example that puts an activity which was launched in a larger container
// into a smaller container.
// The outermost rectangle is the real display bounds.
- // "@" is the container app bounds (parent bounds or fixed orientation bouds)
+ // "@" is the container app bounds (parent bounds or fixed orientation bounds)
// "#" is the {@code resolvedBounds} that applies to application.
// "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
// ------------------------------
@@ -7891,12 +7824,15 @@
mSizeCompatBounds = null;
}
- // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
- // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
+ // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal
+ // decor if needed. Horizontal position is adjusted in
+ // updateResolvedBoundsHorizontalPosition.
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
final boolean fillContainer = resolvedBounds.equals(containingBounds);
final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
- final int screenPosY = containerBounds.top;
+ final int screenPosY = mSizeCompatBounds == null
+ ? (containerBounds.height() - resolvedBounds.height()) / 2
+ : (containerBounds.height() - mSizeCompatBounds.height()) / 2;
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
mSizeCompatBounds.offset(screenPosX, screenPosY);
@@ -8571,7 +8507,7 @@
} else {
lifecycleItem = PauseActivityItem.obtain();
}
- final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), appToken);
+ final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
transaction.addCallback(callbackItem);
transaction.setLifecycleStateRequest(lifecycleItem);
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
@@ -8644,7 +8580,7 @@
// The process will be killed until the activity reports stopped with saved state (see
// {@link ActivityTaskManagerService.activityStopped}).
try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
StopActivityItem.obtain(0 /* configChanges */));
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown during restart " + this, e);
@@ -8990,6 +8926,7 @@
}
proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled());
proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode());
+ proto.write(MIN_ASPECT_RATIO, info.getMinAspectRatio());
}
@Override
@@ -9011,7 +8948,7 @@
}
void writeNameToProto(ProtoOutputStream proto, long fieldId) {
- proto.write(fieldId, appToken.getName());
+ proto.write(fieldId, shortComponentName);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 6ad2f7c..210b0ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -470,7 +470,7 @@
if (started != null && started.getUid() == filterCallingUid) {
// Only the started activity which has the same uid as the source caller
// can be the caller of next activity.
- resultTo = started.appToken;
+ resultTo = started.token;
} else {
resultTo = sourceResultTo;
// Different apps not adjacent to the caller are forced to be new task.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d944c58..b966ed1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1599,7 +1599,7 @@
// transition based on a sub-action.
// Only do the create here (and defer requestStart) since startActivityInner might abort.
final TransitionController transitionController = r.mTransitionController;
- final Transition newTransition = (!transitionController.isCollecting()
+ Transition newTransition = (!transitionController.isCollecting()
&& transitionController.getTransitionPlayer() != null)
? transitionController.createTransition(TRANSIT_OPEN) : null;
RemoteTransition remoteTransition = r.takeRemoteTransition();
@@ -1654,6 +1654,10 @@
// The activity is started new rather than just brought forward, so record
// it as an existence change.
transitionController.collectExistenceChange(r);
+ } else if (result == START_DELIVERED_TO_TOP && newTransition != null) {
+ // We just delivered to top, so there isn't an actual transition here
+ newTransition.abort();
+ newTransition = null;
}
if (isTransient) {
// `r` isn't guaranteed to be the actual relevant activity, so we must wait
@@ -2771,9 +2775,11 @@
// If it exist, we need to reparent target root task from TDA to launch root task.
final TaskDisplayArea tda = mTargetRootTask.getDisplayArea();
final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(),
- mTargetRootTask.getActivityType(), null /** options */, null /** sourceTask */,
- 0 /** launchFlags */);
- if (launchRootTask != null && launchRootTask != mTargetRootTask) {
+ mTargetRootTask.getActivityType(), null /** options */,
+ mSourceRootTask, 0 /** launchFlags */);
+ // If target root task is created by organizer, let organizer handle reparent itself.
+ if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null
+ && launchRootTask != mTargetRootTask) {
mTargetRootTask.reparent(launchRootTask, POSITION_TOP);
mTargetRootTask = launchRootTask;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 960fa50..64950c7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AppProtoEnums;
import android.app.IActivityManager;
import android.app.IApplicationThread;
@@ -265,7 +266,7 @@
/**
* Set focus on an activity.
- * @param token The IApplicationToken for the activity
+ * @param token The activity token.
*/
public abstract void setFocusedActivity(IBinder token);
@@ -663,13 +664,23 @@
* This setting is persisted and will overlay on top of the system locales for
* the said application.
* @return the current {@link PackageConfigurationUpdater} updated with the provided locale.
+ *
+ * <p>NOTE: This method should not be called by clients directly to set app locales,
+ * instead use the {@link LocaleManagerService#setApplicationLocales}
*/
PackageConfigurationUpdater setLocales(LocaleList locales);
/**
* Commit changes.
+ * @return true if the configuration changes were persisted,
+ * false if there were no changes, or if erroneous inputs were provided, such as:
+ * <ui>
+ * <li>Invalid packageName</li>
+ * <li>Invalid userId</li>
+ * <li>no WindowProcessController found for the package</li>
+ * </ui>
*/
- void commit();
+ boolean commit();
}
/**
@@ -688,4 +699,7 @@
public abstract void registerActivityStartInterceptor(
@ActivityInterceptorCallback.OrderedId int id,
ActivityInterceptorCallback callback);
+
+ /** Get the most recent task excluding the first running task (the one on the front most). */
+ public abstract ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 48afb7c0c..a1a357e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1350,7 +1350,7 @@
.setCaller(r.app.getThread())
.setResolvedType(r.resolvedType)
.setActivityInfo(aInfo)
- .setResultTo(resultTo != null ? resultTo.appToken : null)
+ .setResultTo(resultTo != null ? resultTo.token : null)
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setCallingPid(-1)
@@ -3194,7 +3194,7 @@
mViSessionId++;
}
try {
- activity.app.getThread().requestAssistContextExtras(activity.appToken, pae,
+ activity.app.getThread().requestAssistContextExtras(activity.token, pae,
requestType, mViSessionId, flags);
mPendingAssistExtras.add(pae);
mUiHandler.postDelayed(pae, timeout);
@@ -3439,7 +3439,7 @@
// If the keyguard is showing or occluded, then try and dismiss it before
// entering picture-in-picture (this will prompt the user to authenticate if the
// device is currently locked).
- mActivityClientController.dismissKeyguard(r.appToken, new KeyguardDismissCallback() {
+ mActivityClientController.dismissKeyguard(r.token, new KeyguardDismissCallback() {
@Override
public void onDismissSucceeded() {
mH.post(enterPipRunnable);
@@ -4010,7 +4010,7 @@
// to write to the file descriptor directly
pw.flush();
try (TransferPipe tp = new TransferPipe()) {
- appThread.dumpActivity(tp.getWriteFd(), r.appToken, innerPrefix, args);
+ appThread.dumpActivity(tp.getWriteFd(), r.token, innerPrefix, args);
tp.go(fd);
} catch (IOException e) {
pw.println(innerPrefix + "Failure while dumping the activity: " + e);
@@ -4639,7 +4639,7 @@
final Message m = PooledLambda.obtainMessage(
ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
- activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot);
+ activity.mActivityComponent, activity.mUserId, event, activity.token, taskRoot);
mH.sendMessage(m);
}
@@ -5747,7 +5747,7 @@
+ activity);
return null;
}
- return new ActivityTokens(activity.appToken, activity.assistToken,
+ return new ActivityTokens(activity.token, activity.assistToken,
activity.app.getThread(), activity.shareableActivityToken);
}
}
@@ -6609,5 +6609,33 @@
mActivityInterceptorCallbacks.put(id, callback);
}
}
+
+ @Override
+ public ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground() {
+ List<ActivityManager.RunningTaskInfo> runningTaskInfoList = getTasks(1);
+ ActivityManager.RunningTaskInfo runningTaskInfo;
+ if (runningTaskInfoList.size() > 0) {
+ runningTaskInfo = runningTaskInfoList.get(0);
+ } else {
+ Slog.i(TAG, "No running task found!");
+ return null;
+ }
+ // Get 2 most recent tasks.
+ List<ActivityManager.RecentTaskInfo> recentTaskInfoList =
+ getRecentTasks(
+ 2,
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+ mContext.getUserId())
+ .getList();
+ ActivityManager.RecentTaskInfo targetTask = null;
+ for (ActivityManager.RecentTaskInfo info : recentTaskInfoList) {
+ // Find a recent task that is not the current running task on screen.
+ if (info.id != runningTaskInfo.id) {
+ targetTask = info;
+ break;
+ }
+ }
+ return targetTask;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ba30592..11b685e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -850,7 +850,7 @@
// Create activity launch transaction.
final ClientTransaction clientTransaction = ClientTransaction.obtain(
- proc.getThread(), r.appToken);
+ proc.getThread(), r.token);
final boolean isTransitionForward = r.isTransitionForward();
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 529c4f6..5899a4e 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -52,7 +53,7 @@
* @param finishCallback The callback to be invoked when the animation has finished.
*/
void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type,
- OnAnimationFinishedCallback finishCallback);
+ @NonNull OnAnimationFinishedCallback finishCallback);
/**
* Called when the animation that was started with {@link #startAnimation} was cancelled by the
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 91f650f..fa43b39 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -31,7 +31,7 @@
import android.view.InputApplicationHandle;
import com.android.server.am.ActivityManagerService;
-import com.android.server.am.CriticalEventLog;
+import com.android.server.criticalevents.CriticalEventLog;
import java.io.File;
import java.util.ArrayList;
@@ -87,16 +87,12 @@
return;
}
- WindowState windowState = target.asWindowState();
+ WindowState windowState = target.getWindowState();
pid = target.getPid();
- if (windowState != null) {
- activity = windowState.mActivityRecord;
- } else {
- // Don't blame the host process, instead blame the embedded pid.
- activity = null;
- // Use host WindowState for logging and z-order test.
- windowState = target.asEmbeddedWindow().mHostWindowState;
- }
+ // Blame the activity if the input token belongs to the window. If the target is
+ // embedded, then we will blame the pid instead.
+ activity = (windowState.mInputChannelToken == inputToken)
+ ? windowState.mActivityRecord : null;
Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
aboveSystem = isWindowAboveSystem(windowState);
dumpAnrStateLocked(activity, windowState, reason);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index e21a00b..7817f54 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -81,7 +81,6 @@
import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
@@ -97,7 +96,6 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
@@ -126,9 +124,6 @@
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
@@ -151,25 +146,13 @@
// made visible or hidden at the next transition.
public class AppTransition implements Dump {
private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransition" : TAG_WM;
- private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8;
-
- /** Fraction of animation at which the recents thumbnail stays completely transparent */
- private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
- /** Fraction of animation at which the recents thumbnail becomes completely transparent */
- private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
static final int DEFAULT_APP_TRANSITION_DURATION = 336;
- /** Interpolator to be used for animations that respond directly to a touch */
- static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
/**
* Maximum duration for the clip reveal animation. This is used when there is a lot of movement
* involved, to make it more understandable.
*/
- private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
- private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
static final int MAX_APP_TRANSITION_DURATION = 3 * 1000; // 3 secs.
@@ -225,11 +208,6 @@
private boolean mNextAppTransitionAnimationsSpecsPending;
private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
- private Rect mNextAppTransitionInsets = new Rect();
-
- private Rect mTmpFromClipRect = new Rect();
- private Rect mTmpToClipRect = new Rect();
-
private final Rect mTmpRect = new Rect();
private final static int APP_STATE_IDLE = 0;
@@ -238,29 +216,11 @@
private final static int APP_STATE_TIMEOUT = 3;
private int mAppTransitionState = APP_STATE_IDLE;
- private final int mConfigShortAnimTime;
- private final Interpolator mDecelerateInterpolator;
- private final Interpolator mThumbnailFadeInInterpolator;
- private final Interpolator mThumbnailFadeOutInterpolator;
- private final Interpolator mLinearOutSlowInInterpolator;
- private final Interpolator mFastOutLinearInInterpolator;
- private final Interpolator mFastOutSlowInInterpolator;
- private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
-
- private final int mClipRevealTranslationY;
-
- private int mCurrentUserId = 0;
- private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
-
private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener;
private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
- private int mLastClipRevealMaxTranslation;
- private boolean mLastHadClipReveal;
-
private final boolean mGridLayoutRecentsEnabled;
- private final boolean mLowRamRecentsEnabled;
private final int mDefaultWindowAnimationStyleResId;
private boolean mOverrideTaskTransition;
@@ -276,43 +236,8 @@
mHandler = new Handler(service.mH.getLooper());
mDisplayContent = displayContent;
mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG);
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.linear_out_slow_in);
- mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_linear_in);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mConfigShortAnimTime = context.getResources().getInteger(
- com.android.internal.R.integer.config_shortAnimTime);
- mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.decelerate_cubic);
- mThumbnailFadeInInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float input) {
- // Linear response for first fraction, then complete after that.
- if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
- return 0f;
- }
- float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION) /
- (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
- return mFastOutLinearInInterpolator.getInterpolation(t);
- }
- };
- mThumbnailFadeOutInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float input) {
- // Linear response for first fraction, then complete after that.
- if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
- float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
- return mLinearOutSlowInInterpolator.getInterpolation(t);
- }
- return 1f;
- }
- };
- mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP
- * mContext.getResources().getDisplayMetrics().density);
+
mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
- mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
com.android.internal.R.styleable.Window);
@@ -421,9 +346,6 @@
if (!isRunning()) {
setAppTransitionState(APP_STATE_IDLE);
notifyAppTransitionPendingLocked();
- mLastHadClipReveal = false;
- mLastClipRevealMaxTranslation = 0;
- mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
return true;
}
return false;
@@ -443,6 +365,7 @@
int redoLayout = notifyAppTransitionStartingLocked(
AppTransition.isKeyguardGoingAwayTransitOld(transit),
+ AppTransition.isKeyguardOccludeTransitOld(transit),
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
@@ -557,12 +480,14 @@
}
}
- private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOcclude, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
- duration, statusBarAnimationStartTime, statusBarAnimationDuration);
+ keyguardOcclude, duration, statusBarAnimationStartTime,
+ statusBarAnimationDuration);
}
return redoLayout;
}
@@ -599,21 +524,6 @@
}
}
- void getNextAppTransitionStartRect(WindowContainer container, Rect rect) {
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- if (spec == null) {
- spec = mDefaultNextAppTransitionAnimationSpec;
- }
- if (spec == null || spec.rect == null) {
- Slog.e(TAG, "Starting rect for container: " + container
- + " requested, but not available", new Throwable());
- rect.setEmpty();
- } else {
- rect.set(spec.rect);
- }
- }
-
private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height,
HardwareBuffer buffer) {
mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
@@ -621,49 +531,6 @@
}
/**
- * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
- * the start rect is outside of the target rect, and there is a lot of movement going on.
- *
- * @param cutOff whether the start rect was not fully contained by the end rect
- * @param translationX the total translation the surface moves in x direction
- * @param translationY the total translation the surfaces moves in y direction
- * @param displayFrame our display frame
- *
- * @return the duration of the clip reveal animation, in milliseconds
- */
- private long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
- float translationY, Rect displayFrame) {
- if (!cutOff) {
- return DEFAULT_APP_TRANSITION_DURATION;
- }
- final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
- Math.abs(translationY) / displayFrame.height());
- return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction *
- (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
- }
-
- /**
- * Prepares the specified animation with a standard duration, interpolator, etc.
- */
- Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
- // Pick the desired duration. If this is an inter-activity transition,
- // it is the standard duration for that. Otherwise we use the longer
- // task transition duration.
- final int duration;
- switch (transit) {
- case TRANSIT_OLD_ACTIVITY_OPEN:
- case TRANSIT_OLD_ACTIVITY_CLOSE:
- duration = mConfigShortAnimTime;
- break;
- default:
- duration = DEFAULT_APP_TRANSITION_DURATION;
- break;
- }
- return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
- mDecelerateInterpolator);
- }
-
- /**
* Creates an overlay with a background color and a thumbnail for the cross profile apps
* animation.
*/
@@ -691,32 +558,6 @@
mNextAppTransitionScaleUp);
}
- private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
- return new TranslateAnimation(fromX, toX, fromY, toY);
- }
-
- private long getAspectScaleDuration() {
- return THUMBNAIL_APP_TRANSITION_DURATION;
- }
-
- private Interpolator getAspectScaleInterpolator() {
- return TOUCH_RESPONSE_INTERPOLATOR;
- }
-
- private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
- @Nullable Rect surfaceInsets, WindowContainer container) {
- getNextAppTransitionStartRect(container, mTmpRect);
- return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
- true);
- }
-
- private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
- @Nullable Rect surfaceInsets, WindowContainer container) {
- getNextAppTransitionStartRect(container, mTmpRect);
- return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
- false);
- }
-
private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
final float sourceWidth = sourceFrame.width();
@@ -752,48 +593,6 @@
}
/**
- * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
- * when a thumbnail is specified with the pending animation override.
- */
- Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
- HardwareBuffer thumbnailHeader) {
- Animation a;
- getDefaultNextAppTransitionStartRect(mTmpRect);
- final int thumbWidthI = thumbnailHeader.getWidth();
- final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
- final int thumbHeightI = thumbnailHeader.getHeight();
- final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
-
- if (mNextAppTransitionScaleUp) {
- // Animation for the thumbnail zooming from its initial size to the full screen
- float scaleW = appWidth / thumbWidth;
- float scaleH = appHeight / thumbHeight;
- Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
- TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW),
- TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH));
- scale.setInterpolator(mDecelerateInterpolator);
-
- Animation alpha = new AlphaAnimation(1, 0);
- alpha.setInterpolator(mThumbnailFadeOutInterpolator);
-
- // This AnimationSet uses the Interpolators assigned above.
- AnimationSet set = new AnimationSet(false);
- set.addAnimation(scale);
- set.addAnimation(alpha);
- a = set;
- } else {
- // Animation for the thumbnail zooming down from the full screen to its final size
- float scaleW = appWidth / thumbWidth;
- float scaleH = appHeight / thumbHeight;
- a = new ScaleAnimation(scaleW, 1, scaleH, 1,
- TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW),
- TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH));
- }
-
- return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
- }
-
- /**
* @return true if and only if the first frame of the transition can be skipped, i.e. the first
* frame of the transition doesn't change the visuals on screen, so we can start
* directly with the second one
@@ -1542,10 +1341,6 @@
}
}
- public void setCurrentUser(int newUserId) {
- mCurrentUserId = newUserId;
- }
-
boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
return false;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index ffaf710..535a061 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -124,6 +124,7 @@
@interface TransitContainerType {}
private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
+ private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -523,26 +524,44 @@
}
}
+ private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
+ // We don't want to have the client to animate any non-app windows.
+ // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
+ // non-app windows will only be included with those transition types. And we don't currently
+ // have any use case of those for TaskFragment transition.
+ // @see NonAppWindowAnimationAdapter#startNonAppWindowAnimations
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
+ return true;
+ }
+
+ // Check if the wallpaper is going to participate in the transition. We don't want to have
+ // the client to animate the wallpaper windows.
+ // @see WallpaperAnimationAdapter#startWallpaperAnimations
+ return mDisplayContent.mWallpaperController.isWallpaperVisible();
+ }
+
/**
- * Overrides the pending transition with the remote animation defined by the
- * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
- * {@link TaskFragment} that are organized by the same organizer.
- *
- * @return {@code true} if the transition is overridden.
+ * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows
+ * in the current transition.
+ * @return {@code null} if there is no such organizer, or if there are more than one.
*/
- private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes) {
- final ArrayList<WindowContainer> allWindows = new ArrayList<>();
- allWindows.addAll(mDisplayContent.mClosingApps);
- allWindows.addAll(mDisplayContent.mOpeningApps);
- allWindows.addAll(mDisplayContent.mChangingContainers);
+ @Nullable
+ private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() {
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
// It should only animated by the organizer if all windows are below the same leaf Task.
Task leafTask = null;
- for (int i = allWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = getAppFromContainer(allWindows.get(i));
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
if (r == null) {
- return false;
+ leafTask = null;
+ break;
}
// The activity may be a child of embedded Task, but we want to find the owner Task.
// As a result, find the organized TaskFragment first.
@@ -561,26 +580,31 @@
? organizedTaskFragment.getTask()
: r.getTask();
if (task == null) {
- return false;
+ leafTask = null;
+ break;
}
// We don't want the organizer to handle transition of other non-embedded Task.
if (leafTask != null && leafTask != task) {
- return false;
+ leafTask = null;
+ break;
}
final ActivityRecord rootActivity = task.getRootActivity();
// We don't want the organizer to handle transition when the whole app is closing.
if (rootActivity == null) {
- return false;
+ leafTask = null;
+ break;
}
// We don't want the organizer to handle transition of non-embedded activity of other
// app.
if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) {
- return false;
+ leafTask = null;
+ break;
}
leafTask = task;
}
+ mTempTransitionWindows.clear();
if (leafTask == null) {
- return false;
+ return null;
}
// We don't support remote animation for Task with multiple TaskFragmentOrganizers.
@@ -599,12 +623,28 @@
if (hasMultipleOrganizers) {
ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
+ " Task with multiple TaskFragmentOrganizers.");
+ return null;
+ }
+ return organizer[0];
+ }
+
+ /**
+ * Overrides the pending transition with the remote animation defined by the
+ * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+ * {@link TaskFragment} that are organized by the same organizer.
+ *
+ * @return {@code true} if the transition is overridden.
+ */
+ private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+ ArraySet<Integer> activityTypes) {
+ if (transitionMayContainNonAppWindows(transit)) {
return false;
}
- final RemoteAnimationDefinition definition = organizer[0] != null
+ final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows();
+ final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer[0])
+ .getRemoteAnimationDefinition(organizer)
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 99f6fd4..7485a1e 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -272,6 +272,10 @@
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
+ if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+ return;
+ }
+
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
proto.write(NAME, mName);
@@ -332,7 +336,7 @@
}
@Override
- boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback,
+ boolean forAllTaskDisplayAreas(Predicate<TaskDisplayArea> callback,
boolean traverseTopToBottom) {
// Only DisplayArea of Type.ANY may contain TaskDisplayArea as children.
if (mType != DisplayArea.Type.ANY) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5e6f234..611de34 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -81,7 +81,6 @@
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -90,7 +89,9 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -117,6 +118,7 @@
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -127,7 +129,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -223,7 +224,6 @@
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -294,7 +294,7 @@
* The direct child layer of the display to put all non-overlay windows. This is also used for
* screen rotation animation so that there is a parent layer to put the animation leash.
*/
- private final SurfaceControl mWindowingLayer;
+ private SurfaceControl mWindowingLayer;
/**
* The window token of the layer of the hierarchy to mirror, or null if this DisplayContent
@@ -326,7 +326,7 @@
private final ImeContainer mImeWindowsContainer = new ImeContainer(mWmService);
@VisibleForTesting
- final DisplayAreaPolicy mDisplayAreaPolicy;
+ DisplayAreaPolicy mDisplayAreaPolicy;
private WindowState mTmpWindow;
private boolean mUpdateImeTarget;
@@ -579,7 +579,7 @@
/** Caches the value whether told display manager that we have content. */
private boolean mLastHasContent;
- private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil();
+ private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
/**
* The input method window for this display.
@@ -701,8 +701,6 @@
// well and thus won't change the top resumed / focused record
boolean mDontMoveToTop;
- private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
-
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
@@ -986,8 +984,8 @@
final boolean committed = winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
if (w.hasWallpaper()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "First draw done in potential wallpaper target " + w);
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "First draw done in potential wallpaper target %s", w);
mWallpaperMayChange = true;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -1087,41 +1085,9 @@
mDividerControllerLocked = new DockedTaskDividerController(this);
mPinnedTaskController = new PinnedTaskController(mWmService, this);
- final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
- .setOpaque(true)
- .setContainerLayer()
- .setCallsite("DisplayContent");
- mSurfaceControl = b.setName("Root").setContainerLayer().build();
-
- // Setup the policy and build the display area hierarchy.
- mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
- mWmService, this /* content */, this /* root */, mImeWindowsContainer);
-
- final List<DisplayArea<? extends WindowContainer>> areas =
- mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION);
- final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null;
- if (area != null && area.getParent() == this) {
- // The windowed magnification area should contain all non-overlay windows, so just use
- // it as the windowing layer.
- mWindowingLayer = area.mSurfaceControl;
- } else {
- // Need an additional layer for screen level animation, so move the layer containing
- // the windows to the new root.
- mWindowingLayer = mSurfaceControl;
- mSurfaceControl = b.setName("RootWrapper").build();
- getPendingTransaction().reparent(mWindowingLayer, mSurfaceControl)
- .show(mWindowingLayer);
- }
-
- mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
-
- getPendingTransaction()
- .setLayer(mSurfaceControl, 0)
- .setLayerStack(mSurfaceControl, mDisplayId)
- .show(mSurfaceControl)
- .setLayer(mOverlayLayer, Integer.MAX_VALUE)
- .show(mOverlayLayer);
- getPendingTransaction().apply();
+ final Transaction pendingTransaction = getPendingTransaction();
+ configureSurfaces(pendingTransaction);
+ pendingTransaction.apply();
// Sets the display content for the children.
onDisplayChanged(this);
@@ -1135,6 +1101,77 @@
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
}
+ @Override
+ void migrateToNewSurfaceControl(Transaction t) {
+ t.remove(mSurfaceControl);
+
+ mLastSurfacePosition.set(0, 0);
+
+ configureSurfaces(t);
+
+ for (int i = 0; i < mChildren.size(); i++) {
+ SurfaceControl sc = mChildren.get(i).getSurfaceControl();
+ if (sc != null) {
+ t.reparent(sc, mSurfaceControl);
+ }
+ }
+
+ scheduleAnimation();
+ }
+
+ /**
+ * Configures the surfaces hierarchy for DisplayContent
+ * This method always recreates the main surface control but reparents the children
+ * if they are already created.
+ * @param transaction as part of which to perform the configuration
+ */
+ private void configureSurfaces(Transaction transaction) {
+ final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
+ .setOpaque(true)
+ .setContainerLayer()
+ .setCallsite("DisplayContent");
+ mSurfaceControl = b.setName(getName()).setContainerLayer().build();
+
+ if (mDisplayAreaPolicy == null) {
+ // Setup the policy and build the display area hierarchy.
+ // Build the hierarchy only after creating the surface so it is reparented correctly
+ mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
+ mWmService, this /* content */, this /* root */,
+ mImeWindowsContainer);
+ }
+
+ final List<DisplayArea<? extends WindowContainer>> areas =
+ mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION);
+ final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null;
+
+ if (area != null && area.getParent() == this) {
+ // The windowed magnification area should contain all non-overlay windows, so just use
+ // it as the windowing layer.
+ mWindowingLayer = area.mSurfaceControl;
+ transaction.reparent(mWindowingLayer, mSurfaceControl);
+ } else {
+ // Need an additional layer for screen level animation, so move the layer containing
+ // the windows to the new root.
+ mWindowingLayer = mSurfaceControl;
+ mSurfaceControl = b.setName("RootWrapper").build();
+ transaction.reparent(mWindowingLayer, mSurfaceControl)
+ .show(mWindowingLayer);
+ }
+
+ if (mOverlayLayer == null) {
+ mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
+ } else {
+ transaction.reparent(mOverlayLayer, mSurfaceControl);
+ }
+
+ transaction
+ .setLayer(mSurfaceControl, 0)
+ .setLayerStack(mSurfaceControl, mDisplayId)
+ .show(mSurfaceControl)
+ .setLayer(mOverlayLayer, Integer.MAX_VALUE)
+ .show(mOverlayLayer);
+ }
+
boolean isReady() {
// The display is ready when the system and the individual display are both ready.
return mWmService.mDisplayReady && mDisplayReady;
@@ -2030,29 +2067,35 @@
return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation);
}
- private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
- DisplayCutout cutout, int rotation) {
+ static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return WmDisplayCutout.NO_CUTOUT;
}
if (rotation == ROTATION_0) {
return WmDisplayCutout.computeSafeInsets(
- cutout, mInitialDisplayWidth, mInitialDisplayHeight);
+ cutout, displayWidth, displayHeight);
}
final Insets waterfallInsets =
RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final Rect[] newBounds = mRotationUtil.getRotatedBounds(
+ final Rect[] newBounds = sRotationUtil.getRotatedBounds(
cutout.getBoundingRectsAll(),
- rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+ rotation, displayWidth, displayHeight);
final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
info.getCutoutSpec(), rotation, info.getScale());
return WmDisplayCutout.computeSafeInsets(
DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
- rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
- rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
+ rotated ? displayHeight : displayWidth,
+ rotated ? displayWidth : displayHeight);
+ }
+
+ private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
+ DisplayCutout cutout, int rotation) {
+ return calculateDisplayCutoutForRotationAndDisplaySizeUncached(cutout, rotation,
+ mInitialDisplayWidth, mInitialDisplayHeight);
}
RoundedCorners calculateRoundedCornersForRotation(int rotation) {
@@ -3159,6 +3202,11 @@
}
final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, this);
if (t != null) {
+ if (getRotation() != getWindowConfiguration().getRotation()) {
+ mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
+ controller.mTransitionMetricsReporter.associate(t,
+ startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+ }
t.setKnownConfigChanges(this, changes);
}
}
@@ -4575,14 +4623,6 @@
for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
mExitingTokens.get(i).hasVisible = hasVisible;
}
-
- // Initialize state of exiting applications.
- forAllRootTasks(task -> {
- final ArrayList<ActivityRecord> activities = task.mExitingActivities;
- for (int j = activities.size() - 1; j >= 0; --j) {
- activities.get(j).hasVisible = hasVisible;
- }
- });
}
void removeExistingTokensIfPossible() {
@@ -4592,34 +4632,6 @@
mExitingTokens.remove(i);
}
}
-
- // clear first just in case.
- mTmpActivityList.clear();
- // Time to remove any exiting applications?
- forAllRootTasks(task -> {
- final ArrayList<ActivityRecord> activities = task.mExitingActivities;
- for (int j = activities.size() - 1; j >= 0; --j) {
- final ActivityRecord activity = activities.get(j);
- if (!activity.hasVisible && !mDisplayContent.mClosingApps.contains(activity)
- && (!activity.mIsExiting || activity.isEmpty())) {
- mTmpActivityList.add(activity);
- }
- }
- });
- if (!mTmpActivityList.isEmpty()) {
- // Make sure there is no animation running on this activity, so any windows
- // associated with it will be removed as soon as their animations are
- // complete.
- cancelAnimation();
- }
- for (int i = 0; i < mTmpActivityList.size(); ++i) {
- final ActivityRecord activity = mTmpActivityList.get(i);
- ProtoLog.v(WM_DEBUG_ADD_REMOVE,
- "performLayout: Activity exiting now removed %s", activity);
- activity.removeIfPossible();
- }
- // Clear afterwards so we don't hold references.
- mTmpActivityList.clear();
}
@Override
@@ -4659,7 +4671,7 @@
|| mWmService.mPolicy.okToAnimate(ignoreScreenOn));
}
- static final class TaskForResizePointSearchResult {
+ static final class TaskForResizePointSearchResult implements Predicate<Task> {
private Task taskForResize;
private int x;
private int y;
@@ -4672,16 +4684,13 @@
this.y = y;
this.delta = delta;
mTmpRect.setEmpty();
-
- final PooledFunction f = PooledLambda.obtainFunction(
- TaskForResizePointSearchResult::processTask, this, PooledLambda.__(Task.class));
- root.forAllTasks(f);
- f.recycle();
+ root.forAllTasks(this);
return taskForResize;
}
- private boolean processTask(Task task) {
+ @Override
+ public boolean test(Task task) {
if (!task.getRootTask().getWindowConfiguration().canResizeTask()) {
return true;
}
@@ -4933,15 +4942,20 @@
// Keep IME window in surface parent as long as app's starting window
// exists so it get's layered above the starting window.
if (imeTarget != null && !(imeTarget.mActivityRecord != null
- && imeTarget.mActivityRecord.hasStartingWindow()) && (
- !(imeTarget.inMultiWindowMode()
- || imeTarget.mToken.isAppTransitioning()) && (
- imeTarget.getSurfaceControl() != null))) {
- mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
- // TODO: We need to use an extra level on the app surface to ensure
- // this is always above SurfaceView but always below attached window.
- 1, forceUpdate);
- } else if (mInputMethodSurfaceParent != null) {
+ && imeTarget.mActivityRecord.hasStartingWindow())) {
+ final boolean canImeTargetSetRelativeLayer = imeTarget.getSurfaceControl() != null
+ && !imeTarget.inMultiWindowMode()
+ && imeTarget.mToken.getActivity(app -> app.isAnimating(TRANSITION | PARENTS,
+ ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_RECENTS)) == null;
+ if (canImeTargetSetRelativeLayer) {
+ mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
+ // TODO: We need to use an extra level on the app surface to ensure
+ // this is always above SurfaceView but always below attached window.
+ 1, forceUpdate);
+ return;
+ }
+ }
+ if (mInputMethodSurfaceParent != null) {
// The IME surface parent may not be its window parent's surface
// (@see #computeImeParent), so set relative layer here instead of letting the window
// parent to assign layer.
@@ -5126,9 +5140,7 @@
onAppTransitionDone();
changes |= FINISH_LAYOUT_REDO_LAYOUT;
- if (DEBUG_WALLPAPER_LIGHT) {
- Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout");
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
computeImeTarget(true /* updateImeTarget */);
mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 32e43ca..45d7141 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -84,7 +84,7 @@
final Rect safe = mDisplayCutoutSafe;
final DisplayCutout cutout = displayCutout.getDisplayCutout();
if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight
- && state.getDisplayCutout().equals(cutout)
+ && state.getDisplayCutout().equals(cutout)
&& state.getRoundedCorners().equals(roundedCorners)
&& state.getPrivacyIndicatorBounds().equals(indicatorBounds)) {
return false;
@@ -93,24 +93,12 @@
mDisplayHeight = info.logicalHeight;
final Rect unrestricted = mUnrestricted;
unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
- safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
state.setDisplayFrame(unrestricted);
state.setDisplayCutout(cutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
+ state.getDisplayCutoutSafe(safe);
if (!cutout.isEmpty()) {
- if (cutout.getSafeInsetLeft() > 0) {
- safe.left = unrestricted.left + cutout.getSafeInsetLeft();
- }
- if (cutout.getSafeInsetTop() > 0) {
- safe.top = unrestricted.top + cutout.getSafeInsetTop();
- }
- if (cutout.getSafeInsetRight() > 0) {
- safe.right = unrestricted.right - cutout.getSafeInsetRight();
- }
- if (cutout.getSafeInsetBottom() > 0) {
- safe.bottom = unrestricted.bottom - cutout.getSafeInsetBottom();
- }
state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
state.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 881bd35..c23d541 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -29,7 +29,6 @@
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
@@ -37,7 +36,6 @@
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
-import static android.view.ViewRootImpl.computeWindowBounds;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -50,21 +48,15 @@
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -147,6 +139,7 @@
import android.view.ViewDebug;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowLayout;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
@@ -356,15 +349,15 @@
// If nonzero, a panic gesture was performed at that time in uptime millis and is still pending.
private long mPendingPanicGestureUptime;
- private static final Rect sTmpDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
private static final Rect sTmpRect = new Rect();
private static final Rect sTmpNavFrame = new Rect();
private static final Rect sTmpStatusFrame = new Rect();
private static final Rect sTmpDecorFrame = new Rect();
- private static final Rect sTmpScreenDecorFrame = new Rect();
private static final Rect sTmpLastParentFrame = new Rect();
private static final Rect sTmpDisplayFrameBounds = new Rect();
+ private final WindowLayout mWindowLayout = new WindowLayout();
+
private WindowState mTopFullscreenOpaqueWindowState;
private boolean mTopIsFullscreen;
private boolean mForceStatusBar;
@@ -619,7 +612,8 @@
}
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration,
long statusBarAnimationStartTime, long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -913,15 +907,6 @@
// letterboxed. Hence always let them extend under the cutout.
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
break;
- case TYPE_NOTIFICATION_SHADE:
- // If the Keyguard is in a hidden state (occluded by another window), we force to
- // remove the wallpaper and keyguard flag so that any change in-flight after setting
- // the keyguard as occluded wouldn't set these flags again.
- // See {@link #processKeyguardSetHiddenResultLw}.
- if (mService.mPolicy.isKeyguardOccluded()) {
- attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- break;
case TYPE_TOAST:
// While apps should use the dedicated toast APIs to add such windows
@@ -1119,9 +1104,6 @@
switch (attrs.type) {
case TYPE_NOTIFICATION_SHADE:
mNotificationShade = win;
- if (mDisplayContent.isDefaultDisplay) {
- mService.mPolicy.setKeyguardCandidateLw(win);
- }
break;
case TYPE_STATUS_BAR:
mStatusBar = win;
@@ -1317,9 +1299,6 @@
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
} else if (mNotificationShade == win) {
mNotificationShade = null;
- if (mDisplayContent.isDefaultDisplay) {
- mService.mPolicy.setKeyguardCandidateLw(null);
- }
} else if (mClimateBarAlt == win) {
mClimateBarAlt = null;
mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
@@ -1492,7 +1471,7 @@
* @param outInsetsState The insets state of this display from the client's perspective.
* @param localClient Whether the client is from the our process.
* @return Whether to always consume the system bars.
- * See {@link #areSystemBarsForcedShownLw(WindowState)}.
+ * See {@link #areSystemBarsForcedShownLw()}.
*/
boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, InsetsState outInsetsState,
boolean localClient) {
@@ -1547,31 +1526,51 @@
* some temporal states, but doesn't change the window frames used to show on screen.
*/
void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) {
- if (mNavigationBar != null) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (INSETS_LAYOUT_GENERALIZATION) {
- simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ final InsetsStateController insetsStateController =
+ mDisplayContent.getInsetsStateController();
+ for (int type = 0; type < InsetsState.SIZE; type++) {
+ final InsetsSourceProvider provider =
+ insetsStateController.peekSourceProvider(type);
+ if (provider == null || !provider.hasWindow()
+ || provider.mWin.getControllableInsetProvider() != provider) {
+ continue;
+ }
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(provider.mWin, displayFrames, simulatedWindowFrames,
barContentFrames,
contentFrame -> simulateLayoutForContentFrame(displayFrames,
- mNavigationBar, contentFrame));
- } else {
+ provider.mWin, contentFrame));
+ }
+ } else {
+ if (mNavigationBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
contentFrame));
}
- }
- if (mStatusBar != null) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (INSETS_LAYOUT_GENERALIZATION) {
- simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
- barContentFrames,
- contentFrame -> simulateLayoutForContentFrame(displayFrames,
- mStatusBar, contentFrame));
- } else {
+ if (mStatusBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
barContentFrames,
contentFrame -> layoutStatusBar(displayFrames, contentFrame));
}
+ if (mExtraNavBarAlt != null) {
+ // There's no pre-defined behavior for the extra navigation bar, we need to use the
+ // new flexible insets logic anyway.
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mExtraNavBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mExtraNavBarAlt, contentFrame));
+ }
+ if (mClimateBarAlt != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mClimateBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mClimateBarAlt, contentFrame));
+ }
}
}
@@ -1709,108 +1708,24 @@
final int type = attrs.type;
final int fl = attrs.flags;
- final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
displayFrames = win.getDisplayFrames(displayFrames);
final WindowFrames windowFrames = win.getLayoutingWindowFrames();
- sTmpLastParentFrame.set(windowFrames.mParentFrame);
final Rect pf = windowFrames.mParentFrame;
final Rect df = windowFrames.mDisplayFrame;
- windowFrames.setParentFrameWasClippedByDisplayCutout(false);
+ final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null;
+ sTmpLastParentFrame.set(pf);
- final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
- final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
+ // Override the bounds in window token has many side effects. Directly use the display
+ // frame set for the simulated layout for this case.
+ final Rect winBounds = windowFrames.mIsSimulatingDecorWindow ? df : win.getBounds();
- final InsetsState state = win.getInsetsState();
- if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) {
- // Override the bounds in window token has many side effects. Directly use the display
- // frame set for the simulated layout for this case.
- computeWindowBounds(attrs, state, df, df);
- } else {
- computeWindowBounds(attrs, state, win.getBounds(), df);
- }
- if (attached == null) {
- pf.set(df);
- if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
- final InsetsSource source = state.peekSource(ITYPE_IME);
- if (source != null) {
- pf.inset(source.calculateInsets(pf, false /* ignoreVisibility */));
- }
- }
- } else {
- pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrame() : df);
- }
-
- final int cutoutMode = attrs.layoutInDisplayCutoutMode;
- // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
- // the cutout safe zone.
- if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
- final boolean attachedInParent = attached != null && !layoutInScreen;
- final boolean requestedFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR);
- final boolean requestedHideNavigation =
- !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
-
- // TYPE_BASE_APPLICATION windows are never considered floating here because they don't
- // get cropped / shifted to the displayFrame in WindowState.
- final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
- && type != TYPE_BASE_APPLICATION;
- final Rect displayCutoutSafeExceptMaybeBars = sTmpDisplayCutoutSafeExceptMaybeBarsRect;
- displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
- if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
- if (displayFrames.mDisplayWidth < displayFrames.mDisplayHeight) {
- displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
- displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
- } else {
- displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
- displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
- }
- }
-
- if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
- && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
- || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
- // At the top we have the status bar, so apps that are
- // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
- // already expect that there's an inset there and we don't need to exclude
- // the window from that area.
- displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
- }
- if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
- && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
- || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
- // Same for the navigation bar.
- switch (mNavigationBarPosition) {
- case NAV_BAR_BOTTOM:
- displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
- break;
- case NAV_BAR_RIGHT:
- displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
- break;
- case NAV_BAR_LEFT:
- displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
- break;
- }
- }
- if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
- // The IME can always extend under the bottom cutout if the navbar is there.
- displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
- }
- // Windows that are attached to a parent and laid out in said parent already avoid
- // the cutout according to that parent and don't need to be further constrained.
- // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
- // They will later be cropped or shifted using the displayFrame in WindowState,
- // which prevents overlap with the DisplayCutout.
- if (!attachedInParent && !floatingInScreenWindow) {
- sTmpRect.set(pf);
- pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
- windowFrames.setParentFrameWasClippedByDisplayCutout(!sTmpRect.equals(pf));
- }
- // Make sure that NO_LIMITS windows clipped to the display don't extend under the
- // cutout.
- df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
- }
+ final boolean clippedByDisplayCutout = mWindowLayout.computeWindowFrames(attrs,
+ win.getInsetsState(), displayFrames.mDisplayCutoutSafe, winBounds,
+ win.getRequestedVisibilities(), attachedWindowFrame, df, pf);
+ windowFrames.setParentFrameWasClippedByDisplayCutout(clippedByDisplayCutout);
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
// Also, we don't allow windows in multi-window mode to extend out of the screen.
@@ -1831,16 +1746,6 @@
}
win.computeFrameAndUpdateSourceFrame(displayFrames);
- if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) {
- if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
- // Make sure that the zone we're avoiding for the cutout is at least as tall as the
- // status bar; otherwise fullscreen apps will end up cutting halfway into the status
- // bar.
- displayFrames.mDisplayCutoutSafe.top = Math.max(
- displayFrames.mDisplayCutoutSafe.top,
- windowFrames.mFrame.bottom);
- }
- }
}
WindowState getTopFullscreenOpaqueWindow() {
@@ -1878,6 +1783,13 @@
*/
public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
WindowState attached, WindowState imeTarget) {
+ if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_NAVIGATION_BAR) {
+ // Keep mNavigationBarPosition updated to make sure the transient detection and bar
+ // color control is working correctly.
+ final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
+ mNavigationBarPosition = navigationBarPosition(displayFrames.mDisplayWidth,
+ displayFrames.mDisplayHeight, displayFrames.mRotation);
+ }
final boolean affectsSystemUi = win.canAffectSystemUiFlags();
if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
applyKeyguardPolicy(win, imeTarget);
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index dcd1148..fc317a1 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -198,8 +198,8 @@
}
@Override
- public EmbeddedWindow asEmbeddedWindow() {
- return this;
+ public WindowState getWindowState() {
+ return mHostWindowState;
}
@Override
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
index fec7cc9..c7d328a 100644
--- a/services/core/java/com/android/server/wm/InputTarget.java
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -25,13 +25,8 @@
* of both targets.
*/
interface InputTarget {
- default WindowState asWindowState() {
- return null;
- }
-
- default EmbeddedWindowController.EmbeddedWindow asEmbeddedWindow() {
- return null;
- }
+ /* Get the WindowState associated with the target. */
+ WindowState getWindowState();
/* Display id of the target. */
int getDisplayId();
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 403df91..d202587 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -535,7 +535,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
// TODO(b/166736352): Check if we still need to control the IME visibility here.
if (mSource.getType() == ITYPE_IME) {
// TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 520bd8b..a3eb980 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -19,6 +19,7 @@
import static com.android.server.wm.AnimationAdapterProto.LOCAL;
import static com.android.server.wm.LocalAnimationAdapterProto.ANIMATION_SPEC;
+import android.annotation.NonNull;
import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
@@ -51,7 +52,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
mAnimator.startAnimation(mSpec, animationLeash, t,
() -> finishCallback.onAnimationFinished(type, this));
}
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index e50dc51..7abf3b8 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -18,6 +18,7 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import android.annotation.NonNull;
import android.view.SurfaceControl;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -144,7 +145,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
super.startAnimation(animationLeash, t, type, finishCallback);
if (mParent != null && mParent.isValid()) {
t.reparent(animationLeash, mParent);
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 88941eb..9f28509 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -27,6 +27,7 @@
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;
@@ -145,7 +146,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
mCapturedLeash = animationLeash;
mCapturedLeashFinishCallback = finishCallback;
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 081a53e..6de8ef7 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -172,7 +172,7 @@
}
@GuardedBy("mLock")
- void updateFromImpl(String packageName, int userId,
+ boolean updateFromImpl(String packageName, int userId,
PackageConfigurationUpdaterImpl impl) {
synchronized (mLock) {
PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
@@ -198,7 +198,7 @@
}
if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
- return;
+ return false;
}
if (DEBUG) {
@@ -206,6 +206,7 @@
}
mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
}
+ return true;
}
}
diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
index 8bbcf1f..0cd3680 100644
--- a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
+++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
@@ -68,7 +68,7 @@
}
@Override
- public void commit() {
+ public boolean commit() {
synchronized (this) {
synchronized (mAtm.mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
@@ -79,7 +79,7 @@
if (wpc == null) {
Slog.w(TAG, "commit: Override application configuration failed: "
+ "cannot find pid " + mPid);
- return;
+ return false;
}
uid = wpc.mUid;
mUserId = wpc.mUserId;
@@ -90,11 +90,12 @@
if (uid < 0) {
Slog.w(TAG, "commit: update of application configuration failed: "
+ "userId or packageName not valid " + mUserId);
- return;
+ return false;
}
}
updateConfig(uid, mPackageName);
- mAtm.mPackageConfigPersister.updateFromImpl(mPackageName, mUserId, this);
+ return mAtm.mPackageConfigPersister
+ .updateFromImpl(mPackageName, mUserId, this);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
index ef8dee4..11a27c5 100644
--- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -120,8 +120,15 @@
@Surface.Rotation int rotation) {
DisplayInfo updatedDisplayInfo = new DisplayInfo();
updatedDisplayInfo.copyFrom(displayInfo);
- updatedDisplayInfo.rotation = rotation;
+ // Apply rotations before updating width and height
+ updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation,
+ updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight);
+ updatedDisplayInfo.displayCutout =
+ DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth,
+ updatedDisplayInfo.logicalHeight).getDisplayCutout();
+ updatedDisplayInfo.rotation = rotation;
final int naturalWidth = updatedDisplayInfo.getNaturalWidth();
final int naturalHeight = updatedDisplayInfo.getNaturalHeight();
updatedDisplayInfo.logicalWidth = naturalWidth;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ba1cf8a..ee05523 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -170,6 +170,13 @@
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
+ // Cancel any existing recents animation running synchronously (do not hold the
+ // WM lock) before starting the newly requested recents animation as they can not coexist
+ if (mWindowManager.getRecentsAnimationController() != null) {
+ mWindowManager.getRecentsAnimationController().forceCancelAnimation(
+ REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
+ }
+
// If the activity is associated with the root recents task, then try and get that first
Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
mTargetActivityType);
@@ -243,12 +250,7 @@
targetActivity.intent.replaceExtras(mTargetIntent);
// Fetch all the surface controls and pass them to the client to get the animation
- // started. Cancel any existing recents animation running synchronously (do not hold the
- // WM lock)
- if (mWindowManager.getRecentsAnimationController() != null) {
- mWindowManager.getRecentsAnimationController().forceCancelAnimation(
- REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
- }
+ // started
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, mDefaultTaskDisplayArea.getDisplayId(),
mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index a663c62..918eee9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -68,7 +68,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -169,8 +168,9 @@
*/
final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
continueDeferredCancel();
return 0;
}
@@ -793,7 +793,7 @@
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L,
adapter -> {
synchronized (mService.mGlobalLock) {
// If the wallpaper animation is canceled, continue with the recents
@@ -1152,13 +1152,7 @@
private boolean isAnimatingApp(ActivityRecord activity) {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final Task task = mPendingAnimations.get(i).mTask;
- final PooledFunction f = PooledLambda.obtainFunction(
- (a, b) -> a == b, activity,
- PooledLambda.__(ActivityRecord.class));
- boolean isAnimatingApp = task.forAllActivities(f);
- f.recycle();
- if (isAnimatingApp) {
+ if (activity.isDescendantOf(mPendingAnimations.get(i).mTask)) {
return true;
}
}
@@ -1320,7 +1314,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
// Restore position and root task crop until client has a chance to modify it.
t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top);
mTmpRect.set(mLocalBounds);
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 16a45fe..eeac230 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -22,6 +22,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
@@ -207,7 +208,7 @@
if (wrappers.mThumbnailAdapter != null
&& wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
wrappers.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(wrappers.mAdapter.mAnimationType,
+ .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType,
wrappers.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
@@ -218,7 +219,7 @@
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent,
mRemoteAnimationAdapter.getDuration(),
mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
adapter -> {
@@ -260,7 +261,7 @@
}
if (adapters.mThumbnailAdapter != null) {
adapters.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(adapters.mAdapter.mAnimationType,
+ .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType,
adapters.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
@@ -477,7 +478,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
if (mStartBounds.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 2626b87..1fd66fc 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -25,14 +25,13 @@
import android.os.Debug;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledFunction;
-import com.android.internal.util.function.pooled.PooledLambda;
import java.util.ArrayList;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
/** Helper class for processing the reset of a task. */
-class ResetTargetTaskHelper {
+class ResetTargetTaskHelper implements Consumer<Task>, Predicate<ActivityRecord> {
private Task mTask;
private Task mTargetTask;
private Task mTargetRootTask;
@@ -40,6 +39,7 @@
private boolean mForceReset;
private boolean mCanMoveOptions;
private boolean mTargetTaskFound;
+ private boolean mIsTargetTask;
private int mActivityReparentPosition;
private ActivityOptions mTopOptions;
private ArrayList<ActivityRecord> mResultActivities = new ArrayList<>();
@@ -62,32 +62,27 @@
mTargetRootTask = targetTask.getRootTask();
mActivityReparentPosition = -1;
- final PooledConsumer c = PooledLambda.obtainConsumer(
- ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
- targetTask.mWmService.mRoot.forAllLeafTasks(c, true /*traverseTopToBottom*/);
- c.recycle();
+ targetTask.mWmService.mRoot.forAllLeafTasks(this, true /* traverseTopToBottom */);
processPendingReparentActivities();
reset(null);
return mTopOptions;
}
- private void processTask(Task task) {
+ @Override
+ public void accept(Task task) {
reset(task);
mRoot = task.getRootActivity(true);
if (mRoot == null) return;
- final boolean isTargetTask = task == mTargetTask;
- if (isTargetTask) mTargetTaskFound = true;
+ mIsTargetTask = task == mTargetTask;
+ if (mIsTargetTask) mTargetTaskFound = true;
- final PooledFunction f = PooledLambda.obtainFunction(
- ResetTargetTaskHelper::processActivity, this,
- PooledLambda.__(ActivityRecord.class), isTargetTask);
- task.forAllActivities(f);
- f.recycle();
+ task.forAllActivities(this);
}
- private boolean processActivity(ActivityRecord r, boolean isTargetTask) {
+ @Override
+ public boolean test(ActivityRecord r) {
// End processing if we have reached the root.
if (r == mRoot) return true;
@@ -100,7 +95,7 @@
final boolean clearWhenTaskReset =
(r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0;
- if (isTargetTask) {
+ if (mIsTargetTask) {
if (!finishOnTaskLaunch && !clearWhenTaskReset) {
if (r.resultTo != null) {
// If this activity is sending a reply to a previous activity, we can't do
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d9f0091..1277e35 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -48,6 +48,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -72,6 +73,7 @@
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
+import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -79,7 +81,6 @@
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -151,7 +152,6 @@
import com.android.internal.app.ResolverActivity;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.LocalServices;
@@ -169,7 +169,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
-import java.util.function.Function;
+import java.util.function.Predicate;
/** Root {@link WindowContainer} for the device. */
class RootWindowContainer extends WindowContainer<DisplayContent>
@@ -278,8 +278,7 @@
private int mTmpTaskLayerRank;
private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
- private boolean mTmpBoolean;
- private RemoteException mTmpRemoteException;
+ private final AttachApplicationHelper mAttachApplicationHelper = new AttachApplicationHelper();
private String mDestroyAllActivitiesReason;
private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@@ -305,7 +304,7 @@
private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
- static class FindTaskResult implements Function<Task, Boolean> {
+ static class FindTaskResult implements Predicate<Task> {
ActivityRecord mIdealRecord;
ActivityRecord mCandidateRecord;
@@ -347,7 +346,7 @@
}
@Override
- public Boolean apply(Task task) {
+ public boolean test(Task task) {
if (!ConfigurationContainer.isCompatibleActivityType(mActivityType,
task.getActivityType())) {
ProtoLog.d(WM_DEBUG_TASKS, "Skipping task: (mismatch activity/task) %s", task);
@@ -894,7 +893,7 @@
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.mWallpaperMayChange) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting");
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
@@ -1254,6 +1253,11 @@
pw.println(mTopFocusedDisplayId);
}
+ void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
+ pw.print(" mDefaultMinSizeOfResizeableTaskDp=");
+ pw.println(mDefaultMinSizeOfResizeableTaskDp);
+ }
+
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
if (!isLayoutNeeded()) {
return;
@@ -1300,7 +1304,7 @@
mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
proto.write(IS_HOME_RECENTS_COMPONENT,
mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-
+ proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp);
proto.end(token);
}
@@ -1931,58 +1935,11 @@
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
- boolean didSomething = false;
- for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- mTmpRemoteException = null;
- mTmpBoolean = false; // Set to true if an activity was started.
- final DisplayContent display = getChildAt(displayNdx);
- display.forAllRootTasks(rootTask -> {
- if (mTmpRemoteException != null) {
- return;
- }
-
- if (rootTask.getVisibility(null /* starting */)
- == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
- return;
- }
-
- final PooledFunction c = PooledLambda.obtainFunction(
- RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
- PooledLambda.__(ActivityRecord.class), app,
- rootTask.topRunningActivity());
- rootTask.forAllActivities(c);
- c.recycle();
- });
- if (mTmpRemoteException != null) {
- throw mTmpRemoteException;
- }
- didSomething |= mTmpBoolean;
- }
- if (!didSomething) {
- ensureActivitiesVisible(null, 0, false /* preserve_windows */);
- }
- return didSomething;
- }
-
- private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r,
- WindowProcessController app, ActivityRecord top) {
- if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard || r.app != null
- || app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
- return false;
- }
-
try {
- if (mTaskSupervisor.realStartActivityLocked(r, app,
- top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) {
- mTmpBoolean = true;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception in new application when starting activity "
- + top.intent.getComponent().flattenToShortString(), e);
- mTmpRemoteException = e;
- return true;
+ return mAttachApplicationHelper.process(app);
+ } finally {
+ mAttachApplicationHelper.reset();
}
- return false;
}
/**
@@ -3242,7 +3199,7 @@
FinishDisabledPackageActivitiesHelper mFinishDisabledPackageActivitiesHelper =
new FinishDisabledPackageActivitiesHelper();
- class FinishDisabledPackageActivitiesHelper {
+ class FinishDisabledPackageActivitiesHelper implements Predicate<ActivityRecord> {
private String mPackageName;
private Set<String> mFilterByClasses;
private boolean mDoit;
@@ -3266,12 +3223,7 @@
boolean process(String packageName, Set<String> filterByClasses,
boolean doit, boolean evenPersistent, int userId, boolean onlyRemoveNoProcess) {
reset(packageName, filterByClasses, doit, evenPersistent, userId, onlyRemoveNoProcess);
-
- final PooledFunction f = PooledLambda.obtainFunction(
- FinishDisabledPackageActivitiesHelper::collectActivity, this,
- PooledLambda.__(ActivityRecord.class));
- forAllActivities(f);
- f.recycle();
+ forAllActivities(this);
boolean didSomething = false;
final int size = mCollectedActivities.size();
@@ -3296,7 +3248,8 @@
return didSomething;
}
- private boolean collectActivity(ActivityRecord r) {
+ @Override
+ public boolean test(ActivityRecord r) {
final boolean sameComponent =
(r.packageName.equals(mPackageName) && (mFilterByClasses == null
|| mFilterByClasses.contains(r.mActivityComponent.getClassName())))
@@ -3736,4 +3689,67 @@
}
}
}
+
+ private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {
+ private boolean mHasActivityStarted;
+ private RemoteException mRemoteException;
+ private WindowProcessController mApp;
+ private ActivityRecord mTop;
+
+ void reset() {
+ mHasActivityStarted = false;
+ mRemoteException = null;
+ mApp = null;
+ mTop = null;
+ }
+
+ boolean process(WindowProcessController app) throws RemoteException {
+ mApp = app;
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ getChildAt(displayNdx).forAllRootTasks(this);
+ if (mRemoteException != null) {
+ throw mRemoteException;
+ }
+ }
+ if (!mHasActivityStarted) {
+ ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ false /* preserveWindows */);
+ }
+ return mHasActivityStarted;
+ }
+
+ @Override
+ public void accept(Task rootTask) {
+ if (mRemoteException != null) {
+ return;
+ }
+ if (rootTask.getVisibility(null /* starting */)
+ == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
+ return;
+ }
+ mTop = rootTask.topRunningActivity();
+ rootTask.forAllActivities(this);
+ }
+
+ @Override
+ public boolean test(ActivityRecord r) {
+ if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard
+ || r.app != null || mApp.mUid != r.info.applicationInfo.uid
+ || !mApp.mName.equals(r.processName)) {
+ return false;
+ }
+
+ try {
+ if (mTaskSupervisor.realStartActivityLocked(r, mApp,
+ mTop == r && r.isFocusable() /* andResume */, true /* checkConfig */)) {
+ mHasActivityStarted = true;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception in new application when starting activity " + mTop, e);
+ mRemoteException = e;
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 26f0384..8b2a425 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
@@ -86,7 +87,7 @@
int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated,
- boolean useEmpty, boolean useLegacy) {
+ boolean useEmpty, boolean useLegacy, boolean activityDrawn) {
int parameter = 0;
if (newTask) {
parameter |= TYPE_PARAMETER_NEW_TASK;
@@ -109,6 +110,9 @@
if (useLegacy) {
parameter |= TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
}
+ if (activityDrawn) {
+ parameter |= TYPE_PARAMETER_ACTIVITY_DRAWN;
+ }
return parameter;
}
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 9a0cabe..c667db8 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -229,7 +229,7 @@
cancelAnimation(t, false /* restarting */);
return;
}
- mAnimation.startAnimation(mSurfaceControl, t, type, null /* finishCallback */);
+ mAnimation.startAnimation(mSurfaceControl, t, type, (typ, ani) -> { });
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3cb80d6..6987cdb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -195,7 +195,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.Watchdog;
@@ -486,9 +485,6 @@
private static Exception sTmpException;
- /** ActivityRecords that are exiting, but still on screen for animations. */
- final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
-
private boolean mForceShowForAllUsers;
/** When set, will force the task to report as invisible. */
@@ -538,26 +534,23 @@
private static final ResetTargetTaskHelper sResetTargetTaskHelper = new ResetTargetTaskHelper();
private final FindRootHelper mFindRootHelper = new FindRootHelper();
- private class FindRootHelper {
+ private class FindRootHelper implements Predicate<ActivityRecord> {
private ActivityRecord mRoot;
-
- private void clear() {
- mRoot = null;
- }
+ private boolean mIgnoreRelinquishIdentity;
+ private boolean mSetToBottomIfNone;
ActivityRecord findRoot(boolean ignoreRelinquishIdentity, boolean setToBottomIfNone) {
- final PooledFunction f = PooledLambda.obtainFunction(FindRootHelper::processActivity,
- this, PooledLambda.__(ActivityRecord.class), ignoreRelinquishIdentity,
- setToBottomIfNone);
- clear();
- forAllActivities(f, false /*traverseTopToBottom*/);
- f.recycle();
- return mRoot;
+ mIgnoreRelinquishIdentity = ignoreRelinquishIdentity;
+ mSetToBottomIfNone = setToBottomIfNone;
+ forAllActivities(this, false /* traverseTopToBottom */);
+ final ActivityRecord root = mRoot;
+ mRoot = null;
+ return root;
}
- private boolean processActivity(ActivityRecord r,
- boolean ignoreRelinquishIdentity, boolean setToBottomIfNone) {
- if (mRoot == null && setToBottomIfNone) {
+ @Override
+ public boolean test(ActivityRecord r) {
+ if (mRoot == null && mSetToBottomIfNone) {
// This is the first activity we are process. Set it as the candidate root in case
// we don't find a better one.
mRoot = r;
@@ -569,7 +562,7 @@
mRoot = r;
// Only end search if we are ignore relinquishing identity or we are not relinquishing.
- return ignoreRelinquishIdentity
+ return mIgnoreRelinquishIdentity
|| mNeverRelinquishIdentity
|| (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
}
@@ -879,7 +872,7 @@
// entrance of the new window to be properly animated.
// Note here we always set the replacing window first, as the flags might be needed
// during the relaunch. If we end up not doing any relaunch, we clear the flags later.
- windowManager.setWillReplaceWindow(topActivity.appToken, animate);
+ windowManager.setWillReplaceWindow(topActivity.token, animate);
}
mAtmService.deferWindowLayout();
@@ -936,7 +929,7 @@
// If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
// window), we need to clear the replace window settings. Otherwise, we schedule a
// timeout to remove the old window if the replacing window is not coming in time.
- windowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
+ windowManager.scheduleClearWillReplaceWindows(topActivity.token, !kept);
}
if (!deferResume) {
@@ -1001,7 +994,6 @@
mCallingPackage = r.launchedFromPackage;
mCallingFeatureId = r.launchedFromFeatureId;
setIntent(intent != null ? intent : r.intent, info != null ? info : r.info);
- return;
}
setLockTaskAuth(r);
}
@@ -1641,7 +1633,7 @@
final ActivityRecord r = findActivityInHistory(newR.mActivityComponent);
if (r == null) return null;
- final PooledFunction f = PooledLambda.obtainFunction(Task::finishActivityAbove,
+ final PooledPredicate f = PooledLambda.obtainPredicate(Task::finishActivityAbove,
PooledLambda.__(ActivityRecord.class), r);
forAllActivities(f);
f.recycle();
@@ -1791,7 +1783,7 @@
if (root == null) return;
final TaskDescription taskDescription = new TaskDescription();
- final PooledFunction f = PooledLambda.obtainFunction(
+ final PooledPredicate f = PooledLambda.obtainPredicate(
Task::setTaskDescriptionFromActivityAboveRoot,
PooledLambda.__(ActivityRecord.class), root, taskDescription);
forAllActivities(f);
@@ -1926,6 +1918,7 @@
final ActivityRecord r = topRunningActivity();
if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) {
getSyncTransaction().setWindowCrop(mSurfaceControl, null)
+ .setCornerRadius(mSurfaceControl, 0f)
.setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]);
}
}
@@ -3036,7 +3029,7 @@
}
private static boolean isTopRunning(ActivityRecord r, int taskId, IBinder notTop) {
- return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning();
+ return r.getTask().mTaskId != taskId && r.token != notTop && r.canBeTopRunning();
}
ActivityRecord getTopFullscreenActivity() {
@@ -3148,13 +3141,13 @@
}
@Override
- boolean forAllTasks(Function<Task, Boolean> callback) {
+ boolean forAllTasks(Predicate<Task> callback) {
if (super.forAllTasks(callback)) return true;
- return callback.apply(this);
+ return callback.test(this);
}
@Override
- boolean forAllLeafTasks(Function<Task, Boolean> callback) {
+ boolean forAllLeafTasks(Predicate<Task> callback) {
boolean isLeafTask = true;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final Task child = mChildren.get(i).asTask();
@@ -3166,7 +3159,7 @@
}
}
if (isLeafTask) {
- return callback.apply(this);
+ return callback.test(this);
}
return false;
}
@@ -3207,8 +3200,8 @@
}
@Override
- boolean forAllRootTasks(Function<Task, Boolean> callback, boolean traverseTopToBottom) {
- return isRootTask() ? callback.apply(this) : false;
+ boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
+ return isRootTask() ? callback.test(this) : false;
}
@Override
@@ -3325,20 +3318,6 @@
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
super.dump(pw, prefix, dumpAll);
-
- if (!mExitingActivities.isEmpty()) {
- final String doublePrefix = prefix + " ";
- pw.println();
- pw.println(prefix + "Exiting application tokens:");
- for (int i = mExitingActivities.size() - 1; i >= 0; i--) {
- WindowToken token = mExitingActivities.get(i);
- pw.print(doublePrefix + "Exiting App #" + i);
- pw.print(' '); pw.print(token);
- pw.println(':');
- token.dump(pw, doublePrefix, dumpAll);
- }
- pw.println();
- }
mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
}
@@ -3789,7 +3768,7 @@
}
sTmpException = null;
- final PooledFunction f = PooledLambda.obtainFunction(Task::saveActivityToXml,
+ final PooledPredicate f = PooledLambda.obtainPredicate(Task::saveActivityToXml,
PooledLambda.__(ActivityRecord.class), getBottomMostActivity(), out);
forAllActivities(f);
f.recycle();
@@ -4856,11 +4835,11 @@
mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
if (waitingActivity != null) {
- mWmService.setWindowOpaqueLocked(waitingActivity.appToken, false);
+ mWmService.setWindowOpaqueLocked(waitingActivity.token, false);
if (waitingActivity.attachedToProcess()) {
try {
waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
- waitingActivity.appToken, r != null);
+ waitingActivity.token, r != null);
} catch (RemoteException e) {
}
}
@@ -5253,7 +5232,7 @@
});
} else {
// Check if any of the activities are using voice
- final PooledFunction f = PooledLambda.obtainFunction(
+ final PooledPredicate f = PooledLambda.obtainPredicate(
Task::finishIfVoiceActivity, PooledLambda.__(ActivityRecord.class),
binder);
tr.forAllActivities(f);
@@ -5266,7 +5245,7 @@
// Inform of cancellation
r.clearVoiceSessionLocked();
try {
- r.app.getThread().scheduleLocalVoiceInteractionStarted(r.appToken, null);
+ r.app.getThread().scheduleLocalVoiceInteractionStarted(r.token, null);
} catch (RemoteException re) {
// Ok Boomer...
}
@@ -5362,7 +5341,7 @@
// We should consolidate.
IActivityController controller = mAtmService.mController;
if (controller != null) {
- ActivityRecord next = topRunningActivity(srec.appToken, INVALID_TASK_ID);
+ ActivityRecord next = topRunningActivity(srec.token, INVALID_TASK_ID);
if (next != null) {
// ask watcher if this is allowed
boolean resumeOK = true;
@@ -5419,7 +5398,7 @@
.obtainStarter(destIntent, "navigateUpTo")
.setCaller(srec.app.getThread())
.setActivityInfo(aInfo)
- .setResultTo(parent.appToken)
+ .setResultTo(parent.token)
.setCallingPid(-1)
.setCallingUid(callingUid)
.setCallingPackage(srec.packageName)
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 71844ce..7e784ae 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -60,7 +60,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.wm.LaunchParamsController.LaunchParams;
@@ -68,10 +67,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
/**
* {@link DisplayArea} that represents a section of a screen that contains app window containers.
@@ -520,16 +519,16 @@
}
@Override
- boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback,
+ boolean forAllTaskDisplayAreas(Predicate<TaskDisplayArea> callback,
boolean traverseTopToBottom) {
// Apply the callback to all TDAs at or below this container. If the callback returns true,
// stop early.
if (traverseTopToBottom) {
// When it is top to bottom, run on child TDA first as they are on top of the parent.
return super.forAllTaskDisplayAreas(callback, traverseTopToBottom)
- || callback.apply(this);
+ || callback.test(this);
}
- return callback.apply(this) || super.forAllTaskDisplayAreas(callback, traverseTopToBottom);
+ return callback.test(this) || super.forAllTaskDisplayAreas(callback, traverseTopToBottom);
}
@Override
@@ -686,71 +685,6 @@
}
@Override
- boolean forAllWindows(ToBooleanFunction<WindowState> callback,
- boolean traverseTopToBottom) {
- if (traverseTopToBottom) {
- if (super.forAllWindows(callback, traverseTopToBottom)) {
- return true;
- }
- if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
- return true;
- }
- } else {
- if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
- return true;
- }
- if (super.forAllWindows(callback, traverseTopToBottom)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean forAllExitingAppTokenWindows(ToBooleanFunction<WindowState> callback,
- boolean traverseTopToBottom) {
- // For legacy reasons we process the RootTask.mExitingActivities first here before the
- // app tokens.
- // TODO: Investigate if we need to continue to do this or if we can just process them
- // in-order.
- if (traverseTopToBottom) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- // Only run on those of direct Task child, because child TaskDisplayArea has run on
- // its child in #forAllWindows()
- if (mChildren.get(i).asTask() == null) {
- continue;
- }
- final List<ActivityRecord> activities =
- mChildren.get(i).asTask().mExitingActivities;
- for (int j = activities.size() - 1; j >= 0; --j) {
- if (activities.get(j).forAllWindowsUnchecked(callback,
- traverseTopToBottom)) {
- return true;
- }
- }
- }
- } else {
- final int count = mChildren.size();
- for (int i = 0; i < count; ++i) {
- // Only run on those of direct Task child, because child TaskDisplayArea has run on
- // its child in #forAllWindows()
- if (mChildren.get(i).asTask() == null) {
- continue;
- }
- final List<ActivityRecord> activities =
- mChildren.get(i).asTask().mExitingActivities;
- final int appTokensCount = activities.size();
- for (int j = 0; j < appTokensCount; j++) {
- if (activities.get(j).forAllWindowsUnchecked(callback,
- traverseTopToBottom)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- @Override
int getOrientation(int candidate) {
mLastOrientationSource = null;
if (mIgnoreOrientationRequest) {
@@ -1275,6 +1209,11 @@
}
}
}
+ // For better split UX, If task launch by the source task which root task is created by
+ // organizer, it should also launch in that root too.
+ if (sourceTask != null && sourceTask.getRootTask().mCreatedByOrganizer) {
+ return sourceTask.getRootTask();
+ }
return null;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 99f6f34..b8393f6 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -93,7 +93,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
@@ -102,7 +101,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
-import java.util.function.Function;
+import java.util.function.Predicate;
/**
* A basic container that can be used to contain activities or other {@link TaskFragment}, which
@@ -252,7 +251,7 @@
new EnsureActivitiesVisibleHelper(this);
private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
new EnsureVisibleActivitiesConfigHelper();
- private class EnsureVisibleActivitiesConfigHelper {
+ private class EnsureVisibleActivitiesConfigHelper implements Predicate<ActivityRecord> {
private boolean mUpdateConfig;
private boolean mPreserveWindow;
private boolean mBehindFullscreen;
@@ -268,12 +267,8 @@
return;
}
reset(preserveWindow);
-
- final PooledFunction f = PooledLambda.obtainFunction(
- EnsureVisibleActivitiesConfigHelper::processActivity, this,
- PooledLambda.__(ActivityRecord.class));
- forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/);
- f.recycle();
+ forAllActivities(this, start, true /* includeBoundary */,
+ true /* traverseTopToBottom */);
if (mUpdateConfig) {
// Ensure the resumed state of the focus activity if we updated the configuration of
@@ -282,7 +277,8 @@
}
}
- boolean processActivity(ActivityRecord r) {
+ @Override
+ public boolean test(ActivityRecord r) {
mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
mBehindFullscreen |= r.occludesParent();
return mBehindFullscreen;
@@ -1238,7 +1234,7 @@
try {
final ClientTransaction transaction =
- ClientTransaction.obtain(next.app.getThread(), next.appToken);
+ ClientTransaction.obtain(next.app.getThread(), next.token);
// Deliver all pending results.
ArrayList<ResultInfo> a = next.results;
if (a != null) {
@@ -1487,7 +1483,7 @@
prev.shortComponentName, "userLeaving=" + userLeaving, reason);
mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
- prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
+ prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving,
prev.configChangeFlags, pauseImmediately));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
@@ -1619,7 +1615,7 @@
}
@Override
- boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) {
+ boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
boolean isLeafTaskFrag = true;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final TaskFragment child = mChildren.get(i).asTaskFragment();
@@ -1631,7 +1627,7 @@
}
}
if (isLeafTaskFrag) {
- return callback.apply(this);
+ return callback.test(this);
}
return false;
}
@@ -2168,7 +2164,7 @@
&& wc.asActivityRecord() != null
&& wc.asActivityRecord().getPid() == mTaskFragmentOrganizerPid) {
// Only includes Activities that belong to the organizer process for security.
- childActivities.add(wc.asActivityRecord().appToken);
+ childActivities.add(wc.asActivityRecord().token);
}
}
final Point positionInParent = new Point();
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index e31a662..d543c1f 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -717,7 +717,7 @@
}
// First we get the default size we want.
- getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds);
+ getDefaultFreeformSize(root.info, displayArea, layout, orientation, mTmpBounds);
if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
// We're here because either input parameters specified initial bounds, or the suggested
// bounds have the same size of the default freeform size. We should use the suggested
@@ -785,7 +785,8 @@
return orientation;
}
- private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea,
+ private void getDefaultFreeformSize(@NonNull ActivityInfo info,
+ @NonNull TaskDisplayArea displayArea,
@NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
// Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large
// dimension of default size is the small dimension of displayArea size, and the small
@@ -816,11 +817,38 @@
final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
- // Final result.
+ // Aspect ratio requirements.
+ final float minAspectRatio = info.getMinAspectRatio();
+ final float maxAspectRatio = info.getMaxAspectRatio();
+
final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth));
final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight));
+ final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
- bounds.set(0, 0, width, height);
+ // Adjust the width and height to the aspect ratio requirements.
+ int adjWidth = width;
+ int adjHeight = height;
+ if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) {
+ // The aspect ratio is below the minimum, adjust it to the minimum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / minAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / minAspectRatio + 0.5f);
+ }
+ } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) {
+ // The aspect ratio exceeds the maximum, adjust it to the maximum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f);
+ }
+ }
+
+ bounds.set(0, 0, adjWidth, adjHeight);
bounds.offset(stableBounds.left, stableBounds.top);
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 731036b..3d5f988 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -22,6 +22,7 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
@@ -431,7 +432,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
mAnimationLeash = animationLeash;
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 525420e..4d3219d 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -158,7 +158,7 @@
+ "win=" + win + ", resize=" + resize + ", preserveOrientation="
+ preserveOrientation + ", {" + startX + ", " + startY + "}");
- if (win == null || win.getAppToken() == null) {
+ if (win == null || win.mActivityRecord == null) {
Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
return false;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e537c0a..9fc45b9 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -469,6 +469,7 @@
if (mState != STATE_COLLECTING) {
throw new IllegalStateException("Too late to abort.");
}
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
mController.dispatchLegacyAppTransitionCancelled();
mState = STATE_ABORT;
// Syncengine abort will call through to onTransactionReady()
@@ -789,7 +790,8 @@
}
}
if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
- mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
+ mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
+ true /* keyguardOccludingStarted */);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ff4cc3d..bf4cf5f 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,10 +30,12 @@
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.WindowManager;
import android.window.IRemoteTransition;
+import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -45,6 +47,7 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import java.util.ArrayList;
+import java.util.function.LongConsumer;
/**
* Handles all the aspects of recording and synchronizing transitions.
@@ -58,6 +61,8 @@
private static final int LEGACY_STATE_RUNNING = 2;
private ITransitionPlayer mTransitionPlayer;
+ final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
+
final ActivityTaskManagerService mAtm;
final TaskSnapshotController mTaskSnapshotController;
@@ -324,6 +329,8 @@
/** @see Transition#finishTransition */
void finishTransition(@NonNull IBinder token) {
+ // It is usually a no-op but make sure that the metric consumer is removed.
+ mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
final Transition record = Transition.fromBinder(token);
if (record == null || !mPlayingTransitions.contains(record)) {
Slog.e(TAG, "Trying to finish a non-playing transition " + token);
@@ -388,9 +395,10 @@
void dispatchLegacyAppTransitionStarting(TransitionInfo info) {
final boolean keyguardGoingAway = info.isKeyguardGoingAway();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
+ // TODO(shell-transitions): handle (un)occlude transition.
mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
- 0 /* durationHint */, SystemClock.uptimeMillis(),
- AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+ false /* keyguardOcclude */, 0 /* durationHint */,
+ SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
}
}
@@ -419,6 +427,28 @@
proto.end(token);
}
+ static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
+ private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
+
+ void associate(IBinder transitionToken, LongConsumer consumer) {
+ synchronized (mMetricConsumers) {
+ mMetricConsumers.put(transitionToken, consumer);
+ }
+ }
+
+ @Override
+ public void reportAnimationStart(IBinder transitionToken, long startTime) {
+ final LongConsumer c;
+ synchronized (mMetricConsumers) {
+ if (mMetricConsumers.isEmpty()) return;
+ c = mMetricConsumers.remove(transitionToken);
+ }
+ if (c != null) {
+ c.accept(startTime);
+ }
+ }
+ }
+
class Lock {
private int mTransitionWaiters = 0;
void runWhenIdle(long timeout, Runnable r) {
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index 25f7269..2652723 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -20,6 +20,7 @@
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+import android.annotation.NonNull;
import android.graphics.Point;
import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;
@@ -64,18 +65,17 @@
*
* @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
*/
- public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+ public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent,
long durationHint, long statusBarTransitionDelay,
Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+ if (!displayContent.mWallpaperController.isWallpaperVisible()) {
+ ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
+ "\tWallpaper of display=%s is not visible", displayContent);
+ return new RemoteAnimationTarget[0];
+ }
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
- if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tNot visible=%s", wallpaperWindow);
- return;
- }
-
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tvisible=%s", wallpaperWindow);
+ displayContent.forAllWallpaperWindows(wallpaperWindow -> {
final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
wallpaperWindow, durationHint, statusBarTransitionDelay,
animationCanceledRunnable);
@@ -134,7 +134,8 @@
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- @AnimationType int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type,
+ @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
// Restore z-layering until client has a chance to modify it.
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0909462..d93b649 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -24,12 +24,12 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
@@ -49,6 +49,8 @@
import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import java.io.PrintWriter;
@@ -291,10 +293,11 @@
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.setVisibility(false);
- if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
- Slog.d(TAG, "Hiding wallpaper " + token
- + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " "));
+ if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER,
+ "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
+ Debug.getCallers(5));
}
}
}
@@ -544,15 +547,15 @@
// Is it time to stop animating?
if (!mPrevWallpaperTarget.isAnimatingLw()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!");
mPrevWallpaperTarget = null;
mWallpaperTarget = wallpaperTarget;
}
return;
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s",
+ wallpaperTarget, mWallpaperTarget, Debug.getCallers(5));
mPrevWallpaperTarget = null;
@@ -570,8 +573,8 @@
// then we are in our super special mode!
boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
boolean foundAnim = wallpaperTarget.isAnimatingLw();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New animation: " + foundAnim + " old animation: " + oldAnim);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s",
+ foundAnim, oldAnim);
if (!foundAnim || !oldAnim) {
return;
@@ -586,14 +589,14 @@
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
&& !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
- + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
- + " hidden=" + newTargetHidden);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ + "old: %s hidden=%b new: %s hidden=%b",
+ prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden);
mPrevWallpaperTarget = prevWallpaperTarget;
if (newTargetHidden && !oldTargetHidden) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target.");
// Use the old target if new target is hidden but old target
// is not. If they're both hidden, still use the new target.
mWallpaperTarget = prevWallpaperTarget;
@@ -661,8 +664,8 @@
/* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
- + " prev=" + mPrevWallpaperTarget);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+ mWallpaperTarget, mPrevWallpaperTarget);
}
boolean processWallpaperDrawPendingTimeout() {
@@ -798,6 +801,18 @@
wallpaperBuffer.getHardwareBuffer(), wallpaperBuffer.getColorSpace());
}
+ /**
+ * Mirrors the visible wallpaper if it's available.
+ *
+ * @return A SurfaceControl for the parent of the mirrored wallpaper.
+ */
+ SurfaceControl mirrorWallpaperSurface() {
+ final WindowState wallpaperWindowState = getTopVisibleWallpaper();
+ return wallpaperWindowState != null
+ ? SurfaceControl.mirrorSurface(wallpaperWindowState.getSurfaceControl())
+ : null;
+ }
+
WindowState getTopVisibleWallpaper() {
mTmpTopWallpaper = null;
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 75c84c4..3a639f5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -20,7 +20,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -28,7 +28,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -107,8 +106,8 @@
void updateWallpaperWindows(boolean visible) {
if (isVisible() != visible) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
- "Wallpaper token " + token + " visible=" + visible);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
+ token, visible);
setVisibility(visible);
}
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 51ecce0..cae5b14 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -940,21 +940,6 @@
}
/**
- * Similar to {@link #isAnimating(int, int)} except provide a bitmask of
- * {@link AnimationType} to exclude, rather than include
- * @param flags The combination of bitmask flags to specify targets and condition for
- * checking animating status.
- * @param typesToExclude The combination of bitmask {@link AnimationType} to exclude when
- * checking if animating.
- *
- * @deprecated Use {@link #isAnimating(int, int)}
- */
- @Deprecated
- final boolean isAnimatingExcluding(int flags, int typesToExclude) {
- return isAnimating(flags, ANIMATION_TYPE_ALL & ~typesToExclude);
- }
-
- /**
* @deprecated Use {@link #isAnimating(int, int)}
* TODO (b/152333373): Migrate calls to use isAnimating with specified animation type
*/
@@ -1424,12 +1409,11 @@
wrapper.release();
}
- boolean forAllActivities(Function<ActivityRecord, Boolean> callback) {
+ boolean forAllActivities(Predicate<ActivityRecord> callback) {
return forAllActivities(callback, true /*traverseTopToBottom*/);
}
- boolean forAllActivities(
- Function<ActivityRecord, Boolean> callback, boolean traverseTopToBottom) {
+ boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllActivities(callback, traverseTopToBottom)) return true;
@@ -1471,13 +1455,13 @@
* @param traverseTopToBottom direction to traverse the tree.
* @return {@code true} if we ended the search before reaching the end of the tree.
*/
- final boolean forAllActivities(Function<ActivityRecord, Boolean> callback,
+ final boolean forAllActivities(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom) {
return forAllActivities(
callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
}
- private boolean forAllActivities(Function<ActivityRecord, Boolean> callback,
+ private boolean forAllActivities(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound) {
if (traverseTopToBottom) {
@@ -1500,7 +1484,7 @@
return false;
}
- private boolean processForAllActivitiesWithBoundary(Function<ActivityRecord, Boolean> callback,
+ private boolean processForAllActivitiesWithBoundary(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound, WindowContainer wc) {
if (wc == boundary) {
@@ -1665,7 +1649,7 @@
* @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
* stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
*/
- boolean forAllTasks(Function<Task, Boolean> callback) {
+ boolean forAllTasks(Predicate<Task> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllTasks(callback)) {
return true;
@@ -1674,7 +1658,7 @@
return false;
}
- boolean forAllLeafTasks(Function<Task, Boolean> callback) {
+ boolean forAllLeafTasks(Predicate<Task> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllLeafTasks(callback)) {
return true;
@@ -1683,7 +1667,7 @@
return false;
}
- boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) {
+ boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllLeafTaskFragments(callback)) {
return true;
@@ -1698,11 +1682,11 @@
* @param callback Calls the {@link ToBooleanFunction#apply} method for each root task found and
* stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
*/
- boolean forAllRootTasks(Function<Task, Boolean> callback) {
+ boolean forAllRootTasks(Predicate<Task> callback) {
return forAllRootTasks(callback, true /* traverseTopToBottom */);
}
- boolean forAllRootTasks(Function<Task, Boolean> callback, boolean traverseTopToBottom) {
+ boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
@@ -1984,7 +1968,7 @@
* @return {@code true} if the search ended before we reached the end of the hierarchy due to
* callback returning {@code true}.
*/
- boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback,
+ boolean forAllTaskDisplayAreas(Predicate<TaskDisplayArea> callback,
boolean traverseTopToBottom) {
int childCount = mChildren.size();
int i = traverseTopToBottom ? childCount - 1 : 0;
@@ -2005,7 +1989,7 @@
* @return {@code true} if the search ended before we reached the end of the hierarchy due to
* callback returning {@code true}.
*/
- boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback) {
+ boolean forAllTaskDisplayAreas(Predicate<TaskDisplayArea> callback) {
return forAllTaskDisplayAreas(callback, true /* traverseTopToBottom */);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 0840441..c954700 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -43,7 +43,6 @@
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = true;
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index b8e303b..34cb5ae 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -184,7 +184,7 @@
/**
* Called when a pending app transition gets cancelled.
*
- * @param keyguardGoingAway true if keyguard going away transition transition got cancelled.
+ * @param keyguardGoingAway true if keyguard going away transition got cancelled.
*/
public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {}
@@ -197,6 +197,7 @@
* Called when an app transition gets started
*
* @param keyguardGoingAway true if keyguard going away transition is started.
+ * @param keyguardOccluding true if keyguard (un)occlude transition is started.
* @param duration the total duration of the transition
* @param statusBarAnimationStartTime the desired start time for all visual animations in
* the status bar caused by this app transition in uptime millis
@@ -208,8 +209,9 @@
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cf10e70..9acc408 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INPUT_CONSUMER;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
@@ -268,6 +269,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.view.TaskTransitionSpec;
import android.view.View;
import android.view.WindowContentFrameStats;
import android.view.WindowInsets;
@@ -434,7 +436,7 @@
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
@@ -756,6 +758,11 @@
*/
final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
+ /**
+ * Used during task transitions to allow SysUI and launcher to customize task transitions.
+ */
+ TaskTransitionSpec mTaskTransitionSpec;
+
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
SettingsObserver mSettingsObserver;
@@ -1134,7 +1141,7 @@
atoken.mEnteringAnimation = false;
if (atoken.attachedToProcess()) {
try {
- atoken.app.getThread().scheduleEnterAnimationComplete(atoken.appToken);
+ atoken.app.getThread().scheduleEnterAnimationComplete(atoken.token);
} catch (RemoteException e) {
}
}
@@ -2069,26 +2076,14 @@
}
final WindowToken token = win.mToken;
- final ActivityRecord activity = win.mActivityRecord;
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Removing %s from %s", win, token);
// Window will already be removed from token before this post clean-up method is called.
- if (token.isEmpty()) {
- if (!token.mPersistOnEmpty) {
- token.removeImmediately();
- } else if (activity != null) {
- // TODO: Should this be moved into ActivityRecord.removeWindow? Might go away after
- // re-factor.
- activity.firstWindowDrawn = false;
- activity.clearAllDrawn();
- final Task rootTask = activity.getRootTask();
- if (rootTask != null) {
- rootTask.mExitingActivities.remove(activity);
- }
- }
+ if (token.isEmpty() && !token.mPersistOnEmpty) {
+ token.removeImmediately();
}
- if (activity != null) {
- activity.postWindowRemoveStartingWindowCleanup(win);
+ if (win.mActivityRecord != null) {
+ win.mActivityRecord.postWindowRemoveStartingWindowCleanup(win);
}
if (win.mAttrs.type == TYPE_WALLPAPER) {
@@ -2473,7 +2468,8 @@
if (isPrimaryDisplay) {
transformHint = (transformHint + mPrimaryDisplayOrientation) % 4;
}
- outSurfaceControl.setTransformHint(transformHint);
+ outSurfaceControl.setTransformHint(
+ SurfaceControl.rotationToBufferTransform(transformHint));
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Passing transform hint %d for window %s%s",
transformHint, win,
@@ -2583,13 +2579,17 @@
// an exit.
win.mAnimatingExit = true;
} else if (win.mDisplayContent.okToAnimate()
- && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
- // If the wallpaper is currently behind this
- // window, we need to change both of them inside
- // of a transaction to avoid artifacts.
+ && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+ && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+ // If the wallpaper is currently behind this app window, we need to change both of them
+ // inside of a transaction to avoid artifacts.
+ // For NotificationShade, sysui is in charge of running window animation and it updates
+ // the client view visibility only after both NotificationShade and the wallpaper are
+ // hidden. So we don't need to care about exit animation, but can destroy its surface
+ // immediately.
win.mAnimatingExit = true;
} else {
- boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true;
+ boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
// We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
// will later actually destroy the surface if we do not do so here. Normally we leave
// this to the exit animation.
@@ -3468,8 +3468,6 @@
// Notify whether the root docked task exists for the current user
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- mRoot.forAllDisplays(dc -> dc.mAppTransition.setCurrentUser(newUserId));
-
// If the display is already prepared, update the density.
// Otherwise, we'll update it when it's prepared.
if (mDisplayReady) {
@@ -3788,6 +3786,14 @@
}
}
+ @Override
+ public SurfaceControl mirrorWallpaperSurface(int displayId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ return dc.mWallpaperController.mirrorWallpaperSurface();
+ }
+ }
+
/**
* Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the upper region of the screen based on the vertical dimension
@@ -5014,16 +5020,17 @@
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget);
}
- if (newTarget != null && newTarget.asWindowState() != null) {
- WindowState newFocus = newTarget.asWindowState();
- mAnrController.onFocusChanged(newFocus);
- newFocus.reportFocusChangedSerialized(true);
+ // Call WindowState focus change observers
+ WindowState newFocusedWindow = newTarget != null ? newTarget.getWindowState() : null;
+ if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) {
+ mAnrController.onFocusChanged(newFocusedWindow);
+ newFocusedWindow.reportFocusChangedSerialized(true);
notifyFocusChanged();
}
- if (lastTarget != null && lastTarget.asWindowState() != null) {
- WindowState lastFocus = lastTarget.asWindowState();
- lastFocus.reportFocusChangedSerialized(false);
+ WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null;
+ if (lastFocusedWindow != null && lastFocusedWindow.mInputChannelToken == oldToken) {
+ lastFocusedWindow.reportFocusChangedSerialized(false);
}
}
@@ -5823,7 +5830,9 @@
return;
}
- if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) {
+ if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
+ || displayContent.getDisplayInfo().state == Display.STATE_OFF
+ || !displayContent.okToAnimate()) {
// No need to freeze the screen before the display is ready, if the screen is off,
// or we can't currently animate.
return;
@@ -6417,6 +6426,7 @@
pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
mRoot.dumpTopFocusedDisplayId(pw);
+ mRoot.dumpDefaultMinSizeOfResizableTask(pw);
mRoot.forAllDisplays(dc -> {
final int displayId = dc.getDisplayId();
final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING);
@@ -8709,4 +8719,22 @@
public void setTaskSnapshotEnabled(boolean enabled) {
mTaskSnapshotController.setTaskSnapshotEnabled(enabled);
}
+
+ @Override
+ public void setTaskTransitionSpec(TaskTransitionSpec spec) {
+ if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "setTaskTransitionSpec()")) {
+ throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+ }
+
+ mTaskTransitionSpec = spec;
+ }
+
+ @Override
+ public void clearTaskTransitionSpec() {
+ if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "clearTaskTransitionSpec()")) {
+ throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+ }
+
+ mTaskTransitionSpec = null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 54ce5fc..79a46ea 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -66,6 +66,7 @@
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
@@ -83,7 +84,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
/**
* Server side implementation for the interface for organizing windows
@@ -850,7 +850,7 @@
private int reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop,
@Nullable Transition transition, int syncId) {
- WindowContainer currentParent = hop.getContainer() != null
+ WindowContainer<?> currentParent = hop.getContainer() != null
? WindowContainer.fromBinder(hop.getContainer()) : null;
WindowContainer newParent = hop.getNewParent() != null
? WindowContainer.fromBinder(hop.getNewParent()) : null;
@@ -893,24 +893,31 @@
// We want to collect the tasks first before re-parenting to avoid array shifting on us.
final ArrayList<Task> tasksToReparent = new ArrayList<>();
- currentParent.forAllTasks((Consumer<Task>) (task) -> {
+ currentParent.forAllTasks(task -> {
Slog.i(TAG, " Processing task=" + task);
- if (task.mCreatedByOrganizer
- || task.getParent() != finalCurrentParent) {
+ final boolean reparent;
+ if (task.mCreatedByOrganizer || task.getParent() != finalCurrentParent) {
// We only care about non-organized task that are direct children of the thing we
// are reparenting from.
- return;
+ return false;
}
if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) {
Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window,"
+ " task=" + task);
- return;
+ return false;
}
- if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return;
- if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return;
+ if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())
+ || !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
+ return false;
+ }
- tasksToReparent.add(task);
- }, !hop.getToTop());
+ if (hop.getToTop()) {
+ tasksToReparent.add(0, task);
+ } else {
+ tasksToReparent.add(task);
+ }
+ return hop.getReparentTopOnly() && tasksToReparent.size() == 1;
+ });
final int count = tasksToReparent.size();
for (int i = 0; i < count; ++i) {
@@ -1033,6 +1040,11 @@
}
}
+ @Override
+ public ITransitionMetricsReporter getTransitionMetricsReporter() {
+ return mTransitionController.mTransitionMetricsReporter;
+ }
+
/** Whether the configuration changes are important to report back to an organizer. */
static boolean configurationsAreEqualForOrganizer(
Configuration newConfig, @Nullable Configuration oldConfig) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a3d1378..47aafc2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -225,7 +225,6 @@
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.IApplicationToken;
import android.view.IWindow;
import android.view.IWindowFocusObserver;
import android.view.IWindowId;
@@ -260,7 +259,6 @@
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.utils.CoordinateTransforms;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -1178,14 +1176,13 @@
/**
* @return {@code true} if the application runs in size compatibility mode or has an app level
- * scaling override set. This method always returns {@code false} on child window because it
- * should follow parent's scale.
+ * scaling override set.
* @see CompatModePackages#getCompatScale
* @see android.content.res.CompatibilityInfo#supportsScreen
* @see ActivityRecord#hasSizeCompatBounds()
*/
boolean hasCompatScale() {
- return (mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord)) && !mIsChildWindow;
+ return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord);
}
/**
@@ -1506,11 +1503,6 @@
return getTopParentWindow().mAttrs.type;
}
- @Override
- public IApplicationToken getAppToken() {
- return mActivityRecord != null ? mActivityRecord.appToken : null;
- }
-
/** Returns true if this window is participating in voice interaction. */
boolean isVoiceInteraction() {
return mActivityRecord != null && mActivityRecord.mVoiceInteraction;
@@ -1727,7 +1719,7 @@
}
@Override
- public WindowState asWindowState() {
+ public WindowState getWindowState() {
return this;
}
@@ -2491,7 +2483,7 @@
// Cancel the remove starting window animation on shell. The main window might changed
// during animating, checking for all windows would be safer.
if (mActivityRecord != null) {
- mActivityRecord.forAllWindowsUnchecked(w -> {
+ mActivityRecord.forAllWindows(w -> {
if (w.isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
w.cancelAnimation();
return true;
@@ -4484,22 +4476,6 @@
h = Math.min(h, ph);
}
- if (mIsChildWindow) {
- final WindowState parent = getTopParentWindow();
- if (parent.hasCompatScale()) {
- // Scale the containing and display frames because they are in screen coordinates.
- // The position of frames are already relative to parent so only size is scaled.
- mTmpRect.set(containingFrame);
- containingFrame = mTmpRect;
- CoordinateTransforms.scaleRectSize(containingFrame, parent.mInvGlobalScale);
- if (fitToDisplay) {
- mTmpRect2.set(displayFrame);
- displayFrame = mTmpRect2;
- CoordinateTransforms.scaleRectSize(displayFrame, parent.mInvGlobalScale);
- }
- }
- }
-
// Set mFrame
Gravity.apply(attrs.gravity, w, h, containingFrame,
(int) (x + attrs.horizontalMargin * pw),
@@ -4771,7 +4747,7 @@
windowInfo.layer = mLayer;
windowInfo.token = mClient.asBinder();
if (mActivityRecord != null) {
- windowInfo.activityToken = mActivityRecord.appToken.asBinder();
+ windowInfo.activityToken = mActivityRecord.token;
}
windowInfo.title = mAttrs.accessibilityTitle;
// Panel windows have no public way to set the a11y title directly. Use the
@@ -5130,19 +5106,6 @@
}
}
- /**
- * Expand the given rectangle by this windows surface insets. This
- * takes you from the 'window size' to the 'surface size'.
- * The surface insets are positive in each direction, so we inset by
- * the inverse.
- */
- void expandForSurfaceInsets(Rect r) {
- r.inset(-mAttrs.surfaceInsets.left,
- -mAttrs.surfaceInsets.top,
- -mAttrs.surfaceInsets.right,
- -mAttrs.surfaceInsets.bottom);
- }
-
boolean surfaceInsetsChanging() {
return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets);
}
@@ -5460,6 +5423,10 @@
}
private void updateScaleIfNeeded() {
+ if (mIsChildWindow) {
+ // Child window follows parent's scale.
+ return;
+ }
float newHScale = mHScale * mGlobalScale * mWallpaperScale;
float newVScale = mVScale * mGlobalScale * mWallpaperScale;
if (mLastHScale != newHScale ||
@@ -5543,15 +5510,18 @@
// If changed, also adjust getTransformationMatrix
final WindowContainer parentWindowContainer = getParent();
if (isChildWindow()) {
- // TODO: This probably falls apart at some point and we should
- // actually compute relative coordinates.
-
+ final WindowState parent = getParentWindow();
+ outPoint.offset(-parent.mWindowFrames.mFrame.left, -parent.mWindowFrames.mFrame.top);
+ // Undo the scale of window position because the relative coordinates for child are
+ // based on the scaled parent.
+ if (mInvGlobalScale != 1f) {
+ outPoint.x = (int) (outPoint.x * mInvGlobalScale + 0.5f);
+ outPoint.y = (int) (outPoint.y * mInvGlobalScale + 0.5f);
+ }
// Since the parent was outset by its surface insets, we need to undo the outsetting
// with insetting by the same amount.
- final WindowState parent = getParentWindow();
transformSurfaceInsetsPosition(mTmpPoint, parent.mAttrs.surfaceInsets);
- outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x,
- -parent.mWindowFrames.mFrame.top + mTmpPoint.y);
+ outPoint.offset(mTmpPoint.x, mTmpPoint.y);
} else if (parentWindowContainer != null) {
final Rect parentBounds = isStartingWindowAssociatedToTask()
? mStartingData.mAssociatedTask.getBounds()
@@ -5570,7 +5540,7 @@
outPoint.offset(outset, outset);
}
- // Expand for surface insets. See WindowState.expandForSurfaceInsets.
+ // The surface size is larger than the window if the window has positive surface insets.
transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets);
outPoint.offset(-mTmpPoint.x, -mTmpPoint.y);
@@ -5582,7 +5552,9 @@
* scaled, the insets also need to be scaled for surface position in global coordinate.
*/
private void transformSurfaceInsetsPosition(Point outPos, Rect surfaceInsets) {
- if (!hasCompatScale()) {
+ // Ignore the scale for child window because its insets have been scaled with the
+ // parent surface.
+ if (mGlobalScale == 1f || mIsChildWindow) {
outPos.x = surfaceInsets.left;
outPos.y = surfaceInsets.top;
return;
@@ -5945,7 +5917,8 @@
@Override
boolean isSyncFinished() {
- if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE) {
+ if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE
+ && !isVisibleRequested()) {
// Don't wait for GONE windows. However, we don't alter the state in case the window
// becomes un-gone while the syncset is still active.
return true;
diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
index 6d8e07a..a2f37a5 100644
--- a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
+++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
@@ -152,10 +152,4 @@
transform.mapRect(tmp);
inOutRect.set((int) tmp.left, (int) tmp.top, (int) tmp.right, (int) tmp.bottom);
}
-
- /** Scales the rect without changing its position. */
- public static void scaleRectSize(Rect inOutRect, float scale) {
- inOutRect.right = inOutRect.left + (int) (inOutRect.width() * scale + .5f);
- inOutRect.bottom = inOutRect.top + (int) (inOutRect.height() * scale + .5f);
- }
}
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 9029fe7..546b075 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -443,7 +443,7 @@
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping,
frequencyMapping);
- return info.checkAndLogFailure("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
+ return info.isFailedLogged("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
}
static const JNINativeMethod method_table[] = {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0447409..34576a6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10701,7 +10701,7 @@
}
@Override
- public void resetNewUserDisclaimer() {
+ public void acknowledgeNewUserDisclaimer() {
CallerIdentity callerIdentity = getCallerIdentity();
canManageUsers(callerIdentity);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fc60bab..f351a8c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -114,6 +114,7 @@
import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
import com.android.server.clipboard.ClipboardService;
+import com.android.server.communal.CommunalManagerService;
import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
import com.android.server.connectivity.PacProxyService;
@@ -149,7 +150,6 @@
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
-import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -2408,15 +2408,6 @@
mSystemServiceManager.startService(AuthService.class);
t.traceEnd();
-
- t.traceBegin("StartBackgroundDexOptService");
- try {
- BackgroundDexOptService.schedule(context);
- } catch (Throwable e) {
- reportWtf("starting StartBackgroundDexOptService", e);
- }
- t.traceEnd();
-
if (!isWatch) {
// We don't run this on watches as there are no plans to use the data logged
// on watch devices.
@@ -2697,6 +2688,12 @@
mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
t.traceEnd();
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_COMMUNAL_MODE)) {
+ t.traceBegin("CommunalManagerService");
+ mSystemServiceManager.startService(CommunalManagerService.class);
+ t.traceEnd();
+ }
+
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 64c426b..349eb22 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -24,17 +24,17 @@
import android.content.pm.PackageManager
import android.content.pm.SigningDetails
import android.content.pm.parsing.ParsingPackage
-import android.content.pm.parsing.component.ParsedActivity
-import android.content.pm.parsing.component.ParsedAttribution
-import android.content.pm.parsing.component.ParsedComponent
-import android.content.pm.parsing.component.ParsedInstrumentation
-import android.content.pm.parsing.component.ParsedIntentInfo
-import android.content.pm.parsing.component.ParsedPermission
-import android.content.pm.parsing.component.ParsedPermissionGroup
-import android.content.pm.parsing.component.ParsedProcess
-import android.content.pm.parsing.component.ParsedProvider
-import android.content.pm.parsing.component.ParsedService
-import android.content.pm.parsing.component.ParsedUsesPermission
+import android.content.pm.parsing.component.ParsedActivityImpl
+import android.content.pm.parsing.component.ParsedAttributionImpl
+import android.content.pm.parsing.component.ParsedComponentImpl
+import android.content.pm.parsing.component.ParsedInstrumentationImpl
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
+import android.content.pm.parsing.component.ParsedPermissionGroupImpl
+import android.content.pm.parsing.component.ParsedPermissionImpl
+import android.content.pm.parsing.component.ParsedProcessImpl
+import android.content.pm.parsing.component.ParsedProviderImpl
+import android.content.pm.parsing.component.ParsedServiceImpl
+import android.content.pm.parsing.component.ParsedUsesPermissionImpl
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
@@ -43,14 +43,12 @@
import android.util.SparseIntArray
import com.android.internal.R
import com.android.server.pm.parsing.pkg.AndroidPackage
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import java.security.KeyPairGenerator
import java.security.PublicKey
import kotlin.contracts.ExperimentalContracts
-import kotlin.reflect.KFunction1
@ExperimentalContracts
class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
@@ -285,7 +283,7 @@
PackageImpl::addAttribution,
Triple("testTag", 13, listOf("testInherit")),
transformGet = { it.singleOrNull()?.let { Triple(it.tag, it.label, it.inheritFrom) } },
- transformSet = { it?.let { ParsedAttribution(it.first, it.second, it.third) } }
+ transformSet = { it?.let { ParsedAttributionImpl(it.first, it.second, it.third) } }
),
getSetByValue2(
AndroidPackage::getKeySetMapping,
@@ -298,22 +296,25 @@
PackageImpl::addPermissionGroup,
"test.permission.GROUP",
transformGet = { it.singleOrNull()?.name },
- transformSet = { ParsedPermissionGroup().apply { setName(it) } }
+ transformSet = { ParsedPermissionGroupImpl().apply { setName(it) } }
),
getSetByValue2(
AndroidPackage::getPreferredActivityFilters,
PackageImpl::addPreferredActivityFilter,
- "TestClassName" to ParsedIntentInfo().apply {
- addDataScheme("http")
- addDataAuthority("test.pm.server.android.com", null)
+ "TestClassName" to ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ addDataScheme("http")
+ addDataAuthority("test.pm.server.android.com", null)
+ }
},
transformGet = { it.singleOrNull()?.let { it.first to it.second } },
compare = { first, second ->
equalBy(
first, second,
{ it.first },
- { it.second.schemesIterator().asSequence().singleOrNull() },
- { it.second.authoritiesIterator().asSequence().singleOrNull()?.host },
+ { it.second.intentFilter.schemesIterator().asSequence().singleOrNull() },
+ { it.second.intentFilter.authoritiesIterator().asSequence()
+ .singleOrNull()?.host },
)
}
),
@@ -356,35 +357,35 @@
PackageImpl::addActivity,
"TestActivityName",
transformGet = { it.singleOrNull()?.name.orEmpty() },
- transformSet = { ParsedActivity().apply { name = it }.withMimeGroups() }
+ transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() }
),
getSetByValue(
AndroidPackage::getReceivers,
PackageImpl::addReceiver,
"TestReceiverName",
transformGet = { it.singleOrNull()?.name.orEmpty() },
- transformSet = { ParsedActivity().apply { name = it }.withMimeGroups() }
+ transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() }
),
getSetByValue(
AndroidPackage::getServices,
PackageImpl::addService,
"TestServiceName",
transformGet = { it.singleOrNull()?.name.orEmpty() },
- transformSet = { ParsedService().apply { name = it }.withMimeGroups() }
+ transformSet = { ParsedServiceImpl().apply { name = it }.withMimeGroups() }
),
getSetByValue(
AndroidPackage::getProviders,
PackageImpl::addProvider,
"TestProviderName",
transformGet = { it.singleOrNull()?.name.orEmpty() },
- transformSet = { ParsedProvider().apply { name = it }.withMimeGroups() }
+ transformSet = { ParsedProviderImpl().apply { name = it }.withMimeGroups() }
),
getSetByValue(
AndroidPackage::getInstrumentations,
PackageImpl::addInstrumentation,
"TestInstrumentationName",
transformGet = { it.singleOrNull()?.name.orEmpty() },
- transformSet = { ParsedInstrumentation().apply { name = it } }
+ transformSet = { ParsedInstrumentationImpl().apply { name = it } }
),
getSetByValue(
AndroidPackage::getConfigPreferences,
@@ -409,7 +410,7 @@
PackageImpl::addPermission,
"test.PERMISSION",
transformGet = { it.singleOrNull()?.name.orEmpty() },
- transformSet = { ParsedPermission().apply { name = it } }
+ transformSet = { ParsedPermissionImpl().apply { name = it } }
),
getSetByValue(
AndroidPackage::getUsesPermissions,
@@ -420,7 +421,7 @@
it.filterNot { it.name == "test.implicit.PERMISSION" }
.singleOrNull()?.name.orEmpty()
},
- transformSet = { ParsedUsesPermission(it, 0) }
+ transformSet = { ParsedUsesPermissionImpl(it, 0) }
),
getSetByValue(
AndroidPackage::getRequestedFeatures,
@@ -445,7 +446,7 @@
getSetByValue(
AndroidPackage::getProcesses,
PackageImpl::setProcesses,
- mapOf("testProcess" to ParsedProcess().apply { name = "testProcessName" }),
+ mapOf("testProcess" to ParsedProcessImpl().apply { name = "testProcessName" }),
compare = { first, second ->
equalBy(
first, second,
@@ -562,10 +563,12 @@
.generateKeyPair()
.public
- private fun <T : ParsedComponent> T.withMimeGroups() = apply {
+ private fun <T : ParsedComponentImpl> T.withMimeGroups() = apply {
val componentName = name
- addIntent(ParsedIntentInfo().apply {
- addMimeGroup("$componentName/mimeGroup")
+ addIntent(ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ addMimeGroup("$componentName/mimeGroup")
+ }
})
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index ece600b..8170acf 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -18,13 +18,17 @@
import android.content.pm.ActivityInfo
import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedActivityImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedActivityTest : ParsedMainComponentTest(ParsedActivity::class) {
+class ParsedActivityTest : ParsedMainComponentTest(
+ ParsedActivity::class,
+ ParsedActivityImpl::class
+) {
- override val defaultImpl = ParsedActivity()
- override val creator = ParsedActivity.CREATOR
+ override val defaultImpl = ParsedActivityImpl()
+ override val creator = ParsedActivityImpl.CREATOR
override val mainComponentSubclassBaseParams = listOf(
ParsedActivity::getPermission,
@@ -54,7 +58,7 @@
override fun mainComponentSubclassExtraParams() = listOf(
getSetByValue(
ParsedActivity::getWindowLayout,
- ParsedActivity::setWindowLayout,
+ ParsedActivityImpl::setWindowLayout,
ActivityInfo.WindowLayout(1, 1f, 2, 1f, 3, 4, 5),
compare = { first, second ->
equalBy(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
index e739dc7..503301b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
@@ -17,13 +17,15 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedAttribution
+import android.content.pm.parsing.component.ParsedAttributionImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedAttributionTest : ParcelableComponentTest(ParsedAttribution::class) {
+class ParsedAttributionTest : ParcelableComponentTest(ParsedAttribution::class,
+ ParsedAttributionImpl::class) {
- override val defaultImpl = ParsedAttribution("", 0, emptyList())
- override val creator = ParsedAttribution.CREATOR
+ override val defaultImpl = ParsedAttributionImpl("", 0, emptyList())
+ override val creator = ParsedAttributionImpl.CREATOR
override val baseParams = listOf(
ParsedAttribution::getTag,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
index 0a22f6d..e978dd3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
@@ -18,7 +18,9 @@
import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedComponent
+import android.content.pm.parsing.component.ParsedComponentImpl
import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.os.Bundle
import android.os.Parcelable
import kotlin.contracts.ExperimentalContracts
@@ -26,8 +28,11 @@
import kotlin.reflect.KFunction1
@ExperimentalContracts
-abstract class ParsedComponentTest(kClass: KClass<out Parcelable>) :
- ParcelableComponentTest(kClass) {
+abstract class ParsedComponentTest(getterType: KClass<*>, setterType: KClass<out Parcelable>) :
+ ParcelableComponentTest(getterType, setterType) {
+
+ constructor(getterAndSetterType: KClass<out Parcelable>) :
+ this(getterAndSetterType, getterAndSetterType)
final override val excludedMethods
get() = subclassExcludedMethods + listOf(
@@ -57,14 +62,14 @@
final override fun extraParams() = subclassExtraParams() + listOf(
getSetByValue(
ParsedComponent::getIntents,
- ParsedComponent::addIntent,
+ ParsedComponentImpl::addIntent,
"TestLabel",
transformGet = { it.singleOrNull()?.nonLocalizedLabel },
- transformSet = { ParsedIntentInfo().setNonLocalizedLabel(it) },
+ transformSet = { ParsedIntentInfoImpl().setNonLocalizedLabel(it!!) },
),
getSetByValue(
ParsedComponent::getProperties,
- ParsedComponent::addProperty,
+ ParsedComponentImpl::addProperty,
PackageManager.Property(
"testPropertyName",
"testPropertyValue",
@@ -84,7 +89,7 @@
),
getSetByValue(
ParsedComponent::getMetaData,
- ParsedComponent::setMetaData,
+ ParsedComponentImpl::setMetaData,
"testBundleKey" to "testBundleValue",
transformGet = { "testBundleKey" to it?.getString("testBundleKey") },
transformSet = { Bundle().apply { putString(it.first, it.second) } }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
index b7a85cc..f15b911 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
@@ -17,13 +17,17 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedInstrumentation
+import android.content.pm.parsing.component.ParsedInstrumentationImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedInstrumentationTest : ParsedComponentTest(ParsedInstrumentation::class) {
+class ParsedInstrumentationTest : ParsedComponentTest(
+ ParsedInstrumentation::class,
+ ParsedInstrumentationImpl::class
+) {
- override val defaultImpl = ParsedInstrumentation()
- override val creator = ParsedInstrumentation.CREATOR
+ override val defaultImpl = ParsedInstrumentationImpl()
+ override val creator = ParsedInstrumentationImpl.CREATOR
override val subclassBaseParams = listOf(
ParsedInstrumentation::getTargetPackage,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
index e27bdf2..f04e851 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
@@ -17,102 +17,23 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedIntentInfo
-import android.os.Parcel
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.os.Parcelable
import android.os.PatternMatcher
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedIntentInfoTest : ParcelableComponentTest(ParsedIntentInfo::class) {
+class ParsedIntentInfoTest : ParcelableComponentTest(
+ ParsedIntentInfo::class,
+ ParsedIntentInfoImpl::class,
+) {
- override val defaultImpl = ParsedIntentInfo()
-
- override val creator = object : Parcelable.Creator<ParsedIntentInfo> {
- override fun createFromParcel(source: Parcel) = ParsedIntentInfo(source)
- override fun newArray(size: Int) = Array<ParsedIntentInfo?>(size) { null }
- }
+ override val defaultImpl = ParsedIntentInfoImpl()
+ override val creator = ParsedIntentInfoImpl.CREATOR
override val excludedMethods = listOf(
- // Used to parcel
- "writeIntentInfoToParcel",
- // All remaining IntentFilter methods, which are out of scope
- "hasDataPath",
- "hasDataSchemeSpecificPart",
- "matchAction",
- "matchData",
- "actionsIterator",
- "addAction",
- "addCategory",
- "addDataAuthority",
- "addDataPath",
- "addDataScheme",
- "addDataSchemeSpecificPart",
- "addDataType",
- "addDynamicDataType",
- "addMimeGroup",
- "asPredicate",
- "asPredicateWithTypeResolution",
- "authoritiesIterator",
- "categoriesIterator",
- "clearDynamicDataTypes",
- "countActions",
- "countCategories",
- "countDataAuthorities",
- "countDataPaths",
- "countDataSchemeSpecificParts",
- "countDataSchemes",
- "countDataTypes",
- "countMimeGroups",
- "countStaticDataTypes",
- "dataTypes",
- "debugCheck",
- "dump",
- "dumpDebug",
- "getAction",
- "getAutoVerify",
- "getCategory",
- "getDataAuthority",
- "getDataPath",
- "getDataScheme",
- "getDataSchemeSpecificPart",
- "getDataType",
- "getHosts",
- "getHostsList",
- "getMimeGroup",
- "getOrder",
- "getPriority",
- "getVisibilityToInstantApp",
- "handleAllWebDataURI",
- "handlesWebUris",
- "hasAction",
- "hasCategory",
- "hasDataAuthority",
- "hasDataScheme",
- "hasDataType",
- "hasExactDataType",
- "hasExactDynamicDataType",
- "hasExactStaticDataType",
- "hasMimeGroup",
- "isExplicitlyVisibleToInstantApp",
- "isImplicitlyVisibleToInstantApp",
- "isVerified",
- "isVisibleToInstantApp",
- "match",
- "matchCategories",
- "matchDataAuthority",
- "mimeGroupsIterator",
- "needsVerification",
- "pathsIterator",
- "readFromXml",
- "schemeSpecificPartsIterator",
- "schemesIterator",
- "setAutoVerify",
- "setOrder",
- "setPriority",
- "setVerified",
- "setVisibilityToInstantApp",
- "typesIterator",
- "writeToXml",
+ // Tested through extraAssertions, since this isn't a settable field
+ "getIntentFilter"
)
override val baseParams = listOf(
@@ -122,31 +43,30 @@
ParsedIntentInfo::getNonLocalizedLabel,
)
- override fun initialObject() = ParsedIntentInfo().apply {
- addAction("test.ACTION")
- addDataAuthority("testAuthority", "404")
- addCategory("test.CATEGORY")
- addMimeGroup("testMime")
- addDataPath("testPath", PatternMatcher.PATTERN_LITERAL)
+ override fun initialObject() = ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ addAction("test.ACTION")
+ addDataAuthority("testAuthority", "404")
+ addCategory("test.CATEGORY")
+ addMimeGroup("testMime")
+ addDataPath("testPath", PatternMatcher.PATTERN_LITERAL)
+ }
}
override fun extraAssertions(before: Parcelable, after: Parcelable) {
super.extraAssertions(before, after)
- after as ParsedIntentInfo
- expect.that(after.actionsIterator().asSequence().singleOrNull())
+ val intentFilter = (after as ParsedIntentInfo).intentFilter
+ expect.that(intentFilter.actionsIterator().asSequence().singleOrNull())
.isEqualTo("test.ACTION")
- val authority = after.authoritiesIterator().asSequence().singleOrNull()
+ val authority = intentFilter.authoritiesIterator().asSequence().singleOrNull()
expect.that(authority?.host).isEqualTo("testAuthority")
expect.that(authority?.port).isEqualTo(404)
- expect.that(after.categoriesIterator().asSequence().singleOrNull())
+ expect.that(intentFilter.categoriesIterator().asSequence().singleOrNull())
.isEqualTo("test.CATEGORY")
- expect.that(after.mimeGroupsIterator().asSequence().singleOrNull())
+ expect.that(intentFilter.mimeGroupsIterator().asSequence().singleOrNull())
.isEqualTo("testMime")
- expect.that(after.hasDataPath("testPath")).isTrue()
+ expect.that(intentFilter.hasDataPath("testPath")).isTrue()
}
-
- override fun writeToParcel(parcel: Parcel, value: Parcelable) =
- ParsedIntentInfo.PARCELER.parcel(value as ParsedIntentInfo, parcel, 0)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
index 411cb09..214734a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
@@ -17,6 +17,7 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedMainComponent
+import android.content.pm.parsing.component.ParsedMainComponentImpl
import android.content.pm.parsing.component.ParsedService
import android.os.Parcelable
import java.util.Arrays
@@ -25,8 +26,11 @@
import kotlin.reflect.KFunction1
@ExperimentalContracts
-abstract class ParsedMainComponentTest(kClass: KClass<out Parcelable>) :
- ParsedComponentTest(kClass) {
+abstract class ParsedMainComponentTest(getterType: KClass<*>, setterType: KClass<out Parcelable>) :
+ ParsedComponentTest(getterType, setterType) {
+
+ constructor(getterAndSetterType: KClass<out Parcelable>) :
+ this(getterAndSetterType, getterAndSetterType)
final override val subclassBaseParams
get() = mainComponentSubclassBaseParams + listOf(
@@ -42,8 +46,8 @@
final override fun subclassExtraParams() = mainComponentSubclassExtraParams() + listOf(
getSetByValue(
- ParsedService::getAttributionTags,
- ParsedService::setAttributionTags,
+ ParsedMainComponent::getAttributionTags,
+ ParsedMainComponentImpl::setAttributionTags,
arrayOf("testAttributionTag"),
compare = Arrays::equals
),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
index 53c862a..f876ed0 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -17,13 +17,17 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedPermissionGroup
+import android.content.pm.parsing.component.ParsedPermissionGroupImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedPermissionGroupTest : ParsedComponentTest(ParsedPermissionGroup::class) {
+class ParsedPermissionGroupTest : ParsedComponentTest(
+ ParsedPermissionGroup::class,
+ ParsedPermissionGroupImpl::class,
+) {
- override val defaultImpl = ParsedPermissionGroup()
- override val creator = ParsedPermissionGroup.CREATOR
+ override val defaultImpl = ParsedPermissionGroupImpl()
+ override val creator = ParsedPermissionGroupImpl.CREATOR
override val subclassBaseParams = listOf(
ParsedPermissionGroup::getRequestDetailResourceId,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
index bb63e2e2..6f48e24 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
@@ -18,13 +18,18 @@
import android.content.pm.parsing.component.ParsedPermission
import android.content.pm.parsing.component.ParsedPermissionGroup
+import android.content.pm.parsing.component.ParsedPermissionGroupImpl
+import android.content.pm.parsing.component.ParsedPermissionImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedPermissionTest : ParsedComponentTest(ParsedPermission::class) {
+class ParsedPermissionTest : ParsedComponentTest(
+ ParsedPermission::class,
+ ParsedPermissionImpl::class
+) {
- override val defaultImpl = ParsedPermission()
- override val creator = ParsedPermission.CREATOR
+ override val defaultImpl = ParsedPermissionImpl()
+ override val creator = ParsedPermissionImpl.CREATOR
override val subclassExcludedMethods = listOf(
// Utility methods
@@ -47,8 +52,8 @@
getter(ParsedPermission::getKnownCerts, setOf("testCert")),
getSetByValue(
ParsedPermission::getParsedPermissionGroup,
- ParsedPermission::setParsedPermissionGroup,
- ParsedPermissionGroup().apply { name = "test.permission.group" },
+ ParsedPermissionImpl::setParsedPermissionGroup,
+ ParsedPermissionGroupImpl().apply { name = "test.permission.group" },
compare = { first, second -> equalBy(first, second, ParsedPermissionGroup::getName) }
),
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 34f46f2..e633850 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -17,13 +17,14 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedProcess
+import android.content.pm.parsing.component.ParsedProcessImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class) {
+class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedProcessImpl::class) {
- override val defaultImpl = ParsedProcess()
- override val creator = ParsedProcess.CREATOR
+ override val defaultImpl = ParsedProcessImpl()
+ override val creator = ParsedProcessImpl.CREATOR
override val excludedMethods = listOf(
// Copying method
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index e6d5c0f..78e4b79 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -18,14 +18,15 @@
import android.content.pm.PathPermission
import android.content.pm.parsing.component.ParsedProvider
+import android.content.pm.parsing.component.ParsedProviderImpl
import android.os.PatternMatcher
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class) {
+class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) {
- override val defaultImpl = ParsedProvider()
- override val creator = ParsedProvider.CREATOR
+ override val defaultImpl = ParsedProviderImpl()
+ override val creator = ParsedProviderImpl.CREATOR
override val mainComponentSubclassBaseParams = listOf(
ParsedProvider::getAuthority,
@@ -41,7 +42,7 @@
override fun mainComponentSubclassExtraParams() = listOf(
getSetByValue(
ParsedProvider::getUriPermissionPatterns,
- ParsedProvider::setUriPermissionPatterns,
+ ParsedProviderImpl::setUriPermissionPatterns,
PatternMatcher("testPattern", PatternMatcher.PATTERN_LITERAL),
transformGet = { it?.singleOrNull() },
transformSet = { arrayOf(it) },
@@ -55,7 +56,7 @@
),
getSetByValue(
ParsedProvider::getPathPermissions,
- ParsedProvider::setPathPermissions,
+ ParsedProviderImpl::setPathPermissions,
PathPermission(
"testPermissionPattern",
PatternMatcher.PATTERN_LITERAL,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
index 5530184..9363aa3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -17,13 +17,14 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedService
+import android.content.pm.parsing.component.ParsedServiceImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class) {
+class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class, ParsedServiceImpl::class) {
- override val defaultImpl = ParsedService()
- override val creator = ParsedService.CREATOR
+ override val defaultImpl = ParsedServiceImpl()
+ override val creator = ParsedServiceImpl.CREATOR
override val mainComponentSubclassBaseParams = listOf(
ParsedService::getForegroundServiceType,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
index 1131c72..81e800f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
@@ -17,19 +17,22 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.pm.parsing.component.ParsedUsesPermission
-import android.os.Parcelable
+import android.content.pm.parsing.component.ParsedUsesPermissionImpl
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
-class ParsedUsesPermissionTest : ParcelableComponentTest(ParsedUsesPermission::class) {
+class ParsedUsesPermissionTest : ParcelableComponentTest(
+ ParsedUsesPermission::class,
+ ParsedUsesPermissionImpl::class
+) {
- override val defaultImpl = ParsedUsesPermission("", 0)
- override val creator = ParsedUsesPermission.CREATOR
+ override val defaultImpl = ParsedUsesPermissionImpl("", 0)
+ override val creator = ParsedUsesPermissionImpl.CREATOR
override val baseParams = listOf(
ParsedUsesPermission::getName,
ParsedUsesPermission::getUsesPermissionFlags
)
- override fun initialObject() = ParsedUsesPermission("", 0)
+ override fun initialObject() = ParsedUsesPermissionImpl("", 0)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index 4de8d52..33234d5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -18,8 +18,8 @@
import android.content.Intent
import android.content.pm.ApplicationInfo
-import android.content.pm.parsing.component.ParsedActivity
-import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedActivityImpl
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.os.Build
import android.os.PatternMatcher
import android.util.ArraySet
@@ -92,40 +92,44 @@
whenever(targetSdkVersion) { Build.VERSION_CODES.R }
val activityList = listOf(
- ParsedActivity().apply {
- addIntent(
- ParsedIntentInfo().apply {
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataScheme("https")
- addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority("example1.com", null)
- addDataAuthority("invalid1", null)
- }
- )
- },
- ParsedActivity().apply {
- addIntent(
- ParsedIntentInfo().apply {
- setAutoVerify(true)
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataScheme("https")
+ ParsedActivityImpl().apply {
+ addIntent(
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example1.com", null)
+ addDataAuthority("invalid1", null)
+ }
+ }
+ )
+ },
+ ParsedActivityImpl().apply {
+ addIntent(
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ setAutoVerify(true)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
- // The presence of a non-web-scheme as the only autoVerify
- // intent-filter, when non-forced, means that v1 will not pick
- // up the package for verification.
- addDataScheme("nonWebScheme")
- addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority("example2.com", null)
- addDataAuthority("invalid2", null)
- }
- )
- },
+ // The presence of a non-web-scheme as the only autoVerify
+ // intent-filter, when non-forced, means that v1 will not pick
+ // up the package for verification.
+ addDataScheme("nonWebScheme")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ addDataAuthority("invalid2", null)
+ }
+ }
+ )
+ },
)
whenever(activities) { activityList }
@@ -264,9 +268,10 @@
// The intents are split into separate Activities to test that multiple are collected
val activityList = listOf(
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
@@ -277,9 +282,11 @@
addDataAuthority("example1.com", null)
addDataAuthority("invalid1", null)
}
+ }
)
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
@@ -288,11 +295,13 @@
addDataAuthority("example2.com", null)
addDataAuthority("invalid2", null)
}
+ }
)
},
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
@@ -302,11 +311,13 @@
addDataAuthority("example3.com", null)
addDataAuthority("invalid3", null)
}
+ }
)
},
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
@@ -315,9 +326,11 @@
addDataAuthority("example4.com", null)
addDataAuthority("invalid4", null)
}
+ }
)
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_DEFAULT)
@@ -326,9 +339,11 @@
addDataAuthority("example5.com", null)
addDataAuthority("invalid5", null)
}
+ }
)
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
@@ -337,30 +352,37 @@
addDataAuthority("example6.com", null)
addDataAuthority("invalid6", null)
}
+ }
)
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
addDataAuthority("example7.com", null)
}
+ }
)
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
addDataScheme("https")
}
+ }
)
addIntent(
- ParsedIntentInfo().apply {
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
setAutoVerify(autoVerify)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
addDataPath("/sub7", PatternMatcher.PATTERN_LITERAL)
}
+ }
)
},
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 1ad1879..26a7efb 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -19,9 +19,8 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.content.pm.parsing.component.ParsedActivity
-import android.content.pm.parsing.component.ParsedIntentInfo
-import android.content.pm.pkg.PackageUserState
+import android.content.pm.parsing.component.ParsedActivityImpl
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.pkg.PackageUserStateInternal
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationState
@@ -297,15 +296,17 @@
whenever(isEnabled) { true }
whenever(activities) {
listOf(
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("https")
- addDataAuthority("example.com", null)
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataAuthority("example.com", null)
+ }
}
)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index b65995f..34b248c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -19,8 +19,8 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.content.pm.parsing.component.ParsedActivity
-import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedActivityImpl
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.pkg.PackageUserStateInternal
import android.content.pm.verify.domain.DomainOwner
import android.content.pm.verify.domain.DomainVerificationInfo
@@ -520,18 +520,20 @@
whenever(isEnabled) { true }
val activityList = listOf(
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
domains.forEach {
addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataScheme("https")
- addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority(it, null)
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(it, null)
+ }
}
)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index dbec56f..a22563e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -20,8 +20,8 @@
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.content.pm.SigningDetails
-import android.content.pm.parsing.component.ParsedActivity
-import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedActivityImpl
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.pkg.PackageUserStateInternal
import android.content.pm.verify.domain.DomainOwner
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
@@ -813,22 +813,24 @@
whenever(isEnabled) { true }
val activityList = listOf(
- ParsedActivity().apply {
- domains.forEach {
- addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataScheme("https")
- addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority(it, null)
- }
- )
- }
- },
+ ParsedActivityImpl().apply {
+ domains.forEach {
+ addIntent(
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(it, null)
+ }
+ }
+ )
+ }
+ },
)
whenever(activities) { activityList }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 8125128..cbb8e3a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -19,9 +19,8 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.content.pm.parsing.component.ParsedActivity
-import android.content.pm.parsing.component.ParsedIntentInfo
-import android.content.pm.pkg.PackageUserState
+import android.content.pm.parsing.component.ParsedActivityImpl
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.pkg.PackageUserStateInternal
import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
@@ -196,15 +195,17 @@
whenever(isEnabled) { true }
whenever(activities) {
listOf(
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("https")
- addDataAuthority("example.com", null)
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataAuthority("example.com", null)
+ }
}
)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 1711355..473d70c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -19,7 +19,9 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedActivityImpl
import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.pkg.PackageUserState
import android.content.pm.pkg.PackageUserStateInternal
import android.content.pm.verify.domain.DomainVerificationManager
@@ -109,28 +111,32 @@
whenever(isEnabled) { true }
val activityList = listOf(
- ParsedActivity().apply {
+ ParsedActivityImpl().apply {
addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataScheme("https")
- addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority(DOMAIN_ONE, null)
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(DOMAIN_ONE, null)
+ }
}
)
addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority("example2.com", null)
+ ParsedIntentInfoImpl().apply {
+ intentFilter.apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ }
}
)
},
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 680e6db..6bb127a 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -45,7 +45,6 @@
"service-jobscheduler",
"service-permission.impl",
"service-blobstore",
- "service-appsearch",
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
diff --git a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
new file mode 100644
index 0000000..06e691f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.communal;
+
+import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.ActivityInterceptorCallback.COMMUNAL_MODE_ORDERED_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.app.KeyguardManager;
+import android.app.communal.ICommunalManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.wm.ActivityInterceptorCallback;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+/**
+ * Test class for {@link CommunalManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CommunalManagerServiceTest
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+@Presubmit
+public class CommunalManagerServiceTest {
+ private static final int TEST_USER_ID = 1;
+ private static final int TEST_REAL_CALLING_UID = 2;
+ private static final int TEST_REAL_CALLING_PID = 3;
+ private static final String TEST_CALLING_PACKAGE = "com.test.caller";
+ private static final String TEST_PACKAGE_NAME = "com.test.package";
+
+ private MockitoSession mMockingSession;
+ private CommunalManagerService mService;
+
+ @Mock
+ private ActivityTaskManagerInternal mAtmInternal;
+ @Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private ContentResolver mContentResolver;
+
+ private ActivityInterceptorCallback mActivityInterceptorCallback;
+ private ActivityInfo mAInfo;
+ private ICommunalManager mBinder;
+
+ @Before
+ public final void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(LocalServices.class)
+ .mockStatic(ServiceManager.class)
+ .mockStatic(Settings.Secure.class)
+ .mockStatic(KeyguardManager.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+
+ when(mMockContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mMockContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ doReturn(mAtmInternal).when(() -> LocalServices.getService(
+ ActivityTaskManagerInternal.class));
+
+ mService = new CommunalManagerService(mMockContext);
+ mService.onStart();
+
+ ArgumentCaptor<ActivityInterceptorCallback> activityInterceptorCaptor =
+ ArgumentCaptor.forClass(ActivityInterceptorCallback.class);
+ verify(mAtmInternal).registerActivityStartInterceptor(eq(COMMUNAL_MODE_ORDERED_ID),
+ activityInterceptorCaptor.capture());
+ mActivityInterceptorCallback = activityInterceptorCaptor.getValue();
+
+ ArgumentCaptor<IBinder> binderCaptor = ArgumentCaptor.forClass(IBinder.class);
+ verify(() -> ServiceManager.addService(eq(Context.COMMUNAL_MANAGER_SERVICE),
+ binderCaptor.capture(),
+ anyBoolean(), anyInt()));
+ mBinder = ICommunalManager.Stub.asInterface(binderCaptor.getValue());
+
+ mAInfo = new ActivityInfo();
+ mAInfo.applicationInfo = new ApplicationInfo();
+ mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private ActivityInterceptorCallback.ActivityInterceptorInfo buildActivityInfo(Intent intent) {
+ return new ActivityInterceptorCallback.ActivityInterceptorInfo(
+ TEST_REAL_CALLING_UID,
+ TEST_REAL_CALLING_PID,
+ TEST_USER_ID,
+ TEST_CALLING_PACKAGE,
+ "featureId",
+ intent,
+ null,
+ mAInfo,
+ "resolvedType",
+ TEST_REAL_CALLING_PID,
+ TEST_REAL_CALLING_UID,
+ null);
+ }
+
+ private void allowPackages(String packages) {
+ doReturn(packages).when(
+ () -> Settings.Secure.getStringForUser(mContentResolver,
+ Settings.Secure.COMMUNAL_MODE_PACKAGES, UserHandle.USER_SYSTEM));
+ mService.updateSelectedApps();
+ }
+
+ @Test
+ public void testIntercept_unlocked_communalOff_appNotEnabled_showWhenLockedOff() {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ mAInfo.flags = 0;
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+
+ @Test
+ public void testIntercept_unlocked_communalOn_appNotEnabled_showWhenLockedOff()
+ throws RemoteException {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ mBinder.setCommunalViewShowing(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ mAInfo.flags = 0;
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+
+ @Test
+ public void testIntercept_locked_communalOff_appNotEnabled_showWhenLockedOff() {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mAInfo.flags = 0;
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+
+ @Test
+ public void testIntercept_locked_communalOn_appNotEnabled_showWhenLockedOff()
+ throws RemoteException {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ mBinder.setCommunalViewShowing(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mAInfo.flags = 0;
+ // TODO(b/191994709): Fix this assertion once we properly intercept activities.
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+
+ @Test
+ public void testIntercept_locked_communalOn_appNotEnabled_showWhenLockedOn()
+ throws RemoteException {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ mBinder.setCommunalViewShowing(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mAInfo.flags = FLAG_SHOW_WHEN_LOCKED;
+
+ allowPackages("package1,package2");
+ // TODO(b/191994709): Fix this assertion once we properly intercept activities.
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+
+ @Test
+ public void testIntercept_locked_communalOn_appEnabled_showWhenLockedOff()
+ throws RemoteException {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ mBinder.setCommunalViewShowing(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mAInfo.flags = 0;
+
+ allowPackages(TEST_PACKAGE_NAME);
+ // TODO(b/191994709): Fix this assertion once we properly intercept activities.
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+
+ @Test
+ public void testIntercept_locked_communalOn_appEnabled_showWhenLockedOn()
+ throws RemoteException {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ mBinder.setCommunalViewShowing(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mAInfo.flags = FLAG_SHOW_WHEN_LOCKED;
+
+ allowPackages(TEST_PACKAGE_NAME);
+ assertThat(mActivityInterceptorCallback.intercept(buildActivityInfo(intent))).isNull();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index 72bc77e..3ee2348 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -105,7 +105,7 @@
rule.system().validateFinalState()
whenever(appHibernationManager.isHibernatingGlobally(TEST_PACKAGE_2_NAME)).thenReturn(true)
- val optimizablePkgs = pm.optimizablePackages
+ val optimizablePkgs = DexOptHelper(pm).optimizablePackages
assertTrue(optimizablePkgs.contains(TEST_PACKAGE_NAME))
assertFalse(optimizablePkgs.contains(TEST_PACKAGE_2_NAME))
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 9e48045..6751b80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -57,6 +57,11 @@
MockScribe(InternalResourceService irs) {
super(irs);
}
+
+ @Override
+ void postWrite() {
+ // Do nothing
+ }
}
@Before
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
new file mode 100644
index 0000000..b72fc23
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.tare;
+
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for various Scribe behavior, including reading and writing correctly from file.
+ *
+ * atest FrameworksServicesTests:ScribeTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ScribeTest {
+ private static final String TAG = "ScribeTest";
+
+ private static final int TEST_USER_ID = 27;
+ private static final String TEST_PACKAGE = "com.android.test";
+
+ private MockitoSession mMockingSession;
+ private Scribe mScribeUnderTest;
+ private File mTestFileDir;
+ private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
+
+ @Mock
+ private InternalResourceService mIrs;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+ when(mIrs.getLock()).thenReturn(new Object());
+ when(mIrs.isEnabled()).thenReturn(true);
+ when(mIrs.getInstalledPackages()).thenReturn(mInstalledPackages);
+ mTestFileDir = new File(getContext().getFilesDir(), "scribe_test");
+ //noinspection ResultOfMethodCallIgnored
+ mTestFileDir.mkdirs();
+ Log.d(TAG, "Saving data to '" + mTestFileDir + "'");
+ mScribeUnderTest = new Scribe(mIrs, mTestFileDir);
+
+ addInstalledPackage(TEST_USER_ID, TEST_PACKAGE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mScribeUnderTest.tearDownLocked();
+ if (mTestFileDir.exists() && !mTestFileDir.delete()) {
+ Log.w(TAG, "Failed to delete test file directory");
+ }
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void testWriteHighLevelStateToDisk() {
+ long lastReclamationTime = System.currentTimeMillis();
+ long narcsInCirculation = 2000L;
+
+ Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000));
+ // Negative ledger balance shouldn't affect the total circulation value.
+ ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE);
+ ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000));
+ mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+
+ assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked());
+ assertEquals(narcsInCirculation, mScribeUnderTest.getNarcsInCirculationLocked());
+ }
+
+ @Test
+ public void testWritingEmptyLedgerToDisk() {
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+ }
+
+ @Test
+ public void testWritingPopulatedLedgerToDisk() {
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
+ ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
+ ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+ }
+
+ @Test
+ public void testWritingMultipleLedgersToDisk() {
+ final SparseArrayMap<String, Ledger> ledgers = new SparseArrayMap<>();
+ final int numUsers = 3;
+ final int numLedgers = 5;
+ for (int u = 0; u < numUsers; ++u) {
+ final int userId = TEST_USER_ID + u;
+ for (int l = 0; l < numLedgers; ++l) {
+ final String pkgName = TEST_PACKAGE + l;
+ addInstalledPackage(userId, pkgName);
+ final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
+ ledger.recordTransaction(new Ledger.Transaction(
+ 0, 1000L * u + l, 1, null, 51L * u + l));
+ ledger.recordTransaction(new Ledger.Transaction(
+ 1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l));
+ ledger.recordTransaction(new Ledger.Transaction(
+ 2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l));
+ ledgers.add(userId, pkgName, ledger);
+ }
+ }
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ ledgers.forEach((userId, pkgName, ledger)
+ -> assertLedgersEqual(ledger, mScribeUnderTest.getLedgerLocked(userId, pkgName)));
+ }
+
+ @Test
+ public void testDiscardLedgerFromDisk() {
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
+ ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
+ ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+
+ mScribeUnderTest.discardLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ // Make sure there's no more saved ledger.
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(new Ledger(),
+ mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+ }
+
+ @Test
+ public void testLoadingMissingPackageFromDisk() {
+ final String pkgName = TEST_PACKAGE + ".uninstalled";
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName);
+ ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
+ ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
+ ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ // Package isn't installed, so make sure it's not saved to memory after loading.
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName));
+ }
+
+ @Test
+ public void testLoadingMissingUserFromDisk() {
+ final int userId = TEST_USER_ID + 1;
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE);
+ ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
+ ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
+ ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ // User doesn't show up with any packages, so make sure nothing is saved after loading.
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE));
+ }
+
+ private void assertLedgersEqual(Ledger expected, Ledger actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.getCurrentBalance(), actual.getCurrentBalance());
+ List<Ledger.Transaction> expectedTransactions = expected.getTransactions();
+ List<Ledger.Transaction> actualTransactions = actual.getTransactions();
+ assertEquals(expectedTransactions.size(), actualTransactions.size());
+ for (int i = 0; i < expectedTransactions.size(); ++i) {
+ assertTransactionsEqual(expectedTransactions.get(i), actualTransactions.get(i));
+ }
+ }
+
+ private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.startTimeMs, actual.startTimeMs);
+ assertEquals(expected.endTimeMs, actual.endTimeMs);
+ assertEquals(expected.eventId, actual.eventId);
+ assertEquals(expected.tag, actual.tag);
+ assertEquals(expected.delta, actual.delta);
+ }
+
+ private void addInstalledPackage(int userId, String pkgName) {
+ PackageInfo pkgInfo = new PackageInfo();
+ pkgInfo.packageName = pkgName;
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
+ pkgInfo.applicationInfo = applicationInfo;
+ mInstalledPackages.add(pkgInfo);
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 8848098..ba580ec 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -56,7 +56,6 @@
"framework-protos",
"hamcrest-library",
"servicestests-utils",
- "service-appsearch",
"service-jobscheduler",
"service-permission.impl",
// TODO: remove once Android migrates to JUnit 4.12,
@@ -91,7 +90,6 @@
"libbinder",
"libc++",
"libcutils",
- "libicing",
"liblog",
"liblzma",
"libnativehelper",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 68b8469..80f2729 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -171,7 +171,7 @@
</intent-filter>
</receiver>
- <service android:name="com.android.server.job.MockPriorityJobService"
+ <service android:name="com.android.server.job.MockBiasJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
<activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity"/>
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 2a5bb18..b1da890 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -16,7 +16,10 @@
package com.android.server.accessibility;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -38,7 +41,6 @@
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.IBinder;
-import android.os.UserHandle;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -51,6 +53,7 @@
import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.WindowMagnificationManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -92,6 +95,7 @@
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
@Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
@Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+ @Mock private UserManagerInternal mMockUserManagerInternal;
@Mock private IBinder mMockBinder;
@Mock private IAccessibilityServiceClient mMockServiceClient;
@Mock private WindowMagnificationManager mMockWindowMagnificationMgr;
@@ -109,16 +113,20 @@
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+ LocalServices.addService(
+ UserManagerInternal.class, mMockUserManagerInternal);
when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
mMockWindowMagnificationMgr);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
+ when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt())).thenReturn(true);
mA11yms = new AccessibilityManagerService(
InstrumentationRegistry.getContext(),
mMockPackageManager,
@@ -131,16 +139,15 @@
mMockResources = mock(Resources.class);
when(mMockContext.getResources()).thenReturn(mMockResources);
- final AccessibilityUserState userState = new AccessibilityUserState(
+ mUserState = new AccessibilityUserState(
mA11yms.getCurrentUserIdLocked(), mMockContext, mA11yms);
- mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+ mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), mUserState);
}
private void setupAccessibilityServiceConnection() {
when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(
InstrumentationRegistry.getContext().getSystemService(
Context.DISPLAY_SERVICE));
- mUserState = new AccessibilityUserState(UserHandle.USER_SYSTEM, mMockContext, mA11yms);
when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
@@ -226,4 +233,27 @@
assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
userState.getMagnificationModeLocked());
}
+
+ @SmallTest
+ public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
+ mUserState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+ mUserState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(true);
+ }
+
+ @SmallTest
+ public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
+ setupAccessibilityServiceConnection();
+ when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(true);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index 8a8a624..6faa7e7 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -202,6 +202,7 @@
public class TestBatteryStatsImpl extends BatteryStatsImpl {
public TestBatteryStatsImpl(Context context) {
mPowerProfile = new PowerProfile(context, true /* forTest */);
+ initTimersAndCounters();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
new file mode 100644
index 0000000..ea746d1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.camera;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.InstrumentationRegistry;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
+import android.view.Display;
+import android.view.Surface;
+
+import java.util.HashMap;
+
+@RunWith(JUnit4.class)
+public class CameraServiceProxyTest {
+
+ @Test
+ public void testGetCropRotateScale() {
+
+ Context ctx = InstrumentationRegistry.getContext();
+
+ // Check resizeability and SDK
+ CameraServiceProxy.TaskInfo taskInfo = new CameraServiceProxy.TaskInfo();
+ taskInfo.isResizeable = true;
+ taskInfo.displayId = Display.DEFAULT_DISPLAY;
+ taskInfo.isFixedOrientationLandscape = false;
+ taskInfo.isFixedOrientationPortrait = true;
+ // Resizeable apps should be ignored
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90 , CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/false)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ // Resizeable apps will be considered in case the ignore flag is set
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ taskInfo.isResizeable = false;
+ // Non-resizeable apps should be considered
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/false)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ // The ignore flag for non-resizeable should have no effect
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ // Non-fixed orientation should be ignored
+ taskInfo.isFixedOrientationLandscape = false;
+ taskInfo.isFixedOrientationPortrait = false;
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ // Check rotation and lens facing combinations
+ HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
+ put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
+ put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
+ }};
+ taskInfo.isFixedOrientationPortrait = true;
+ backFacingMap.forEach((key, value) -> {
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ key, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
+ });
+ HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
+ put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
+ put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
+ }};
+ frontFacingMap.forEach((key, value) -> {
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ key, CameraCharacteristics.LENS_FACING_FRONT,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
+ });
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/camera/OWNERS b/services/tests/servicestests/src/com/android/server/camera/OWNERS
new file mode 100644
index 0000000..f48a95c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/camera/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/av:/camera/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java b/services/tests/servicestests/src/com/android/server/criticalevents/CriticalEventLogTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java
rename to services/tests/servicestests/src/com/android/server/criticalevents/CriticalEventLogTest.java
index 903d7f2..dca666c 100644
--- a/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java
+++ b/services/tests/servicestests/src/com/android/server/criticalevents/CriticalEventLogTest.java
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package com.android.server.am;
+package com.android.server.criticalevents;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import com.android.framework.protobuf.nano.MessageNano;
-import com.android.server.am.CriticalEventLog.ILogLoader;
-import com.android.server.am.CriticalEventLog.LogLoader;
-import com.android.server.am.nano.CriticalEventLogProto;
-import com.android.server.am.nano.CriticalEventLogStorageProto;
-import com.android.server.am.nano.CriticalEventProto;
-import com.android.server.am.nano.CriticalEventProto.HalfWatchdog;
-import com.android.server.am.nano.CriticalEventProto.Watchdog;
+import com.android.server.criticalevents.CriticalEventLog.ILogLoader;
+import com.android.server.criticalevents.CriticalEventLog.LogLoader;
+import com.android.server.criticalevents.nano.CriticalEventLogProto;
+import com.android.server.criticalevents.nano.CriticalEventLogStorageProto;
+import com.android.server.criticalevents.nano.CriticalEventProto;
+import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.criticalevents.nano.CriticalEventProto.Watchdog;
import org.junit.Before;
import org.junit.Rule;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index abc1468e..d441143 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -242,6 +242,101 @@
doReturn(true).when(resources).getBoolean(
R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadLpcm_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadLpcmEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadLpcmEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadLpcmDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadLpcmDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDd_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadDdDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg1_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg1Enabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg1Enabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg1Disabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg1Disabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMp3_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMp3Enabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMp3Enabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMp3Disabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadMp3Disabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg2_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg2Enabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg2Enabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg2Disabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadMpeg2Disabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAac_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAacEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAacEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAacDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadAacDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDts_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtsEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtsEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtsDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadDtsDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAtrac_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAtracEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAtracEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadAtracDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadAtracDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecQuerySadOnebitaudio_userConfigurable);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecQuerySadOnebitaudioEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecQuerySadOnebitaudioEnabled_default);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecQuerySadOnebitaudioDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(
+ R.bool.config_cecQuerySadOnebitaudioDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdp_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdpEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdpEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDdpDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadDdpDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtshd_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtshdEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtshdEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDtshdDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadDtshdDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadTruehd_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadTruehdEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadTruehdEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadTruehdDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadTruehdDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDst_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDstEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDstEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadDstDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadDstDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadWmapro_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadWmaproEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadWmaproEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadWmaproDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadWmaproDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMax_userConfigurable);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMaxEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMaxEnabled_default);
+ doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMaxDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadMaxDisabled_default);
+
return resources;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index c1d9857..85d30a6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -26,13 +25,11 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.hdmi.HdmiControlManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.provider.Settings.Global;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -40,16 +37,12 @@
import com.android.internal.R;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
@@ -96,7 +89,22 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
HdmiControlManager
- .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU);
+ .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX);
}
@Test
@@ -119,7 +127,22 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
HdmiControlManager
- .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU);
+ .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX);
}
@Test
@@ -142,7 +165,22 @@
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
HdmiControlManager
- .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU);
+ .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX);
}
@Test
@@ -367,18 +405,6 @@
}
@Test
- public void getIntValue_GlobalSetting_BasicSanity() {
- when(mStorageAdapter.retrieveGlobalSetting(
- Global.HDMI_CONTROL_ENABLED,
- Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED)))
- .thenReturn(Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED));
- HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter);
- assertThat(hdmiCecConfig.getIntValue(
- HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED))
- .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
- }
-
- @Test
public void getIntValue_SharedPref_BasicSanity() {
when(mStorageAdapter.retrieveSharedPref(
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
@@ -454,16 +480,6 @@
}
@Test
- public void setIntValue_GlobalSetting_BasicSanity() {
- HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter);
- hdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
- HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
- verify(mStorageAdapter).storeGlobalSetting(
- Global.HDMI_CONTROL_ENABLED,
- Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED));
- }
-
- @Test
public void setIntValue_SharedPref_BasicSanity() {
HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter);
hdmiCecConfig.setIntValue(
@@ -502,51 +518,4 @@
verify(mSettingChangeListener, never()).onChange(
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING);
}
-
- /**
- * Externally modified Global Settings still need to be supported. This test verifies that
- * setting change notification is being forwarded to listeners registered via HdmiCecConfig.
- */
- @Test
- @Ignore("b/175381065")
- public void globalSettingObserver_BasicSanity() throws Exception {
- CountDownLatch notifyLatch = new CountDownLatch(1);
- // Get current value of the setting in the system.
- String originalValue = Global.getString(mContext.getContentResolver(),
- Global.HDMI_CONTROL_ENABLED);
- try {
- HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter);
- hdmiCecConfig.registerGlobalSettingsObserver(mTestLooper.getLooper());
- HdmiCecConfig.SettingChangeListener latchUpdateListener =
- new HdmiCecConfig.SettingChangeListener() {
- @Override
- public void onChange(
- @NonNull @HdmiControlManager.CecSettingName String setting) {
- notifyLatch.countDown();
- assertThat(setting).isEqualTo(
- HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
- }
- };
- hdmiCecConfig.registerChangeListener(
- HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
- latchUpdateListener);
-
- // Flip the value of the setting.
- String valueToSet = ((originalValue == null || originalValue.equals("1")) ? "0" : "1");
- Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED,
- valueToSet);
- assertThat(Global.getString(mContext.getContentResolver(),
- Global.HDMI_CONTROL_ENABLED)).isEqualTo(valueToSet);
- mTestLooper.dispatchAll();
-
- if (!notifyLatch.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
- fail("Timed out waiting for the notify callback");
- }
- hdmiCecConfig.unregisterGlobalSettingsObserver();
- } finally {
- // Restore the previous value of the setting in the system.
- Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED,
- originalValue);
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 823ed2a..2d81fc9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -22,6 +22,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -68,6 +69,7 @@
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mTvLogicalAddress;
private List<byte[]> mSupportedSads;
private RequestSadCallback mCallback =
new RequestSadCallback() {
@@ -94,7 +96,7 @@
mMyLooper = mTestLooper.getLooper();
mHdmiControlService =
- new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+ new HdmiControlService(context,
Collections.emptyList()) {
@Override
boolean isControlEnabled() {
@@ -129,6 +131,9 @@
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
+ synchronized (mHdmiCecLocalDeviceTv.mLock) {
+ mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
+ }
mNativeWrapper.clearResultMessages();
}
@@ -140,8 +145,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
mNativeWrapper.clearResultMessages();
@@ -153,8 +157,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
mNativeWrapper.clearResultMessages();
@@ -166,8 +169,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
mNativeWrapper.clearResultMessages();
@@ -179,8 +181,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
mNativeWrapper.clearResultMessages();
@@ -200,14 +201,12 @@
action.start();
mTestLooper.dispatchAll();
HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress,
Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
Constants.ABORT_INVALID_OPERAND);
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
mNativeWrapper.clearResultMessages();
@@ -215,8 +214,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
mNativeWrapper.clearResultMessages();
@@ -224,8 +222,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
mNativeWrapper.clearResultMessages();
@@ -233,8 +230,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
action.processCommand(featureAbort);
@@ -251,8 +247,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
0x01, 0x18, 0x4A,
@@ -260,16 +255,14 @@
0x03, 0x4B, 0x00,
0x04, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
mNativeWrapper.clearResultMessages();
action.processCommand(response1);
mTestLooper.dispatchAll();
HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
0x05, 0x18, 0x4A,
@@ -277,16 +270,14 @@
0x07, 0x4B, 0x00,
0x08, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
mNativeWrapper.clearResultMessages();
action.processCommand(response2);
mTestLooper.dispatchAll();
HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[]{
0x09, 0x18, 0x4A,
@@ -294,24 +285,21 @@
0x0B, 0x4B, 0x00,
0x0C, 0x20, 0x0A};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
mNativeWrapper.clearResultMessages();
action.processCommand(response3);
mTestLooper.dispatchAll();
HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
0x0D, 0x18, 0x4A,
0x0E, 0x64, 0x5A,
0x0F, 0x4B, 0x00};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
action.processCommand(response4);
mTestLooper.dispatchAll();
@@ -335,38 +323,33 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
0x01, 0x18, 0x4A,
0x03, 0x4B, 0x00,
0x04, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
mNativeWrapper.clearResultMessages();
action.processCommand(response1);
mTestLooper.dispatchAll();
HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
0x08, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
mNativeWrapper.clearResultMessages();
action.processCommand(response2);
mTestLooper.dispatchAll();
HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[]{
0x09, 0x18, 0x4A,
@@ -374,22 +357,19 @@
0x0B, 0x4B, 0x00,
0x0C, 0x20, 0x0A};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
mNativeWrapper.clearResultMessages();
action.processCommand(response3);
mTestLooper.dispatchAll();
HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
0x0F, 0x4B, 0x00};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
action.processCommand(response4);
mTestLooper.dispatchAll();
@@ -413,8 +393,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
0x20, 0x18, 0x4A,
@@ -422,16 +401,14 @@
0x22, 0x4B, 0x00,
0x23, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
mNativeWrapper.clearResultMessages();
action.processCommand(response1);
mTestLooper.dispatchAll();
HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
0x24, 0x18, 0x4A,
@@ -439,16 +416,14 @@
0x26, 0x4B, 0x00,
0x27, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
mNativeWrapper.clearResultMessages();
action.processCommand(response2);
mTestLooper.dispatchAll();
HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[]{
0x28, 0x18, 0x4A,
@@ -456,24 +431,21 @@
0x2A, 0x4B, 0x00,
0x2B, 0x20, 0x0A};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
mNativeWrapper.clearResultMessages();
action.processCommand(response3);
mTestLooper.dispatchAll();
HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
0x2C, 0x18, 0x4A,
0x2D, 0x64, 0x5A,
0x2E, 0x4B, 0x00};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
action.processCommand(response4);
mTestLooper.dispatchAll();
@@ -489,8 +461,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
0x01, 0x18,
@@ -498,8 +469,7 @@
0x03, 0x4B, 0x00,
0x04, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
mNativeWrapper.clearResultMessages();
action.processCommand(response1);
@@ -512,8 +482,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
0x05, 0x18, 0x4A,
@@ -521,8 +490,7 @@
0x07,
0x08, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
mNativeWrapper.clearResultMessages();
action.processCommand(response2);
@@ -535,13 +503,11 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[0];
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
mNativeWrapper.clearResultMessages();
action.processCommand(response3);
@@ -554,16 +520,14 @@
mTestLooper.dispatchAll();
HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_AUDIO_SYSTEM,
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
0x0D, 0x18, 0x4A,
0x0E, 0x64, 0x5A,
0x0F, 0x4B};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
- Constants.ADDR_AUDIO_SYSTEM,
- mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
mNativeWrapper.clearResultMessages();
action.processCommand(response4);
@@ -576,4 +540,77 @@
assertThat(mSupportedSads.size()).isEqualTo(0);
}
+
+ @Test
+ public void selectedSads_allSupported_completeResult() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ HdmiControlManager.QUERY_SAD_DISABLED);
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ HdmiControlManager.QUERY_SAD_DISABLED);
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ HdmiControlManager.QUERY_SAD_DISABLED);
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
+ new int[]{Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD,
+ Constants.AUDIO_CODEC_MP3, Constants.AUDIO_CODEC_MPEG2});
+ byte[] sadsToRespond_1 = new byte[]{
+ 0x01, 0x18, 0x4A,
+ 0x02, 0x64, 0x5A,
+ 0x04, 0x20, 0x0A,
+ 0x05, 0x18, 0x4A};
+ HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response1);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
+ new int[]{Constants.AUDIO_CODEC_AAC, Constants.AUDIO_CODEC_DTS,
+ Constants.AUDIO_CODEC_ATRAC, Constants.AUDIO_CODEC_DDP});
+ byte[] sadsToRespond_2 = new byte[]{
+ 0x06, 0x64, 0x5A,
+ 0x07, 0x4B, 0x00,
+ 0x08, 0x20, 0x0A,
+ 0x09, 0x18, 0x4A};
+ HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response2);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
+ new int[]{Constants.AUDIO_CODEC_DTSHD, Constants.AUDIO_CODEC_TRUEHD,
+ Constants.AUDIO_CODEC_DST, Constants.AUDIO_CODEC_MAX});
+ byte[] sadsToRespond_3 = new byte[]{
+ 0x0B, 0x4B, 0x00,
+ 0x0C, 0x20, 0x0A,
+ 0x0D, 0x18, 0x4A,
+ 0x0F, 0x4B, 0x00};
+ HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response3);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(12);
+ assertThat(Arrays.equals(sadsToRespond_1,
+ concatenateSads(mSupportedSads.subList(0, 4)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_2,
+ concatenateSads(mSupportedSads.subList(4, 8)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_3,
+ concatenateSads(mSupportedSads.subList(8, 12)))).isTrue();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
new file mode 100644
index 0000000..bda7cf6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job;
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+import com.android.server.job.MockBiasJobService.TestEnvironment;
+import com.android.server.job.MockBiasJobService.TestEnvironment.Event;
+
+import java.util.ArrayList;
+
+@TargetApi(24)
+public class BiasSchedulingTest extends AndroidTestCase {
+ /** Environment that notifies of JobScheduler callbacks. */
+ private static final TestEnvironment sTestEnvironment = TestEnvironment.getTestEnvironment();
+ /** Handle for the service which receives the execution callbacks from the JobScheduler. */
+ private static ComponentName sJobServiceComponent;
+ private JobScheduler mJobScheduler;
+
+ // The system overrides the test app bias to be a minimum of FOREGROUND_SERVICE. We can
+ // bypass that override by using a bias of at least bound foreground service.
+ private static final int HIGH_BIAS = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE + 1;
+ private static final int LOW_BIAS = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ sTestEnvironment.setUp();
+ sJobServiceComponent = new ComponentName(getContext(), MockBiasJobService.class);
+ mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ mJobScheduler.cancelAll();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mJobScheduler.cancelAll();
+ super.tearDown();
+ }
+
+ public void testLowerBiasJobPreempted() throws Exception {
+ for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
+ JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent)
+ .setBias(LOW_BIAS)
+ .setOverrideDeadline(0)
+ .build();
+ mJobScheduler.schedule(job);
+ }
+ final int higherBiasJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+ JobInfo jobHigher = new JobInfo.Builder(higherBiasJobId, sJobServiceComponent)
+ .setBias(HIGH_BIAS)
+ .setMinimumLatency(2000)
+ .setOverrideDeadline(4000)
+ .build();
+ mJobScheduler.schedule(jobHigher);
+ Thread.sleep(10000); // Wait for jobHigher to preempt one of the lower bias jobs
+
+ Event jobHigherExecution = new Event(TestEnvironment.EVENT_START_JOB, higherBiasJobId);
+ ArrayList<Event> executedEvents = sTestEnvironment.getExecutedEvents();
+ boolean wasJobHigherExecuted = executedEvents.contains(jobHigherExecution);
+ boolean wasSomeJobPreempted = false;
+ for (Event event: executedEvents) {
+ if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
+ wasSomeJobPreempted = true;
+ break;
+ }
+ }
+ assertTrue("No job was preempted.", wasSomeJobPreempted);
+ assertTrue("Lower bias jobs were not preempted.", wasJobHigherExecuted);
+ }
+
+ public void testHigherBiasJobNotPreempted() throws Exception {
+ for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
+ JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent)
+ .setBias(HIGH_BIAS)
+ .setOverrideDeadline(0)
+ .build();
+ mJobScheduler.schedule(job);
+ }
+ final int lowerBiasJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+ JobInfo jobLower = new JobInfo.Builder(lowerBiasJobId, sJobServiceComponent)
+ .setBias(LOW_BIAS)
+ .setMinimumLatency(2000)
+ .setOverrideDeadline(3000)
+ .build();
+ mJobScheduler.schedule(jobLower);
+ Thread.sleep(10000);
+
+ Event jobLowerExecution = new Event(TestEnvironment.EVENT_START_JOB, lowerBiasJobId);
+ boolean wasLowerExecuted = sTestEnvironment.getExecutedEvents().contains(jobLowerExecution);
+ assertFalse("Higher bias job was preempted.", wasLowerExecuted);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 8eb3cf3..243f7b4 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -313,10 +313,10 @@
}
@Test
- public void testPriorityPersisted() throws Exception {
+ public void testBiasPersisted() throws Exception {
JobInfo.Builder b = new Builder(92, mComponent)
.setOverrideDeadline(5000)
- .setPriority(42)
+ .setBias(42)
.setPersisted(true);
final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(js);
@@ -325,7 +325,7 @@
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
- assertEquals("Priority not correctly persisted.", 42, loaded.getPriority());
+ assertEquals("Bias not correctly persisted.", 42, loaded.getBias());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockBiasJobService.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
rename to services/tests/servicestests/src/com/android/server/job/MockBiasJobService.java
index 87881bf..d324d62 100644
--- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
+++ b/services/tests/servicestests/src/com/android/server/job/MockBiasJobService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.server.job;
@@ -24,8 +24,8 @@
import java.util.ArrayList;
@TargetApi(24)
-public class MockPriorityJobService extends JobService {
- private static final String TAG = "MockPriorityJobService";
+public class MockBiasJobService extends JobService {
+ private static final String TAG = "MockBiasJobService";
@Override
public void onCreate() {
@@ -36,7 +36,7 @@
@Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "Test job executing: " + params.getJobId());
- TestEnvironment.getTestEnvironment().executedEvents.add(
+ TestEnvironment.getTestEnvironment().mExecutedEvents.add(
new TestEnvironment.Event(TestEnvironment.EVENT_START_JOB, params.getJobId()));
return true; // Job not finished
}
@@ -51,7 +51,7 @@
event = TestEnvironment.EVENT_PREEMPT_JOB;
Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
}
- TestEnvironment.getTestEnvironment().executedEvents
+ TestEnvironment.getTestEnvironment().mExecutedEvents
.add(new TestEnvironment.Event(event, params.getJobId()));
return false; // Do not reschedule
}
@@ -62,15 +62,15 @@
public static final int EVENT_PREEMPT_JOB = 1;
public static final int EVENT_STOP_JOB = 2;
- private static TestEnvironment kTestEnvironment;
+ private static TestEnvironment sTestEnvironment;
- private ArrayList<Event> executedEvents = new ArrayList<Event>();
+ private final ArrayList<Event> mExecutedEvents = new ArrayList<>();
public static TestEnvironment getTestEnvironment() {
- if (kTestEnvironment == null) {
- kTestEnvironment = new TestEnvironment();
+ if (sTestEnvironment == null) {
+ sTestEnvironment = new TestEnvironment();
}
- return kTestEnvironment;
+ return sTestEnvironment;
}
public static class Event {
@@ -96,11 +96,11 @@
}
public void setUp() {
- executedEvents.clear();
+ mExecutedEvents.clear();
}
public ArrayList<Event> getExecutedEvents() {
- return executedEvents;
+ return mExecutedEvents;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
deleted file mode 100644
index 9ecba59..0000000
--- a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import android.annotation.TargetApi;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.test.AndroidTestCase;
-
-import com.android.server.job.MockPriorityJobService.TestEnvironment;
-import com.android.server.job.MockPriorityJobService.TestEnvironment.Event;
-
-import java.util.ArrayList;
-
-@TargetApi(24)
-public class PrioritySchedulingTest extends AndroidTestCase {
- /** Environment that notifies of JobScheduler callbacks. */
- static TestEnvironment kTestEnvironment = TestEnvironment.getTestEnvironment();
- /** Handle for the service which receives the execution callbacks from the JobScheduler. */
- static ComponentName kJobServiceComponent;
- JobScheduler mJobScheduler;
-
- // The system overrides the test app priority to be a minimum of FOREGROUND_SERVICE. We can
- // bypass that override by using a priority of at least bound foreground service.
- private static final int HIGH_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE + 1;
- private static final int LOW_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- kTestEnvironment.setUp();
- kJobServiceComponent = new ComponentName(getContext(), MockPriorityJobService.class);
- mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
- mJobScheduler.cancelAll();
- }
-
- @Override
- public void tearDown() throws Exception {
- mJobScheduler.cancelAll();
- super.tearDown();
- }
-
- public void testLowerPriorityJobPreempted() throws Exception {
- for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
- JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent)
- .setPriority(LOW_PRIORITY)
- .setOverrideDeadline(0)
- .build();
- mJobScheduler.schedule(job);
- }
- final int higherPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
- JobInfo jobHigher = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent)
- .setPriority(HIGH_PRIORITY)
- .setMinimumLatency(2000)
- .setOverrideDeadline(4000)
- .build();
- mJobScheduler.schedule(jobHigher);
- Thread.sleep(10000); // Wait for jobHigher to preempt one of the lower priority jobs
-
- Event jobHigherExecution = new Event(TestEnvironment.EVENT_START_JOB, higherPriorityJobId);
- ArrayList<Event> executedEvents = kTestEnvironment.getExecutedEvents();
- boolean wasJobHigherExecuted = executedEvents.contains(jobHigherExecution);
- boolean wasSomeJobPreempted = false;
- for (Event event: executedEvents) {
- if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
- wasSomeJobPreempted = true;
- break;
- }
- }
- assertTrue("No job was preempted.", wasSomeJobPreempted);
- assertTrue("Lower priority jobs were not preempted.", wasJobHigherExecuted);
- }
-
- public void testHigherPriorityJobNotPreempted() throws Exception {
- for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
- JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent)
- .setPriority(HIGH_PRIORITY)
- .setOverrideDeadline(0)
- .build();
- mJobScheduler.schedule(job);
- }
- final int lowerPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
- JobInfo jobLower = new JobInfo.Builder(lowerPriorityJobId, kJobServiceComponent)
- .setPriority(LOW_PRIORITY)
- .setMinimumLatency(2000)
- .setOverrideDeadline(3000)
- .build();
- mJobScheduler.schedule(jobLower);
- Thread.sleep(10000);
-
- Event jobLowerExecution = new Event(TestEnvironment.EVENT_START_JOB, lowerPriorityJobId);
- boolean wasLowerExecuted = kTestEnvironment.getExecutedEvents().contains(jobLowerExecution);
- assertFalse("Higher priority job was preempted.", wasLowerExecuted);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
index 1613b11..6cdae53 100644
--- a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
+++ b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
@@ -43,7 +43,9 @@
}
@Override
- public void commit() {}
+ public boolean commit() {
+ return mLocales != null;
+ }
/**
* Returns the locales that were stored during the test run. Returns {@code null} if no locales
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 2bda120..847fe2e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -348,24 +348,6 @@
}
@Test
- public void testInstallPackageDowngrade() throws Exception {
- File activeApex = extractResource("test.apex_rebootless_v2",
- "test.rebootless_apex_v2.apex");
- ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
- /* isFactory= */ false, activeApex);
- when(mApexService.getAllPackages()).thenReturn(new ApexInfo[]{activeApexInfo});
- mApexManager.scanApexPackagesTraced(mPackageParser2,
- ParallelPackageParser.makeExecutorService());
-
- File installedApex = extractResource("test.apex_rebootless_v1",
- "test.rebootless_apex_v1.apex");
- PackageManagerException e = expectThrows(PackageManagerException.class,
- () -> mApexManager.installPackage(installedApex, mPackageParser2));
- assertThat(e).hasMessageThat().contains(
- "Downgrade of APEX package test.apex.rebootless is not allowed");
- }
-
- @Test
public void testInstallPackage_activeOnSystem() throws Exception {
ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
/* isFactory= */ true, extractResource("test.apex_rebootless_v1",
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 76f233c..f91661b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,9 +35,13 @@
import android.content.pm.UserInfo;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedActivityImpl;
import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedInstrumentationImpl;
import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedIntentInfoImpl;
import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedProviderImpl;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -82,9 +86,17 @@
private static final int DUMMY_OVERLAY_APPID = 10756;
private static final int SYSTEM_USER = 0;
private static final int SECONDARY_USER = 10;
+ private static final int ADDED_USER = 11;
private static final int[] USER_ARRAY = {SYSTEM_USER, SECONDARY_USER};
- private static final UserInfo[] USER_INFO_LIST = Arrays.stream(USER_ARRAY).mapToObj(
- id -> new UserInfo(id, Integer.toString(id), 0)).toArray(UserInfo[]::new);
+ private static final int[] USER_ARRAY_WITH_ADDED = {SYSTEM_USER, SECONDARY_USER, ADDED_USER};
+ private static final UserInfo[] USER_INFO_LIST = toUserInfos(USER_ARRAY);
+ private static final UserInfo[] USER_INFO_LIST_WITH_ADDED = toUserInfos(USER_ARRAY_WITH_ADDED);
+
+ private static UserInfo[] toUserInfos(int[] userIds) {
+ return Arrays.stream(userIds)
+ .mapToObj(id -> new UserInfo(id, Integer.toString(id), 0))
+ .toArray(UserInfo[]::new);
+ }
@Mock
AppsFilter.FeatureConfig mFeatureConfigMock;
@@ -146,21 +158,22 @@
}
private static ParsedActivity createActivity(String packageName, IntentFilter[] filters) {
- ParsedActivity activity = new ParsedActivity();
+ ParsedActivityImpl activity = new ParsedActivityImpl();
activity.setPackageName(packageName);
for (IntentFilter filter : filters) {
- final ParsedIntentInfo info = new ParsedIntentInfo();
+ final ParsedIntentInfoImpl info = new ParsedIntentInfoImpl();
+ final IntentFilter intentInfoFilter = info.getIntentFilter();
if (filter.countActions() > 0) {
- filter.actionsIterator().forEachRemaining(info::addAction);
+ filter.actionsIterator().forEachRemaining(intentInfoFilter::addAction);
}
if (filter.countCategories() > 0) {
- filter.actionsIterator().forEachRemaining(info::addAction);
+ filter.actionsIterator().forEachRemaining(intentInfoFilter::addAction);
}
if (filter.countDataAuthorities() > 0) {
- filter.authoritiesIterator().forEachRemaining(info::addDataAuthority);
+ filter.authoritiesIterator().forEachRemaining(intentInfoFilter::addDataAuthority);
}
if (filter.countDataSchemes() > 0) {
- filter.schemesIterator().forEachRemaining(info::addDataScheme);
+ filter.schemesIterator().forEachRemaining(intentInfoFilter::addDataScheme);
}
activity.addIntent(info);
activity.setExported(true);
@@ -170,13 +183,13 @@
private static ParsingPackage pkgWithInstrumentation(
String packageName, String instrumentationTargetPackage) {
- ParsedInstrumentation instrumentation = new ParsedInstrumentation();
+ ParsedInstrumentationImpl instrumentation = new ParsedInstrumentationImpl();
instrumentation.setTargetPackage(instrumentationTargetPackage);
return pkg(packageName).addInstrumentation(instrumentation);
}
private static ParsingPackage pkgWithProvider(String packageName, String authority) {
- ParsedProvider provider = new ParsedProvider();
+ ParsedProviderImpl provider = new ParsedProviderImpl();
provider.setPackageName(packageName);
provider.setExported(true);
provider.setAuthority(authority);
@@ -318,6 +331,64 @@
}
@Test
+ public void testOnUserUpdated_FilterMatches() throws Exception {
+ final AppsFilter appsFilter =
+ new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ mMockExecutor);
+ simulateAddBasicAndroid(appsFilter);
+
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkgQueriesProvider("com.some.other.package", "com.some.authority"),
+ DUMMY_CALLING_APPID);
+
+ for (int subjectUserId : USER_ARRAY) {
+ for (int otherUserId : USER_ARRAY) {
+ assertFalse(appsFilter.shouldFilterApplication(
+ UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
+ otherUserId));
+ }
+ }
+
+ // adds new user
+ doAnswer(invocation -> {
+ ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+ .currentState(mExisting, USER_INFO_LIST_WITH_ADDED);
+ return new Object();
+ }).when(mStateProvider)
+ .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
+ appsFilter.onUserCreated(ADDED_USER);
+
+ for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
+ for (int otherUserId : USER_ARRAY_WITH_ADDED) {
+ assertFalse(appsFilter.shouldFilterApplication(
+ UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
+ otherUserId));
+ }
+ }
+
+ // delete user
+ doAnswer(invocation -> {
+ ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+ .currentState(mExisting, USER_INFO_LIST);
+ return new Object();
+ }).when(mStateProvider)
+ .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
+ appsFilter.onUserDeleted(ADDED_USER);
+
+ for (int subjectUserId : USER_ARRAY) {
+ for (int otherUserId : USER_ARRAY) {
+ assertFalse(appsFilter.shouldFilterApplication(
+ UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
+ otherUserId));
+ }
+ }
+ }
+
+ @Test
public void testQueriesDifferentProvider_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index c85b2e8..cbd1aa3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -45,14 +45,24 @@
import android.content.pm.SigningDetails;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedActivityImpl;
import android.content.pm.parsing.component.ParsedComponent;
import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedInstrumentationImpl;
import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedIntentInfoImpl;
import android.content.pm.parsing.component.ParsedPermission;
import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedPermissionGroupImpl;
+import android.content.pm.parsing.component.ParsedPermissionImpl;
+import android.content.pm.parsing.component.ParsedPermissionUtils;
import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedProviderImpl;
import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedServiceImpl;
import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.parsing.component.ParsedUsesPermissionImpl;
+import android.content.pm.permission.CompatibilityPermissionInfo;
import android.content.pm.pkg.PackageUserState;
import android.os.Bundle;
import android.os.Parcel;
@@ -522,7 +532,7 @@
final ParsedPackage pkg = new TestPackageParser2()
.parsePackage(testFile, 0 /*flags*/, false /*useCaches*/);
final List<String> compatPermissions =
- Arrays.stream(COMPAT_PERMS).map(ParsedUsesPermission::getName)
+ Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
.collect(toList());
assertWithMessage(
"Compatibility permissions shouldn't be added into uses permissions.")
@@ -548,19 +558,19 @@
.parsePackage(testFile, 0 /*flags*/, false /*useCaches*/);
assertWithMessage(
"Compatibility permissions should be added into uses permissions.")
- .that(Arrays.stream(COMPAT_PERMS).map(ParsedUsesPermission::getName)
+ .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
.allMatch(pkg.getUsesPermissions().stream()
.map(ParsedUsesPermission::getName)
.collect(toList())::contains))
.isTrue();
assertWithMessage(
"Compatibility permissions should be added into requested permissions.")
- .that(Arrays.stream(COMPAT_PERMS).map(ParsedUsesPermission::getName)
+ .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
.allMatch(pkg.getRequestedPermissions()::contains))
.isTrue();
assertWithMessage(
"Compatibility permissions should be added into implicit permissions.")
- .that(Arrays.stream(COMPAT_PERMS).map(ParsedUsesPermission::getName)
+ .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
.allMatch(pkg.getImplicitPermissions()::contains))
.isTrue();
} finally {
@@ -770,7 +780,8 @@
// Verify basic flags in PermissionInfo to make sure they're consistent. We don't perform
// a full structural equality here because the code that serializes them isn't parser
// specific and is tested elsewhere.
- assertEquals(a.getProtection(), b.getProtection());
+ assertEquals(ParsedPermissionUtils.getProtection(a),
+ ParsedPermissionUtils.getProtection(b));
assertEquals(a.getGroup(), b.getGroup());
assertEquals(a.getFlags(), b.getFlags());
@@ -895,8 +906,8 @@
Bundle bundle = new Bundle();
bundle.putString("key", "value");
- ParsedPermission permission = new ParsedPermission();
- permission.setParsedPermissionGroup(new ParsedPermissionGroup());
+ ParsedPermissionImpl permission = new ParsedPermissionImpl();
+ permission.setParsedPermissionGroup(new ParsedPermissionGroupImpl());
((ParsedPackage) pkg.setBaseRevisionCode(100)
.setBaseHardwareAccelerated(true)
@@ -912,13 +923,13 @@
.setUse32BitAbi(true)
.setVolumeUuid("d52ef59a-7def-4541-bf21-4c28ed4b65a0")
.addPermission(permission)
- .addPermissionGroup(new ParsedPermissionGroup())
- .addActivity(new ParsedActivity())
- .addReceiver(new ParsedActivity())
- .addProvider(new ParsedProvider())
- .addService(new ParsedService())
- .addInstrumentation(new ParsedInstrumentation())
- .addUsesPermission(new ParsedUsesPermission("foo7", 0))
+ .addPermissionGroup(new ParsedPermissionGroupImpl())
+ .addActivity(new ParsedActivityImpl())
+ .addReceiver(new ParsedActivityImpl())
+ .addProvider(new ParsedProviderImpl())
+ .addService(new ParsedServiceImpl())
+ .addInstrumentation(new ParsedInstrumentationImpl())
+ .addUsesPermission(new ParsedUsesPermissionImpl("foo7", 0))
.addImplicitPermission("foo25")
.addProtectedBroadcast("foo8")
.setStaticSharedLibName("foo23")
@@ -946,7 +957,7 @@
.setOverlayTarget("foo21")
.setOverlayPriority(100)
.setUpgradeKeySets(new ArraySet<>())
- .addPreferredActivityFilter("className", new ParsedIntentInfo())
+ .addPreferredActivityFilter("className", new ParsedIntentInfoImpl())
.addConfigPreference(new ConfigurationInfo())
.addReqFeature(new FeatureInfo())
.addFeatureGroup(new FeatureGroupInfo())
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 82bf2f4..6188bc4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -43,7 +43,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.parsing.ParsingPackage;
-import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.parsing.component.ParsedUsesPermissionImpl;
import android.content.res.TypedArray;
import android.os.Environment;
import android.os.UserHandle;
@@ -89,6 +89,8 @@
PackageManagerServiceInjector mMockInjector;
@Mock
PackageManagerService mMockPackageManager;
+ @Mock
+ Installer mMockInstaller;
@Before
public void setupInjector() {
@@ -103,6 +105,7 @@
when(mMockInjector.getDomainVerificationManagerInternal())
.thenReturn(domainVerificationManager);
+ when(mMockInjector.getInstaller()).thenReturn(mMockInstaller);
}
@Before
@@ -432,9 +435,11 @@
@Test
public void factoryTestFlagSet() throws Exception {
final ParsingPackage basicPackage = createBasicPackage(DUMMY_PACKAGE_NAME)
- .addUsesPermission(new ParsedUsesPermission(Manifest.permission.FACTORY_TEST, 0));
+ .addUsesPermission(
+ new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
- final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mMockPackageManager);
+ final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
+ mMockPackageManager, mMockInjector);
final ScanResult scanResult = scanPackageHelper.scanPackageOnlyLI(
createBasicScanRequestBuilder(basicPackage).build(),
mMockInjector,
@@ -483,7 +488,8 @@
private ScanResult executeScan(
ScanRequest scanRequest) throws PackageManagerException {
- final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mMockPackageManager);
+ final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
+ mMockPackageManager, mMockInjector);
ScanResult result = scanPackageHelper.scanPackageOnlyLI(
scanRequest,
mMockInjector,
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 1502839..c990342 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -23,6 +23,7 @@
import android.apex.ApexInfo;
import android.content.Context;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -33,7 +34,9 @@
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageUtils;
import android.content.pm.parsing.component.ParsedComponent;
+import android.content.pm.parsing.component.ParsedIntentInfo;
import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
@@ -63,6 +66,7 @@
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -409,7 +413,7 @@
private void assertPermission(String name, int protectionLevel, ParsedPermission permission) {
assertEquals(name, permission.getName());
- assertEquals(protectionLevel, permission.getProtection());
+ assertEquals(protectionLevel, ParsedPermissionUtils.getProtection(permission));
}
private void assertMetadata(Bundle b, String... keysAndValues) {
@@ -512,12 +516,13 @@
findAndRemoveAppDetailsActivity(p);
assertEquals("Expected exactly one activity", 1, p.getActivities().size());
- assertEquals("Expected exactly one intent filter",
- 1, p.getActivities().get(0).getIntents().size());
- assertEquals("Expected exactly one mime group in intent filter",
- 1, p.getActivities().get(0).getIntents().get(0).countMimeGroups());
+ List<ParsedIntentInfo> intentInfos = p.getActivities().get(0).getIntents();
+ assertEquals("Expected exactly one intent filter", 1, intentInfos.size());
+ IntentFilter intentFilter = intentInfos.get(0).getIntentFilter();
+ assertEquals("Expected exactly one mime group in intent filter", 1,
+ intentFilter.countMimeGroups());
assertTrue("Did not find expected mime group 'mime_group_1'",
- p.getActivities().get(0).getIntents().get(0).hasMimeGroup("mime_group_1"));
+ intentFilter.hasMimeGroup("mime_group_1"));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
index 4dc9a90..c4aa862 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -20,10 +20,7 @@
import android.content.Context
import android.content.pm.parsing.ParsingPackage
import android.content.pm.parsing.ParsingPackageUtils
-import android.content.pm.parsing.result.ParseInput
-import android.content.pm.parsing.result.ParseInput.DeferredError
import android.content.pm.parsing.result.ParseResult
-import android.os.Build
import androidx.test.InstrumentationRegistry
import com.android.frameworks.servicestests.R
import com.google.common.truth.Truth.assertThat
@@ -54,14 +51,6 @@
private val context: Context = InstrumentationRegistry.getContext()
- private val inputCallback = ParseInput.Callback { changeId, _, targetSdk ->
- when (changeId) {
- DeferredError.MISSING_APP_TAG -> targetSdk > Build.VERSION_CODES.Q
- DeferredError.EMPTY_INTENT_ACTION_CATEGORY -> targetSdk > Build.VERSION_CODES.Q
- else -> throw IllegalStateException("changeId $changeId is not mocked for test")
- }
- }
-
@get:Rule
val tempFolder = TemporaryFolder(context.filesDir)
@@ -76,8 +65,9 @@
assertThat(first.name).isEqualTo(TEST_ACTIVITY)
val intents = first.intents
assertThat(intents).hasSize(1)
- assertThat(intents.first().hasCategory(TEST_CATEGORY)).isTrue()
- assertThat(intents.first().hasAction(TEST_ACTION)).isTrue()
+ val intentFilter = intents.first().intentFilter
+ assertThat(intentFilter.hasCategory(TEST_CATEGORY)).isTrue()
+ assertThat(intentFilter.hasAction(TEST_ACTION)).isTrue()
}
@Test
@@ -97,7 +87,8 @@
assertThat(first.name).isEqualTo(TEST_ACTIVITY)
val intents = first.intents
assertThat(intents).hasSize(1)
- assertThat(intents.first().hasAction(TEST_ACTION)).isTrue()
+ val intentFilter = intents.first().intentFilter
+ assertThat(intentFilter.hasAction(TEST_ACTION)).isTrue()
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index e84e365..e47a07c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -866,6 +866,8 @@
startSystem();
advanceTime(20);
assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+ PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
}
@Test
@@ -886,6 +888,80 @@
assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
advanceTime(60);
assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+ PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+ }
+
+ @Test
+ public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected()
+ throws Exception {
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+ final String pkg = mContextSpy.getOpPackageName();
+ final Binder token = new Binder();
+ final String tag = "testInattentiveSleep_wakeLockOnAfterRelease";
+
+ setMinimumScreenOffTimeoutConfig(5);
+ setAttentiveTimeout(2000);
+ createService();
+ startSystem();
+
+ mService.getBinderServiceInstance().acquireWakeLock(token,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
+ null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+
+ advanceTime(1500);
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+ advanceTime(520);
+ assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+ PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+ }
+
+ @Test
+ public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected()
+ throws Exception {
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+ setMinimumScreenOffTimeoutConfig(5);
+ setAttentiveTimeout(2000);
+ createService();
+ startSystem();
+
+ advanceTime(1500);
+ mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
+
+ advanceTime(520);
+ assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+ PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+ }
+
+ @Test
+ public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended()
+ throws Exception {
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+ setMinimumScreenOffTimeoutConfig(5);
+ setAttentiveTimeout(2000);
+ createService();
+ startSystem();
+
+ advanceTime(1500);
+ mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER, 0 /* flags */);
+
+ advanceTime(520);
+ assertThat(mService.getWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index aaa74dd..36e988f 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -17,6 +17,7 @@
package com.android.server.power;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -457,4 +458,33 @@
watcher.mSevereThresholds.erase();
assertTrue(Float.isNaN(watcher.getForecast(0)));
}
+
+ @Test
+ public void testTemperatureWatcherGetForecastUpdate() throws Exception {
+ ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
+
+ // Reduce the inactivity threshold to speed up testing
+ watcher.mInactivityThresholdMillis = 2000;
+
+ // Make sure mSamples is empty before updateTemperature
+ assertTrue(isWatcherSamplesEmpty(watcher));
+
+ // Call getForecast once to trigger updateTemperature
+ watcher.getForecast(0);
+
+ // After 1 second, the samples should be updated
+ Thread.sleep(1000);
+ assertFalse(isWatcherSamplesEmpty(watcher));
+
+ // After mInactivityThresholdMillis, the samples should be cleared
+ Thread.sleep(watcher.mInactivityThresholdMillis);
+ assertTrue(isWatcherSamplesEmpty(watcher));
+ }
+
+ // Helper function to hold mSamples lock, avoid GuardedBy lint errors
+ private boolean isWatcherSamplesEmpty(ThermalManagerService.TemperatureWatcher watcher) {
+ synchronized (watcher.mSamples) {
+ return watcher.mSamples.isEmpty();
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 3f8cf9c..16cfd13 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -717,7 +717,8 @@
hwCallback.recognitionCallback(TestUtil.createRecognitionEvent_2_0(handle, status), 99);
mCanonical.flushCallbacks();
verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+ TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+ false);
}
{
@@ -732,7 +733,7 @@
mCanonical.flushCallbacks();
verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
- RecognitionStatus.SUCCESS);
+ RecognitionStatus.SUCCESS, false);
}
verifyNoMoreInteractions(canonicalCallback);
clearInvocations(canonicalCallback);
@@ -752,7 +753,22 @@
99);
mCanonical.flushCallbacks();
verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+ TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+ false);
+ }
+
+ {
+ final int handle = 87;
+ final int status = 3; // FORCED;
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+
+ hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
+ 99);
+ mCanonical.flushCallbacks();
+ verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
+ TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED,
+ true);
}
{
@@ -767,7 +783,21 @@
mCanonical.flushCallbacks();
verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
- RecognitionStatus.SUCCESS);
+ RecognitionStatus.SUCCESS, false);
+ }
+
+ {
+ final int handle = 102;
+ final int status = 3; // FORCED;
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+
+ hwCallback.phraseRecognitionCallback_2_1(
+ TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
+ mCanonical.flushCallbacks();
+ verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
+ TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+ RecognitionStatus.FORCED, true);
}
verifyNoMoreInteractions(canonicalCallback);
clearInvocations(canonicalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 1daf831..0eba6a3 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -286,16 +287,31 @@
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
- // Signal a capture from the driver.
- RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
- RecognitionStatus.SUCCESS);
+ {
+ // Signal a capture from the driver (with "still active").
+ RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
+ RecognitionStatus.SUCCESS, true);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
- verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
- // Validate the event.
- assertEquals(event, eventCaptor.getValue());
+ // Validate the event.
+ assertEquals(event, eventCaptor.getValue());
+ }
+
+ {
+ // Signal a capture from the driver (without "still active").
+ RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
+ RecognitionStatus.SUCCESS, false);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, times(2)).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
+
+ // Validate the event.
+ assertEquals(event, eventCaptor.getValue());
+ }
// Unload the model.
unloadModel(module, handle, hwHandle);
@@ -318,7 +334,7 @@
// Signal a capture from the driver.
PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
- RecognitionStatus.SUCCESS);
+ RecognitionStatus.SUCCESS, false);
ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
PhraseRecognitionEvent.class);
@@ -352,7 +368,7 @@
// Signal a capture from the driver.
RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
- RecognitionStatus.FORCED);
+ RecognitionStatus.FORCED, true);
ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
RecognitionEvent.class);
@@ -420,7 +436,7 @@
// Signal a capture from the driver.
PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
- RecognitionStatus.FORCED);
+ RecognitionStatus.FORCED, true);
ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
PhraseRecognitionEvent.class);
@@ -484,7 +500,7 @@
startRecognition(module, handle, hwHandle);
// Abort.
- hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED);
+ hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
RecognitionEvent.class);
@@ -514,7 +530,7 @@
startRecognition(module, handle, hwHandle);
// Abort.
- hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED);
+ hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
PhraseRecognitionEvent.class);
@@ -604,15 +620,18 @@
mCallback = callback;
}
- private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status) {
- RecognitionEvent event = TestUtil.createRecognitionEvent(status);
+ private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status,
+ boolean recognitionStillActive) {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(status,
+ recognitionStillActive);
mCallback.recognitionCallback(hwHandle, event);
return event;
}
private PhraseRecognitionEvent sendPhraseRecognitionEvent(int hwHandle,
- @RecognitionStatus int status) {
- PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status);
+ @RecognitionStatus int status, boolean recognitionStillActive) {
+ PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status,
+ recognitionStillActive);
mCallback.phraseRecognitionCallback(hwHandle, event);
return event;
}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 43d646a..e687a2a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -330,7 +330,8 @@
return format;
}
- static RecognitionEvent createRecognitionEvent(@RecognitionStatus int status) {
+ static RecognitionEvent createRecognitionEvent(@RecognitionStatus int status,
+ boolean recognitionStillActive) {
RecognitionEvent event = new RecognitionEvent();
event.status = status;
event.type = SoundModelType.GENERIC;
@@ -346,6 +347,7 @@
event.audioConfig.base.format = createAudioFormatMp3();
//event.audioConfig.offloadInfo is irrelevant.
event.data = new byte[]{31, 32, 33};
+ event.recognitionStillActive = recognitionStillActive;
return event;
}
@@ -360,7 +362,8 @@
return halEvent;
}
- static void validateRecognitionEvent(RecognitionEvent event, @RecognitionStatus int status) {
+ static void validateRecognitionEvent(RecognitionEvent event, @RecognitionStatus int status,
+ boolean recognitionStillActive) {
assertEquals(status, event.status);
assertEquals(SoundModelType.GENERIC, event.type);
assertTrue(event.captureAvailable);
@@ -372,11 +375,13 @@
event.audioConfig.base.channelMask);
assertEquals(createAudioFormatMp3(), event.audioConfig.base.format);
assertArrayEquals(new byte[]{31, 32, 33}, event.data);
+ assertEquals(recognitionStillActive, event.recognitionStillActive);
}
- static PhraseRecognitionEvent createPhraseRecognitionEvent(@RecognitionStatus int status) {
+ static PhraseRecognitionEvent createPhraseRecognitionEvent(@RecognitionStatus int status,
+ boolean recognitionStillActive) {
PhraseRecognitionEvent event = new PhraseRecognitionEvent();
- event.common = createRecognitionEvent(status);
+ event.common = createRecognitionEvent(status, recognitionStillActive);
PhraseRecognitionExtra extra = new PhraseRecognitionExtra();
extra.id = 123;
@@ -434,8 +439,8 @@
}
static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event,
- @RecognitionStatus int status) {
- validateRecognitionEvent(event.common, status);
+ @RecognitionStatus int status, boolean recognitionStillActive) {
+ validateRecognitionEvent(event.common, status, recognitionStillActive);
assertEquals(1, event.phraseExtras.length);
assertEquals(123, event.phraseExtras[0].id);
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/OWNERS b/services/tests/servicestests/src/com/android/server/timedetector/OWNERS
index 8f80897..a0f46e1 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /services/core/java/com/android/server/timedetector/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
index 8f80897..6165260 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
index 5870a70..79f8b0e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
@@ -34,26 +34,36 @@
@Test
public void testEquals() {
- GeolocationTimeZoneSuggestion one = new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS1);
- assertEquals(one, one);
+ long time1 = 1111L;
+ GeolocationTimeZoneSuggestion certain1v1 =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS1);
+ assertEquals(certain1v1, certain1v1);
- GeolocationTimeZoneSuggestion two = new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS1);
- assertEquals(one, two);
- assertEquals(two, one);
-
- GeolocationTimeZoneSuggestion nullZone = new GeolocationTimeZoneSuggestion(null);
- assertNotEquals(one, nullZone);
- assertNotEquals(nullZone, one);
- assertEquals(nullZone, nullZone);
-
- GeolocationTimeZoneSuggestion three =
- new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS2);
- assertNotEquals(one, three);
- assertNotEquals(three, one);
+ GeolocationTimeZoneSuggestion certain1v2 =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS1);
+ assertEquals(certain1v1, certain1v2);
+ assertEquals(certain1v2, certain1v1);
// DebugInfo must not be considered in equals().
- one.addDebugInfo("Debug info 1");
- two.addDebugInfo("Debug info 2");
- assertEquals(one, two);
+ certain1v1.addDebugInfo("Debug info 1");
+ certain1v2.addDebugInfo("Debug info 2");
+ assertEquals(certain1v1, certain1v2);
+
+ long time2 = 2222L;
+ GeolocationTimeZoneSuggestion certain2 =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1);
+ assertNotEquals(certain1v1, certain2);
+ assertNotEquals(certain2, certain1v1);
+
+ GeolocationTimeZoneSuggestion uncertain =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(time1);
+ assertNotEquals(certain1v1, uncertain);
+ assertNotEquals(uncertain, certain1v1);
+ assertEquals(uncertain, uncertain);
+
+ GeolocationTimeZoneSuggestion certain3 =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS2);
+ assertNotEquals(certain1v1, certain3);
+ assertNotEquals(certain3, certain1v1);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS b/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS
index 8f80897..a6ff1ba 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 918babc..5864620 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -35,6 +35,7 @@
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorInternalImplTest {
+ private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList("TestZoneId");
private Context mMockContext;
@@ -99,6 +100,7 @@
}
private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
- return new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
+ return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_ZONE_IDS);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 28838ae..773abf8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -50,11 +50,14 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorServiceTest {
private static final int ARBITRARY_USER_ID = 9999;
+ private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId");
+ private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private Context mMockContext;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategy;
@@ -374,7 +377,8 @@
}
private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
- return new GeolocationTimeZoneSuggestion(Arrays.asList("TestZoneId"));
+ return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_TIME_ZONE_IDS);
}
private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 331f76c..e2e8755 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -33,10 +33,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneConfiguration;
@@ -171,6 +174,8 @@
private FakeEnvironment mFakeEnvironment;
private MockConfigChangeListener mMockConfigChangeListener;
+ // A fake source of time for suggestions. This will typically be incremented after every use.
+ @ElapsedRealtimeLong private long mElapsedRealtimeMillis;
@Before
public void setUp() {
@@ -487,7 +492,7 @@
*/
// Each test case will have the same or lower score than the last.
- List<TelephonyTestCase> descendingCasesByScore = list(TELEPHONY_TEST_CASES);
+ List<TelephonyTestCase> descendingCasesByScore = Arrays.asList(TELEPHONY_TEST_CASES);
Collections.reverse(descendingCasesByScore);
for (TelephonyTestCase testCase : descendingCasesByScore) {
@@ -750,7 +755,7 @@
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
- GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
+ GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
.verifyTimeZoneNotChanged();
@@ -766,7 +771,7 @@
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
- GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
+ GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
.verifyTimeZoneNotChanged();
@@ -778,7 +783,7 @@
@Test
public void testGeoSuggestion_oneZone() {
GeolocationTimeZoneSuggestion suggestion =
- createGeoLocationSuggestion(list("Europe/London"));
+ createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
@@ -799,11 +804,11 @@
@Test
public void testGeoSuggestion_multiZone() {
GeolocationTimeZoneSuggestion londonOnlySuggestion =
- createGeoLocationSuggestion(list("Europe/London"));
+ createCertainGeolocationSuggestion("Europe/London");
GeolocationTimeZoneSuggestion londonOrParisSuggestion =
- createGeoLocationSuggestion(list("Europe/Paris", "Europe/London"));
+ createCertainGeolocationSuggestion("Europe/Paris", "Europe/London");
GeolocationTimeZoneSuggestion parisOnlySuggestion =
- createGeoLocationSuggestion(list("Europe/Paris"));
+ createCertainGeolocationSuggestion("Europe/Paris");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
@@ -836,7 +841,7 @@
@Test
public void testGeoSuggestion_togglingGeoDetectionClearsLastSuggestion() {
GeolocationTimeZoneSuggestion suggestion =
- createGeoLocationSuggestion(list("Europe/London"));
+ createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
@@ -863,7 +868,7 @@
@Test
public void testChangingGeoDetectionEnabled() {
GeolocationTimeZoneSuggestion geolocationSuggestion =
- createGeoLocationSuggestion(list("Europe/London"));
+ createCertainGeolocationSuggestion("Europe/London");
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
"Europe/Paris");
@@ -960,7 +965,7 @@
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, "Zone2");
GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
- createGeoLocationSuggestion(Arrays.asList("Zone3", "Zone2"));
+ createCertainGeolocationSuggestion("Zone3", "Zone2");
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
.verifyTimeZoneNotChanged()
.simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
@@ -1052,13 +1057,18 @@
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
}
- private static GeolocationTimeZoneSuggestion createUncertainGeoLocationSuggestion() {
- return createGeoLocationSuggestion(null);
+ private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
+ return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ mElapsedRealtimeMillis++, null);
}
- private static GeolocationTimeZoneSuggestion createGeoLocationSuggestion(
- @Nullable List<String> zoneIds) {
- GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
+ private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
+ @NonNull String... zoneIds) {
+ assertNotNull(zoneIds);
+
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ mElapsedRealtimeMillis++, Arrays.asList(zoneIds));
suggestion.addDebugInfo("Test suggestion");
return suggestion;
}
@@ -1321,8 +1331,4 @@
mOnChangeCalled = false;
}
}
-
- private static <T> List<T> list(T... values) {
- return Arrays.asList(values);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index da746ca..7d6772e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -33,9 +33,12 @@
import static java.util.Arrays.asList;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -65,9 +68,9 @@
private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
createSuggestionEvent(asList("Europe/Paris"));
private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
- TimeZoneProviderEvent.createUncertainEvent();
+ TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_TIME_MILLIS);
private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
- TimeZoneProviderEvent.createPermanentFailureEvent("Test");
+ TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test");
private TestThreadingDomain mTestThreadingDomain;
private TestCallback mTestCallback;
@@ -286,8 +289,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -325,8 +328,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -365,8 +368,8 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -394,8 +397,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
@@ -415,8 +418,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -455,8 +458,8 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
@@ -478,8 +481,8 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -507,8 +510,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate an uncertain event being received from the primary provider. This should not
@@ -534,8 +537,8 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate an uncertain event being received from the secondary provider. This should not
@@ -559,7 +562,8 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -587,8 +591,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
@@ -612,8 +616,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -681,8 +685,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -722,8 +726,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate the user change (but geo detection still enabled).
@@ -788,8 +792,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
@@ -895,8 +899,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate uncertainty from the primary. The secondary cannot be started.
@@ -1097,8 +1101,8 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestCallback.assertSuggestionMadeAndCommit(
- USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
+ mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Trigger destroy().
@@ -1124,6 +1128,7 @@
private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
return TimeZoneProviderEvent.createSuggestionEvent(
+ ARBITRARY_TIME_MILLIS,
new TimeZoneProviderSuggestion.Builder()
.setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
.setTimeZoneIds(timeZoneIds)
@@ -1136,10 +1141,13 @@
// (initialization timeout * 2) < uncertainty delay
//
// That makes the order of initialization timeout Vs uncertainty delay deterministic.
- static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
- static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
+ private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+ private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15);
+ private static final Duration PROVIDER_EVENT_FILTERING_AGE_THRESHOLD =
+ Duration.ofMinutes(3);
+
private final LocationTimeZoneProviderController mController;
private ConfigurationInternal mConfigurationInternal;
@@ -1172,10 +1180,22 @@
}
@Override
+ Duration getProviderEventFilteringAgeThreshold() {
+ return PROVIDER_EVENT_FILTERING_AGE_THRESHOLD;
+ }
+
+ @Override
Duration getUncertaintyDelay() {
return UNCERTAINTY_DELAY;
}
+ @Override
+ long elapsedRealtimeMillis() {
+ // The properties of the real clock will also work for tests, i.e. it doesn't go
+ // backwards.
+ return SystemClock.elapsedRealtime();
+ }
+
void simulateConfigChange(ConfigurationInternal newConfig) {
ConfigurationInternal oldConfig = mConfigurationInternal;
mConfigurationInternal = Objects.requireNonNull(newConfig);
@@ -1199,19 +1219,52 @@
mLatestSuggestion.set(suggestion);
}
- void assertSuggestionMadeAndCommit(@Nullable List<String> expectedZoneIds) {
- mLatestSuggestion.assertHasBeenSet();
- assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
- mLatestSuggestion.commitLatest();
+ void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ // Test coding error if this fails.
+ assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType());
+
+ TimeZoneProviderSuggestion suggestion = event.getSuggestion();
+ assertSuggestionMadeAndCommit(
+ suggestion.getElapsedRealtimeMillis(),
+ suggestion.getTimeZoneIds());
}
void assertNoSuggestionMade() {
mLatestSuggestion.assertHasNotBeenSet();
}
+ /** Asserts that an uncertain suggestion has been made from the supplied event. */
+ void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ // Test coding error if this fails.
+ assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType());
+
+ assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null);
+ }
+
+ /**
+ * Asserts that an uncertain suggestion has been made.
+ * Ignores the suggestion's effectiveFromElapsedMillis.
+ */
void assertUncertainSuggestionMadeAndCommit() {
// An "uncertain" suggestion has null time zone IDs.
- assertSuggestionMadeAndCommit(null);
+ assertSuggestionMadeAndCommit(null, null);
+ }
+
+ /**
+ * Asserts that a suggestion has been made and some properties of that suggestion.
+ * When expectedEffectiveFromElapsedMillis is null then its value isn't checked.
+ */
+ private void assertSuggestionMadeAndCommit(
+ @Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis,
+ @Nullable List<String> expectedZoneIds) {
+ mLatestSuggestion.assertHasBeenSet();
+ if (expectedEffectiveFromElapsedMillis != null) {
+ assertEquals(
+ expectedEffectiveFromElapsedMillis.longValue(),
+ mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis());
+ }
+ assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
+ mLatestSuggestion.commitLatest();
}
}
@@ -1254,7 +1307,7 @@
}
@Override
- void onStartUpdates(Duration initializationTimeout) {
+ void onStartUpdates(Duration initializationTimeout, Duration eventFilteringAgeThreshold) {
// Nothing needed for tests.
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
index e75d05c..52e9d3a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -16,6 +16,8 @@
package com.android.server.timezonedetector.location;
+import android.service.timezone.TimeZoneProviderEvent;
+
/**
* Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
* are valid or always uncertain if {@link #enterUncertainMode()} was called.
@@ -28,7 +30,8 @@
@Override
public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
if (mIsUncertain) {
- return TimeZoneProviderEvent.createUncertainEvent();
+ return TimeZoneProviderEvent.createUncertainEvent(
+ timeZoneProviderEvent.getCreationElapsedMillis());
}
return timeZoneProviderEvent;
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 03d56c7..cb2905d 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -91,10 +92,12 @@
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
provider.startUpdates(config, arbitraryInitializationTimeout,
- arbitraryInitializationTimeoutFuzz);
+ arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
- provider.assertOnStartCalled(arbitraryInitializationTimeout);
+ provider.assertOnStartCalled(
+ arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
currentState = assertAndReturnProviderState(
provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING);
@@ -117,7 +120,8 @@
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(Arrays.asList("Europe/London"))
.build();
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion);
provider.simulateProviderEventReceived(event);
currentState = assertAndReturnProviderState(
@@ -129,7 +133,7 @@
mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
// Simulate an uncertain event being received.
- event = TimeZoneProviderEvent.createUncertainEvent();
+ event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS);
provider.simulateProviderEventReceived(event);
currentState = assertAndReturnProviderState(
@@ -178,8 +182,9 @@
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ Duration eventFilteringAgeThreshold = Duration.ofMinutes(3);
provider.startUpdates(config, arbitraryInitializationTimeout,
- arbitraryInitializationTimeoutFuzz);
+ arbitraryInitializationTimeoutFuzz, eventFilteringAgeThreshold);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_INITIALIZING);
// Simulate a suggestion event being received.
@@ -187,12 +192,13 @@
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(Arrays.asList("Europe/London"))
.build();
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
// Simulate an uncertain event being received.
- event = TimeZoneProviderEvent.createUncertainEvent();
+ event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -220,16 +226,17 @@
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ Duration eventFilteringAgeThreshold = Duration.ofMinutes(3);
provider.startUpdates(config, arbitraryInitializationTimeout,
- arbitraryInitializationTimeoutFuzz);
+ arbitraryInitializationTimeoutFuzz, eventFilteringAgeThreshold);
List<String> invalidTimeZoneIds = asList("Atlantic/Atlantis");
TimeZoneProviderSuggestion invalidIdSuggestion = new TimeZoneProviderSuggestion.Builder()
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(invalidTimeZoneIds)
.build();
- TimeZoneProviderEvent event =
- TimeZoneProviderEvent.createSuggestionEvent(invalidIdSuggestion);
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
}
@@ -277,6 +284,7 @@
private boolean mOnDestroyCalled;
private boolean mOnStartUpdatesCalled;
private Duration mInitializationTimeout;
+ private Duration mEventFilteringAgeThreshold;
private boolean mOnStopUpdatesCalled;
/** Creates the instance. */
@@ -300,9 +308,11 @@
}
@Override
- void onStartUpdates(@NonNull Duration initializationTimeout) {
+ void onStartUpdates(@NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold) {
mOnStartUpdatesCalled = true;
mInitializationTimeout = initializationTimeout;
+ mEventFilteringAgeThreshold = eventFilteringAgeThreshold;
}
@Override
@@ -319,9 +329,11 @@
assertTrue(mOnInitializeCalled);
}
- void assertOnStartCalled(Duration expectedInitializationTimeout) {
+ void assertOnStartCalled(Duration expectedInitializationTimeout,
+ Duration eventFilteringAgeThreshold) {
assertTrue(mOnStartUpdatesCalled);
assertEquals(expectedInitializationTimeout, mInitializationTimeout);
+ assertEquals(eventFilteringAgeThreshold, mEventFilteringAgeThreshold);
}
void simulateProviderEventReceived(TimeZoneProviderEvent event) {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index 173705be..ab4fe29 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import org.junit.Test;
@@ -53,14 +54,17 @@
for (String timeZone : nonExistingTimeZones) {
TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ TimeZoneProviderEvent expectedResultEvent =
+ TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis());
assertWithMessage(timeZone + " is not a valid time zone")
.that(mPreProcessor.preProcess(event))
- .isEqualTo(TimeZoneProviderEvent.createUncertainEvent());
+ .isEqualTo(expectedResultEvent);
}
}
private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
return TimeZoneProviderEvent.createSuggestionEvent(
+ ARBITRARY_TIME_MILLIS,
new TimeZoneProviderSuggestion.Builder()
.setTimeZoneIds(Arrays.asList(timeZoneIds))
.setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
index f361f4a..4ed4c23 100644
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
@@ -915,6 +915,23 @@
}
}
+ // Fill new cells in the matrix which has enlarged capacity.
+ private void fillNew(WatchedSparseBooleanMatrix matrix, int initialCapacity,
+ int newCapacity, int[] indexes) {
+ final int size = newCapacity;
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ if (i < initialCapacity && j < initialCapacity) {
+ // Do not touch old cells
+ continue;
+ }
+ final int row = indexes[i];
+ final int col = indexes[j];
+ matrix.put(row, col, cellValue(i, j));
+ }
+ }
+ }
+
// Verify the content of a matrix. This asserts on mismatch. Selected indices may
// have been deleted.
private void verify(WatchedSparseBooleanMatrix matrix, int[] indexes, boolean[] absent) {
@@ -989,6 +1006,24 @@
assertTrue("Matrix shrink", finalCapacity - matrix.size() < matrix.STEP);
}
+ private void matrixSetCapacity(WatchedSparseBooleanMatrix matrix, int newCapacity,
+ IndexGenerator indexer) {
+ final int initialCapacity = matrix.capacity();
+ final int[] indexes = indexer.indexes(Math.max(initialCapacity, newCapacity));
+ fill(matrix, initialCapacity, indexes);
+
+ matrix.setCapacity(newCapacity);
+ fillNew(matrix, initialCapacity, newCapacity, indexes);
+
+ assertEquals(matrix.size(), indexes.length);
+ verify(matrix, indexes, null);
+ // Test the keyAt/indexOfKey methods
+ for (int i = 0; i < matrix.size(); i++) {
+ int key = indexes[i];
+ assertEquals(matrix.keyAt(matrix.indexOfKey(key)), key);
+ }
+ }
+
@Test
public void testWatchedSparseBooleanMatrix() {
final String name = "WatchedSparseBooleanMatrix";
@@ -1052,6 +1087,46 @@
}
@Test
+ public void testWatchedSparseBooleanMatrix_setCapacity() {
+ final IndexGenerator indexer = new IndexGenerator(3);
+ matrixSetCapacity(new WatchedSparseBooleanMatrix(500), 1000, indexer);
+ matrixSetCapacity(new WatchedSparseBooleanMatrix(1000), 500, indexer);
+ }
+
+ @Test
+ public void testWatchedSparseBooleanMatrix_removeRangeAndShrink() {
+ final IndexGenerator indexer = new IndexGenerator(3);
+ final int initialCapacity = 500;
+ final int removeCounts = 33;
+ final WatchedSparseBooleanMatrix matrix = new WatchedSparseBooleanMatrix(initialCapacity);
+ final int[] indexes = indexer.indexes(initialCapacity);
+ final boolean[] absents = new boolean[initialCapacity];
+ fill(matrix, initialCapacity, indexes);
+ assertEquals(matrix.size(), initialCapacity);
+
+ for (int i = 0; i < initialCapacity / removeCounts; i++) {
+ final int size = matrix.size();
+ final int fromIndex = (size / 2 < removeCounts ? 0 : size / 2 - removeCounts);
+ final int toIndex = (fromIndex + removeCounts > size ? size : fromIndex + removeCounts);
+ for (int index = fromIndex; index < toIndex; index++) {
+ final int key = matrix.keyAt(index);
+ for (int j = 0; j < indexes.length; j++) {
+ if (key == indexes[j]) {
+ absents[j] = true;
+ break;
+ }
+ }
+ }
+ matrix.removeRange(fromIndex, toIndex);
+ assertEquals(matrix.size(), size - (toIndex - fromIndex));
+ verify(matrix, indexes, absents);
+
+ matrix.compact();
+ verify(matrix, indexes, absents);
+ }
+ }
+
+ @Test
public void testNestedArrays() {
final String name = "NestedArrays";
WatchableTester tester;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 378304d..777e3f4 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -51,6 +51,7 @@
private final FakeNativeWrapper mNativeWrapper;
private boolean mIsAvailable = true;
+ private boolean mIsInfoLoadSuccessful = true;
private long mLatency;
private int mOffCount;
@@ -172,7 +173,7 @@
infoBuilder.setFrequencyMapping(new VibratorInfo.FrequencyMapping(mMinFrequency,
mResonantFrequency, mFrequencyResolution, suggestedFrequencyRange,
mMaxAmplitudes));
- return true;
+ return mIsInfoLoadSuccessful;
}
private void applyLatency() {
@@ -213,6 +214,14 @@
}
/**
+ * Sets the result for the method that loads the {@link VibratorInfo}, for faking a vibrator
+ * that fails to load some of the hardware data.
+ */
+ public void setVibratorInfoLoadSuccessful(boolean successful) {
+ mIsInfoLoadSuccessful = successful;
+ }
+
+ /**
* Sets the latency this controller should fake for turning the vibrator hardware on or setting
* it's vibration amplitude.
*/
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
index b90df21..4c3312c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
@@ -103,21 +103,18 @@
public void testStepSegments_withShortZeroSegment_replaceWithStepsDown() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
- new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10),
- new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100)));
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10)));
List<VibrationEffectSegment> expectedSegments = Arrays.asList(
new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 5),
- new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 5),
- new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100));
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 5));
- assertEquals(1, mAdapter.apply(segments, 1, TEST_VIBRATOR_INFO));
-
+ assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
assertEquals(expectedSegments, segments);
}
@Test
- public void testStepSegments_withLongZeroSegment_replaceWithStepsDown() {
+ public void testStepSegments_withLongZeroSegment_replaceWithStepsDownWithRemainingOffSegment() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
@@ -131,9 +128,67 @@
new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 35),
new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100));
+ assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
+ assertEquals(expectedSegments, segments);
+ }
+
+ @Test
+ public void testStepSegments_withZeroSegmentBeforeRepeat_fixesRepeat() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 50),
+ new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100)));
+ List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.75f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.25f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 35),
+ new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100));
+
// Repeat index fixed after intermediate steps added
assertEquals(5, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
+ assertEquals(expectedSegments, segments);
+ }
+ @Test
+ public void testStepSegments_withZeroSegmentAfterRepeat_preservesRepeat() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100)));
+ List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 0, /* duration= */ 100));
+
+ assertEquals(3, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
+ assertEquals(expectedSegments, segments);
+ }
+
+ @Test
+ public void testStepSegments_withZeroSegmentAtRepeat_fixesRepeatAndAppendOriginalToListEnd() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 50),
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 100)));
+ List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.75f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.25f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 35),
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ 0, /* duration= */ 100),
+ // Original zero segment appended to the end of new looping vibration,
+ // then converted to ramp down as well.
+ new StepSegment(/* amplitude= */ 0.75f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0.25f, /* frequency= */ 0, /* duration= */ 5),
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 35));
+
+ // Repeat index fixed after intermediate steps added
+ assertEquals(5, mAdapter.apply(segments, 1, TEST_VIBRATOR_INFO));
assertEquals(expectedSegments, segments);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index f9e63d1..6118169 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -287,7 +287,26 @@
}
@Test
- public void getVibratorInfo_withExistingVibratorId_returnsHalInfoForVibrator() {
+ public void getVibratorInfo_vibratorFailedLoadBeforeSystemReady_returnsNull() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
+ assertNull(createService().getVibratorInfo(1));
+ }
+
+ @Test
+ public void getVibratorInfo_vibratorFailedLoadAfterSystemReady_returnsInfoForVibrator() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
+ mVibratorProviders.get(1).setResonantFrequency(123.f);
+ VibratorInfo info = createSystemReadyService().getVibratorInfo(1);
+
+ assertNotNull(info);
+ assertEquals(1, info.getId());
+ assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+ }
+
+ @Test
+ public void getVibratorInfo_vibratorSuccessfulLoadBeforeSystemReady_returnsInfoForVibrator() {
mockVibrators(1);
FakeVibratorControllerProvider vibrator = mVibratorProviders.get(1);
vibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -295,7 +314,7 @@
vibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
vibrator.setResonantFrequency(123.f);
vibrator.setQFactor(Float.NaN);
- VibratorInfo info = createSystemReadyService().getVibratorInfo(1);
+ VibratorInfo info = createService().getVibratorInfo(1);
assertNotNull(info);
assertEquals(1, info.getId());
@@ -313,6 +332,24 @@
}
@Test
+ public void getVibratorInfo_vibratorFailedThenSuccessfulLoad_returnsNullThenInfo() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
+
+ VibratorManagerService service = createService();
+ assertNull(createService().getVibratorInfo(1));
+
+ mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(true);
+ mVibratorProviders.get(1).setResonantFrequency(123.f);
+ service.systemReady();
+
+ VibratorInfo info = createService().getVibratorInfo(1);
+ assertNotNull(info);
+ assertEquals(1, info.getId());
+ assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+ }
+
+ @Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -1013,6 +1050,8 @@
throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
createSystemReadyService();
IExternalVibrationController firstController = mock(IExternalVibrationController.class);
@@ -1021,8 +1060,11 @@
firstController);
int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
- ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
- secondController);
+ AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ ringtoneAudioAttrs, secondController);
int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
assertEquals(IExternalVibratorService.SCALE_NONE, firstScale);
@@ -1057,6 +1099,37 @@
mVibratorProviders.get(1).getExternalControlStates());
}
+ @Test
+ public void onExternalVibration_withRingtone_usesRingerModeSettings() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ AudioAttributes audioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 577e36c..a834e2b6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -118,7 +118,7 @@
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
assertEquals(canBubble(i), ranking.canBubble());
- assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
+ assertEquals(isTextChanged(i), ranking.isTextChanged());
assertEquals(isConversation(i), ranking.isConversation());
assertEquals(getShortcutInfo(i).getId(), ranking.getConversationShortcutInfo().getId());
assertEquals(getRankingAdjustment(i), ranking.getRankingAdjustment());
@@ -189,7 +189,7 @@
(ArrayList) tweak.getSmartActions(),
(ArrayList) tweak.getSmartReplies(),
tweak.canBubble(),
- tweak.visuallyInterruptive(),
+ tweak.isTextChanged(),
tweak.isConversation(),
tweak.getConversationShortcutInfo(),
tweak.getRankingAdjustment(),
@@ -270,7 +270,7 @@
getSmartActions(key, i),
getSmartReplies(key, i),
canBubble(i),
- visuallyInterruptive(i),
+ isTextChanged(i),
isConversation(i),
getShortcutInfo(i),
getRankingAdjustment(i),
@@ -379,7 +379,7 @@
return index % 4 == 0;
}
- private boolean visuallyInterruptive(int index) {
+ private boolean isTextChanged(int index) {
return index % 4 == 0;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 1ae219d..2bd237b1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,8 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -130,6 +132,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
@@ -182,6 +185,7 @@
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
@@ -245,7 +249,11 @@
@Mock
private PackageManager mPackageManagerClient;
@Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
private WindowManagerInternal mWindowManagerInternal;
+ @Mock
+ private PermissionHelper mPermissionHelper;
private TestableContext mContext = spy(getContext());
private final String PKG = mContext.getPackageName();
private TestableLooper mTestableLooper;
@@ -312,6 +320,8 @@
@Mock
AppOpsManager mAppOpsManager;
@Mock
+ IAppOpsService mAppOpsService;
+ @Mock
private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
mNotificationAssistantAccessGrantedCallback;
@Mock
@@ -334,83 +344,6 @@
private NotificationManagerService.WorkerHandler mWorkerHandler;
- // Use a Testable subclass so we can simulate calls from the system without failing.
- private static class TestableNotificationManagerService extends NotificationManagerService {
- int countSystemChecks = 0;
- boolean isSystemUid = true;
- int countLogSmartSuggestionsVisible = 0;
- @Nullable
- NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
-
- TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
- InstanceIdSequence notificationInstanceIdSequence) {
- super(context, logger, notificationInstanceIdSequence);
- }
-
- RankingHelper getRankingHelper() {
- return mRankingHelper;
- }
-
- @Override
- protected boolean isCallingUidSystem() {
- countSystemChecks++;
- return isSystemUid;
- }
-
- @Override
- protected boolean isCallerSystemOrPhone() {
- countSystemChecks++;
- return isSystemUid;
- }
-
- @Override
- protected ICompanionDeviceManager getCompanionManager() {
- return null;
- }
-
- @Override
- protected void reportUserInteraction(NotificationRecord r) {
- return;
- }
-
- @Override
- protected void handleSavePolicyFile() {
- return;
- }
-
- @Override
- void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
- super.logSmartSuggestionsVisible(r, notificationLocation);
- countLogSmartSuggestionsVisible++;
- }
-
- @Override
- protected void setNotificationAssistantAccessGrantedForUserInternal(
- ComponentName assistant, int userId, boolean granted, boolean userSet) {
- if (mNotificationAssistantAccessGrantedCallback != null) {
- mNotificationAssistantAccessGrantedCallback.onGranted(assistant, userId, granted,
- userSet);
- return;
- }
- super.setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted,
- userSet);
- }
-
- @Override
- protected String[] getStringArrayResource(int key) {
- return new String[] {PKG_O};
- }
-
- private void setNotificationAssistantAccessGrantedCallback(
- @Nullable NotificationAssistantAccessGrantedCallback callback) {
- this.mNotificationAssistantAccessGrantedCallback = callback;
- }
-
- interface NotificationAssistantAccessGrantedCallback {
- void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet);
- }
- }
-
private class TestableToastCallback extends ITransientNotification.Stub {
@Override
public void show(IBinder windowToken) {
@@ -445,6 +378,8 @@
LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmi);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
@@ -516,13 +451,17 @@
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
+ // apps allowed as convos
+ mService.setStringArrayResourceValue(PKG_O);
+
mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
- mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
- mAmi, mToastRateLimiter);
+ mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
+ mock(TelephonyManager.class),
+ mAmi, mToastRateLimiter, mPermissionHelper);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -2199,6 +2138,11 @@
public void testUpdateAppNotifyCreatorBlock() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
+ // should not trigger a broadcast
+ when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED);
+ mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+ // should trigger a broadcast
mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -2221,6 +2165,11 @@
public void testUpdateAppNotifyCreatorUnblock() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
+ // should not trigger a broadcast
+ when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
+ mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+ // should trigger a broadcast
mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -3872,6 +3821,27 @@
}
@Test
+ public void testVisuallyInterruptive_notSeen() throws Exception {
+ NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(original);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, original.getSbn().getId(),
+ original.getSbn().getTag(), mUid, 0,
+ new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("new title").build(),
+ UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ mService.addEnqueuedNotification(update);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(update.getKey());
+ runnable.run();
+ waitForIdle();
+
+ assertFalse(update.isInterruptive());
+ }
+
+ @Test
public void testApplyAdjustmentMultiUser() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
@@ -6685,6 +6655,8 @@
enableInteractAcrossUsers();
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
mUid + UserHandle.PER_USER_RANGE);
+
+ verify(mPermissionHelper, never()).hasPermission(anyInt());
}
@Test
@@ -8335,4 +8307,20 @@
assertTrue(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 1001)));
assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("test", 1002)));
}
+
+ @Test
+ public void testMigrationDisabledByDefault() {
+ assertThat(mService.mEnableAppSettingMigration).isFalse();
+ }
+
+ @Test
+ public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getImportance(PKG, mUid)).thenReturn(IMPORTANCE_NONE);
+
+ assertThat(mBinderService.getNotificationChannelsBypassingDnd(PKG, mUid).getList())
+ .isEmpty();
+ verify(mPermissionHelper, never()).hasPermission(anyInt());
+ verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
new file mode 100755
index 0000000..c36d7ad
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS;
+import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS;
+import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.INotificationManager;
+import android.app.IUriGrantsManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.StatsManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.usage.UsageStatsManagerInternal;
+import android.companion.ICompanionDeviceManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutServiceInternal;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerFilter;
+import android.service.notification.StatusBarNotification;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestablePermissions;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.UiServiceTestCase;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+import com.android.server.notification.NotificationManagerService.NotificationAssistants;
+import com.android.server.notification.NotificationManagerService.NotificationListeners;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.utils.quota.MultiRateLimiter;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+/**
+ * Tests that NMS reads/writes the app notification state from Package/PermissionManager when
+ * migration is enabled. Because the migration field is read onStart
+ * TODO (b/194833441): migrate these tests to NotificationManagerServiceTest when the migration is
+ * permanently enabled.
+ */
+public class NotificationPermissionMigrationTest extends UiServiceTestCase {
+ private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
+ private static final int UID_HEADLESS = 1000000;
+
+ private final int mUid = Binder.getCallingUid();
+ private TestableNotificationManagerService mService;
+ private INotificationManager mBinderService;
+ private NotificationManagerInternal mInternalService;
+ private ShortcutHelper mShortcutHelper;
+ @Mock
+ private IPackageManager mPackageManager;
+ @Mock
+ private PackageManager mPackageManagerClient;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private WindowManagerInternal mWindowManagerInternal;
+ @Mock
+ private PermissionHelper mPermissionHelper;
+ private TestableContext mContext = spy(getContext());
+ private final String PKG = mContext.getPackageName();
+ private TestableLooper mTestableLooper;
+ @Mock
+ private RankingHelper mRankingHelper;
+ @Mock private PreferencesHelper mPreferencesHelper;
+ AtomicFile mPolicyFile;
+ File mFile;
+ @Mock
+ private NotificationUsageStats mUsageStats;
+ @Mock
+ private UsageStatsManagerInternal mAppUsageStats;
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private ShortcutServiceInternal mShortcutServiceInternal;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ ActivityManager mActivityManager;
+ @Mock
+ Resources mResources;
+ @Mock
+ RankingHandler mRankingHandler;
+ @Mock
+ ActivityManagerInternal mAmi;
+ @Mock
+ private Looper mMainLooper;
+
+ @Mock
+ IIntentSender pi1;
+
+ private static final int MAX_POST_DELAY = 1000;
+
+ private NotificationChannel mTestNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+ private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
+
+ @Mock
+ private NotificationListeners mListeners;
+ @Mock
+ private NotificationListenerFilter mNlf;
+ @Mock private NotificationAssistants mAssistants;
+ @Mock private ConditionProviders mConditionProviders;
+ private ManagedServices.ManagedServiceInfo mListener;
+ @Mock private ICompanionDeviceManager mCompanionMgr;
+ @Mock SnoozeHelper mSnoozeHelper;
+ @Mock GroupHelper mGroupHelper;
+ @Mock
+ IBinder mPermOwner;
+ @Mock
+ IActivityManager mAm;
+ @Mock
+ ActivityTaskManagerInternal mAtm;
+ @Mock
+ IUriGrantsManager mUgm;
+ @Mock
+ UriGrantsManagerInternal mUgmInternal;
+ @Mock
+ AppOpsManager mAppOpsManager;
+ @Mock
+ private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
+ mNotificationAssistantAccessGrantedCallback;
+ @Mock
+ UserManager mUm;
+ @Mock
+ NotificationHistoryManager mHistoryManager;
+ @Mock
+ StatsManager mStatsManager;
+ @Mock
+ AlarmManager mAlarmManager;
+ @Mock
+ MultiRateLimiter mToastRateLimiter;
+ BroadcastReceiver mPackageIntentReceiver;
+ BroadcastReceiver mNASIntentReceiver;
+ NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
+ private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
+ 1 << 30);
+ @Mock
+ StatusBarManagerInternal mStatusBar;
+
+ private NotificationManagerService.WorkerHandler mWorkerHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ // These should be the only difference in setup from NMSTest
+ Settings.Secure.putIntForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 1, USER_SYSTEM);
+ Settings.Global.putInt(getContext().getContentResolver(),
+ Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 1);
+
+ // Shell permissions will override permissions of our app, so add all necessary permissions
+ // for this test here:
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ "android.permission.WRITE_DEVICE_CONFIG",
+ "android.permission.READ_DEVICE_CONFIG",
+ "android.permission.READ_CONTACTS");
+
+ MockitoAnnotations.initMocks(this);
+
+ DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
+ when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L);
+
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.addService(StatusBarManagerInternal.class, mStatusBar);
+ LocalServices.removeServiceForTest(DeviceIdleInternal.class);
+ LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mAmi);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+ mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
+
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+
+ mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
+ mNotificationInstanceIdSequence);
+
+ // Use this testable looper.
+ mTestableLooper = TestableLooper.get(this);
+ // MockPackageManager - default returns ApplicationInfo with matching calling UID
+ mContext.setMockPackageManager(mPackageManagerClient);
+
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
+ .thenAnswer((Answer<ApplicationInfo>) invocation -> {
+ Object[] args = invocation.getArguments();
+ return getApplicationInfo((String) args[0], mUid);
+ });
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenAnswer((Answer<ApplicationInfo>) invocation -> {
+ Object[] args = invocation.getArguments();
+ return getApplicationInfo((String) args[0], mUid);
+ });
+ when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
+ final LightsManager mockLightsManager = mock(LightsManager.class);
+ when(mockLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
+ when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
+ when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
+ when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG});
+ when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG});
+ mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
+
+ // write to a test file; the system file isn't readable from tests
+ mFile = new File(mContext.getCacheDir(), "test.xml");
+ mFile.createNewFile();
+ final String preupgradeXml = "<notification-policy></notification-policy>";
+ mPolicyFile = new AtomicFile(mFile);
+ FileOutputStream fos = mPolicyFile.startWrite();
+ fos.write(preupgradeXml.getBytes());
+ mPolicyFile.finishWrite(fos);
+
+ // Setup managed services
+ when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
+ when(mNlf.isPackageAllowed(any())).thenReturn(true);
+ when(mNlf.isPackageAllowed(null)).thenReturn(true);
+ when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
+ mListener = mListeners.new ManagedServiceInfo(
+ null, new ComponentName(PKG, "test_class"),
+ UserHandle.getUserId(mUid), true, null, 0, 123);
+ ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
+ ArraySet<ComponentName> components = new ArraySet<>();
+ components.add(defaultComponent);
+ when(mListeners.getDefaultComponents()).thenReturn(components);
+ when(mConditionProviders.getDefaultPackages())
+ .thenReturn(new ArraySet<>(Arrays.asList("config")));
+ when(mAssistants.getDefaultComponents()).thenReturn(components);
+ when(mAssistants.queryPackageForServices(
+ anyString(), anyInt(), anyInt())).thenReturn(components);
+ when(mListeners.checkServiceTokenLocked(null)).thenReturn(mListener);
+ ManagedServices.Config listenerConfig = new ManagedServices.Config();
+ listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS;
+ when(mListeners.getConfig()).thenReturn(listenerConfig);
+ ManagedServices.Config assistantConfig = new ManagedServices.Config();
+ assistantConfig.xmlTag = NotificationAssistants.TAG_ENABLED_NOTIFICATION_ASSISTANTS;
+ when(mAssistants.getConfig()).thenReturn(assistantConfig);
+ ManagedServices.Config dndConfig = new ManagedServices.Config();
+ dndConfig.xmlTag = ConditionProviders.TAG_ENABLED_DND_APPS;
+ when(mConditionProviders.getConfig()).thenReturn(dndConfig);
+
+ when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
+
+ // apps allowed as convos
+ mService.setStringArrayResourceValue(PKG_O);
+
+ mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
+ mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
+ mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
+ mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
+ mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
+ mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager,
+ mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper);
+ // Return first true for RoleObserver main-thread check
+ when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
+
+ mService.setAudioManager(mAudioManager);
+
+ mShortcutHelper = mService.getShortcutHelper();
+ mShortcutHelper.setLauncherApps(mLauncherApps);
+ mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal);
+ mShortcutHelper.setUserManager(mUserManager);
+
+ // Capture PackageIntentReceiver
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+
+ verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(),
+ any(), intentFilterCaptor.capture(), any(), any());
+ verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(),
+ intentFilterCaptor.capture());
+ List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+ List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+ for (int i = 0; i < intentFilters.size(); i++) {
+ final IntentFilter filter = intentFilters.get(i);
+ if (filter.hasAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)
+ && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED)
+ && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
+ mPackageIntentReceiver = broadcastReceivers.get(i);
+ } else if (filter.hasAction(ACTION_ENABLE_NAS)
+ && filter.hasAction(ACTION_DISABLE_NAS)
+ && filter.hasAction(ACTION_LEARNMORE_NAS)) {
+ mNASIntentReceiver = broadcastReceivers.get(i);
+ }
+ }
+ assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
+ assertNotNull("nas intent receiver should exist", mNASIntentReceiver);
+
+ // Pretend the shortcut exists
+ List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+ ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(PKG);
+ when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
+ when(info.isLongLived()).thenReturn(true);
+ when(info.isEnabled()).thenReturn(true);
+ shortcutInfos.add(info);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+ when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any())).thenReturn(true);
+ when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
+
+ // Set the testable bubble extractor
+ RankingHelper rankingHelper = mService.getRankingHelper();
+ BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class);
+ extractor.setActivityManager(mActivityManager);
+
+ // Tests call directly into the Binder.
+ mBinderService = mService.getBinderService();
+ mInternalService = mService.getInternalService();
+
+ mBinderService.createNotificationChannels(
+ PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+ mBinderService.createNotificationChannels(
+ PKG_P, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+ mBinderService.createNotificationChannels(
+ PKG_O, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+ assertNotNull(mBinderService.getNotificationChannel(
+ PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
+ clearInvocations(mRankingHandler);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mFile != null) mFile.delete();
+
+ try {
+ mService.onDestroy();
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ // can throw if a broadcast receiver was never registered
+ }
+
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().dropShellPermissionIdentity();
+ // Remove scheduled messages that would be processed when the test is already done, and
+ // could cause issues, for example, messages that remove/cancel shown toasts (this causes
+ // problematic interactions with mocks when they're no longer working as expected).
+ mWorkerHandler.removeCallbacksAndMessages(null);
+ }
+
+ private ApplicationInfo getApplicationInfo(String pkg, int uid) {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = uid;
+ switch (pkg) {
+ case PKG_N_MR1:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ break;
+ case PKG_O:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ break;
+ case PKG_P:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+ break;
+ default:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ break;
+ }
+ return applicationInfo;
+ }
+
+ public void waitForIdle() {
+ mTestableLooper.processAllMessages();
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
+ return generateNotificationRecord(channel, null);
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel,
+ Notification.TvExtender extender) {
+ if (channel == null) {
+ channel = mTestNotificationChannel;
+ }
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "test", null).build());
+ if (extender != null) {
+ nb.extend(extender);
+ }
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
+ private void enableInteractAcrossUsers() {
+ TestablePermissions perms = mContext.getTestablePermissions();
+ perms.setPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, PERMISSION_GRANTED);
+ }
+
+ @Test
+ public void testAreNotificationsEnabledForPackage() throws Exception {
+ mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
+ mUid);
+
+ verify(mPermissionHelper).hasPermission(mUid);
+ }
+
+ @Test
+ public void testAreNotificationsEnabledForPackage_crossUser() throws Exception {
+ try {
+ mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
+ mUid + UserHandle.PER_USER_RANGE);
+ fail("Cannot call cross user without permission");
+ } catch (SecurityException e) {
+ // pass
+ }
+ verify(mPermissionHelper, never()).hasPermission(anyInt());
+
+ // cross user, with permission, no problem
+ enableInteractAcrossUsers();
+ mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
+ mUid + UserHandle.PER_USER_RANGE);
+
+ verify(mPermissionHelper).hasPermission(mUid + UserHandle.PER_USER_RANGE);
+ }
+
+ @Test
+ public void testGetPackageImportance() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
+ .isEqualTo(IMPORTANCE_DEFAULT);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+ assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
+ .isEqualTo(IMPORTANCE_NONE);
+ }
+
+ @Test
+ public void testEnqueueNotificationInternal_noChannel() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+ NotificationRecord nr = generateNotificationRecord(
+ new NotificationChannel("did not create", "", IMPORTANCE_DEFAULT));
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ verify(mPermissionHelper).hasPermission(mUid);
+ verify(mPermissionHelper, never()).hasPermission(Process.SYSTEM_UID);
+
+ reset(mPermissionHelper);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ verify(mPermissionHelper).hasPermission(mUid);
+ assertThat(mService.mChannelToastsSent).contains(mUid);
+ }
+
+ @Test
+ public void testSetNotificationsEnabledForPackage_noChange() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, true);
+
+ verify(mPermissionHelper, never()).setNotificationPermission(
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetNotificationsEnabledForPackage() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
+
+ verify(mPermissionHelper).setNotificationPermission(
+ mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
+
+ verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testUpdateAppNotifyCreatorBlock() throws Exception {
+ when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED);
+
+ mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+ }
+
+ @Test
+ public void testUpdateAppNotifyCreatorUnblock() throws Exception {
+ when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
+
+ mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+ }
+
+ @Test
+ public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
+ mService.setPreferencesHelper(mPreferencesHelper);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+
+ assertThat(mBinderService.getNotificationChannelsBypassingDnd(PKG, mUid).getList())
+ .isEmpty();
+ verify(mPreferencesHelper, never()).getImportance(anyString(), anyInt());
+ verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
new file mode 100644
index 0000000..55b12dd
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
+import android.permission.IPermissionManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PermissionHelperTest extends UiServiceTestCase {
+
+ @Mock
+ private PermissionManagerServiceInternal mPmi;
+ @Mock
+ private IPackageManager mPackageManager;
+ @Mock
+ private IPermissionManager mPermManager;
+
+ private PermissionHelper mPermissionHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true);
+ }
+
+ // TODO (b/194833441): Remove when the migration is enabled
+ @Test
+ public void testMethodsThrowIfMigrationDisabled() throws IllegalAccessException,
+ InvocationTargetException {
+ PermissionHelper permHelper =
+ new PermissionHelper(mPmi, mPackageManager, mPermManager, false);
+
+ Method[] allMethods = PermissionHelper.class.getDeclaredMethods();
+ for (Method method : allMethods) {
+ if (Modifier.isPublic(method.getModifiers()) &&
+ !Objects.equals("isMigrationEnabled", method.getName())) {
+ Parameter[] params = method.getParameters();
+ List<Object> args = Lists.newArrayListWithCapacity(params.length);
+ for (int i = 0; i < params.length; i++) {
+ Type type = params[i].getParameterizedType();
+ if (type.getTypeName().equals("java.lang.String")) {
+ args.add("");
+ } else if (type.getTypeName().equals("boolean")){
+ args.add(false);
+ } else if (type.getTypeName().equals("int")) {
+ args.add(1);
+ }
+ }
+ try {
+ method.invoke(permHelper, args.toArray());
+ fail("Method should have thrown because migration flag is disabled");
+ } catch (InvocationTargetException e) {
+ if (!(e.getTargetException() instanceof IllegalStateException)) {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testHasPermission() throws Exception {
+ when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
+ .thenReturn(PERMISSION_GRANTED);
+
+ assertThat(mPermissionHelper.hasPermission(1)).isTrue();
+
+ when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
+ .thenReturn(PERMISSION_SOFT_DENIED);
+
+ assertThat(mPermissionHelper.hasPermission(1)).isFalse();
+ }
+
+ @Test
+ public void testGetAppsRequestingPermission() throws Exception {
+ // App that does not request permission
+ PackageInfo notThis = new PackageInfo();
+ notThis.packageName = "wrong.permission";
+ notThis.requestedPermissions = new String[] {"something else"};
+ // App that does not request any permissions (null check
+ PackageInfo none = new PackageInfo();
+ none.packageName = "no.permissions";
+ // 2 apps that request the permission
+ PackageInfo first = new PackageInfo();
+ first.packageName = "first";
+ first.requestedPermissions =
+ new String[] {"something else", Manifest.permission.POST_NOTIFICATIONS};
+ ApplicationInfo aiFirst = new ApplicationInfo();
+ aiFirst.uid = 1;
+ first.applicationInfo = aiFirst;
+ PackageInfo second = new PackageInfo();
+ second.packageName = "second";
+ second.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS};
+ ApplicationInfo aiSecond = new ApplicationInfo();
+ aiSecond.uid = 2;
+ second.applicationInfo = aiSecond;
+
+ Map<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
+
+ ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
+ ImmutableList.of(notThis, none, first, second));
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())).thenReturn(infos);
+
+ Map<Integer, String> actual = mPermissionHelper.getAppsRequestingPermission(0);
+
+ assertThat(actual).containsExactlyEntriesIn(expected);
+ }
+
+ @Test
+ public void testGetAppsGrantedPermission_noApps() throws Exception {
+ int userId = 1;
+ ParceledListSlice<PackageInfo> infos = ParceledListSlice.emptyList();
+ when(mPackageManager.getPackagesHoldingPermissions(
+ eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
+ .thenReturn(infos);
+ assertThat(mPermissionHelper.getAppsGrantedPermission(userId)).isNotNull();
+ }
+
+ @Test
+ public void testGetAppsGrantedPermission() throws Exception {
+ int userId = 1;
+ PackageInfo first = new PackageInfo();
+ first.packageName = "first";
+ first.requestedPermissions =
+ new String[] {"something else", Manifest.permission.POST_NOTIFICATIONS};
+ ApplicationInfo aiFirst = new ApplicationInfo();
+ aiFirst.uid = 1;
+ first.applicationInfo = aiFirst;
+ PackageInfo second = new PackageInfo();
+ second.packageName = "second";
+ second.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS};
+ ApplicationInfo aiSecond = new ApplicationInfo();
+ aiSecond.uid = 2;
+ second.applicationInfo = aiSecond;
+
+ ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
+ ImmutableList.of(first, second));
+ when(mPackageManager.getPackagesHoldingPermissions(
+ eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
+ .thenReturn(infos);
+
+ Map<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
+
+ assertThat(mPermissionHelper.getAppsGrantedPermission(userId))
+ .containsExactlyEntriesIn(expected);
+ }
+
+ @Test
+ public void testSetNotificationPermission_grantUserSet() throws Exception {
+ mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
+
+ verify(mPermManager).grantRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
+ FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ }
+
+ @Test
+ public void testSetNotificationPermission_revokeUserSet() throws Exception {
+ mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
+
+ verify(mPermManager).revokeRuntimePermission(
+ eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+ verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
+ FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ }
+
+ @Test
+ public void testSetNotificationPermission_grantNotUserSet() throws Exception {
+ mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
+
+ verify(mPermManager).grantRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager, never()).updatePermissionFlags(
+ anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testSetNotificationPermission_revokeNotUserSet() throws Exception {
+ mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
+
+ verify(mPermManager).revokeRuntimePermission(
+ eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+ verify(mPermManager, never()).updatePermissionFlags(
+ anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 66d1577..5324ec5f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -153,7 +153,7 @@
Uri.parse("content://" + TEST_AUTHORITY
+ "/internal/audio/media/10?title=Test&canonical=1");
- @Mock NotificationUsageStats mUsageStats;
+ @Mock PermissionHelper mPermissionHelper;
@Mock RankingHandler mHandler;
@Mock PackageManager mPm;
IContentProvider mTestIContentProvider;
@@ -269,8 +269,8 @@
mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
- mAppOpsManager, mStatsEventBuilderFactory);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -1393,16 +1393,6 @@
assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
user).getList().size());
- // block notifications from this app
- mHelper.setEnabled(PKG_N_MR1, user, false);
- assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
- user).getList().size());
-
- // re-enable notifications from this app
- mHelper.setEnabled(PKG_N_MR1, user, true);
- assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
- user).getList().size());
-
// setBypassDnd false for some channels
channel1.setBypassDnd(false);
channel2.setBypassDnd(false);
@@ -1416,78 +1406,7 @@
}
@Test
- public void testGetAppsBypassingDndCount_noAppsBypassing() throws Exception {
- assertEquals(0, mHelper.getAppsBypassingDndCount(USER.getIdentifier()));
- }
-
- @Test
- public void testGetAppsBypassingDndCount_noAppsForUserIdBypassing() throws Exception {
- int user = 9;
- NotificationChannel channel = new NotificationChannel("id", "name",
- NotificationManager.IMPORTANCE_MAX);
- channel.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
-
- assertEquals(0, mHelper.getAppsBypassingDndCount(user));
- }
-
- @Test
- public void testGetAppsBypassingDndCount_oneChannelBypassing_groupBlocked() {
- int user = USER.getIdentifier();
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- NotificationChannel channel1 = new NotificationChannel("id1", "name1",
- NotificationManager.IMPORTANCE_MAX);
- channel1.setBypassDnd(true);
- channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ true);
- mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
-
- assertEquals(1, mHelper.getAppsBypassingDndCount(user));
-
- // disable group
- ncg.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ false);
- assertEquals(0, mHelper.getAppsBypassingDndCount(user));
- }
-
- @Test
- public void testGetAppsBypassingDndCount_oneAppBypassing() {
- int user = USER.getIdentifier();
- NotificationChannel channel1 = new NotificationChannel("id1", "name1",
- NotificationManager.IMPORTANCE_MAX);
- NotificationChannel channel2 = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_MAX);
- NotificationChannel channel3 = new NotificationChannel("id3", "name3",
- NotificationManager.IMPORTANCE_MAX);
- channel1.setBypassDnd(true);
- channel2.setBypassDnd(true);
- channel3.setBypassDnd(true);
- // has DND access, so can set bypassDnd attribute
- mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
- mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
- mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
- assertEquals(1, mHelper.getAppsBypassingDndCount(user));
-
- // block notifications from this app
- mHelper.setEnabled(PKG_N_MR1, user, false);
- assertEquals(0, mHelper.getAppsBypassingDndCount(user)); // no apps can bypass dnd
-
- // re-enable notifications from this app
- mHelper.setEnabled(PKG_N_MR1, user, true);
- assertEquals(1, mHelper.getAppsBypassingDndCount(user));
-
- // setBypassDnd false for some channels
- channel1.setBypassDnd(false);
- channel2.setBypassDnd(false);
- assertEquals(1, mHelper.getAppsBypassingDndCount(user));
-
- // setBypassDnd false for rest of the channels
- channel3.setBypassDnd(false);
- assertEquals(0, mHelper.getAppsBypassingDndCount(user));
- }
-
- @Test
- public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
+ public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() throws Exception {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
// create notification channel that can't bypass dnd
@@ -1501,6 +1420,70 @@
// create notification channel that can bypass dnd
// expected result: areChannelsBypassingDnd = true
+ assertTrue(mHelper.getImportance(PKG_N_MR1, uid) != IMPORTANCE_NONE);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // delete channels
+ mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId());
+ assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId());
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testCreateAndUpdateChannelsBypassingDnd_permissionHelper() {
+ int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+
+ when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
+
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // Recreate a channel & now the app has dnd access granted and can set the bypass dnd field
+ NotificationChannel update = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, update, true, true);
+
+ assertTrue(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testCreateAndDeleteCanChannelsBypassDnd_permissionHelper() throws Exception {
+ int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+
+ when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
+
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // create notification channel that can bypass dnd, using local app level settings
+ // expected result: areChannelsBypassingDnd = true
NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
@@ -1521,6 +1504,79 @@
}
@Test
+ public void testBlockedGroupDoesNotBypassDnd() throws Exception {
+ int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+
+ // start in a 'allowed to bypass dnd state'
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+
+ // create notification channel that can bypass dnd, but app is blocked
+ // expected result: areChannelsBypassingDnd = false
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, uid, group, false);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setGroup("group");
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testBlockedAppsDoNotBypassDnd_localSettings() throws Exception {
+ int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+
+ // start in a 'allowed to bypass dnd state'
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ mHelper.setImportance(PKG_N_MR1, uid, IMPORTANCE_NONE);
+ // create notification channel that can bypass dnd, but app is blocked
+ // expected result: areChannelsBypassingDnd = false
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testBlockedAppsDoNotBypassDnd_permissionHelper() throws Exception {
+ int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+ when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(false);
+ // start in a 'allowed to bypass dnd state'
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ // create notification channel that can bypass dnd, but app is blocked
+ // expected result: areChannelsBypassingDnd = false
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
public void testUpdateCanChannelsBypassDnd() throws Exception {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
@@ -1557,7 +1613,8 @@
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
assertFalse(mHelper.areChannelsBypassingDnd());
verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
@@ -1569,7 +1626,8 @@
// start notification policy off with mAreChannelsBypassingDnd = false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
assertFalse(mHelper.areChannelsBypassingDnd());
verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
@@ -2338,26 +2396,6 @@
}
@Test
- public void testGetBlockedAppCount_noApps() {
- assertEquals(0, mHelper.getBlockedAppCount(0));
- }
-
- @Test
- public void testGetBlockedAppCount_noAppsForUserId() {
- mHelper.setEnabled(PKG_N_MR1, 100, false);
- assertEquals(0, mHelper.getBlockedAppCount(9));
- }
-
- @Test
- public void testGetBlockedAppCount_appsForUserId() {
- mHelper.setEnabled(PKG_N_MR1, 1020, false);
- mHelper.setEnabled(PKG_N_MR1, 1030, false);
- mHelper.setEnabled(PKG_N_MR1, 1060, false);
- mHelper.setEnabled(PKG_N_MR1, 1000, true);
- assertEquals(3, mHelper.getBlockedAppCount(0));
- }
-
- @Test
public void testAppBlockedLogging() {
mHelper.setEnabled(PKG_N_MR1, 1020, false);
assertEquals(1, mLogger.getCalls().size());
@@ -2375,7 +2413,8 @@
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ "</package>\n"
+ "</ranking>\n";
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM);
@@ -2388,7 +2427,8 @@
mHelper.setHideSilentStatusIcons(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2485,7 +2525,8 @@
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2497,7 +2538,8 @@
mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2510,7 +2552,8 @@
mHelper.revokeNotificationDelegate(PKG_O, UID_O);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2523,7 +2566,8 @@
mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2542,7 +2586,8 @@
mHelper.revokeNotificationDelegate(PKG_O, UID_O);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2561,7 +2606,8 @@
assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O));
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2616,7 +2662,8 @@
mHelper.getAppLockedFields(PKG_O, UID_O));
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -2653,7 +2700,8 @@
mHelper.getAppLockedFields(PKG_O, UID_O));
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3255,7 +3303,8 @@
@Test
public void testPlaceholderConversationId_shortcutRequired() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
final String xml = "<ranking version=\"1\">\n"
@@ -3274,7 +3323,8 @@
@Test
public void testNormalConversationId_shortcutRequired() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
final String xml = "<ranking version=\"1\">\n"
@@ -3293,7 +3343,8 @@
@Test
public void testNoConversationId_shortcutRequired() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
final String xml = "<ranking version=\"1\">\n"
@@ -3312,7 +3363,8 @@
@Test
public void testDeleted_noTime() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
final String xml = "<ranking version=\"1\">\n"
@@ -3331,7 +3383,8 @@
@Test
public void testDeleted_twice() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
@@ -3342,7 +3395,8 @@
@Test
public void testDeleted_recentTime() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
@@ -3359,7 +3413,8 @@
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
null);
parser.nextTag();
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
@@ -3370,7 +3425,8 @@
@Test
public void testUnDelete_time() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
@@ -3389,7 +3445,8 @@
@Test
public void testDeleted_longTime() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
mAppOpsManager, mStatsEventBuilderFactory);
long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 27ae46c..59d9a35 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,6 +48,7 @@
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
@@ -62,6 +63,7 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.server.LocalServices;
@@ -87,6 +89,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
+// TODO (b/194833441): remove when notification permission is enabled
public class RoleObserverTest extends UiServiceTestCase {
private TestableNotificationManagerService mService;
private NotificationManagerService.RoleObserver mRoleObserver;
@@ -159,10 +162,11 @@
mock(UsageStatsManagerInternal.class),
mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
mock(UriGrantsManagerInternal.class),
- mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
+ mock(AppOpsManager.class), mock(IAppOpsService.class),
+ mUm, mock(NotificationHistoryManager.class),
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class),
- mock(MultiRateLimiter.class));
+ mock(MultiRateLimiter.class), mock(PermissionHelper.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
new file mode 100644
index 0000000..bde0485
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.companion.ICompanionDeviceManager;
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceIdSequence;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class TestableNotificationManagerService extends NotificationManagerService {
+ int countSystemChecks = 0;
+ boolean isSystemUid = true;
+ int countLogSmartSuggestionsVisible = 0;
+ Set<Integer> mChannelToastsSent = new HashSet<>();
+
+ String stringArrayResourceValue;
+ @Nullable
+ NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
+
+ TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
+ InstanceIdSequence notificationInstanceIdSequence) {
+ super(context, logger, notificationInstanceIdSequence);
+ }
+
+ RankingHelper getRankingHelper() {
+ return mRankingHelper;
+ }
+
+ @Override
+ protected boolean isCallingUidSystem() {
+ countSystemChecks++;
+ return isSystemUid;
+ }
+
+ @Override
+ protected boolean isCallerSystemOrPhone() {
+ countSystemChecks++;
+ return isSystemUid;
+ }
+
+ @Override
+ protected ICompanionDeviceManager getCompanionManager() {
+ return null;
+ }
+
+ @Override
+ protected void reportUserInteraction(NotificationRecord r) {
+ return;
+ }
+
+ @Override
+ protected void handleSavePolicyFile() {
+ return;
+ }
+
+ @Override
+ void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+ super.logSmartSuggestionsVisible(r, notificationLocation);
+ countLogSmartSuggestionsVisible++;
+ }
+
+ @Override
+ protected void setNotificationAssistantAccessGrantedForUserInternal(
+ ComponentName assistant, int userId, boolean granted, boolean userSet) {
+ if (mNotificationAssistantAccessGrantedCallback != null) {
+ mNotificationAssistantAccessGrantedCallback.onGranted(assistant, userId, granted,
+ userSet);
+ return;
+ }
+ super.setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted,
+ userSet);
+ }
+
+ @Override
+ protected String[] getStringArrayResource(int key) {
+ return new String[] {stringArrayResourceValue};
+ }
+
+ protected void setStringArrayResourceValue(String value) {
+ stringArrayResourceValue = value;
+ }
+
+ void setNotificationAssistantAccessGrantedCallback(
+ @Nullable NotificationAssistantAccessGrantedCallback callback) {
+ this.mNotificationAssistantAccessGrantedCallback = callback;
+ }
+
+ interface NotificationAssistantAccessGrantedCallback {
+ void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet);
+ }
+
+ @Override
+ protected void doChannelWarningToast(int uid, CharSequence toastText) {
+ mChannelToastsSent.add(uid);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 65733d7..1435b6f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -77,6 +77,7 @@
import static com.android.server.wm.ActivityRecord.State.STARTED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
@@ -508,7 +509,7 @@
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(newConfig);
verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
- eq(activity.appToken), eq(expected));
+ eq(activity.token), eq(expected));
}
@Test
@@ -723,7 +724,7 @@
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(newConfig);
verify(mAtm.getLifecycleManager()).scheduleTransaction(
- eq(activity.app.getThread()), eq(activity.appToken), eq(expected));
+ eq(activity.app.getThread()), eq(activity.token), eq(expected));
} finally {
stack.getDisplayArea().removeChild(stack);
}
@@ -787,7 +788,7 @@
final ActivityRecord activity = createActivityWithTask();
assertTrue(activity.hasSavedState());
- ActivityRecord.activityResumedLocked(activity.appToken, false /* handleSplashScreenExit */);
+ ActivityRecord.activityResumedLocked(activity.token, false /* handleSplashScreenExit */);
assertFalse(activity.hasSavedState());
assertNull(activity.getSavedState());
}
@@ -1610,7 +1611,7 @@
setup.accept(activity);
activity.getTask().removeImmediately("test");
try {
- verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken),
+ verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.token),
isA(DestroyActivityItem.class));
} catch (RemoteException ignored) {
}
@@ -1751,6 +1752,11 @@
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Set to visible so the activity can freeze the screen.
activity.setVisibility(true);
+ // Update the display policy to make the screen fully turned on so the freeze is allowed
+ display.getDisplayPolicy().screenTurnedOn(null);
+ display.getDisplayPolicy().finishKeyguardDrawn();
+ display.getDisplayPolicy().finishWindowsDrawn();
+ display.getDisplayPolicy().finishScreenTurningOn();
display.rotateInDifferentOrientationIfNeeded(activity);
display.setFixedRotationLaunchingAppUnchecked(activity);
@@ -2463,7 +2469,7 @@
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
assertHasStartingWindow(activity);
activity.removeStartingWindow();
@@ -2476,7 +2482,7 @@
activity.mTargetSdk = targetSdk;
activity.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
assertHasStartingWindow(activity);
assertEquals(activity.mStartingData.mTypeParams & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN,
@@ -2514,11 +2520,11 @@
.setVisible(false).build();
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
activity2.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1,
- true, true, false, true, false, false);
+ true, true, false, true, false, false, false);
waitUntilHandlersIdle();
assertFalse(mDisplayContent.mSkipAppTransitionAnimation);
assertNoStartingWindow(activity1);
@@ -2536,11 +2542,11 @@
activity2.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0,
activity1, true, true, false,
- true, false, false);
+ true, false, false, false);
});
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(activity1);
assertHasStartingWindow(activity2);
@@ -2553,11 +2559,11 @@
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
activity2.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1,
- true, true, false, true, false, false);
+ true, true, false, true, false, false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(activity1);
assertHasStartingWindow(activity2);
@@ -2599,7 +2605,7 @@
"Test", 0 /* labelRes */, 0 /* icon */, 0 /* logo */, 0 /* windowFlags */,
null /* transferFrom */, true /* newTask */, true /* taskSwitch */,
false /* processRunning */, false /* allowTaskSnapshot */,
- false /* activityCreate */, false /* suggestEmpty */);
+ false /* activityCreate */, false /* suggestEmpty */, false /* activityAllDrawn */);
waitUntilHandlersIdle();
assertHasStartingWindow(activity);
@@ -2647,7 +2653,7 @@
task.positionChildAt(topActivity, POSITION_TOP);
activity.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
// Make activities to have different rotation from it display and set fixed rotation
@@ -2664,7 +2670,7 @@
// on activity2.
topActivity.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity,
- false, false, false, true, false, false);
+ false, false, false, true, false, false, false);
waitUntilHandlersIdle();
assertTrue(topActivity.hasFixedRotationTransform());
}
@@ -2680,7 +2686,7 @@
// Add a starting window.
activityTop.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false, false);
+ false, false, false);
waitUntilHandlersIdle();
// Make the top one invisible, and try transferring the starting window from the top to the
@@ -2999,7 +3005,7 @@
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Expect IME insets frozen state will reset when the activity has no IME focusable window.
- app.mActivityRecord.forAllWindowsUnchecked(w -> {
+ app.mActivityRecord.forAllWindows(w -> {
w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
return true;
}, true);
@@ -3026,6 +3032,10 @@
// Because the app is waiting for transition, it should not hide the surface.
assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Ensure onAnimationFinished will callback when the closing animation is finished.
+ verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+ eq(null));
}
private void assertHasStartingWindow(ActivityRecord atoken) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 3e8a2e9..0cab911 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -264,7 +264,7 @@
final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT)
|| containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
- ? source.appToken : null;
+ ? source.token : null;
final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE)
? 1 : 0;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 5d1a068..b0fb812 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -104,12 +104,12 @@
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
assertTrue("Activity must be finished", mAtm.mActivityClientController.finishActivity(
- activity.appToken, 0 /* resultCode */, null /* resultData */,
+ activity.token, 0 /* resultCode */, null /* resultData */,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
assertTrue(activity.finishing);
assertTrue("Duplicate activity finish request must also return 'true'",
- mAtm.mActivityClientController.finishActivity(activity.appToken, 0 /* resultCode */,
+ mAtm.mActivityClientController.finishActivity(activity.token, 0 /* resultCode */,
null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
}
@@ -839,6 +839,30 @@
wpc.getConfiguration().getLocales());
}
+ @Test
+ public void testPackageConfigUpdate_commitConfig_configSuccessfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true,
+ DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ mAtm.mInternal.onProcessAdded(wpc);
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_ID);
+ // committing new configuration returns true;
+ assertTrue(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .commit());
+ // applying the same configuration returns false.
+ assertFalse(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .commit());
+ assertTrue(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
+ .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit());
+ }
+
private WindowProcessController createWindowProcessController(String packageName,
int userId) {
WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 82140f4..5d0e34a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -43,6 +43,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -893,6 +894,33 @@
}
@Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ activity.allDrawn = true;
+ // Set wallpaper as visible.
+ final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+ mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+ spyOn(mDisplayContent.mWallpaperController);
+ doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+
+ // Should not be overridden when there is wallpaper in the transition.
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
public void testTransitionGoodToGoForTaskFragments() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 1f123cc..d94e6c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -24,7 +24,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
-import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -76,6 +75,7 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
/**
* Tests for the {@link DisplayArea} container.
@@ -153,7 +153,7 @@
public void testForAllTaskDisplayAreas_onlyTraversesDisplayAreaOfTypeAny() {
final RootDisplayArea root =
new DisplayAreaPolicyBuilderTest.SurfacelessDisplayAreaRoot(mWm);
- final Function<TaskDisplayArea, Boolean> callback0 = tda -> false;
+ final Predicate<TaskDisplayArea> callback0 = tda -> false;
final Consumer<TaskDisplayArea> callback1 = tda -> { };
final BiFunction<TaskDisplayArea, Integer, Integer> callback2 = (tda, result) -> result;
final Function<TaskDisplayArea, TaskDisplayArea> callback3 = tda -> null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 79918e2..663d45b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -174,25 +174,31 @@
final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
mDisplayContent, "exiting app");
final ActivityRecord exitingApp = exitingAppWindow.mActivityRecord;
- // Wait until everything in animation handler get executed to prevent the exiting window
- // from being removed during WindowSurfacePlacer Traversal.
- waitUntilHandlersIdle();
-
+ exitingApp.startAnimation(exitingApp.getPendingTransaction(), mock(AnimationAdapter.class),
+ false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION);
exitingApp.mIsExiting = true;
- exitingApp.getTask().getRootTask().mExitingActivities.add(exitingApp);
+ // If the activity is animating, its window should not be removed.
+ mDisplayContent.handleCompleteDeferredRemoval();
- assertForAllWindowsOrder(Arrays.asList(
+ final ArrayList<WindowState> windows = new ArrayList<>(Arrays.asList(
mWallpaperWindow,
- exitingAppWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
+ exitingAppWindow,
mDockedDividerWindow,
mImeWindow,
mImeDialogWindow,
mStatusBarWindow,
mNotificationShadeWindow,
mNavBarWindow));
+ assertForAllWindowsOrder(windows);
+
+ exitingApp.cancelAnimation();
+ // The exiting window will be removed because its parent is no longer animating.
+ mDisplayContent.handleCompleteDeferredRemoval();
+ windows.remove(exitingAppWindow);
+ assertForAllWindowsOrder(windows);
}
@UseTestDisplay(addAllCommonWindows = true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index a680cba..9d2a691 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -257,7 +257,7 @@
verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */);
// Simulate the app transition finishing
- mController.mAppTransitionListener.onAppTransitionStartingLocked(false, 0, 0, 0);
+ mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0);
verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6407c92..6f0bea7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -149,7 +149,7 @@
final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
resizeDisplay(mTask.mDisplayContent, 600, 1200);
// The visible activity should recompute configuration according to the last parent bounds.
- mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.appToken);
+ mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.token);
assertEquals(RESTARTING_PROCESS, mActivity.getState());
assertNotEquals(originalOverrideBounds, mActivity.getBounds());
@@ -174,7 +174,9 @@
// The activity should be able to accept negative x position [-150, 100 - 150, 600].
final int dx = bounds.left + bounds.width() / 2;
- mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom);
+ final int dy = bounds.top + bounds.height() / 2;
+ mTask.setBounds(bounds.left - dx, bounds.top - dy, bounds.right - dx, bounds.bottom - dy);
+ // expected:<Rect(-150, 100 - 150, 600)> but was:<Rect(-150, 0 - 150, 500)>
assertEquals(mTask.getBounds(), mActivity.getBounds());
final int density = mActivity.getConfiguration().densityDpi;
@@ -1850,7 +1852,7 @@
// At launch.
/* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
+ /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(0, 0, 350, 700));
}
@@ -1863,7 +1865,7 @@
// At launch.
/* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(525, 0, 875, 700));
}
@@ -1878,7 +1880,7 @@
// At launch.
/* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(525, 0, 875, 700));
@@ -1888,7 +1890,7 @@
// At launch.
/* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(525, 0, 875, 700));
}
@@ -1901,7 +1903,7 @@
// At launch.
/* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
+ /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
}
@@ -2093,7 +2095,7 @@
assertTrue(mActivity.inSizeCompatMode());
// Activity is in size compat mode but not scaled.
- assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
+ assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds());
}
private static WindowState addWindowToActivity(ActivityRecord activity) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index d475c46e..cbdf4fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -411,7 +411,7 @@
.build();
mAtm.mWindowOrganizerController.mLaunchTaskFragments
.put(mFragmentToken, mTaskFragment);
- mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.appToken);
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
clearInvocations(mAtm.mRootWindowContainer);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index e528a4a..168c250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -1321,6 +1321,50 @@
}
@Test
+ public void testDefaultFreeformSizeRespectsMinAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMinAspectRatio(5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder()
+ .setOptions(options).calculate());
+
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio,
+ aspectRatio >= 5f);
+ }
+
+ @Test
+ public void testDefaultFreeformSizeRespectsMaxAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMaxAspectRatio(1.5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder()
+ .setOptions(options).calculate());
+
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio,
+ aspectRatio <= 1.5f);
+ }
+
+ @Test
public void testCascadesToSourceSizeForFreeform() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1348,6 +1392,72 @@
}
@Test
+ public void testCascadesToSourceSizeForFreeformRespectingMinAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMinAspectRatio(5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio,
+ aspectRatio >= 5f);
+ }
+
+ @Test
+ public void testCascadesToSourceSizeForFreeformRespectingMaxAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMaxAspectRatio(1.5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio,
+ aspectRatio <= 1.5f);
+ }
+
+ @Test
public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index ce568f1..5fde7eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1056,9 +1056,9 @@
final ActivityRecord activity1 = task1.getBottomMostActivity();
assertEquals(task0.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity0.token, false /* onlyRoot */));
assertEquals(task1.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity1.token, false /* onlyRoot */));
}
/**
@@ -1077,11 +1077,11 @@
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity0.token, true /* onlyRoot */));
assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity1.token, true /* onlyRoot */));
assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
- ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity2.token, true /* onlyRoot */));
}
/**
@@ -1100,11 +1100,11 @@
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity0.token, true /* onlyRoot */));
assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity1.token, true /* onlyRoot */));
assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
- ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity2.token, true /* onlyRoot */));
}
/**
@@ -1126,11 +1126,11 @@
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity0.token, false /* onlyRoot */));
assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity1.token, false /* onlyRoot */));
assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity2.appToken, false /* onlyRoot */));
+ ActivityRecord.getTaskForActivityLocked(activity2.token, false /* onlyRoot */));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index acadb74..9001578 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -128,10 +128,6 @@
}
@Override
- public void setKeyguardCandidateLw(WindowState win) {
- }
-
- @Override
public Animation createHiddenByKeyguardExit(boolean onWallpaper,
boolean goingToNotificationShade, boolean subtleAnimation) {
return null;
@@ -368,7 +364,7 @@
}
@Override
- public int applyKeyguardOcclusionChange() {
+ public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) {
return 0;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index bbeb980..db7def8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -43,6 +43,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -442,7 +443,7 @@
assertTrue(window.isAnimating());
assertFalse(window.isAnimating(0, ANIMATION_TYPE_SCREEN_ROTATION));
assertTrue(window.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
- assertFalse(window.isAnimatingExcluding(0, ANIMATION_TYPE_APP_TRANSITION));
+ assertFalse(window.isAnimating(0, ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_APP_TRANSITION));
final TestWindowContainer child = window.addChildWindow();
assertFalse(child.isAnimating());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a8a9188..ca2b4ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -78,6 +78,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
@@ -563,7 +564,7 @@
final WindowState child = createWindow(w, TYPE_APPLICATION_PANEL, "child");
assertTrue(w.hasCompatScale());
- assertFalse(child.hasCompatScale());
+ assertTrue(child.hasCompatScale());
makeWindowVisible(w, child);
w.setRequestedSize(100, 200);
@@ -574,21 +575,26 @@
w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
child.mAttrs.gravity = Gravity.CENTER;
DisplayContentTests.performLayout(mDisplayContent);
+ final Rect parentFrame = w.getFrame();
+ final Rect childFrame = child.getFrame();
// Frame on screen = 200x400 (200, 200 - 400, 600). Compat frame on client = 100x200.
final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
unscaledCompatFrame.scale(overrideScale);
- final Rect parentFrame = w.getFrame();
- assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+ assertEquals(parentFrame, unscaledCompatFrame);
- final Rect childFrame = child.getFrame();
- assertEquals(childFrame, child.getWindowFrames().mCompatFrame);
- // Child frame = 50x100 (225, 250 - 275, 350) according to Gravity.CENTER.
- final int childX = parentFrame.left + child.mRequestedWidth / 2;
- final int childY = parentFrame.top + child.mRequestedHeight / 2;
- final Rect expectedChildFrame = new Rect(childX, childY, childX + child.mRequestedWidth,
- childY + child.mRequestedHeight);
- assertEquals(expectedChildFrame, childFrame);
+ // Frame on screen = 100x200 (250, 300 - 350, 500). Compat frame on client = 50x100.
+ unscaledCompatFrame.set(child.getWindowFrames().mCompatFrame);
+ unscaledCompatFrame.scale(overrideScale);
+ assertEquals(childFrame, unscaledCompatFrame);
+
+ // The position of child is relative to parent. So the local coordinates should be scaled.
+ final Point expectedChildPos = new Point(
+ (int) ((childFrame.left - parentFrame.left) / overrideScale),
+ (int) ((childFrame.top - parentFrame.top) / overrideScale));
+ final Point childPos = new Point();
+ child.transformFrameToSurfacePosition(childFrame.left, childFrame.top, childPos);
+ assertEquals(expectedChildPos, childPos);
// Surface should apply the scale.
w.prepareSurfaces();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 22ea3d5..6c3a1f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -24,6 +24,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
@@ -39,10 +40,12 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -401,6 +404,30 @@
}
@Test
+ public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
+ final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
+ mAppWindow.mActivityRecord, "imeAppTarget");
+ mDisplayContent.setImeInputTarget(imeAppTarget);
+ mDisplayContent.setImeLayeringTarget(imeAppTarget);
+ mDisplayContent.updateImeParent();
+
+ // Simulate the ime layering target task is animating with recents animation.
+ final Task imeAppTargetTask = imeAppTarget.getTask();
+ final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
+ spyOn(imeTargetTaskAnimator);
+ doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
+ doReturn(true).when(imeTargetTaskAnimator).isAnimating();
+
+ mDisplayContent.assignChildLayers(mTransaction);
+
+ // Ime should on top of the application window when in recents animation and keep
+ // attached on app.
+ assertTrue(mDisplayContent.shouldImeAttachedToApp());
+ assertWindowHigher(mImeWindow, imeAppTarget);
+ }
+
+
+ @Test
public void testAssignWindowLayers_ForNegativelyZOrderedSubtype() {
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index be37a91..24ce7e7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -830,7 +830,7 @@
return;
}
- if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ if (!event.recognitionStillActive) {
model.setStopped();
}
@@ -971,7 +971,7 @@
return;
}
- if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ if (!event.recognitionStillActive) {
modelData.setStopped();
}
diff --git a/startop/OWNERS b/startop/OWNERS
index 2d1eb38..11d5ad0 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -1,7 +1,2 @@
-# mailing list: startop-eng@google.com
-calin@google.com
-chriswailes@google.com
-eholk@google.com
-iam@google.com
-mathieuc@google.com
-yawanng@google.com
+include platform/art:/OWNERS
+keunyoung@google.com
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 1a0e526..77046f2 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -21,16 +21,13 @@
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
-import android.app.job.JobService;
import android.app.job.JobScheduler;
+import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
import android.os.Handler;
-import android.os.Parcel;
+import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -45,18 +42,15 @@
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.wm.ActivityMetricsLaunchObserver;
-import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
-import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.time.Duration;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
-import java.util.HashMap;
-import java.util.List;
/**
* System-server-local proxy into the {@code IIorap} native service.
@@ -347,7 +341,8 @@
launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
- BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
+ BackgroundDexOptService.getService().addPackagesUpdatedListener(
+ mDexOptPackagesUpdated);
mRegisteredListeners = true;
@@ -555,7 +550,6 @@
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_IORAPD, IORAPD_COMPONENT_NAME);
builder.setPeriodic(JOB_INTERVAL_MS);
- builder.setPrefetch(true);
builder.setRequiresCharging(true);
builder.setRequiresDeviceIdle(true);
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
index 391dce1..775f1b8 100644
--- a/telecomm/TEST_MAPPING
+++ b/telecomm/TEST_MAPPING
@@ -25,14 +25,6 @@
]
},
{
- "name": "CtsTelephonySdk28TestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
- {
"name": "CtsTelephony2TestCases",
"options": [
{
diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING
index 02d4eb3..73e3dcd 100644
--- a/telephony/TEST_MAPPING
+++ b/telephony/TEST_MAPPING
@@ -33,14 +33,6 @@
]
},
{
- "name": "CtsTelephonySdk28TestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
- {
"name": "CtsTelephony3TestCases",
"options": [
{
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7257dd8..edfb7bf 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1319,9 +1319,14 @@
/**
* Determines whether a maximum size limit for IMS conference calls is enforced on the device.
* When {@code true}, IMS conference calls will be limited to at most
- * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants. When {@code false}, no attempt is made
- * to limit the number of participants in a conference (the carrier will raise an error when an
- * attempt is made to merge too many participants into a conference).
+ * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants. When {@code false}, no attempt is
+ * made to limit the number of participants in a conference (the carrier will raise an error
+ * when an attempt is made to merge too many participants into a conference).
+ * <p>
+ * Note: The maximum size of a conference can ONLY be supported where
+ * {@link #KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL} is {@code true} since the platform
+ * needs conference event package data to accurately know the number of participants in the
+ * conference.
*/
public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL =
"is_ims_conference_size_enforced_bool";
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index ec12040..5b44dba 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -43,6 +43,10 @@
@VisibleForTesting
static final int FPLMN_BYTE_SIZE = 3;
+ // ICCID used for tests by some OEMs
+ // TODO(b/159354974): Replace the constant here with UiccPortInfo.ICCID_REDACTED once ready
+ private static final String TEST_ICCID = "FFFFFFFFFFFFFFFFFFFF";
+
// A table mapping from a number to a hex character for fast encoding hex strings.
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
@@ -923,6 +927,9 @@
* Strip all the trailing 'F' characters of a string, e.g., an ICCID.
*/
public static String stripTrailingFs(String s) {
+ if (TEST_ICCID.equals(s)) {
+ return s;
+ }
return s == null ? null : s.replaceAll("(?i)f*$", "");
}
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index 2e4b6da..fe2fe0b 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@
private static BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.bp b/tests/DynamicCodeLoggerIntegrationTests/Android.bp
new file mode 100644
index 0000000..448d46f
--- /dev/null
+++ b/tests/DynamicCodeLoggerIntegrationTests/Android.bp
@@ -0,0 +1,60 @@
+//
+// Copyright 2017 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_test_helper_library {
+ name: "DynamicCodeLoggerTestLibrary",
+ srcs: ["src/com/android/dcl/**/*.java"],
+
+}
+
+cc_library_shared {
+ name: "DynamicCodeLoggerNativeTestLibrary",
+ srcs: ["src/cpp/com_android_dcl_Jni.cpp"],
+ header_libs: ["jni_headers"],
+ sdk_version: "28",
+ stl: "c++_static",
+}
+
+cc_binary {
+ name: "DynamicCodeLoggerNativeExecutable",
+ srcs: ["src/cpp/test_executable.cpp"],
+}
+
+android_test {
+ name: "DynamicCodeLoggerIntegrationTests",
+
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "shared",
+ srcs: ["src/com/android/server/pm/**/*.java"],
+
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ ],
+
+ compile_multilib: "both",
+ jni_libs: ["DynamicCodeLoggerNativeTestLibrary"],
+
+ java_resources: [
+ ":DynamicCodeLoggerTestLibrary",
+ ":DynamicCodeLoggerNativeExecutable",
+ ],
+}
diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.mk b/tests/DynamicCodeLoggerIntegrationTests/Android.mk
deleted file mode 100644
index dab8304..0000000
--- a/tests/DynamicCodeLoggerIntegrationTests/Android.mk
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Copyright 2017 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-# Build a tiny library that the test app can dynamically load
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := DynamicCodeLoggerTestLibrary
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl)
-
-include $(BUILD_JAVA_LIBRARY)
-
-dynamiccodeloggertest_jar := $(LOCAL_BUILT_MODULE)
-
-
-# Also build a native library that the test app can dynamically load
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := DynamicCodeLoggerNativeTestLibrary
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
-LOCAL_HEADER_LIBRARIES := jni_headers
-LOCAL_SDK_VERSION := 28
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_SHARED_LIBRARY)
-
-# And a standalone native executable that we can exec.
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := DynamicCodeLoggerNativeExecutable
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES := src/cpp/test_executable.cpp
-
-include $(BUILD_EXECUTABLE)
-
-dynamiccodeloggertest_executable := $(LOCAL_BUILT_MODULE)
-
-# Build the test app itself
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := DynamicCodeLoggerIntegrationTests
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := shared
-LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.rules \
- truth-prebuilt \
-
-# Include both versions of the .so if we have 2 arch
-LOCAL_MULTILIB := both
-LOCAL_JNI_SHARED_LIBRARIES := \
- DynamicCodeLoggerNativeTestLibrary \
-
-# This gets us the javalib.jar built by DynamicCodeLoggerTestLibrary above as well as the various
-# native binaries.
-LOCAL_JAVA_RESOURCE_FILES := \
- $(dynamiccodeloggertest_jar) \
- $(dynamiccodeloggertest_executable) \
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-include $(BUILD_PACKAGE)
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index 883c172..5430dee 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -114,7 +114,8 @@
// Obtained via "echo -n copied.jar | sha256sum"
String expectedNameHash =
"1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
- String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
+ String expectedContentHash = copyAndHashResource(
+ "/DynamicCodeLoggerTestLibrary.jar", privateCopyFile);
// Feed the jar to a class loader and make sure it contains what we expect.
ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
@@ -135,7 +136,8 @@
File privateCopyFile = privateFile("copied2.jar");
String expectedNameHash =
"202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
- String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
+ String expectedContentHash = copyAndHashResource(
+ "/DynamicCodeLoggerTestLibrary.jar", privateCopyFile);
// This time make sure an unknown class loader is an ancestor of the class loader we use.
ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 511fc26..5aa1e27 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -17,7 +17,9 @@
package com.android.server.wm.flicker.close
import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -35,6 +37,8 @@
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.flicker.replacesLayer
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
+import org.junit.Rule
import org.junit.Test
/**
@@ -44,6 +48,9 @@
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
/**
* Specification of the test transition to execute
*/
@@ -189,4 +196,22 @@
open fun launcherLayerReplacesApp() {
testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT)
}
+
+ @Postsubmit
+ @Test
+ fun runPresubmitAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ @Postsubmit
+ @Test
+ fun runPostsubmitAssertion() {
+ flickerRule.checkPostsubmitAssertions()
+ }
+
+ @FlakyTest
+ @Test
+ fun runFlakyAssertion() {
+ flickerRule.checkFlakyAssertions()
+ }
}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 663af70..a05b78c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -17,11 +17,13 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
+import android.view.Display
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
@@ -73,16 +75,22 @@
device.pressHome()
wmHelper.waitForAppTransitionIdle()
device.pressRecentApps()
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitFor(
+ WindowManagerConditionsFactory
+ .isAppTransitionIdle(Display.DEFAULT_DISPLAY),
+ WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT),
+ WindowManagerConditionsFactory.hasLayersAnimating().negate()
+ )
this.setRotation(testSpec.config.startRotation)
}
}
transitions {
device.reopenAppFromOverview(wmHelper)
wmHelper.waitFor(
- WindowManagerConditionsFactory.hasLayersAnimating().negate(),
- WindowManagerConditionsFactory.isWMStateComplete(),
- WindowManagerConditionsFactory.isHomeActivityVisible().negate()
+ WindowManagerConditionsFactory.hasLayersAnimating().negate(),
+ WindowManagerConditionsFactory.isWMStateComplete(),
+ WindowManagerConditionsFactory.isLayerVisible(LAUNCHER_COMPONENT).negate(),
+ WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT).negate()
)
wmHelper.waitForFullScreenApp(testApp.component)
}
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
index e5d6518..f5fe8f2 100644
--- a/tests/InputMethodStressTest/AndroidManifest.xml
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -19,7 +19,8 @@
package="com.android.inputmethod.stresstest">
<application>
- <activity android:name=".TestActivity"/>
+ <activity android:name=".AutoShowTest$TestActivity"/>
+ <activity android:name=".ImeOpenCloseStressTest$TestActivity"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
new file mode 100644
index 0000000..ad07205
--- /dev/null
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "InputMethodStressTest"
+ }
+ ]
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
new file mode 100644
index 0000000..33cad78
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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.inputmethod.stresstest;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.RootPermissionTest;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RootPermissionTest
+@RunWith(AndroidJUnit4.class)
+public final class AutoShowTest {
+
+ @Test
+ public void autoShow() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+ EditText editText = activity.getEditText();
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+ waitOnMainUntilImeIsShown(editText);
+ }
+
+ public static class TestActivity extends Activity {
+ private EditText mEditText;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // IME will be auto-shown if the following conditions are met:
+ // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED.
+ // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE.
+ getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE);
+ LinearLayout rootView = new LinearLayout(this);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ mEditText = new EditText(this);
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
+ // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true).
+ mEditText.requestFocus();
+ }
+
+ public EditText getEditText() {
+ return mEditText;
+ }
+ }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 5427fd8..0e86bc8 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -16,64 +16,81 @@
package com.android.inputmethod.stresstest;
-import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.google.common.truth.Truth.assertThat;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
+import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
+import android.os.Bundle;
import android.platform.test.annotations.RootPermissionTest;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
@RootPermissionTest
@RunWith(AndroidJUnit4.class)
-public class ImeOpenCloseStressTest {
+public final class ImeOpenCloseStressTest {
- private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final int NUM_TEST_ITERATIONS = 100;
- private Instrumentation mInstrumentation;
-
@Test
public void test() {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
Intent intent = new Intent()
.setAction(Intent.ACTION_MAIN)
- .setClass(mInstrumentation.getContext(), TestActivity.class)
+ .setClass(instrumentation.getContext(), TestActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- TestActivity activity = (TestActivity) mInstrumentation.startActivitySync(intent);
- eventually(() -> assertThat(callOnMainSync(activity::hasWindowFocus)).isTrue(), TIMEOUT);
+ TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+ EditText editText = activity.getEditText();
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
- mInstrumentation.runOnMainSync(activity::showIme);
- eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isTrue(), TIMEOUT);
- mInstrumentation.runOnMainSync(activity::hideIme);
- eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isFalse(), TIMEOUT);
+ instrumentation.runOnMainSync(activity::showIme);
+ waitOnMainUntilImeIsShown(editText);
+ instrumentation.runOnMainSync(activity::hideIme);
+ waitOnMainUntilImeIsHidden(editText);
}
}
- private <V> V callOnMainSync(Callable<V> callable) {
- AtomicReference<V> result = new AtomicReference<>();
- AtomicReference<Exception> thrownException = new AtomicReference<>();
- mInstrumentation.runOnMainSync(() -> {
- try {
- result.set(callable.call());
- } catch (Exception e) {
- thrownException.set(e);
- }
- });
- if (thrownException.get() != null) {
- throw new RuntimeException("Exception thrown from Main thread", thrownException.get());
+ public static class TestActivity extends Activity {
+
+ private EditText mEditText;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout rootView = new LinearLayout(this);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ mEditText = new EditText(this);
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
}
- return result.get();
+
+ public EditText getEditText() {
+ return mEditText;
+ }
+
+ public void showIme() {
+ mEditText.requestFocus();
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.showSoftInput(mEditText, 0);
+ }
+
+ public void hideIme() {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+ }
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
new file mode 100644
index 0000000..ba2ba3c
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 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.inputmethod.stresstest;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Utility methods for IME stress test. */
+public final class ImeStressTestUtil {
+
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ private ImeStressTestUtil() {
+ }
+
+ /** Checks if the IME is shown on the window that the given view belongs to. */
+ public static boolean isImeShown(View view) {
+ WindowInsets insets = view.getRootWindowInsets();
+ return insets.isVisible(WindowInsets.Type.ime());
+ }
+
+ /** Calls the callable on the main thread and returns the result. */
+ public static <V> V callOnMainSync(Callable<V> callable) {
+ AtomicReference<V> result = new AtomicReference<>();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ try {
+ result.set(callable.call());
+ } catch (Exception e) {
+ throw new RuntimeException("Exception was thrown", e);
+ }
+ });
+ return result.get();
+ }
+
+ /**
+ * Waits until {@code pred} returns true, or throws on timeout.
+ *
+ * <p>The given {@code pred} will be called on the main thread.
+ */
+ public static void waitOnMainUntil(String message, Callable<Boolean> pred) {
+ eventually(() -> assertWithMessage(message).that(pred.call()).isTrue(), TIMEOUT);
+ }
+
+ /** Waits until IME is shown, or throws on timeout. */
+ public static void waitOnMainUntilImeIsShown(View view) {
+ eventually(() -> assertWithMessage("IME should be shown").that(
+ callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT);
+ }
+
+ /** Waits until IME is hidden, or throws on timeout. */
+ public static void waitOnMainUntilImeIsHidden(View view) {
+ //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+ eventually(() -> assertWithMessage("IME should be hidden").that(
+ callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+ }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/TestActivity.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/TestActivity.java
deleted file mode 100644
index 7baf037..0000000
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/TestActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 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.inputmethod.stresstest;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowInsets;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-public class TestActivity extends Activity {
-
- private EditText mEditText;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- LinearLayout rootView = new LinearLayout(this);
- rootView.setOrientation(LinearLayout.VERTICAL);
- mEditText = new EditText(this);
- rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
- setContentView(rootView);
- }
-
- public boolean hasWindowFocus() {
- return mEditText.hasWindowFocus();
- }
-
- public boolean isImeShown() {
- WindowInsets insets = mEditText.getRootWindowInsets();
- return insets.isVisible(WindowInsets.Type.ime());
- }
-
- public void showIme() {
- mEditText.requestFocus();
- InputMethodManager imm = getSystemService(InputMethodManager.class);
- imm.showSoftInput(mEditText, 0);
- }
-
- public void hideIme() {
- InputMethodManager imm = getSystemService(InputMethodManager.class);
- imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
- }
-}
diff --git a/tests/LockTaskTests/Android.bp b/tests/LockTaskTests/Android.bp
new file mode 100644
index 0000000..dce681e
--- /dev/null
+++ b/tests/LockTaskTests/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "LockTaskTests",
+
+ privileged: true,
+
+ sdk_version: "current",
+ certificate: "platform",
+
+ srcs: [
+ "src/**/I*.aidl",
+ "src/**/*.java",
+ ],
+
+}
diff --git a/tests/LockTaskTests/Android.mk b/tests/LockTaskTests/Android.mk
deleted file mode 100644
index 5406ee1..0000000
--- a/tests/LockTaskTests/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/priv-app
-
-LOCAL_PACKAGE_NAME := LockTaskTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_CERTIFICATE := platform
-
-LOCAL_SRC_FILES := $(call all-Iaidl-files-under, src) $(call all-java-files-under, src)
-
-include $(BUILD_PACKAGE)
-
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 6e1cef4..9f6ce4e 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -31,7 +31,8 @@
test_config: "RollbackTest.xml",
java_resources: [
":com.android.apex.apkrollback.test_v2",
- ":com.android.apex.apkrollback.test_v2Crashing"
+ ":com.android.apex.apkrollback.test_v2Crashing",
+ ":test.rebootless_apex_v2",
],
}
@@ -47,7 +48,10 @@
],
test_suites: ["general-tests"],
test_config: "StagedRollbackTest.xml",
- data: [":com.android.apex.apkrollback.test_v1"],
+ data: [
+ ":com.android.apex.apkrollback.test_v1",
+ ":test.rebootless_apex_v1",
+ ],
}
java_test_host {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 539090b..ffc8f47 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -58,6 +58,7 @@
public class StagedRollbackTest {
private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
"watchdog_trigger_failure_count";
+ private static final String REBOOTLESS_APEX_NAME = "test.apex.rebootless";
/**
* Adopts common shell permissions needed for rollback tests.
@@ -241,6 +242,55 @@
}
@Test
+ public void testRollbackRebootlessApex() throws Exception {
+ final String packageName = REBOOTLESS_APEX_NAME;
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
+
+ // install
+ TestApp apex1 = new TestApp("TestRebootlessApexV1", packageName, 1,
+ /* isApex= */ true, "test.rebootless_apex_v1.apex");
+ TestApp apex2 = new TestApp("TestRebootlessApexV2", packageName, 2,
+ /* isApex= */ true, "test.rebootless_apex_v2.apex");
+ Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN)
+ .commit();
+
+ // verify rollback
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), packageName);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(Rollback.from(apex2).to(apex1));
+ assertThat(rollback).isNotStaged();
+
+ // rollback
+ RollbackUtils.rollback(rollback.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
+ }
+
+ @Test
+ public void testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(REBOOTLESS_APEX_NAME)).isEqualTo(1);
+
+ TestApp apex2 = new TestApp("TestRebootlessApexV2", REBOOTLESS_APEX_NAME, 2,
+ /* isApex= */ true, "test.rebootless_apex_v2.apex");
+ Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN)
+ .commit();
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+
+ RollbackUtils.waitForAvailableRollback(TestApp.A);
+ RollbackUtils.waitForAvailableRollback(REBOOTLESS_APEX_NAME);
+ }
+
+ @Test
+ public void testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify() throws Exception {
+ // Check only rebootless apex is rolled back. Other rollbacks should remain unchanged.
+ assertThat(RollbackUtils.getCommittedRollback(REBOOTLESS_APEX_NAME)).isNotNull();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+ }
+
+ @Test
public void hasMainlineModule() throws Exception {
String pkgName = getModuleMetadataPackageName();
boolean existed = InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 293f159..1ab59a8 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -96,7 +96,9 @@
deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
"/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "*",
- apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*");
+ apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*",
+ "/system/apex/test.rebootless_apex_v*.apex",
+ "/data/apex/active/test.apex.rebootless*.apex");
}
/**
@@ -160,7 +162,7 @@
*/
@Test
public void testRollbackApexWithApkCrashing() throws Exception {
- pushTestApex();
+ pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
// Install an apex with apk that crashes
runPhase("testRollbackApexWithApkCrashing_Phase1_Install");
@@ -181,6 +183,27 @@
}
/**
+ * Tests rollback is supported correctly for rebootless apex
+ */
+ @Test
+ public void testRollbackRebootlessApex() throws Exception {
+ pushTestApex("test.rebootless_apex_v1.apex");
+ runPhase("testRollbackRebootlessApex");
+ }
+
+ /**
+ * Tests only rebootless apex (if any) is rolled back when native crash happens
+ */
+ @Test
+ public void testNativeWatchdogTriggersRebootlessApexRollback() throws Exception {
+ pushTestApex("test.rebootless_apex_v1.apex");
+ runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install");
+ crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+ getDevice().waitForDeviceAvailable();
+ runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify");
+ }
+
+ /**
* Tests that packages are monitored across multiple reboots.
*/
@Test
@@ -204,9 +227,8 @@
runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
}
- private void pushTestApex() throws Exception {
+ private void pushTestApex(String fileName) throws Exception {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
- final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
final File apex = buildHelper.getTestFile(fileName);
try {
getDevice().enableAdbRoot();
@@ -243,4 +265,18 @@
return false;
}
}
+
+ private void crashProcess(String processName, int numberOfCrashes) throws Exception {
+ String pid = "";
+ String lastPid = "invalid";
+ for (int i = 0; i < numberOfCrashes; ++i) {
+ // This condition makes sure before we kill the process, the process is running AND
+ // the last crash was finished.
+ while ("".equals(pid) || lastPid.equals(pid)) {
+ pid = getDevice().executeShellCommand("pidof " + processName);
+ }
+ getDevice().executeShellCommand("kill " + pid);
+ lastPid = pid;
+ }
+ }
}
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 7a564fc..ae62bec 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -59,6 +59,7 @@
":StagedInstallTestApexV2_WrongSha",
":TestAppAv1",
":test.rebootless_apex_v1",
+ ":test.rebootless_apex_v2",
],
test_suites: ["general-tests"],
test_config: "StagedInstallInternalTest.xml",
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index c610641..6e24448 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -476,6 +476,18 @@
assertThat(captor.getValue().stagedApexModuleNames).hasLength(0);
}
+ @Test
+ public void testRebootlessDowngrade() throws Exception {
+ final String packageName = "test.apex.rebootless";
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(2);
+ TestApp apex1 = new TestApp("TestRebootlessApexV1", packageName, 1,
+ /* isApex= */ true, "test.rebootless_apex_v1.apex");
+ InstallUtils.commitExpectingFailure(AssertionError.class,
+ "INSTALL_FAILED_VERSION_DOWNGRADE", Install.single(apex1));
+ Install.single(apex1).setRequestDowngrade().commit();
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
+ }
+
private IPackageManagerNative getPackageManagerNative() {
IBinder binder = ServiceManager.waitForService("package_native");
assertThat(binder).isNotNull();
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 3102103..8d696f5 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -91,7 +91,7 @@
deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
"/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
"/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex",
- "/system/apex/test.rebootless_apex_v1.apex",
+ "/system/apex/test.rebootless_apex_v*.apex",
"/data/apex/active/test.apex.rebootless*.apex",
TEST_VENDOR_APEX_ALLOW_LIST);
}
@@ -493,6 +493,13 @@
runPhase("testStagedApexObserver");
}
+ @Test
+ public void testRebootlessDowngrade() throws Exception {
+ pushTestApex("test.rebootless_apex_v2.apex");
+ getDevice().reboot();
+ runPhase("testRebootlessDowngrade");
+ }
+
private List<String> getStagingDirectories() throws DeviceNotAvailableException {
String baseDir = "/data/app-staging";
try {
diff --git a/tests/componentalias/src/com/android/compatibility/common/util/BroadcastMessenger.java b/tests/componentalias/src/com/android/compatibility/common/util/BroadcastMessenger.java
deleted file mode 100644
index a3a2abe..0000000
--- a/tests/componentalias/src/com/android/compatibility/common/util/BroadcastMessenger.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2021 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.compatibility.common.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.HandlerThread;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Objects;
-
-/**
- * Provides a one-way communication mechanism using a Parcelable as a payload, via broadcasts.
- *
- * Use {@link #send(Context, String, Parcelable)} to send a message.
- * USe {@link Receiver} to receive a message.
- *
- * Pick a unique "suffix" for your test, and use it with both the sender and receiver, in order
- * to avoid "cross-talks" between different tests. (if they ever run at the same time.)
- *
- * TODO: Move it to compatibility-device-util-axt.
- */
-public final class BroadcastMessenger {
- private static final String TAG = "BroadcastMessenger";
-
- private static final String ACTION_MESSAGE =
- "com.android.compatibility.common.util.BroadcastMessenger.ACTION_MESSAGE_";
- private static final String ACTION_PING =
- "com.android.compatibility.common.util.BroadcastMessenger.ACTION_PING_";
- private static final String EXTRA_MESSAGE =
- "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_MESSAGE";
-
- /**
- * We need to drop messages that were sent before the receiver was created. We keep
- * track of the message send time in this extra.
- */
- private static final String EXTRA_SENT_TIME =
- "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_SENT_TIME";
-
- public static final int DEFAULT_TIMEOUT_MS = 10_000;
-
- private static long getCurrentTime() {
- return SystemClock.uptimeMillis();
- }
-
- private static void sendBroadcast(@NonNull Intent i, @NonNull Context context,
- @NonNull String broadcastSuffix, @Nullable String receiverPackage) {
- i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- i.setPackage(receiverPackage);
- i.putExtra(EXTRA_SENT_TIME, getCurrentTime());
-
- context.sendBroadcast(i);
- }
-
- /** Send a message to the {@link Receiver} expecting a given "suffix". */
- public static <T extends Parcelable> void send(@NonNull Context context,
- @NonNull String broadcastSuffix, @NonNull T message) {
- final Intent i = new Intent(ACTION_MESSAGE + Objects.requireNonNull(broadcastSuffix));
- i.putExtra(EXTRA_MESSAGE, Objects.requireNonNull(message));
-
- Log.i(TAG, "Sending: " + message);
- sendBroadcast(i, context, broadcastSuffix, /*receiverPackage=*/ null);
- }
-
- private static void sendPing(@NonNull Context context,@NonNull String broadcastSuffix,
- @NonNull String receiverPackage) {
- final Intent i = new Intent(ACTION_PING + Objects.requireNonNull(broadcastSuffix));
-
- Log.i(TAG, "Sending a ping");
- sendBroadcast(i, context, broadcastSuffix, receiverPackage);
- }
-
- /**
- * Receive messages sent with {@link #send}. Note it'll ignore all the messages that were
- * sent before instantiated.
- */
- public static final class Receiver<T extends Parcelable> implements AutoCloseable {
- private final Context mContext;
- private final String mBroadcastSuffix;
- private final HandlerThread mReceiverThread = new HandlerThread(TAG);
-
- // @GuardedBy("mMessages")
- private final ArrayList<T> mMessages = new ArrayList<>();
- private final long mCreatedTime = getCurrentTime();
- private boolean mRegistered;
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Log.d(TAG, "Received intent: " + intent);
- if (intent.getAction().equals(ACTION_MESSAGE + mBroadcastSuffix)
- || intent.getAction().equals(ACTION_PING + mBroadcastSuffix)) {
- // OK
- } else {
- throw new RuntimeException("Unknown broadcast received: " + intent);
- }
- if (intent.getLongExtra(EXTRA_SENT_TIME, 0) < mCreatedTime) {
- Log.i(TAG, "Dropping stale broadcast: " + intent);
- return;
- }
-
- // Note for a PING, the message will be null.
- final T message = intent.getParcelableExtra(EXTRA_MESSAGE);
- if (message != null) {
- Log.i(TAG, "Received: " + message);
- }
-
- synchronized (mMessages) {
- mMessages.add(message);
- mMessages.notifyAll();
- }
- }
- };
-
- /**
- * Constructor.
- */
- public Receiver(@NonNull Context context, @NonNull String broadcastSuffix) {
- mContext = context;
- mBroadcastSuffix = Objects.requireNonNull(broadcastSuffix);
-
- mReceiverThread.start();
-
- final IntentFilter fi = new IntentFilter(ACTION_MESSAGE + mBroadcastSuffix);
- fi.addAction(ACTION_PING + mBroadcastSuffix);
-
- context.registerReceiver(mReceiver, fi, /* permission=*/ null,
- mReceiverThread.getThreadHandler(), Context.RECEIVER_EXPORTED);
- mRegistered = true;
- }
-
- @Override
- public void close() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mReceiverThread.quit();
- mRegistered = false;
- }
- }
-
- /**
- * Receive the next message with a 60 second timeout.
- */
- @NonNull
- public T waitForNextMessage() {
- return waitForNextMessage(DEFAULT_TIMEOUT_MS);
- }
-
- /**
- * Receive the next message.
- */
- @NonNull
- public T waitForNextMessage(long timeoutMillis) {
- synchronized (mMessages) {
- final long timeout = System.currentTimeMillis() + timeoutMillis;
- while (mMessages.size() == 0) {
- final long wait = timeout - System.currentTimeMillis();
- if (wait <= 0) {
- throw new RuntimeException("Timeout waiting for the next message");
- }
- try {
- mMessages.wait(wait);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- return mMessages.remove(0);
- }
- }
-
- /**
- * Ensure that no further messages have been received.
- *
- * Call it before {@link #close()}.
- */
- public void ensureNoMoreMessages() {
- // Send a ping to myself.
- sendPing(mContext, mBroadcastSuffix, mContext.getPackageName());
-
- final T m = waitForNextMessage();
- if (m == null) {
- return; // Okay. Ping will deliver a null message.
- }
- throw new RuntimeException("No more messages expected, but received: " + m);
- }
- }
-}
diff --git a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java b/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java
index 7cda977..5d639f6 100644
--- a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java
+++ b/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java
@@ -409,10 +409,10 @@
sleepIfYouCan(500);
L("Parceling notifications...");
- // we want to be able to use this test on older OSes that do not have getBlobAshmemSize
- Method getBlobAshmemSize = null;
+ // we want to be able to use this test on older OSes that do not have getOpenAshmemSize
+ Method getOpenAshmemSize = null;
try {
- getBlobAshmemSize = Parcel.class.getMethod("getBlobAshmemSize");
+ getOpenAshmemSize = Parcel.class.getMethod("getOpenAshmemSize");
} catch (NoSuchMethodException ex) {
}
for (int i=0; i<mNotifications.size(); i++) {
@@ -424,8 +424,8 @@
time = SystemClock.currentThreadTimeMillis() - time;
L(" %s: write parcel=%dms size=%d ashmem=%s",
summarize(n), time, p.dataPosition(),
- (getBlobAshmemSize != null)
- ? getBlobAshmemSize.invoke(p)
+ (getOpenAshmemSize != null)
+ ? getOpenAshmemSize.invoke(p)
: "???");
p.setDataPosition(0);
}
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index df444ba..7103944 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -16,6 +16,9 @@
#include "Debug.h"
+#include <androidfw/TypeWrappers.h>
+#include <format/binary/ResChunkPullParser.h>
+
#include <algorithm>
#include <map>
#include <memory>
@@ -23,17 +26,16 @@
#include <set>
#include <vector>
-#include "android-base/logging.h"
-#include "android-base/stringprintf.h"
-
#include "ResourceTable.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "idmap2/Policies.h"
#include "text/Printer.h"
#include "util/Util.h"
-#include "idmap2/Policies.h"
-
using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::StringPrintf;
@@ -584,4 +586,260 @@
}
}
+namespace {
+
+using namespace android;
+
+class ChunkPrinter {
+ public:
+ ChunkPrinter(const void* data, size_t len, Printer* printer, IDiagnostics* diag)
+ : data_(data), data_len_(len), printer_(printer), diag_(diag) {
+ }
+
+ void PrintChunkHeader(const ResChunk_header* chunk) {
+ switch (util::DeviceToHost16(chunk->type)) {
+ case RES_STRING_POOL_TYPE:
+ printer_->Print("[RES_STRING_POOL_TYPE]");
+ break;
+ case RES_TABLE_LIBRARY_TYPE:
+ printer_->Print("[RES_TABLE_LIBRARY_TYPE]");
+ break;
+ case RES_TABLE_TYPE:
+ printer_->Print("[ResTable_header]");
+ break;
+ case RES_TABLE_PACKAGE_TYPE:
+ printer_->Print("[ResTable_package]");
+ break;
+ case RES_TABLE_TYPE_TYPE:
+ printer_->Print("[ResTable_type]");
+ break;
+ case RES_TABLE_TYPE_SPEC_TYPE:
+ printer_->Print("[RES_TABLE_TYPE_SPEC_TYPE]");
+ break;
+ default:
+ break;
+ }
+
+ printer_->Print(StringPrintf(" chunkSize: %u", util::DeviceToHost32(chunk->size)));
+ printer_->Print(StringPrintf(" headerSize: %u", util::DeviceToHost32(chunk->headerSize)));
+ }
+
+ bool PrintTable(const ResTable_header* chunk) {
+ printer_->Print(
+ StringPrintf(" Package count: %u\n", util::DeviceToHost32(chunk->packageCount)));
+
+ // Print the chunks contained within the table
+ printer_->Indent();
+ bool success = PrintChunk(
+ ResChunkPullParser(GetChunkData(&chunk->header), GetChunkDataLen(&chunk->header)));
+ printer_->Undent();
+ return success;
+ }
+
+ void PrintResValue(const Res_value* value, const ConfigDescription& config,
+ const ResourceType* type) {
+ printer_->Print("[Res_value]");
+ printer_->Print(StringPrintf(" size: %u", util::DeviceToHost32(value->size)));
+ printer_->Print(StringPrintf(" dataType: 0x%02x", util::DeviceToHost32(value->dataType)));
+ printer_->Print(StringPrintf(" data: 0x%08x", util::DeviceToHost32(value->data)));
+
+ if (type) {
+ auto item =
+ ResourceUtils::ParseBinaryResValue(*type, config, value_pool_, *value, &out_pool_);
+ printer_->Print(" (");
+ item->PrettyPrint(printer_);
+ printer_->Print(")");
+ }
+
+ printer_->Print("\n");
+ }
+
+ bool PrintTableType(const ResTable_type* chunk) {
+ printer_->Print(StringPrintf(" id: 0x%02x", util::DeviceToHost32(chunk->id)));
+ printer_->Print(StringPrintf(
+ " name: %s", util::GetString(type_pool_, util::DeviceToHost32(chunk->id) - 1).c_str()));
+ printer_->Print(StringPrintf(" flags: 0x%02x", util::DeviceToHost32(chunk->flags)));
+ printer_->Print(StringPrintf(" entryCount: %u", util::DeviceToHost32(chunk->entryCount)));
+ printer_->Print(StringPrintf(" entryStart: %u", util::DeviceToHost32(chunk->entriesStart)));
+
+ ConfigDescription config;
+ config.copyFromDtoH(chunk->config);
+ printer_->Print(StringPrintf(" config: %s\n", config.to_string().c_str()));
+
+ const ResourceType* type =
+ ParseResourceType(util::GetString(type_pool_, util::DeviceToHost32(chunk->id) - 1));
+
+ printer_->Indent();
+
+ TypeVariant tv(chunk);
+ for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+ const ResTable_entry* entry = *it;
+ if (!entry) {
+ continue;
+ }
+
+ printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]"
+ : "[ResTable_entry]");
+ printer_->Print(StringPrintf(" id: 0x%04x", it.index()));
+ printer_->Print(StringPrintf(
+ " name: %s", util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)).c_str()));
+ printer_->Print(StringPrintf(" keyIndex: %u", util::DeviceToHost32(entry->key.index)));
+ printer_->Print(StringPrintf(" size: %u", util::DeviceToHost32(entry->size)));
+ printer_->Print(StringPrintf(" flags: 0x%04x", util::DeviceToHost32(entry->flags)));
+
+ printer_->Indent();
+
+ if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+ auto map_entry = (const ResTable_map_entry*)entry;
+ printer_->Print(StringPrintf(" count: 0x%04x", util::DeviceToHost32(map_entry->count)));
+ printer_->Print(
+ StringPrintf(" parent: 0x%08x\n", util::DeviceToHost32(map_entry->parent.ident)));
+
+ // Print the name and value mappings
+ auto maps =
+ (const ResTable_map*)((const uint8_t*)entry + util::DeviceToHost32(entry->size));
+ for (size_t i = 0, count = util::DeviceToHost32(map_entry->count); i < count; i++) {
+ PrintResValue(&(maps[i].value), config, type);
+
+ printer_->Print(StringPrintf(
+ " name: %s name-id:%d\n",
+ util::GetString(key_pool_, util::DeviceToHost32(maps[i].name.ident)).c_str(),
+ util::DeviceToHost32(maps[i].name.ident)));
+ }
+ } else {
+ printer_->Print("\n");
+
+ // Print the value of the entry
+ auto value = (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size));
+ PrintResValue(value, config, type);
+ }
+
+ printer_->Undent();
+ }
+
+ printer_->Undent();
+ return true;
+ }
+
+ void PrintStringPool(const ResStringPool_header* chunk) {
+ // Initialize the string pools
+
+ ResStringPool* pool;
+ if (value_pool_.getError() == NO_INIT) {
+ pool = &value_pool_;
+ } else if (type_pool_.getError() == NO_INIT) {
+ pool = &type_pool_;
+ } else if (key_pool_.getError() == NO_INIT) {
+ pool = &key_pool_;
+ } else {
+ return;
+ }
+
+ pool->setTo(chunk,
+ util::DeviceToHost32((reinterpret_cast<const ResChunk_header*>(chunk))->size));
+
+ printer_->Print("\n");
+
+ for (size_t i = 0; i < pool->size(); i++) {
+ printer_->Print(StringPrintf("#%zd : %s\n", i, util::GetString(*pool, i).c_str()));
+ }
+ }
+
+ bool PrintPackage(const ResTable_package* chunk) {
+ printer_->Print(StringPrintf(" id: 0x%02x", util::DeviceToHost32(chunk->id)));
+
+ size_t len = strnlen16((const char16_t*)chunk->name, std::size(chunk->name));
+ std::u16string package_name(len, u'\0');
+ package_name.resize(len);
+ for (size_t i = 0; i < len; i++) {
+ package_name[i] = util::DeviceToHost16(chunk->name[i]);
+ }
+
+ printer_->Print(StringPrintf("name: %s", String8(package_name.c_str()).c_str()));
+ printer_->Print(StringPrintf(" typeStrings: %u", util::DeviceToHost32(chunk->typeStrings)));
+ printer_->Print(
+ StringPrintf(" lastPublicType: %u", util::DeviceToHost32(chunk->lastPublicType)));
+ printer_->Print(StringPrintf(" keyStrings: %u", util::DeviceToHost32(chunk->keyStrings)));
+ printer_->Print(StringPrintf(" lastPublicKey: %u", util::DeviceToHost32(chunk->lastPublicKey)));
+ printer_->Print(StringPrintf(" typeIdOffset: %u\n", util::DeviceToHost32(chunk->typeIdOffset)));
+
+ // Print the chunks contained within the table
+ printer_->Indent();
+ bool success = PrintChunk(
+ ResChunkPullParser(GetChunkData(&chunk->header), GetChunkDataLen(&chunk->header)));
+ printer_->Undent();
+ return success;
+ }
+
+ bool PrintChunk(ResChunkPullParser&& parser) {
+ while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+ auto chunk = parser.chunk();
+ PrintChunkHeader(chunk);
+
+ switch (util::DeviceToHost16(chunk->type)) {
+ case RES_STRING_POOL_TYPE:
+ PrintStringPool(reinterpret_cast<const ResStringPool_header*>(chunk));
+ break;
+
+ case RES_TABLE_TYPE:
+ PrintTable(reinterpret_cast<const ResTable_header*>(chunk));
+ break;
+
+ case RES_TABLE_PACKAGE_TYPE:
+ type_pool_.uninit();
+ key_pool_.uninit();
+ PrintPackage(reinterpret_cast<const ResTable_package*>(chunk));
+ break;
+
+ case RES_TABLE_TYPE_TYPE:
+ PrintTableType(reinterpret_cast<const ResTable_type*>(chunk));
+ break;
+
+ default:
+ printer_->Print("\n");
+ break;
+ }
+ }
+
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error());
+ return false;
+ }
+
+ return true;
+ }
+
+ void Print() {
+ PrintChunk(ResChunkPullParser(data_, data_len_));
+ printer_->Print("[End]\n");
+ }
+
+ private:
+ const Source source_;
+ const void* data_;
+ const size_t data_len_;
+ Printer* printer_;
+ IDiagnostics* diag_;
+
+ // The standard value string pool for resource values.
+ ResStringPool value_pool_;
+
+ // The string pool that holds the names of the types defined
+ // in this table.
+ ResStringPool type_pool_;
+
+ // The string pool that holds the names of the entries defined
+ // in this table.
+ ResStringPool key_pool_;
+
+ StringPool out_pool_;
+};
+
+} // namespace
+
+void Debug::DumpChunks(const void* data, size_t len, Printer* printer, IDiagnostics* diag) {
+ ChunkPrinter chunk_printer(data, len, printer, diag);
+ chunk_printer.Print();
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 9443d60..4da9204 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -40,6 +40,7 @@
static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
+ static void DumpChunks(const void* data, size_t len, text::Printer* printer, IDiagnostics* diag);
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index ad014a2..dae89b0 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -436,11 +436,11 @@
const size_t index = type_index_iter->second;
if (new_packages.size() == index) {
new_packages.emplace_back(ResourceTablePackageView{package.name, package.id});
- type_new_package_index[type.type] = index + 1;
}
// Move the type into a new package
auto& other_package = new_packages[index];
+ type_new_package_index[type.type] = index + 1;
type_inserter.Insert(other_package.types, std::move(type));
type_it = package.types.erase(type_it);
}
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 0a1e021..bcce3e5 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -560,4 +560,21 @@
32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10};
+int DumpChunks::Dump(LoadedApk* apk) {
+ auto file = apk->GetFileCollection()->FindFile("resources.arsc");
+ if (!file) {
+ GetDiagnostics()->Error(DiagMessage() << "Failed to find resources.arsc in APK");
+ return 1;
+ }
+
+ auto data = file->OpenAsData();
+ if (!data) {
+ GetDiagnostics()->Error(DiagMessage() << "Failed to open resources.arsc ");
+ return 1;
+ }
+
+ Debug::DumpChunks(data->data(), data->size(), GetPrinter(), GetDiagnostics());
+ return 0;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 52616fa..ec320ec 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -17,6 +17,9 @@
#ifndef AAPT2_DUMP_H
#define AAPT2_DUMP_H
+#include <io/FileStream.h>
+#include <io/ZipArchive.h>
+
#include "Command.h"
#include "Debug.h"
#include "LoadedApk.h"
@@ -226,6 +229,16 @@
std::vector<std::string> files_;
};
+class DumpChunks : public DumpApkCommand {
+ public:
+ DumpChunks(text::Printer* printer, IDiagnostics* diag) : DumpApkCommand("chunks", printer, diag) {
+ SetDescription("Print the chunk information of the compiled resources.arsc in the APK.");
+ }
+
+ int Dump(LoadedApk* apk) override;
+};
+
+/** Prints the tree of a compiled xml in an APK. */
class DumpXmlTreeCommand : public DumpApkCommand {
public:
explicit DumpXmlTreeCommand(text::Printer* printer, IDiagnostics* diag)
@@ -263,6 +276,7 @@
AddOptionalSubcommand(util::make_unique<DumpStringsCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpStyleParentCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
+ AddOptionalSubcommand(util::make_unique<DumpChunks>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 8b7eadf9..dd60f17 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -349,7 +349,8 @@
size_t len;
const char16_t* str16 = tree.getText(&len);
if (str16) {
- text->text = util::Utf16ToUtf8(StringPiece16(str16, len));
+ text->text =
+ ResTable::normalizeForOutput(util::Utf16ToUtf8(StringPiece16(str16, len)).c_str());
}
CHECK(!node_stack.empty());
node_stack.top()->AppendChild(std::move(text));
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 6c717dc..3a2d656 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -139,6 +139,19 @@
EXPECT_THAT(attr->value, Eq("\""));
}
+TEST(XmlDomTest, XmlEscapeSingleQuotes) {
+ std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(
+ <foo><![CDATA[oh no' (line=1001)
+E: this-is-not-an-element (line=88)
+ T: 'blah]]></foo>)");
+
+ Element* el = doc->root.get();
+ Text* text = xml::NodeCast<xml::Text>(el->children[0].get());
+ ASSERT_THAT(text, NotNull());
+ EXPECT_THAT(text->text,
+ Eq("oh no' (line=1001)\nE: this-is-not-an-element (line=88)\n T: 'blah"));
+}
+
class TestVisitor : public PackageAwareVisitor {
public:
using PackageAwareVisitor::Visit;