Merge "Address failing tests for CtsMultiUserHostTest."
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 4242cf8..5d67c96 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.job.IJobScheduler;
import android.app.job.IUserVisibleJobObserver;
@@ -25,10 +26,13 @@
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
import android.content.Context;
+import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
+import android.util.ArrayMap;
import java.util.List;
-
+import java.util.Map;
+import java.util.Set;
/**
* Concrete implementation of the JobScheduler interface
@@ -41,16 +45,42 @@
public class JobSchedulerImpl extends JobScheduler {
IJobScheduler mBinder;
private final Context mContext;
+ private final String mNamespace;
public JobSchedulerImpl(@NonNull Context context, IJobScheduler binder) {
+ this(context, binder, null);
+ }
+
+ private JobSchedulerImpl(@NonNull Context context, IJobScheduler binder,
+ @Nullable String namespace) {
mContext = context;
mBinder = binder;
+ mNamespace = namespace;
+ }
+
+ private JobSchedulerImpl(JobSchedulerImpl jsi, @Nullable String namespace) {
+ this(jsi.mContext, jsi.mBinder, namespace);
+ }
+
+ @NonNull
+ @Override
+ public JobScheduler forNamespace(@NonNull String namespace) {
+ if (namespace == null) {
+ throw new IllegalArgumentException("namespace cannot be null");
+ }
+ return new JobSchedulerImpl(this, namespace);
+ }
+
+ @Nullable
+ @Override
+ public String getNamespace() {
+ return mNamespace;
}
@Override
public int schedule(JobInfo job) {
try {
- return mBinder.schedule(job);
+ return mBinder.schedule(mNamespace, job);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
@@ -59,7 +89,7 @@
@Override
public int enqueue(JobInfo job, JobWorkItem work) {
try {
- return mBinder.enqueue(job, work);
+ return mBinder.enqueue(mNamespace, job, work);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
@@ -68,7 +98,7 @@
@Override
public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) {
try {
- return mBinder.scheduleAsPackage(job, packageName, userId, tag);
+ return mBinder.scheduleAsPackage(mNamespace, job, packageName, userId, tag);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
@@ -77,23 +107,44 @@
@Override
public void cancel(int jobId) {
try {
- mBinder.cancel(jobId);
+ mBinder.cancel(mNamespace, jobId);
} catch (RemoteException e) {}
-
}
@Override
public void cancelAll() {
try {
+ mBinder.cancelAllInNamespace(mNamespace);
+ } catch (RemoteException e) {}
+ }
+
+ @Override
+ public void cancelInAllNamespaces() {
+ try {
mBinder.cancelAll();
} catch (RemoteException e) {}
-
}
@Override
public List<JobInfo> getAllPendingJobs() {
try {
- return mBinder.getAllPendingJobs().getList();
+ return mBinder.getAllPendingJobsInNamespace(mNamespace).getList();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public Map<String, List<JobInfo>> getPendingJobsInAllNamespaces() {
+ try {
+ final Map<String, ParceledListSlice<JobInfo>> parceledList =
+ mBinder.getAllPendingJobs();
+ final ArrayMap<String, List<JobInfo>> jobMap = new ArrayMap<>();
+ final Set<String> keys = parceledList.keySet();
+ for (String key : keys) {
+ jobMap.put(key, parceledList.get(key).getList());
+ }
+ return jobMap;
} catch (RemoteException e) {
return null;
}
@@ -102,7 +153,7 @@
@Override
public JobInfo getPendingJob(int jobId) {
try {
- return mBinder.getPendingJob(jobId);
+ return mBinder.getPendingJob(mNamespace, jobId);
} catch (RemoteException e) {
return null;
}
@@ -111,7 +162,7 @@
@Override
public int getPendingJobReason(int jobId) {
try {
- return mBinder.getPendingJobReason(jobId);
+ return mBinder.getPendingJobReason(mNamespace, jobId);
} catch (RemoteException e) {
return PENDING_JOB_REASON_UNDEFINED;
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index c87a2af..a1f1954 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -21,20 +21,24 @@
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
import android.content.pm.ParceledListSlice;
+import java.util.Map;
/**
* IPC interface that supports the app-facing {@link #JobScheduler} api.
* {@hide}
*/
interface IJobScheduler {
- int schedule(in JobInfo job);
- int enqueue(in JobInfo job, in JobWorkItem work);
- int scheduleAsPackage(in JobInfo job, String packageName, int userId, String tag);
- void cancel(int jobId);
+ int schedule(String namespace, in JobInfo job);
+ int enqueue(String namespace, in JobInfo job, in JobWorkItem work);
+ int scheduleAsPackage(String namespace, in JobInfo job, String packageName, int userId, String tag);
+ void cancel(String namespace, int jobId);
void cancelAll();
- ParceledListSlice getAllPendingJobs();
- JobInfo getPendingJob(int jobId);
- int getPendingJobReason(int jobId);
+ void cancelAllInNamespace(String namespace);
+ // Returns Map<String, ParceledListSlice>, where the keys are the namespaces.
+ Map<String, ParceledListSlice<JobInfo>> getAllPendingJobs();
+ ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace);
+ JobInfo getPendingJob(String namespace, int jobId);
+ int getPendingJobReason(String namespace, int jobId);
boolean canRunLongJobs(String packageName);
boolean hasRunLongJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index deb97a5..3bbbb15 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1251,6 +1251,9 @@
* in them all being treated the same. The priorities each have slightly different
* behaviors, as noted in their relevant javadoc.
*
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * the priority will only affect sorting order within the job's namespace.
+ *
* <b>NOTE:</b> Setting all of your jobs to high priority will not be
* beneficial to your app and in fact may hurt its performance in the
* long run.
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index a5a7f93..18ddffb 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -277,6 +277,8 @@
@UnsupportedAppUsage
private final int jobId;
+ @Nullable
+ private final String mJobNamespace;
private final PersistableBundle extras;
private final Bundle transientExtras;
private final ClipData clipData;
@@ -295,7 +297,7 @@
private String debugStopReason; // Human readable stop reason for debugging.
/** @hide */
- public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
+ public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras,
Bundle transientExtras, ClipData clipData, int clipGrantFlags,
boolean overrideDeadlineExpired, boolean isExpedited,
boolean isUserInitiated, Uri[] triggeredContentUris,
@@ -312,6 +314,7 @@
this.mTriggeredContentUris = triggeredContentUris;
this.mTriggeredContentAuthorities = triggeredContentAuthorities;
this.network = network;
+ this.mJobNamespace = namespace;
}
/**
@@ -322,6 +325,19 @@
}
/**
+ * Get the namespace this job was placed in.
+ *
+ * @see JobScheduler#forNamespace(String)
+ * @return The namespace this job was scheduled in. Will be {@code null} if there was no
+ * explicit namespace set and this job is therefore in the default namespace.
+ * @hide
+ */
+ @Nullable
+ public String getJobNamespace() {
+ return mJobNamespace;
+ }
+
+ /**
* @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
* be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
* yet been called.
@@ -540,6 +556,7 @@
private JobParameters(Parcel in) {
jobId = in.readInt();
+ mJobNamespace = in.readString();
extras = in.readPersistableBundle();
transientExtras = in.readBundle();
if (in.readInt() != 0) {
@@ -581,6 +598,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(jobId);
+ dest.writeString(mJobNamespace);
dest.writePersistableBundle(extras);
dest.writeBundle(transientExtras);
if (clipData != null) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index c9981da..3764249 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -38,6 +38,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Map;
/**
* This is an API for scheduling various types of jobs against the framework that will be executed
@@ -264,6 +265,31 @@
}
/**
+ * Get a JobScheduler instance that is dedicated to a specific namespace. Any API calls using
+ * this instance will interact with jobs in that namespace, unless the API documentation says
+ * otherwise. Attempting to update a job scheduled in another namespace will not be possible
+ * but will instead create or update the job inside the current namespace. A JobScheduler
+ * instance dedicated to a namespace must be used to schedule or update jobs in that namespace.
+ * @see #getNamespace()
+ * @hide
+ */
+ @NonNull
+ public JobScheduler forNamespace(@NonNull String namespace) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Get the namespace this JobScheduler instance is operating in. A {@code null} value means
+ * that the app has not specified a namespace for this instance, and it is therefore using the
+ * default namespace.
+ * @hide
+ */
+ @Nullable
+ public String getNamespace() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Schedule a job to be executed. Will replace any currently scheduled job with the same
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
* running, it will be stopped.
@@ -364,6 +390,15 @@
public abstract void cancelAll();
/**
+ * Cancel <em>all</em> jobs that have been scheduled by the calling application, regardless of
+ * namespace.
+ * @hide
+ */
+ public void cancelInAllNamespaces() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Retrieve all jobs that have been scheduled by the calling application.
*
* @return a list of all of the app's scheduled jobs. This includes jobs that are
@@ -372,6 +407,21 @@
public abstract @NonNull List<JobInfo> getAllPendingJobs();
/**
+ * Retrieve all jobs that have been scheduled by the calling application within the current
+ * namespace.
+ *
+ * @return a list of all of the app's scheduled jobs scheduled with the current namespace.
+ * If a namespace hasn't been explicitly set with {@link #forNamespace(String)},
+ * then this will return jobs in the default namespace.
+ * This includes jobs that are currently started as well as those that are still waiting to run.
+ * @hide
+ */
+ @NonNull
+ public Map<String, List<JobInfo>> getPendingJobsInAllNamespaces() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Look up the description of a scheduled job.
*
* @return The {@link JobInfo} description of the given scheduled job, or {@code null}
diff --git a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
index afcbe7d..311a9b2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
+++ b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
@@ -17,9 +17,12 @@
package android.app.job;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* Summary of a scheduled job that the user is meant to be aware of.
*
@@ -30,13 +33,16 @@
private final int mSourceUserId;
@NonNull
private final String mSourcePackageName;
+ @Nullable
+ private final String mNamespace;
private final int mJobId;
public UserVisibleJobSummary(int callingUid, int sourceUserId,
- @NonNull String sourcePackageName, int jobId) {
+ @NonNull String sourcePackageName, String namespace, int jobId) {
mCallingUid = callingUid;
mSourceUserId = sourceUserId;
mSourcePackageName = sourcePackageName;
+ mNamespace = namespace;
mJobId = jobId;
}
@@ -44,6 +50,7 @@
mCallingUid = in.readInt();
mSourceUserId = in.readInt();
mSourcePackageName = in.readString();
+ mNamespace = in.readString();
mJobId = in.readInt();
}
@@ -55,6 +62,10 @@
return mJobId;
}
+ public String getNamespace() {
+ return mNamespace;
+ }
+
public int getSourceUserId() {
return mSourceUserId;
}
@@ -71,6 +82,7 @@
return mCallingUid == that.mCallingUid
&& mSourceUserId == that.mSourceUserId
&& mSourcePackageName.equals(that.mSourcePackageName)
+ && Objects.equals(mNamespace, that.mNamespace)
&& mJobId == that.mJobId;
}
@@ -80,6 +92,9 @@
result = 31 * result + mCallingUid;
result = 31 * result + mSourceUserId;
result = 31 * result + mSourcePackageName.hashCode();
+ if (mNamespace != null) {
+ result = 31 * result + mNamespace.hashCode();
+ }
result = 31 * result + mJobId;
return result;
}
@@ -90,6 +105,7 @@
+ "callingUid=" + mCallingUid
+ ", sourceUserId=" + mSourceUserId
+ ", sourcePackageName='" + mSourcePackageName + "'"
+ + ", namespace=" + mNamespace
+ ", jobId=" + mJobId
+ "}";
}
@@ -104,6 +120,7 @@
dest.writeInt(mCallingUid);
dest.writeInt(mSourceUserId);
dest.writeString(mSourcePackageName);
+ dest.writeString(mNamespace);
dest.writeInt(mJobId);
}
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 397d2c4..b806ef8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -615,7 +615,7 @@
private boolean isSimilarJobRunningLocked(JobStatus job) {
for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
JobStatus js = mRunningJobs.valueAt(i);
- if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) {
+ if (job.matches(js.getUid(), js.getNamespace(), js.getJobId())) {
return true;
}
}
@@ -1687,12 +1687,12 @@
@GuardedBy("mLock")
boolean executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId,
- boolean hasJobId, int jobId) {
+ @Nullable String namespace, boolean hasJobId, int jobId) {
boolean foundSome = false;
for (int i = 0; i < mActiveServices.size(); i++) {
final JobServiceContext jc = mActiveServices.get(i);
final JobStatus js = jc.getRunningJobLocked();
- if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
+ if (jc.timeoutIfExecutingLocked(pkgName, userId, namespace, hasJobId, jobId, "shell")) {
foundSome = true;
pw.print("Timing out: ");
js.printUniqueId(pw);
@@ -1709,11 +1709,13 @@
*/
@Nullable
@GuardedBy("mLock")
- Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid, int jobId) {
+ Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid,
+ String namespace, int jobId) {
for (int i = 0; i < mActiveServices.size(); i++) {
final JobServiceContext jc = mActiveServices.get(i);
final JobStatus js = jc.getRunningJobLocked();
- if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+ if (js != null && js.matches(uid, namespace, jobId)
+ && js.getSourcePackageName().equals(pkgName)) {
return jc.getEstimatedNetworkBytes();
}
}
@@ -1726,11 +1728,13 @@
*/
@Nullable
@GuardedBy("mLock")
- Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid, int jobId) {
+ Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid,
+ String namespace, int jobId) {
for (int i = 0; i < mActiveServices.size(); i++) {
final JobServiceContext jc = mActiveServices.get(i);
final JobStatus js = jc.getRunningJobLocked();
- if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+ if (js != null && js.matches(uid, namespace, jobId)
+ && js.getSourcePackageName().equals(pkgName)) {
return jc.getTransferredNetworkBytes();
}
}
@@ -1753,6 +1757,9 @@
pendingJobQueue.resetIterator();
while ((js = pendingJobQueue.next()) != null) {
s.append("(")
+ .append("{")
+ .append(js.getNamespace())
+ .append("} ")
.append(js.getJob().getId())
.append(", ")
.append(js.getUid())
@@ -1777,6 +1784,9 @@
if (job == null) {
s.append("nothing");
} else {
+ if (job.getNamespace() != null) {
+ s.append(job.getNamespace()).append(":");
+ }
s.append(job.getJobId()).append("/").append(job.getUid());
}
s.append(")");
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 c032513..c7a2997 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -89,6 +89,7 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseSetArray;
@@ -149,6 +150,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -377,8 +379,12 @@
@GuardedBy("mLock")
private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
+ /**
+ * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason.
+ */
@GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
- private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+ private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache =
+ new SparseArrayMap<>();
/**
* Named indices into standby bucket arrays, for clarity in referring to
@@ -1333,7 +1339,7 @@
private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
- int userId, String tag) {
+ int userId, @Nullable String namespace, String tag) {
// Rate limit excessive schedule() calls.
final String servicePkg = job.getService().getPackageName();
if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
@@ -1392,7 +1398,7 @@
}
synchronized (mLock) {
- final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
+ final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
if (work != null && toCancel != null) {
// Fast path: we are adding work to an existing job, and the JobInfo is not
@@ -1409,7 +1415,8 @@
}
}
- JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
+ JobStatus jobStatus =
+ JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag);
// Return failure early if expedited job quota used up.
if (jobStatus.isRequestedExpeditedJob()) {
@@ -1504,26 +1511,47 @@
return JobScheduler.RESULT_SUCCESS;
}
- public List<JobInfo> getPendingJobs(int uid) {
+ private ArrayMap<String, List<JobInfo>> getPendingJobs(int uid) {
+ final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
+ synchronized (mLock) {
+ ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
+ // Write out for loop to avoid addAll() creating an Iterator.
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ final JobStatus job = jobs.valueAt(i);
+ List<JobInfo> outList = outMap.get(job.getNamespace());
+ if (outList == null) {
+ outList = new ArrayList<JobInfo>(jobs.size());
+ outMap.put(job.getNamespace(), outList);
+ }
+
+ outList.add(job.getJob());
+ }
+ return outMap;
+ }
+ }
+
+ private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
// Write out for loop to avoid addAll() creating an Iterator.
for (int i = jobs.size() - 1; i >= 0; i--) {
final JobStatus job = jobs.valueAt(i);
- outList.add(job.getJob());
+ if (Objects.equals(namespace, job.getNamespace())) {
+ outList.add(job.getJob());
+ }
}
return outList;
}
}
@JobScheduler.PendingJobReason
- private int getPendingJobReason(int uid, int jobId) {
+ private int getPendingJobReason(int uid, String namespace, int jobId) {
int reason;
// Some apps may attempt to query this frequently, so cache the reason under a separate lock
// so that the rest of JS processing isn't negatively impacted.
synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
if (jobIdToReason != null) {
reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
@@ -1532,16 +1560,17 @@
}
}
synchronized (mLock) {
- reason = getPendingJobReasonLocked(uid, jobId);
+ reason = getPendingJobReasonLocked(uid, namespace, jobId);
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+ Slog.v(TAG, "getPendingJobReason("
+ + uid + "," + namespace + "," + jobId + ")=" + reason);
}
}
synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
if (jobIdToReason == null) {
jobIdToReason = new SparseIntArray();
- mPendingJobReasonCache.put(uid, jobIdToReason);
+ mPendingJobReasonCache.add(uid, namespace, jobIdToReason);
}
jobIdToReason.put(jobId, reason);
}
@@ -1550,10 +1579,10 @@
@JobScheduler.PendingJobReason
@GuardedBy("mLock")
- private int getPendingJobReasonLocked(int uid, int jobId) {
+ private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
// Very similar code to isReadyToBeExecutedLocked.
- JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+ JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (job == null) {
// Job doesn't exist.
return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
@@ -1645,12 +1674,12 @@
return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
}
- public JobInfo getPendingJob(int uid, int jobId) {
+ private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) {
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
for (int i = jobs.size() - 1; i >= 0; i--) {
JobStatus job = jobs.valueAt(i);
- if (job.getJobId() == jobId) {
+ if (job.getJobId() == jobId && Objects.equals(namespace, job.getNamespace())) {
return job.getJob();
}
}
@@ -1726,12 +1755,20 @@
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
*
- * @param uid Uid to check against for removal of a job.
+ * @param uid Uid to check against for removal of a job.
* @param includeSourceApp Whether to include jobs scheduled for this UID by another UID.
* If false, only jobs scheduled by this UID will be cancelled.
*/
public boolean cancelJobsForUid(int uid, boolean includeSourceApp,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+ return cancelJobsForUid(uid, includeSourceApp,
+ /* namespaceOnly */ false, /* namespace */ null,
+ reason, internalReasonCode, debugReason);
+ }
+
+ private boolean cancelJobsForUid(int uid, boolean includeSourceApp,
+ boolean namespaceOnly, @Nullable String namespace,
+ @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return false;
@@ -1748,8 +1785,10 @@
}
for (int i = 0; i < jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.valueAt(i);
- cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason);
- jobsCanceled = true;
+ if (!namespaceOnly || Objects.equals(namespace, toRemove.getNamespace())) {
+ cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason);
+ jobsCanceled = true;
+ }
}
}
return jobsCanceled;
@@ -1763,11 +1802,11 @@
* @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
- private boolean cancelJob(int uid, int jobId, int callingUid,
+ private boolean cancelJob(int uid, String namespace, int jobId, int callingUid,
@JobParameters.StopReason int reason) {
JobStatus toCancel;
synchronized (mLock) {
- toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
+ toCancel = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (toCancel != null) {
cancelJobImplLocked(toCancel, null, reason,
JobParameters.INTERNAL_STOP_REASON_CANCELED,
@@ -2197,7 +2236,8 @@
jobStatus.stopTrackingJobLocked(incomingJob);
synchronized (mPendingJobReasonCache) {
- SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+ SparseIntArray reasonCache =
+ mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
if (reasonCache != null) {
reasonCache.delete(jobStatus.getJobId());
}
@@ -2228,7 +2268,8 @@
/** Remove the pending job reason for this job from the cache. */
void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
synchronized (mPendingJobReasonCache) {
- final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
+ final SparseIntArray reasons =
+ mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
if (reasons != null) {
reasons.delete(jobStatus.getJobId());
}
@@ -2490,7 +2531,8 @@
if (DEBUG) {
Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
}
- JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+ JobStatus newJs = mJobs.getJobByUidAndJobId(
+ jobStatus.getUid(), jobStatus.getNamespace(), jobStatus.getJobId());
if (newJs != null) {
// This job was stopped because the app scheduled a new job with the same job ID.
// Check if the new job is ready to run.
@@ -2947,10 +2989,12 @@
synchronized (mPendingJobReasonCache) {
for (int i = 0; i < numRunnableJobs; ++i) {
final JobStatus job = runnableJobs.get(i);
- SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+ SparseIntArray reasons =
+ mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
if (reasons == null) {
reasons = new SparseIntArray();
- mPendingJobReasonCache.put(job.getUid(), reasons);
+ mPendingJobReasonCache
+ .add(job.getUid(), job.getNamespace(), reasons);
}
// We're force batching these jobs, so consider it an optimization
// policy reason.
@@ -3749,7 +3793,7 @@
// IJobScheduler implementation
@Override
- public int schedule(JobInfo job) throws RemoteException {
+ public int schedule(String namespace, JobInfo job) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling job: " + job.toString());
}
@@ -3770,10 +3814,14 @@
return result;
}
+ if (namespace != null) {
+ namespace = namespace.intern();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
- null);
+ namespace, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3781,7 +3829,7 @@
// IJobScheduler implementation
@Override
- public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
+ public int enqueue(String namespace, JobInfo job, JobWorkItem work) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
}
@@ -3801,18 +3849,22 @@
return result;
}
+ if (namespace != null) {
+ namespace = namespace.intern();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
- null);
+ namespace, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
- throws RemoteException {
+ public int scheduleAsPackage(String namespace, JobInfo job, String packageName, int userId,
+ String tag) throws RemoteException {
final int callerUid = Binder.getCallingUid();
if (DEBUG) {
Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
@@ -3835,46 +3887,70 @@
return result;
}
+ if (namespace != null) {
+ namespace = namespace.intern();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
- packageName, userId, tag);
+ packageName, userId, namespace, tag);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
+ public Map<String, ParceledListSlice<JobInfo>> getAllPendingJobs() throws RemoteException {
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
+ final ArrayMap<String, List<JobInfo>> jobs =
+ JobSchedulerService.this.getPendingJobs(uid);
+ final ArrayMap<String, ParceledListSlice<JobInfo>> outMap = new ArrayMap<>();
+ for (int i = 0; i < jobs.size(); ++i) {
+ outMap.put(jobs.keyAt(i), new ParceledListSlice<>(jobs.valueAt(i)));
+ }
+ return outMap;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public int getPendingJobReason(int jobId) throws RemoteException {
+ public ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace)
+ throws RemoteException {
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+ return new ParceledListSlice<>(
+ JobSchedulerService.this.getPendingJobsInNamespace(uid, namespace));
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public JobInfo getPendingJob(int jobId) throws RemoteException {
+ public JobInfo getPendingJob(String namespace, int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.getPendingJob(uid, jobId);
+ return JobSchedulerService.this.getPendingJob(uid, namespace, jobId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public int getPendingJobReason(String namespace, int jobId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return JobSchedulerService.this.getPendingJobReason(uid, namespace, jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3897,12 +3973,29 @@
}
@Override
- public void cancel(int jobId) throws RemoteException {
+ public void cancelAllInNamespace(String namespace) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ JobSchedulerService.this.cancelJobsForUid(uid,
+ // Documentation says only jobs scheduled BY the app will be cancelled
+ /* includeSourceApp */ false,
+ /* namespaceOnly */ true, namespace,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP,
+ JobParameters.INTERNAL_STOP_REASON_CANCELED,
+ "cancelAllInNamespace() called by app, callingUid=" + uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void cancel(String namespace, int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJob(uid, jobId, uid,
+ JobSchedulerService.this.cancelJob(uid, namespace, jobId, uid,
JobParameters.STOP_REASON_CANCELLED_BY_APP);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4080,8 +4173,9 @@
}
// Shell command infrastructure: run the given job immediately
- int executeRunCommand(String pkgName, int userId, int jobId, boolean satisfied, boolean force) {
- Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + userId
+ int executeRunCommand(String pkgName, int userId, @Nullable String namespace,
+ int jobId, boolean satisfied, boolean force) {
+ Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
+ " " + jobId + " s=" + satisfied + " f=" + force);
try {
@@ -4092,7 +4186,7 @@
}
synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (js == null) {
return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
}
@@ -4122,14 +4216,14 @@
// Shell command infrastructure: immediately timeout currently executing jobs
int executeTimeoutCommand(PrintWriter pw, String pkgName, int userId,
- boolean hasJobId, int jobId) {
+ @Nullable String namespace, boolean hasJobId, int jobId) {
if (DEBUG) {
Slog.v(TAG, "executeTimeoutCommand(): " + pkgName + "/" + userId + " " + jobId);
}
synchronized (mLock) {
final boolean foundSome = mConcurrencyManager.executeTimeoutCommandLocked(pw,
- pkgName, userId, hasJobId, jobId);
+ pkgName, userId, namespace, hasJobId, jobId);
if (!foundSome) {
pw.println("No matching executing jobs found.");
}
@@ -4138,7 +4232,7 @@
}
// Shell command infrastructure: cancel a scheduled job
- int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+ int executeCancelCommand(PrintWriter pw, String pkgName, int userId, @Nullable String namespace,
boolean hasJobId, int jobId) {
if (DEBUG) {
Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
@@ -4166,7 +4260,8 @@
}
} else {
pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
- if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
+ if (!cancelJob(pkgUid, namespace, jobId,
+ Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
pw.println("No matching job found.");
}
}
@@ -4213,8 +4308,8 @@
}
}
- int getEstimatedNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
- int byteOption) {
+ int getEstimatedNetworkBytes(PrintWriter pw, String pkgName, int userId, String namespace,
+ int jobId, int byteOption) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4226,9 +4321,10 @@
}
synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (DEBUG) {
- Slog.d(TAG, "get-estimated-network-bytes " + uid + "/" + jobId + ": " + js);
+ Slog.d(TAG, "get-estimated-network-bytes " + uid + "/"
+ + namespace + "/" + jobId + ": " + js);
}
if (js == null) {
pw.print("unknown("); UserHandle.formatUid(pw, uid);
@@ -4238,8 +4334,8 @@
final long downloadBytes;
final long uploadBytes;
- final Pair<Long, Long> bytes =
- mConcurrencyManager.getEstimatedNetworkBytesLocked(pkgName, uid, jobId);
+ final Pair<Long, Long> bytes = mConcurrencyManager.getEstimatedNetworkBytesLocked(
+ pkgName, uid, namespace, jobId);
if (bytes == null) {
downloadBytes = js.getEstimatedNetworkDownloadBytes();
uploadBytes = js.getEstimatedNetworkUploadBytes();
@@ -4260,8 +4356,8 @@
return 0;
}
- int getTransferredNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
- int byteOption) {
+ int getTransferredNetworkBytes(PrintWriter pw, String pkgName, int userId, String namespace,
+ int jobId, int byteOption) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4273,9 +4369,10 @@
}
synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (DEBUG) {
- Slog.d(TAG, "get-transferred-network-bytes " + uid + "/" + jobId + ": " + js);
+ Slog.d(TAG, "get-transferred-network-bytes " + uid
+ + namespace + "/" + "/" + jobId + ": " + js);
}
if (js == null) {
pw.print("unknown("); UserHandle.formatUid(pw, uid);
@@ -4285,8 +4382,8 @@
final long downloadBytes;
final long uploadBytes;
- final Pair<Long, Long> bytes =
- mConcurrencyManager.getTransferredNetworkBytesLocked(pkgName, uid, jobId);
+ final Pair<Long, Long> bytes = mConcurrencyManager.getTransferredNetworkBytesLocked(
+ pkgName, uid, namespace, jobId);
if (bytes == null) {
downloadBytes = 0;
uploadBytes = 0;
@@ -4335,7 +4432,8 @@
}
// Shell command infrastructure
- int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
+ int getJobState(PrintWriter pw, String pkgName, int userId, @Nullable String namespace,
+ int jobId) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4347,11 +4445,17 @@
}
synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
- if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js);
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "get-job-state " + namespace + "/" + uid + "/" + jobId + ": " + js);
+ }
if (js == null) {
- pw.print("unknown("); UserHandle.formatUid(pw, uid);
- pw.print("/jid"); pw.print(jobId); pw.println(")");
+ pw.print("unknown(");
+ UserHandle.formatUid(pw, uid);
+ pw.print("/jid");
+ pw.print(jobId);
+ pw.println(")");
return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
}
@@ -4527,7 +4631,9 @@
}
jobPrinted = true;
- pw.print("JOB #"); job.printUniqueId(pw); pw.print(": ");
+ pw.print("JOB ");
+ job.printUniqueId(pw);
+ pw.print(": ");
pw.println(job.toShortStringExceptUniqueId());
pw.increaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 36ba8dd..2eeb25e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.pm.IPackageManager;
@@ -107,7 +108,8 @@
}
}
- private boolean printError(int errCode, String pkgName, int userId, int jobId) {
+ private boolean printError(int errCode, String pkgName, int userId, @Nullable String namespace,
+ int jobId) {
PrintWriter pw;
switch (errCode) {
case CMD_ERR_NO_PACKAGE:
@@ -124,6 +126,10 @@
pw.print(jobId);
pw.print(" in package ");
pw.print(pkgName);
+ if (namespace != null) {
+ pw.print(" / namespace ");
+ pw.print(namespace);
+ }
pw.print(" / user ");
pw.println(userId);
return true;
@@ -134,6 +140,10 @@
pw.print(jobId);
pw.print(" in package ");
pw.print(pkgName);
+ if (namespace != null) {
+ pw.print(" / namespace ");
+ pw.print(namespace);
+ }
pw.print(" / user ");
pw.print(userId);
pw.println(" has functional constraints but --force not specified");
@@ -150,6 +160,7 @@
boolean force = false;
boolean satisfied = false;
int userId = UserHandle.USER_SYSTEM;
+ String namespace = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -169,6 +180,11 @@
userId = Integer.parseInt(getNextArgRequired());
break;
+ case "-n":
+ case "--namespace":
+ namespace = getNextArgRequired();
+ break;
+
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -185,8 +201,9 @@
final long ident = Binder.clearCallingIdentity();
try {
- int ret = mInternal.executeRunCommand(pkgName, userId, jobId, satisfied, force);
- if (printError(ret, pkgName, userId, jobId)) {
+ int ret = mInternal.executeRunCommand(pkgName, userId, namespace,
+ jobId, satisfied, force);
+ if (printError(ret, pkgName, userId, namespace, jobId)) {
return ret;
}
@@ -207,6 +224,7 @@
checkPermission("force timeout jobs");
int userId = UserHandle.USER_ALL;
+ String namespace = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -216,6 +234,11 @@
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "-n":
+ case "--namespace":
+ namespace = getNextArgRequired();
+ break;
+
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -232,7 +255,8 @@
final long ident = Binder.clearCallingIdentity();
try {
- return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+ return mInternal.executeTimeoutCommand(pw, pkgName, userId, namespace,
+ jobIdStr != null, jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -242,6 +266,7 @@
checkPermission("cancel jobs");
int userId = UserHandle.USER_SYSTEM;
+ String namespace = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -251,6 +276,11 @@
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "-n":
+ case "--namespace":
+ namespace = getNextArgRequired();
+ break;
+
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -268,7 +298,8 @@
final long ident = Binder.clearCallingIdentity();
try {
- return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+ return mInternal.executeCancelCommand(pw, pkgName, userId, namespace,
+ jobIdStr != null, jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -319,6 +350,7 @@
checkPermission("get estimated bytes");
int userId = UserHandle.USER_SYSTEM;
+ String namespace = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -328,6 +360,11 @@
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "-n":
+ case "--namespace":
+ namespace = getNextArgRequired();
+ break;
+
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -344,8 +381,9 @@
final long ident = Binder.clearCallingIdentity();
try {
- int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, jobId, byteOption);
- printError(ret, pkgName, userId, jobId);
+ int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, namespace,
+ jobId, byteOption);
+ printError(ret, pkgName, userId, namespace, jobId);
return ret;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -368,6 +406,7 @@
checkPermission("get transferred bytes");
int userId = UserHandle.USER_SYSTEM;
+ String namespace = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -377,6 +416,11 @@
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "-n":
+ case "--namespace":
+ namespace = getNextArgRequired();
+ break;
+
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -393,8 +437,9 @@
final long ident = Binder.clearCallingIdentity();
try {
- int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, jobId, byteOption);
- printError(ret, pkgName, userId, jobId);
+ int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, namespace,
+ jobId, byteOption);
+ printError(ret, pkgName, userId, namespace, jobId);
return ret;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -405,6 +450,7 @@
checkPermission("get job state");
int userId = UserHandle.USER_SYSTEM;
+ String namespace = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -414,6 +460,11 @@
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "-n":
+ case "--namespace":
+ namespace = getNextArgRequired();
+ break;
+
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -430,8 +481,8 @@
final long ident = Binder.clearCallingIdentity();
try {
- int ret = mInternal.getJobState(pw, pkgName, userId, jobId);
- printError(ret, pkgName, userId, jobId);
+ int ret = mInternal.getJobState(pw, pkgName, userId, namespace, jobId);
+ printError(ret, pkgName, userId, namespace, jobId);
return ret;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -521,7 +572,8 @@
pw.println("Job scheduler (jobscheduler) commands:");
pw.println(" help");
pw.println(" Print this help text.");
- pw.println(" run [-f | --force] [-s | --satisfied] [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" run [-f | --force] [-s | --satisfied] [-u | --user USER_ID]"
+ + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
pw.println(" Trigger immediate execution of a specific scheduled job. For historical");
pw.println(" reasons, some constraints, such as battery, are ignored when this");
pw.println(" command is called. If you don't want any constraints to be ignored,");
@@ -530,23 +582,30 @@
pw.println(" -f or --force: run the job even if technical constraints such as");
pw.println(" connectivity are not currently met. This is incompatible with -f ");
pw.println(" and so an error will be reported if both are given.");
+ pw.println(" -n or --namespace: specify the namespace this job sits in; the default");
+ pw.println(" is null (no namespace).");
pw.println(" -s or --satisfied: run the job only if all constraints are met.");
pw.println(" This is incompatible with -f and so an error will be reported");
pw.println(" if both are given.");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" the primary or system user");
- pw.println(" timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]");
+ pw.println(" timeout [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
+ + " [PACKAGE] [JOB_ID]");
pw.println(" Trigger immediate timeout of currently executing jobs, as if their.");
pw.println(" execution timeout had expired.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" all users");
- pw.println(" cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+ pw.println(" -n or --namespace: specify the namespace this job sits in; the default");
+ pw.println(" is null (no namespace).");
+ pw.println(" cancel [-u | --user USER_ID] [-n | --namespace NAMESPACE] PACKAGE [JOB_ID]");
pw.println(" Cancel a scheduled job. If a job ID is not supplied, all jobs scheduled");
pw.println(" by that package will be canceled. USE WITH CAUTION.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" the primary or system user");
+ pw.println(" -n or --namespace: specify the namespace this job sits in; the default");
+ pw.println(" is null (no namespace).");
pw.println(" heartbeat [num]");
pw.println(" No longer used.");
pw.println(" monitor-battery [on|off]");
@@ -558,12 +617,14 @@
pw.println(" Return whether the battery is currently considered to be charging.");
pw.println(" get-battery-not-low");
pw.println(" Return whether the battery is currently considered to not be low.");
- pw.println(" get-estimated-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" get-estimated-download-bytes [-u | --user USER_ID]"
+ + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
pw.println(" Return the most recent estimated download bytes for the job.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" the primary or system user");
- pw.println(" get-estimated-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" get-estimated-upload-bytes [-u | --user USER_ID]"
+ + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
pw.println(" Return the most recent estimated upload bytes for the job.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
@@ -572,17 +633,20 @@
pw.println(" Return the last storage update sequence number that was received.");
pw.println(" get-storage-not-low");
pw.println(" Return whether storage is currently considered to not be low.");
- pw.println(" get-transferred-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" get-transferred-download-bytes [-u | --user USER_ID]"
+ + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
pw.println(" Return the most recent transferred download bytes for the job.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" the primary or system user");
- pw.println(" get-transferred-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" get-transferred-upload-bytes [-u | --user USER_ID]"
+ + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
pw.println(" Return the most recent transferred upload bytes for the job.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" the primary or system user");
- pw.println(" get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" get-job-state [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
+ + " PACKAGE JOB_ID");
pw.println(" Return the current state of a job, may be any combination of:");
pw.println(" pending: currently on the pending list, waiting to be active");
pw.println(" active: job is actively running");
@@ -594,6 +658,8 @@
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" the primary or system user");
+ pw.println(" -n or --namespace: specify the namespace this job sits in; the default");
+ pw.println(" is null (no namespace).");
pw.println(" trigger-dock-state [idle|active]");
pw.println(" Trigger wireless charging dock state. Active by default.");
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 15fc3c9..285b982 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -64,6 +64,8 @@
import com.android.server.tare.EconomyManagerInternal;
import com.android.server.tare.JobSchedulerEconomicPolicy;
+import java.util.Objects;
+
/**
* Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
* class.
@@ -304,7 +306,8 @@
job.changedAuthorities.toArray(triggeredAuthorities);
}
final JobInfo ji = job.getJob();
- mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
+ mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(),
+ ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities,
@@ -518,11 +521,12 @@
}
@GuardedBy("mLock")
- boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
- String reason) {
+ boolean timeoutIfExecutingLocked(String pkgName, int userId, @Nullable String namespace,
+ boolean matchJobId, int jobId, String reason) {
final JobStatus executing = getRunningJobLocked();
if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
&& (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
+ && Objects.equals(namespace, executing.getNamespace())
&& (!matchJobId || jobId == executing.getJobId())) {
if (mVerb == VERB_EXECUTING) {
mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
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 a1153e3..5f5f447 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -66,6 +66,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
@@ -386,8 +387,8 @@
* @return the JobStatus that matches the provided uId and jobId, or null if none found.
*/
@Nullable
- public JobStatus getJobByUidAndJobId(int uid, int jobId) {
- return mJobSet.get(uid, jobId);
+ public JobStatus getJobByUidAndJobId(int uid, @Nullable String namespace, int jobId) {
+ return mJobSet.get(uid, namespace, jobId);
}
/**
@@ -764,6 +765,9 @@
if (jobStatus.getSourcePackageName() != null) {
out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
}
+ if (jobStatus.getNamespace() != null) {
+ out.attribute(null, "namespace", jobStatus.getNamespace());
+ }
if (jobStatus.getSourceTag() != null) {
out.attribute(null, "sourceTag", jobStatus.getSourceTag());
}
@@ -1135,6 +1139,7 @@
}
String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
+ final String namespace = parser.getAttributeValue(null, "namespace");
final String sourceTag = parser.getAttributeValue(null, "sourceTag");
int eventType;
@@ -1292,7 +1297,7 @@
sourceUserId, nowElapsed);
JobStatus js = new JobStatus(
builtJob, uid, sourcePackageName, sourceUserId,
- appBucket, sourceTag,
+ appBucket, namespace, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
(rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
@@ -1592,12 +1597,12 @@
return jobs != null && jobs.contains(job);
}
- public JobStatus get(int uid, int jobId) {
+ public JobStatus get(int uid, @Nullable String namespace, int jobId) {
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs != null) {
for (int i = jobs.size() - 1; i >= 0; i--) {
JobStatus job = jobs.valueAt(i);
- if (job.getJobId() == jobId) {
+ if (job.getJobId() == jobId && Objects.equals(namespace, job.getNamespace())) {
return job;
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 0eacfd6..36a26f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.PriorityQueue;
/**
@@ -280,12 +281,14 @@
return job1EJ ? -1 : 1;
}
- final int job1Priority = job1.getEffectivePriority();
- final int job2Priority = job2.getEffectivePriority();
- if (job1Priority != job2Priority) {
- // Use the priority set by an app for intra-app job ordering. Higher
- // priority should be before lower priority.
- return Integer.compare(job2Priority, job1Priority);
+ if (Objects.equals(job1.getNamespace(), job2.getNamespace())) {
+ final int job1Priority = job1.getEffectivePriority();
+ final int job2Priority = job2.getEffectivePriority();
+ if (job1Priority != job2Priority) {
+ // Use the priority set by an app for intra-app job ordering. Higher
+ // priority should be before lower priority.
+ return Integer.compare(job2Priority, job1Priority);
+ }
}
if (job1.lastEvaluatedBias != job2.lastEvaluatedBias) {
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 9b6186e..5712599 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
@@ -30,6 +30,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -70,6 +71,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -225,6 +227,8 @@
final int sourceUserId;
final int sourceUid;
final String sourceTag;
+ @Nullable
+ private final String mNamespace;
final String tag;
@@ -515,6 +519,7 @@
* @param standbyBucket The standby bucket that the source package is currently assigned to,
* cached here for speed of handling during runnability evaluations (and updated when bucket
* assignments are changed)
+ * @param namespace The custom namespace the app put this job in.
* @param tag A string associated with the job for debugging/logging purposes.
* @param numFailures Count of how many times this job has requested a reschedule because
* its work was not yet finished.
@@ -529,13 +534,15 @@
* @param internalFlags Non-API property flags about this job
*/
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
- int sourceUserId, int standbyBucket, String tag, int numFailures, int numSystemStops,
+ int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
+ int numFailures, int numSystemStops,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
int dynamicConstraints) {
this.job = job;
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
+ mNamespace = namespace;
int tempSourceUid = -1;
if (sourceUserId != -1 && sourcePackageName != null) {
@@ -658,7 +665,7 @@
public JobStatus(JobStatus jobStatus) {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
- jobStatus.getStandbyBucket(),
+ jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
@@ -680,13 +687,13 @@
* standby bucket is whatever the OS thinks it should be at this moment.
*/
public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
- int standbyBucket, String sourceTag,
+ int standbyBucket, @Nullable String namespace, String sourceTag,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC,
int innerFlags, int dynamicConstraints) {
this(job, callingUid, sourcePkgName, sourceUserId,
- standbyBucket,
+ standbyBucket, namespace,
sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
@@ -710,7 +717,7 @@
long lastSuccessfulRunTime, long lastFailedRunTime) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
- rescheduling.getStandbyBucket(),
+ rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
rescheduling.getSourceTag(), numFailures, numSystemStops,
newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
@@ -727,7 +734,7 @@
* caller.
*/
public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
- int sourceUserId, String tag) {
+ int sourceUserId, @Nullable String namespace, String tag) {
final long elapsedNow = sElapsedRealtimeClock.millis();
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
if (job.isPeriodic()) {
@@ -749,7 +756,7 @@
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
- standbyBucket, tag, /* numFailures */ 0, /* numSystemStops */ 0,
+ standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/*innerFlags=*/ 0, /* dynamicConstraints */ 0);
@@ -897,6 +904,12 @@
}
public void printUniqueId(PrintWriter pw) {
+ if (mNamespace != null) {
+ pw.print(mNamespace);
+ pw.print(":");
+ } else {
+ pw.print("#");
+ }
UserHandle.formatUid(pw, callingUid);
pw.print("/");
pw.print(job.getId());
@@ -1036,6 +1049,10 @@
return true;
}
+ public String getNamespace() {
+ return mNamespace;
+ }
+
public String getSourceTag() {
return sourceTag;
}
@@ -1362,7 +1379,8 @@
public UserVisibleJobSummary getUserVisibleJobSummary() {
if (mUserVisibleJobSummary == null) {
mUserVisibleJobSummary = new UserVisibleJobSummary(
- callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
+ callingUid, getSourceUserId(), getSourcePackageName(),
+ getNamespace(), getJobId());
}
return mUserVisibleJobSummary;
}
@@ -1989,8 +2007,12 @@
return (sat & mRequiredConstraintsOfInterest) == mRequiredConstraintsOfInterest;
}
- public boolean matches(int uid, int jobId) {
- return this.job.getId() == jobId && this.callingUid == uid;
+ /**
+ * Returns true if the given parameters match this job's unique identifier.
+ */
+ public boolean matches(int uid, @Nullable String namespace, int jobId) {
+ return this.job.getId() == jobId && this.callingUid == uid
+ && Objects.equals(mNamespace, namespace);
}
@Override
@@ -1998,7 +2020,13 @@
StringBuilder sb = new StringBuilder(128);
sb.append("JobStatus{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" #");
+ if (mNamespace != null) {
+ sb.append(" ");
+ sb.append(mNamespace);
+ sb.append(":");
+ } else {
+ sb.append(" #");
+ }
UserHandle.formatUid(sb, callingUid);
sb.append("/");
sb.append(job.getId());
@@ -2087,6 +2115,9 @@
public String toShortString() {
StringBuilder sb = new StringBuilder();
sb.append(Integer.toHexString(System.identityHashCode(this)));
+ if (mNamespace != null) {
+ sb.append(" {").append(mNamespace).append("}");
+ }
sb.append(" #");
UserHandle.formatUid(sb, callingUid);
sb.append("/");
diff --git a/core/api/current.txt b/core/api/current.txt
index 5bb6652..a77040c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18263,6 +18263,7 @@
field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe
field public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16; // 0x10
field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7
+ field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW = 6; // 0x6
field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT = 0; // 0x0
field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 1; // 0x1
field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 4; // 0x4
@@ -18522,6 +18523,7 @@
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_RAW_CROP_REGION;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SCALER_ROTATE_AND_CROP;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ac5c54d..97b9a51 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4244,6 +4244,7 @@
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
+ field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000
field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
}
@@ -6682,6 +6683,7 @@
}
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+ method public int getChannelMask();
method public int getClientPid();
method public int getClientUid();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy();
@@ -6689,9 +6691,11 @@
method public android.media.PlayerProxy getPlayerProxy();
method public int getPlayerState();
method public int getPlayerType();
+ method @IntRange(from=0) public int getSampleRate();
method @IntRange(from=0) public int getSessionId();
method public boolean isActive();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
+ method public boolean isSpatialized();
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 320af06..11b80cc 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3756,6 +3756,7 @@
* <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD VIDEO_RECORD}</li>
* <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL PREVIEW_VIDEO_STILL}</li>
* <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL VIDEO_CALL}</li>
+ * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW CROPPED_RAW}</li>
* </ul>
*
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -3765,6 +3766,7 @@
* @see #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD
* @see #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL
* @see #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL
+ * @see #SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index bf2e563..577c8a3 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -900,13 +900,27 @@
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td colspan="3" id="rb"></td> <td>Preview with video recording or in-app video processing</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td colspan="3" id="rb"></td> <td>Preview with in-application image processing</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr>
- * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream with JPEG or YUV still capture</td> </tr>
+ * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>MultI-purpose stream with JPEG or YUV still capture</td> </tr>
* <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr>
* </table><br>
* </p>
*
+ * <p>Devices that include the {@link CameraMetadata#SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW}
+ * stream use-case in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES},
+ * support the additional stream combinations below:
+ *
+ * <table>
+ * <tr><th colspan="10">STREAM_USE_CASE_CROPPED_RAW capability additional guaranteed configurations</th></tr>
+ * <tr><th colspan="3" id="rb">Target 1</th><th colspan="3" id="rb">Target 2</th><th colspan="3" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr>
+ * <tr><th>Type</th><th id="rb">Max size</th><th>Usecase</th><th>Type</th><th id="rb">Max size</th><th>Usecase</th><th>Type</th><th id="rb">Max size</th><th>Usecase</th> </tr>
+ * <tr> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Cropped RAW still capture without preview</td> </tr>
+ * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td colspan="3" id="rb"></td> <td>Preview with cropped RAW still capture</td> </tr>
+ * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td>Preview with YUV / JPEG and cropped RAW still capture</td> </tr>
+ * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code VIDEO_RECORD / PREVIEW}</td> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td>Video recording with preview and cropped RAW still capture</td> </tr>
+ *
+ *
*<p> For devices where {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES}
* includes {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION},
* the following stream combinations are guaranteed,
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 67dcd93..788302b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1489,6 +1489,31 @@
public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL = 0x5;
/**
+ * <p>Cropped RAW stream when the client chooses to crop the field of view.</p>
+ * <p>Certain types of image sensors can run in binned modes in order to improve signal to
+ * noise ratio while capturing frames. However, at certain zoom levels and / or when
+ * other scene conditions are deemed fit, the camera sub-system may choose to un-bin and
+ * remosaic the sensor's output. This results in a RAW frame which is cropped in field
+ * of view and yet has the same number of pixels as full field of view RAW, thereby
+ * improving image detail.</p>
+ * <p>The resultant field of view of the RAW stream will be greater than or equal to
+ * croppable non-RAW streams. The effective crop region for this RAW stream will be
+ * reflected in the CaptureResult key {@link CaptureResult#SCALER_RAW_CROP_REGION android.scaler.rawCropRegion}.</p>
+ * <p>If this stream use case is set on a non-RAW stream, i.e. not one of :</p>
+ * <ul>
+ * <li>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</li>
+ * <li>{@link android.graphics.ImageFormat#RAW10 RAW10}</li>
+ * <li>{@link android.graphics.ImageFormat#RAW12 RAW12}</li>
+ * </ul>
+ * <p>session configuration is not guaranteed to succeed.</p>
+ * <p>This stream use case may not be supported on some devices.</p>
+ *
+ * @see CaptureResult#SCALER_RAW_CROP_REGION
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
+ */
+ public static final int SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW = 0x6;
+
+ /**
* <p>Vendor defined use cases. These depend on the vendor implementation.</p>
* @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
* @hide
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 43bfdcc..3d83009 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3086,9 +3086,9 @@
* <p>Output streams use this rectangle to produce their output, cropping to a smaller region
* if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
* match the output's configured resolution.</p>
- * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
- * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
- * croppable. The crop region will be ignored by raw streams.</p>
+ * <p>The crop region is usually applied after the RAW to other color space (e.g. YUV)
+ * conversion. As a result RAW streams are not croppable unless supported by the
+ * camera device. See {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES android.scaler.availableStreamUseCases}#CROPPED_RAW for details.</p>
* <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
* final pixel area of the stream.</p>
* <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
@@ -3183,6 +3183,7 @@
* @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
* @see CameraCharacteristics#SCALER_CROPPING_TYPE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index fb52cc6..dad7d3e 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3748,9 +3748,9 @@
* <p>Output streams use this rectangle to produce their output, cropping to a smaller region
* if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
* match the output's configured resolution.</p>
- * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
- * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
- * croppable. The crop region will be ignored by raw streams.</p>
+ * <p>The crop region is usually applied after the RAW to other color space (e.g. YUV)
+ * conversion. As a result RAW streams are not croppable unless supported by the
+ * camera device. See {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES android.scaler.availableStreamUseCases}#CROPPED_RAW for details.</p>
* <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
* final pixel area of the stream.</p>
* <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
@@ -3845,6 +3845,7 @@
* @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
* @see CameraCharacteristics#SCALER_CROPPING_TYPE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
@@ -3952,6 +3953,60 @@
new Key<Integer>("android.scaler.rotateAndCrop", int.class);
/**
+ * <p>The region of the sensor that corresponds to the RAW read out for this
+ * capture when the stream use case of a RAW stream is set to CROPPED_RAW.</p>
+ * <p>The coordinate system follows that of {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</p>
+ * <p>This CaptureResult key will be set when the corresponding CaptureRequest has a RAW target
+ * with stream use case set to
+ * {@link android.hardware.camera2.CameraMetadata#SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW },
+ * otherwise it will be {@code null}.
+ * The value of this key specifies the region of the sensor used for the RAW capture and can
+ * be used to calculate the corresponding field of view of RAW streams.
+ * This field of view will always be >= field of view for (processed) non-RAW streams for the
+ * capture. Note: The region specified may not necessarily be centered.</p>
+ * <p>For example: Assume a camera device has a pre correction active array size of
+ * {@code {0, 0, 1500, 2000}}. If the RAW_CROP_REGION is {@code {500, 375, 1500, 1125}}, that
+ * corresponds to a centered crop of 1/4th of the full field of view RAW stream.</p>
+ * <p>The metadata keys which describe properties of RAW frames:</p>
+ * <ul>
+ * <li>{@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}</li>
+ * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP android.statistics.lensShadingCorrectionMap}</li>
+ * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
+ * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li>
+ * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li>
+ * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
+ * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li>
+ * </ul>
+ * <p>should be interpreted in the effective after raw crop field-of-view coordinate system.
+ * In this coordinate system,
+ * {preCorrectionActiveArraySize.left, preCorrectionActiveArraySize.top} corresponds to the
+ * the top left corner of the cropped RAW frame and
+ * {preCorrectionActiveArraySize.right, preCorrectionActiveArraySize.bottom} corresponds to
+ * the bottom right corner. Client applications must use the values of the keys
+ * in the CaptureResult metadata if present.</p>
+ * <p>Crop regions (android.scaler.CropRegion), AE/AWB/AF regions and face coordinates still
+ * use the {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} coordinate system as usual.</p>
+ * <p><b>Units</b>: Pixel coordinates relative to
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
+ * capability and mode</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_DISTORTION
+ * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+ * @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<android.graphics.Rect> SCALER_RAW_CROP_REGION =
+ new Key<android.graphics.Rect>("android.scaler.rawCropRegion", android.graphics.Rect.class);
+
+ /**
* <p>Duration each pixel is exposed to
* light.</p>
* <p>If the sensor can't expose this exact duration, it will shorten the
@@ -5643,4 +5698,5 @@
+
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 9a16474..8d742b5 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1461,6 +1461,23 @@
return ret;
}
+ private boolean isCroppedRawSupported() {
+ boolean ret = false;
+
+ long[] streamUseCases =
+ getBase(CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
+ if (streamUseCases == null) {
+ return false;
+ }
+ for (long useCase : streamUseCases) {
+ if (useCase == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW) {
+ return true;
+ }
+ }
+
+ return ret;
+ }
+
private MandatoryStreamCombination[] getMandatoryStreamCombinationsHelper(
int mandatoryStreamsType) {
int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
@@ -1472,7 +1489,8 @@
int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder(
mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap(),
- getStreamConfigurationMapMaximumResolution(), isPreviewStabilizationSupported());
+ getStreamConfigurationMapMaximumResolution(), isPreviewStabilizationSupported(),
+ isCroppedRawSupported());
List<MandatoryStreamCombination> combs = null;
switch (mandatoryStreamsType) {
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 0905e1b..6f77d12 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -375,6 +375,8 @@
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL;
private static final long STREAM_USE_CASE_VIDEO_CALL =
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL;
+ private static final long STREAM_USE_CASE_CROPPED_RAW =
+ CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW;
/**
* Create a new {@link MandatoryStreamCombination}.
@@ -1262,6 +1264,86 @@
"Preview, in-application image processing, and YUV still image capture"),
};
+ private static StreamCombinationTemplate sCroppedRawStreamUseCaseCombinations[] = {
+ // Single stream combination
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Cropped RAW still image capture without preview"),
+
+ // 2 Stream combinations
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Cropped RAW still image capture with preview"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "In-app image processing with cropped RAW still image capture"),
+
+ // 3 stream combinations
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_STILL_CAPTURE),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Preview with YUV and RAW still image capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_STILL_CAPTURE),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "In-app image processing with YUV and RAW still image capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_STILL_CAPTURE),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Preview with JPEG and RAW still image capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_STILL_CAPTURE),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "In-app image processing with JPEG and RAW still image capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_RECORD),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Preview with video recording and RAW snapshot"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Preview with in-app image processing and RAW still image capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+ STREAM_USE_CASE_PREVIEW),
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+ STREAM_USE_CASE_CROPPED_RAW)},
+ "Two input in-app processing and RAW still image capture"),
+ };
+
private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = {
// 1 stream combinations
new StreamCombinationTemplate(new StreamTemplate [] {
@@ -1317,7 +1399,8 @@
private StreamConfigurationMap mStreamConfigMap;
private StreamConfigurationMap mStreamConfigMapMaximumResolution;
private boolean mIsHiddenPhysicalCamera;
- private boolean mIsPreviewStabilizationSupported;
+ private boolean mIsPreviewStabilizationSupported = false;
+ private boolean mIsCroppedRawSupported = false;
private final Size kPreviewSizeBound = new Size(1920, 1088);
@@ -1331,10 +1414,13 @@
* @param sm The camera device stream configuration map.
* @param smMaxResolution The camera device stream configuration map when it runs in max
* resolution mode.
+ * @param previewStabilization The camera device supports preview stabilization.
+ * @param croppedRaw The camera device supports the cropped raw stream use case.
*/
public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
@NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm,
- StreamConfigurationMap smMaxResolution, boolean previewStabilization) {
+ StreamConfigurationMap smMaxResolution, boolean previewStabilization,
+ boolean isCroppedRawSupported) {
mCameraId = cameraId;
mDisplaySize = displaySize;
mCapabilities = capabilities;
@@ -1344,6 +1430,7 @@
mIsHiddenPhysicalCamera =
CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId));
mIsPreviewStabilizationSupported = previewStabilization;
+ mIsCroppedRawSupported = isCroppedRawSupported;
}
private @Nullable List<MandatoryStreamCombination>
@@ -1483,10 +1570,23 @@
Log.e(TAG, "Available size enumeration failed!");
return null;
}
+ ArrayList<StreamCombinationTemplate> availableTemplates =
+ new ArrayList<StreamCombinationTemplate> ();
+ availableTemplates.addAll(Arrays.asList(sStreamUseCaseCombinations));
ArrayList<MandatoryStreamCombination> availableStreamCombinations = new ArrayList<>();
- availableStreamCombinations.ensureCapacity(sStreamUseCaseCombinations.length);
- for (StreamCombinationTemplate combTemplate : sStreamUseCaseCombinations) {
+ int capacity = sStreamUseCaseCombinations.length;
+ if (mIsCroppedRawSupported) {
+ capacity += sCroppedRawStreamUseCaseCombinations.length;
+ availableStreamCombinations.ensureCapacity(capacity);
+ availableTemplates.addAll(Arrays.asList(sCroppedRawStreamUseCaseCombinations));
+ }
+ else {
+ availableStreamCombinations.ensureCapacity(capacity);
+ }
+
+
+ for (StreamCombinationTemplate combTemplate : availableTemplates) {
ArrayList<MandatoryStreamInformation> streamsInfo =
new ArrayList<MandatoryStreamInformation>();
streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
@@ -2012,6 +2112,7 @@
private @Nullable HashMap<Pair<SizeThreshold, Integer>, List<Size>>
enumerateAvailableSizes() {
final int[] formats = {
+ ImageFormat.RAW_SENSOR,
ImageFormat.PRIVATE,
ImageFormat.YUV_420_888,
ImageFormat.JPEG,
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 65df5d1..8b7c5ec 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -289,7 +289,8 @@
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE,
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD,
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL,
- CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL})
+ CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL,
+ CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW})
public @interface StreamUseCase {};
/**
@@ -998,7 +999,7 @@
*/
public void setStreamUseCase(@StreamUseCase long streamUseCase) {
// Verify that the value is in range
- long maxUseCaseValue = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL;
+ long maxUseCaseValue = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW;
if (streamUseCase > maxUseCaseValue &&
streamUseCase < CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VENDOR_START) {
throw new IllegalArgumentException("Not a valid stream use case value " +
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 23d108f..42803a1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -139,6 +139,7 @@
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
+ VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface VirtualDisplayFlag {}
@@ -429,6 +430,18 @@
public static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15;
+ /**
+ * Virtual display flags: Indicates that the display should not become the top focused display
+ * by stealing the top focus from another display.
+ *
+ * @see Display#FLAG_STEAL_TOP_FOCUS_DISABLED
+ * @see #createVirtualDisplay
+ * @see #VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+ * @hide
+ */
+ @SystemApi
+ public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 16;
+
/** @hide */
@IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
MATCH_CONTENT_FRAMERATE_UNKNOWN,
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 891ba36..abd647f 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -23,7 +23,6 @@
import android.annotation.Nullable;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.media.projection.MediaProjection;
-import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
@@ -35,7 +34,8 @@
/**
* Holds configuration used to create {@link VirtualDisplay} instances. See
- * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}.
+ * {@link MediaProjection#createVirtualDisplay} and
+ * {@link android.companion.virtual.VirtualDeviceManager.VirtualDevice#createVirtualDisplay}.
*
* @hide
*/
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index a31c2e6..ada5c55 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -982,6 +982,43 @@
/**
+ * Wait until a debugger attaches. As soon as a debugger attaches,
+ * suspend all Java threads and send VM_START (a.k.a VM_INIT)
+ * packet.
+ *
+ * @hide
+ */
+ public static void suspendAllAndSendVmStart() {
+ if (!VMDebug.isDebuggingEnabled()) {
+ return;
+ }
+
+ // if DDMS is listening, inform them of our plight
+ System.out.println("Sending WAIT chunk");
+ byte[] data = new byte[] { 0 }; // 0 == "waiting for debugger"
+ Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1);
+ DdmServer.sendChunk(waitChunk);
+
+ // We must wait until a debugger is connected (debug socket is
+ // open and at least one non-DDM JDWP packedt has been received.
+ // This guarantees that oj-libjdwp has been attached and that
+ // ART's default implementation of suspendAllAndSendVmStart has
+ // been replaced with an implementation that will suspendAll and
+ // send VM_START.
+ System.out.println("Waiting for debugger first packet");
+ while (!isDebuggerConnected()) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ }
+ }
+
+ System.out.println("Debug.suspendAllAndSentVmStart");
+ VMDebug.suspendAllAndSendVmStart();
+ System.out.println("Debug.suspendAllAndSendVmStart, resumed");
+ }
+
+ /**
* Wait until a debugger attaches. As soon as the debugger attaches,
* this returns, so you will need to place a breakpoint after the
* waitForDebugger() call if you want to start tracing immediately.
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index cfb195b..4892312 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -18,6 +18,7 @@
import android.os.Environment;
import android.os.UserHandle;
+
import java.io.File;
/**
@@ -32,11 +33,20 @@
private final File mUserRemovedCaDir;
private SystemCertificateSource() {
- super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"));
+ super(getDirectory());
File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
mUserRemovedCaDir = new File(configDir, "cacerts-removed");
}
+ private static File getDirectory() {
+ // TODO(miguelaranda): figure out correct code path.
+ File updatable_dir = new File("/apex/com.android.conscrypt/cacerts");
+ if (updatable_dir.exists()) {
+ return updatable_dir;
+ }
+ return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
+ }
+
public static SystemCertificateSource getInstance() {
return NoPreloadHolder.INSTANCE;
}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 65e2390..b85cf6d 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -61,7 +61,7 @@
* the same package that will be launched embedded in the device controls space.
*
* The activity must be exported, enabled and protected by
- * {@link Manifest.permission.BIND_CONTROLS}.
+ * {@link Manifest.permission#BIND_CONTROLS}.
*
* It is recommended that the activity is declared {@code android:resizeableActivity="true"}.
*/
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 71030bcc..689dce8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -332,6 +332,34 @@
public static final int FLAG_OWN_FOCUS = 1 << 11;
/**
+ * Flag: Indicates that the display should not become the top focused display by stealing the
+ * top focus from another display.
+ *
+ * <p>The result is that only targeted input events (displayId of input event matches the
+ * displayId of the display) can reach this display. A display with this flag set can still
+ * become the top focused display, if the system consists of only one display or if all
+ * displays have this flag set. In both cases the default display becomes the top focused
+ * display.
+ *
+ * <p>Note: A display only has a focused window if either
+ * - the display is the top focused display or
+ * - the display manages its own focus (via {@link #FLAG_OWN_FOCUS})
+ * - or all the displays manage their own focus (via {@code config_perDisplayFocusEnabled} flag)
+ * If a display has no focused window, no input event is dispatched to it. Therefore this
+ * flag is only useful together with {@link #FLAG_OWN_FOCUS} and will be
+ * ignored if it is not set.
+ *
+ * <p>Note: The framework only supports IME on the top focused display (b/262520411). Therefore,
+ * Enabling this flag on a display implicitly disables showing any IME. This is not intended
+ * behavior but cannot be fixed until b/262520411 is implemented. If you need IME on display do
+ * not set this flag.
+ *
+ * @hide
+ * @see #getFlags()
+ */
+ public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 12;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
@@ -1705,6 +1733,16 @@
return (mFlags & FLAG_TRUSTED) == FLAG_TRUSTED;
}
+ /**
+ * @return {@code true} if the display can steal the top focus from another display.
+ *
+ * @see #FLAG_STEAL_TOP_FOCUS_DISABLED
+ * @hide
+ */
+ public boolean canStealTopFocus() {
+ return (mFlags & FLAG_STEAL_TOP_FOCUS_DISABLED) == 0;
+ }
+
private void updateDisplayInfoLocked() {
// Note: The display manager caches display info objects on our behalf.
DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
diff --git a/core/jni/com_android_internal_os_KernelAllocationStats.cpp b/core/jni/com_android_internal_os_KernelAllocationStats.cpp
index 5b10497..35c9487 100644
--- a/core/jni/com_android_internal_os_KernelAllocationStats.cpp
+++ b/core/jni/com_android_internal_os_KernelAllocationStats.cpp
@@ -44,7 +44,7 @@
static jobjectArray KernelAllocationStats_getDmabufAllocations(JNIEnv *env, jobject) {
std::vector<DmaBuffer> buffers;
- if (!dmabufinfo::ReadDmaBufs(&buffers)) {
+ if (!dmabufinfo::ReadProcfsDmaBufs(&buffers)) {
return nullptr;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6dd7073..3274e85 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7429,6 +7429,14 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
<provider
android:name="com.android.server.textclassifier.IconsContentProvider"
android:authorities="com.android.textclassifier.icons"
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9b23a41..bddf452 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2035,6 +2035,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/SurfaceAnimator.java"
},
+ "-206549078": {
+ "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: config_perDisplayFocusEnabled",
+ "level": "INFO",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"-203358733": {
"message": "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
"level": "INFO",
@@ -2263,6 +2269,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "34682671": {
+ "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: FLAG_STEAL_TOP_FOCUS_DISABLED",
+ "level": "INFO",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"35398067": {
"message": "goodToGo(): onAnimationStart, transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index f32e0ee..1f693166 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -29,7 +29,8 @@
* {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
* where a {@link MeshSpecification} is required along with various attributes for
* detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
- * for the mesh.
+ * for the mesh. Once generated, a mesh object can be drawn through
+ * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)}
*
* @hide
*/
@@ -53,8 +54,12 @@
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
* @param mode {@link Mode} enum
- * @param vertexBuffer vertex buffer representing through {@link Buffer}.
- * @param vertexCount the number of vertices represented in the vertexBuffer.
+ * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
+ * for all attributes provided within the meshSpec for every vertex. That
+ * is, a vertex buffer should be (attributes size * number of vertices) in
+ * length to be valid. Note that currently implementation will have a CPU
+ * backed buffer generated.
+ * @param vertexCount the number of vertices represented in the vertexBuffer and mesh.
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
@@ -70,13 +75,20 @@
}
/**
- * Generates an indexed {@link Mesh} object.
+ * Generates a {@link Mesh} object.
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
* @param mode {@link Mode} enum
- * @param vertexBuffer vertex buffer representing through {@link Buffer}.
- * @param vertexCount the number of vertices represented in the vertexBuffer.
- * @param indexBuffer index buffer representing through {@link ShortBuffer}.
+ * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
+ * for all attributes provided within the meshSpec for every vertex. That
+ * is, a vertex buffer should be (attributes size * number of vertices) in
+ * length to be valid. Note that currently implementation will have a CPU
+ * backed buffer generated.
+ * @param vertexCount the number of vertices represented in the vertexBuffer and mesh.
+ * @param indexBuffer index buffer representing through {@link ShortBuffer}. Indices are
+ * required to be 16 bits, so ShortBuffer is necessary. Note that
+ * currently implementation will have a CPU
+ * backed buffer generated.
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
@@ -93,7 +105,10 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+ * does not have a uniform with that name or if the uniform is declared with a type other than
+ * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+ * thrown.
*
* @param uniformName name matching the color uniform declared in the shader program.
* @param color the provided sRGB color will be converted into the shader program's output
@@ -104,7 +119,10 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+ * does not have a uniform with that name or if the uniform is declared with a type other than
+ * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+ * thrown.
*
* @param uniformName name matching the color uniform declared in the shader program.
* @param color the provided sRGB color will be converted into the shader program's output
@@ -116,7 +134,10 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+ * does not have a uniform with that name or if the uniform is declared with a type other than
+ * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+ * thrown.
*
* @param uniformName name matching the color uniform declared in the shader program.
* @param color the provided sRGB color will be converted into the shader program's output
@@ -132,7 +153,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * float or float[1] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value float value corresponding to the float uniform with the given name.
@@ -142,7 +165,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * vec2 or float[2] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value1 first float value corresponding to the float uniform with the given name.
@@ -153,7 +178,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * vec3 or float[3] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value1 first float value corresponding to the float uniform with the given name.
@@ -166,7 +193,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * vec4 or float[4] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value1 first float value corresponding to the float uniform with the given name.
@@ -180,7 +209,10 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * float (for N=1), vecN, or float[N], where N is the length of the values param, then an
+ * IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param values float value corresponding to the vec4 float uniform with the given name.
@@ -210,7 +242,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than int
+ * or int[1] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value value corresponding to the int uniform with the given name.
@@ -220,7 +254,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than ivec2
+ * or int[2] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
@@ -231,7 +267,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than ivec3
+ * or int[3] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
@@ -243,7 +281,9 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than ivec4
+ * or int[4] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
@@ -256,7 +296,10 @@
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than an
+ * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an
+ * IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param values int values corresponding to the vec4 int uniform with the given name.
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 45c13af..dd8fb7a 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -25,7 +25,7 @@
* generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
* the mesh are supplied, including attributes, vertex stride, varyings, and
* vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an alpha type.
+ * {@link ColorSpace} as well as an {@link AlphaType}.
*
* Note that there are several limitations on various mesh specifications:
* 1. The max amount of attributes allowed is 8.
@@ -43,15 +43,30 @@
/**
* Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
- * to determine alpha type
+ * to determine alpha type. Describes how to interpret the alpha component of a pixel.
*/
@IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
public @interface AlphaType {
}
+ /**
+ * uninitialized.
+ */
public static final int UNKNOWN = 0;
+
+ /**
+ * Pixel is opaque.
+ */
public static final int OPAQUE = 1;
+
+ /**
+ * Pixel components are premultiplied by alpha.
+ */
public static final int PREMUL = 2;
+
+ /**
+ * Pixel components are independent of alpha.
+ */
public static final int UNPREMULT = 3;
/**
@@ -61,15 +76,41 @@
public @interface Type {
}
+ /**
+ * Represents one float. Its equivalent shader type is float.
+ */
public static final int FLOAT = 0;
+
+ /**
+ * Represents two floats. Its equivalent shader type is float2.
+ */
public static final int FLOAT2 = 1;
+
+ /**
+ * Represents three floats. Its equivalent shader type is float3.
+ */
public static final int FLOAT3 = 2;
+
+ /**
+ * Represents four floats. Its equivalent shader type is float4.
+ */
public static final int FLOAT4 = 3;
+
+ /**
+ * Represents four bytes. Its equivalent shader type is half4.
+ */
public static final int UBYTE4 = 4;
/**
* Data class to represent a single attribute in a shader. Note that type parameter must be
* one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+ *
+ * Note that offset is the offset in number of bytes. For example, if we had two attributes
+ *
+ * Float3 att1
+ * Float att2
+ *
+ * att1 would have an offset of 0, while att2 would have an offset of 12 bytes.
*/
public static class Attribute {
@Type
@@ -106,14 +147,19 @@
}
/**
- * Creates a {@link MeshSpecification} object.
+ * Creates a {@link MeshSpecification} object for use within {@link Mesh}.
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
* 8.
- * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param vertexStride length of vertex stride in bytes. This should be the size of a single
+ * vertex' attributes. Max of 1024 is accepted.
* @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
- * @param vertexShader vertex shader to be supplied to the mesh.
- * @param fragmentShader fragment shader to be suppied to the mesh.
+ * Note that `position` is provided by default, does not need to be
+ * provided in the list, and does not count towards
+ * the 6 varyings allowed.
+ * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
+ * varying is set within the shader to get proper results.
+ * @param fragmentShader fragment shader to be supplied to the mesh.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
public static MeshSpecification make(Attribute[] attributes, int vertexStride,
@@ -131,10 +177,14 @@
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
* 8.
- * @param vertexStride length of vertex stride. Max of 1024 is accepted.
- * @param varyings List of varyings represented by {@link Varying}. Can hold a max of
- * 6.
- * @param vertexShader vertex shader to be supplied to the mesh.
+ * @param vertexStride length of vertex stride in bytes. This should be the size of a single
+ * vertex' attributes. Max of 1024 is accepted.
+ * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
+ * Note that `position` is provided by default, does not need to be
+ * provided in the list, and does not count towards
+ * the 6 varyings allowed.
+ * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
+ * varying is set within the shader to get proper results.
* @param fragmentShader fragment shader to be supplied to the mesh.
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
@@ -154,10 +204,15 @@
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
* 8.
- * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param vertexStride length of vertex stride in bytes. This should be the size of a single
+ * vertex' attributes. Max of 1024 is accepted.
* @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
- * @param vertexShader vertex shader code to be supplied to the mesh.
- * @param fragmentShader fragment shader code to be suppied to the mesh.
+ * Note that `position` is provided by default, does not need to be
+ * provided in the list, and does not count towards
+ * the 6 varyings allowed.
+ * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
+ * varying is set within the shader to get proper results.
+ * @param fragmentShader fragment shader to be supplied to the mesh.
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @param alphaType Describes how to interpret the alpha component for a pixel. Must be
* one of {@link AlphaType} values.
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 59a0f7b..f64e5cc 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -32,6 +32,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -178,6 +180,11 @@
* Used to update the mute state of a player through its port id
*/
public static final int PLAYER_UPDATE_MUTED = 7;
+ /**
+ * @hide
+ * Used to update the spatialization status and format of a player through its port id
+ */
+ public static final int PLAYER_UPDATE_FORMAT = 8;
/** @hide */
@IntDef({
@@ -190,6 +197,7 @@
PLAYER_UPDATE_DEVICE_ID,
PLAYER_UPDATE_PORT_ID,
PLAYER_UPDATE_MUTED,
+ PLAYER_UPDATE_FORMAT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerState {}
@@ -206,6 +214,7 @@
case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID";
case PLAYER_UPDATE_PORT_ID: return "PLAYER_UPDATE_PORT_ID";
case PLAYER_UPDATE_MUTED: return "PLAYER_UPDATE_MUTED";
+ case PLAYER_UPDATE_FORMAT: return "PLAYER_UPDATE_FORMAT";
default:
return "invalid state " + state;
}
@@ -213,6 +222,27 @@
/**
* @hide
+ * Used to update the spatialization status of a player through its port ID. Must be kept in
+ * sync with frameworks/native/include/audiomanager/AudioManager.h
+ */
+ public static final String EXTRA_PLAYER_EVENT_SPATIALIZED =
+ "android.media.extra.PLAYER_EVENT_SPATIALIZED";
+ /**
+ * @hide
+ * Used to update the sample rate of a player through its port ID. Must be kept in sync with
+ * frameworks/native/include/audiomanager/AudioManager.h
+ */
+ public static final String EXTRA_PLAYER_EVENT_SAMPLE_RATE =
+ "android.media.extra.PLAYER_EVENT_SAMPLE_RATE";
+ /**
+ * @hide
+ * Used to update the channel mask of a player through its port ID. Must be kept in sync with
+ * frameworks/native/include/audiomanager/AudioManager.h
+ */
+ public static final String EXTRA_PLAYER_EVENT_CHANNEL_MASK =
+ "android.media.extra.PLAYER_EVENT_CHANNEL_MASK";
+ /**
+ * @hide
* Used to update the mute state of a player through its port ID. Must be kept in sync with
* frameworks/native/include/audiomanager/AudioManager.h
*/
@@ -284,10 +314,16 @@
private int mPlayerState;
private AudioAttributes mPlayerAttr; // never null
+ // lock for updateable properties
+ private final Object mUpdateablePropLock = new Object();
+
+ @GuardedBy("mUpdateablePropLock")
private int mDeviceId;
-
+ @GuardedBy("mUpdateablePropLock")
private int mSessionId;
-
+ @GuardedBy("mUpdateablePropLock")
+ private @NonNull FormatInfo mFormatInfo;
+ @GuardedBy("mUpdateablePropLock")
@PlayerMuteEvent private int mMutedState;
/**
@@ -320,6 +356,7 @@
mIPlayerShell = null;
}
mSessionId = pic.mSessionId;
+ mFormatInfo = FormatInfo.DEFAULT;
}
/**
@@ -333,13 +370,23 @@
}
}
+ // sets the fields that are updateable and require synchronization
+ private void setUpdateableFields(int deviceId, int sessionId, int mutedState, FormatInfo format)
+ {
+ synchronized (mUpdateablePropLock) {
+ mDeviceId = deviceId;
+ mSessionId = sessionId;
+ mMutedState = mutedState;
+ mFormatInfo = format;
+ }
+ }
// Note that this method is called server side, so no "privileged" information is ever sent
// to a client that is not supposed to have access to it.
/**
* @hide
* Creates a copy of the playback configuration that is stripped of any data enabling
* identification of which application it is associated with ("anonymized").
- * @param toSanitize
+ * @param in the instance to copy from
*/
public static AudioPlaybackConfiguration anonymizedCopy(AudioPlaybackConfiguration in) {
final AudioPlaybackConfiguration anonymCopy = new AudioPlaybackConfiguration(in.mPlayerIId);
@@ -357,14 +404,16 @@
builder.setUsage(in.mPlayerAttr.getUsage());
}
anonymCopy.mPlayerAttr = builder.build();
- anonymCopy.mDeviceId = in.mDeviceId;
// anonymized data
- anonymCopy.mMutedState = 0;
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
anonymCopy.mIPlayerShell = null;
- anonymCopy.mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
+ anonymCopy.setUpdateableFields(
+ /*deviceId*/ PLAYER_DEVICEID_INVALID,
+ /*sessionId*/ AudioSystem.AUDIO_SESSION_ALLOCATE,
+ /*mutedState*/ 0,
+ FormatInfo.DEFAULT);
return anonymCopy;
}
@@ -401,10 +450,14 @@
* @return the audio playback device or null if the device is not available at the time of query
*/
public @Nullable AudioDeviceInfo getAudioDeviceInfo() {
- if (mDeviceId == PLAYER_DEVICEID_INVALID) {
+ final int deviceId;
+ synchronized (mUpdateablePropLock) {
+ deviceId = mDeviceId;
+ }
+ if (deviceId == PLAYER_DEVICEID_INVALID) {
return null;
}
- return AudioManager.getDeviceForPortId(mDeviceId, AudioManager.GET_DEVICES_OUTPUTS);
+ return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS);
}
/**
@@ -415,7 +468,9 @@
*/
@SystemApi
public @IntRange(from = 0) int getSessionId() {
- return mSessionId;
+ synchronized (mUpdateablePropLock) {
+ return mSessionId;
+ }
}
/**
@@ -428,7 +483,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public boolean isMuted() {
- return mMutedState != 0;
+ synchronized (mUpdateablePropLock) {
+ return mMutedState != 0;
+ }
}
/**
@@ -440,7 +497,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@PlayerMuteEvent public int getMutedBy() {
- return mMutedState;
+ synchronized (mUpdateablePropLock) {
+ return mMutedState;
+ }
}
/**
@@ -500,6 +559,43 @@
/**
* @hide
+ * Return whether this player's output is spatialized
+ * @return true if spatialized, false if not or playback hasn't started
+ */
+ @SystemApi
+ public boolean isSpatialized() {
+ synchronized (mUpdateablePropLock) {
+ return mFormatInfo.mIsSpatialized;
+ }
+ }
+
+ /**
+ * @hide
+ * Return the sample rate in Hz of the content being played.
+ * @return the sample rate in Hertz, or 0 if playback hasn't started
+ */
+ @SystemApi
+ public @IntRange(from = 0) int getSampleRate() {
+ synchronized (mUpdateablePropLock) {
+ return mFormatInfo.mSampleRate;
+ }
+ }
+
+ /**
+ * @hide
+ * Return the player's channel mask
+ * @return the channel mask, or 0 if playback hasn't started. See {@link AudioFormat} and
+ * the definitions for the <code>CHANNEL_OUT_*</code> values used for the mask's bitfield
+ */
+ @SystemApi
+ public int getChannelMask() {
+ synchronized (mUpdateablePropLock) {
+ return (AudioFormat.convertNativeChannelMaskToOutMask(mFormatInfo.mNativeChannelMask));
+ }
+ }
+
+ /**
+ * @hide
* @return the IPlayer interface for the associated player
*/
IPlayer getIPlayer() {
@@ -527,9 +623,11 @@
* @param sessionId the audio session ID
*/
public boolean handleSessionIdEvent(int sessionId) {
- final boolean changed = sessionId != mSessionId;
- mSessionId = sessionId;
- return changed;
+ synchronized (mUpdateablePropLock) {
+ final boolean changed = sessionId != mSessionId;
+ mSessionId = sessionId;
+ return changed;
+ }
}
/**
@@ -539,9 +637,25 @@
* @return true if the state changed, false otherwise
*/
public boolean handleMutedEvent(@PlayerMuteEvent int mutedState) {
- final boolean changed = mMutedState != mutedState;
- mMutedState = mutedState;
- return changed;
+ synchronized (mUpdateablePropLock) {
+ final boolean changed = mMutedState != mutedState;
+ mMutedState = mutedState;
+ return changed;
+ }
+ }
+
+ /**
+ * @hide
+ * Handle a change of playback format
+ * @param fi the latest format information
+ * @return true if the format changed, false otherwise
+ */
+ public boolean handleFormatEvent(@NonNull FormatInfo fi) {
+ synchronized (mUpdateablePropLock) {
+ final boolean changed = !mFormatInfo.equals(fi);
+ mFormatInfo = fi;
+ return changed;
+ }
}
/**
@@ -558,7 +672,7 @@
*/
public boolean handleStateEvent(int event, int deviceId) {
boolean changed = false;
- synchronized (this) {
+ synchronized (mUpdateablePropLock) {
// Do not update if it is only device id update
if (event != PLAYER_UPDATE_DEVICE_ID) {
@@ -647,8 +761,10 @@
@Override
public int hashCode() {
- return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid, mClientPid,
- mSessionId);
+ synchronized (mUpdateablePropLock) {
+ return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid,
+ mClientPid, mSessionId);
+ }
}
@Override
@@ -658,20 +774,23 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mPlayerIId);
- dest.writeInt(mDeviceId);
- dest.writeInt(mMutedState);
- dest.writeInt(mPlayerType);
- dest.writeInt(mClientUid);
- dest.writeInt(mClientPid);
- dest.writeInt(mPlayerState);
- mPlayerAttr.writeToParcel(dest, 0);
- final IPlayerShell ips;
- synchronized (this) {
- ips = mIPlayerShell;
+ synchronized (mUpdateablePropLock) {
+ dest.writeInt(mPlayerIId);
+ dest.writeInt(mDeviceId);
+ dest.writeInt(mMutedState);
+ dest.writeInt(mPlayerType);
+ dest.writeInt(mClientUid);
+ dest.writeInt(mClientPid);
+ dest.writeInt(mPlayerState);
+ mPlayerAttr.writeToParcel(dest, 0);
+ final IPlayerShell ips;
+ synchronized (this) {
+ ips = mIPlayerShell;
+ }
+ dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
+ dest.writeInt(mSessionId);
+ mFormatInfo.writeToParcel(dest, 0);
}
- dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
- dest.writeInt(mSessionId);
}
private AudioPlaybackConfiguration(Parcel in) {
@@ -686,6 +805,7 @@
final IPlayer p = IPlayer.Stub.asInterface(in.readStrongBinder());
mIPlayerShell = (p == null) ? null : new IPlayerShell(null, p);
mSessionId = in.readInt();
+ mFormatInfo = FormatInfo.CREATOR.createFromParcel(in);
}
@Override
@@ -707,35 +827,39 @@
@Override
public String toString() {
StringBuilder apcToString = new StringBuilder();
- apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append(
- " deviceId:").append(mDeviceId).append(" type:").append(
- toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append(mClientUid).append(
- "/").append(mClientPid).append(" state:").append(
- toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
- " sessionId:").append(mSessionId).append(" mutedState:");
- if (mMutedState == 0) {
- apcToString.append("none ");
- } else {
- if ((mMutedState & MUTED_BY_MASTER) != 0) {
- apcToString.append("master ");
+ synchronized (mUpdateablePropLock) {
+ apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append(
+ " deviceId:").append(mDeviceId).append(" type:").append(
+ toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append(
+ mClientUid).append(
+ "/").append(mClientPid).append(" state:").append(
+ toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(
+ mPlayerAttr).append(
+ " sessionId:").append(mSessionId).append(" mutedState:");
+ if (mMutedState == 0) {
+ apcToString.append("none ");
+ } else {
+ if ((mMutedState & MUTED_BY_MASTER) != 0) {
+ apcToString.append("master ");
+ }
+ if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
+ apcToString.append("streamVolume ");
+ }
+ if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
+ apcToString.append("streamMute ");
+ }
+ if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
+ apcToString.append("appOps ");
+ }
+ if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
+ apcToString.append("clientVolume ");
+ }
+ if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
+ apcToString.append("volumeShaper ");
+ }
}
- if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
- apcToString.append("streamVolume ");
- }
- if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
- apcToString.append("streamMute ");
- }
- if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
- apcToString.append("appOps ");
- }
- if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
- apcToString.append("clientVolume ");
- }
- if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
- apcToString.append("volumeShaper ");
- }
+ apcToString.append(" ").append(mFormatInfo);
}
-
return apcToString.toString();
}
@@ -788,6 +912,79 @@
}
//=====================================================================
+
+ /**
+ * @hide
+ * Class to store sample rate, channel mask, and spatialization status
+ */
+ public static final class FormatInfo implements Parcelable {
+ static final FormatInfo DEFAULT = new FormatInfo(
+ /*spatialized*/ false, /*channel mask*/ 0, /*sample rate*/ 0);
+ final boolean mIsSpatialized;
+ final int mNativeChannelMask;
+ final int mSampleRate;
+
+ public FormatInfo(boolean isSpatialized, int nativeChannelMask, int sampleRate) {
+ mIsSpatialized = isSpatialized;
+ mNativeChannelMask = nativeChannelMask;
+ mSampleRate = sampleRate;
+ }
+
+ @Override
+ public String toString() {
+ return "FormatInfo{"
+ + "isSpatialized=" + mIsSpatialized
+ + ", channelMask=0x" + Integer.toHexString(mNativeChannelMask)
+ + ", sampleRate=" + mSampleRate
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FormatInfo)) return false;
+ FormatInfo that = (FormatInfo) o;
+ return mIsSpatialized == that.mIsSpatialized
+ && mNativeChannelMask == that.mNativeChannelMask
+ && mSampleRate == that.mSampleRate;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsSpatialized, mNativeChannelMask, mSampleRate);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mIsSpatialized);
+ dest.writeInt(mNativeChannelMask);
+ dest.writeInt(mSampleRate);
+ }
+
+ private FormatInfo(Parcel in) {
+ this(
+ in.readBoolean(), // isSpatialized
+ in.readInt(), // channelMask
+ in.readInt() // sampleRate
+ );
+ }
+
+ public static final @NonNull Parcelable.Creator<FormatInfo> CREATOR =
+ new Parcelable.Creator<FormatInfo>() {
+ public FormatInfo createFromParcel(Parcel p) {
+ return new FormatInfo(p);
+ }
+ public FormatInfo[] newArray(int size) {
+ return new FormatInfo[size];
+ }
+ };
+ }
+ //=====================================================================
// Utilities
/** @hide */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index f55fb97..9058510 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -169,11 +169,9 @@
setFloatUniform("in_progress", value)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
- setFloatUniform(
- "in_size",
- /* width= */ maxSize.x * curvedProg,
- /* height= */ maxSize.y * curvedProg
- )
+ currentWidth = maxSize.x * curvedProg
+ currentHeight = maxSize.y * curvedProg
+ setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values.
setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
@@ -237,4 +235,10 @@
* False for a ring effect.
*/
var rippleFill: Boolean = false
+
+ var currentWidth: Float = 0f
+ private set
+
+ var currentHeight: Float = 0f
+ private set
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index ae28a8b..b37c734 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -36,7 +36,7 @@
*/
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private lateinit var rippleShader: RippleShader
+ protected lateinit var rippleShader: RippleShader
lateinit var rippleShape: RippleShape
private set
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index efd683f..e4e0bd4 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -190,7 +190,6 @@
app:layout_constraintBottom_toBottomOf="parent"
android:contentDescription="@string/screenshot_dismiss_work_profile">
<ImageView
- android:id="@+id/screenshot_dismiss_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/overlay_dismiss_button_margin"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6a9149e..ea1095d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1286,6 +1286,9 @@
translate into their final position. -->
<dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
+ <!-- DREAMING -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
+ <dimen name="dreaming_to_lockscreen_transition_lockscreen_translation_y">40dp</dimen>
+
<!-- The amount of vertical offset for the keyguard during the full shade transition. -->
<dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 8b9823b..b8e196f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static java.util.Collections.emptySet;
+
import android.content.Context;
import android.os.Trace;
import android.util.AttributeSet;
@@ -88,8 +90,9 @@
}
/** Sets a translationY value on every child view except for the media view. */
- public void setChildrenTranslationYExcludingMediaView(float translationY) {
- setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
+ public void setChildrenTranslationY(float translationY, boolean excludeMedia) {
+ setChildrenTranslationYExcluding(translationY,
+ excludeMedia ? Set.of(mMediaHostContainer) : emptySet());
}
/** Sets a translationY value on every view except for the views in the provided set. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 7849747..aec3063 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,8 @@
import android.util.Slog;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -59,6 +61,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController,
DozeParameters dozeParameters,
+ FeatureFlags featureFlags,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
@@ -67,6 +70,8 @@
mConfigurationController = configurationController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true);
+ mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled(
+ featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION));
}
@Override
@@ -115,8 +120,8 @@
/**
* Sets a translationY on the views on the keyguard, except on the media view.
*/
- public void setTranslationYExcludingMedia(float translationY) {
- mView.setChildrenTranslationYExcludingMediaView(translationY);
+ public void setTranslationY(float translationY, boolean excludeMedia) {
+ mView.setChildrenTranslationY(translationY, excludeMedia);
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 498304b..bde0692 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -44,6 +44,7 @@
private boolean mAnimateYPos;
private boolean mKeyguardViewVisibilityAnimating;
private boolean mLastOccludedState = false;
+ private boolean mIsUnoccludeTransitionFlagEnabled = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
public KeyguardVisibilityHelper(View view,
@@ -62,6 +63,10 @@
return mKeyguardViewVisibilityAnimating;
}
+ public void setOcclusionTransitionFlagEnabled(boolean enabled) {
+ mIsUnoccludeTransitionFlagEnabled = enabled;
+ }
+
/**
* Set the visibility of a keyguard view based on some new state.
*/
@@ -129,7 +134,7 @@
// since it may need to be cancelled due to keyguard lifecycle events.
mScreenOffAnimationController.animateInKeyguard(
mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
- } else if (mLastOccludedState && !isOccluded) {
+ } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) {
// An activity was displayed over the lock screen, and has now gone away
mView.setVisibility(View.VISIBLE);
mView.setAlpha(0f);
@@ -142,7 +147,9 @@
.start();
} else {
mView.setVisibility(View.VISIBLE);
- mView.setAlpha(1f);
+ if (!mIsUnoccludeTransitionFlagEnabled) {
+ mView.setAlpha(1f);
+ }
}
} else {
mView.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 58dfe30..4268d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -188,10 +188,6 @@
// TODO(b/260619425): Tracking Bug
@JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer")
- // TODO(b/262780002): Tracking Bug
- @JvmField
- val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
-
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
@JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -200,6 +196,15 @@
// TODO(b/244313043): Tracking bug
@JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
+ // TODO(b/262780002): Tracking Bug
+ @JvmField
+ val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
+
+ /** A different path for unocclusion transitions back to keyguard */
+ // TODO(b/262859270): Tracking Bug
+ @JvmField
+ val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = false)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -315,6 +320,10 @@
// TODO(b/261734857): Tracking Bug
@JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
+ // TODO(b/263272731): Tracking Bug
+ val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
+ unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d597455..3beec36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -33,7 +33,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.TO_LOCKSCREEN_DURATION_MS;
+import static com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_DURATION_MS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1233,7 +1233,7 @@
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
- mDreamCloseAnimationDuration = (int) TO_LOCKSCREEN_DURATION_MS;
+ mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
index 4d60579..188930c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
@@ -179,6 +179,5 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 1183.milliseconds
- @JvmField val TO_LOCKSCREEN_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 9b36e8c..402fac1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -46,12 +47,11 @@
/** Dream overlay views alpha - fade out */
val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
- /** Dream background alpha - fade out */
- val dreamBackgroundAlpha: Flow<Float> = flowForAnimation(DREAM_BACKGROUND_ALPHA).map { 1f - it }
-
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { it * translatePx }
+ return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+ -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
+ }
}
/** Lockscreen views alpha */
@@ -68,10 +68,12 @@
companion object {
/* Length of time before ending the dream activity, in order to start unoccluding */
val DREAM_ANIMATION_DURATION = 250.milliseconds
+ @JvmField
+ val LOCKSCREEN_ANIMATION_DURATION_MS =
+ (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
- val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
+ val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
- val DREAM_BACKGROUND_ALPHA = AnimationParams(duration = 250.milliseconds)
val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
val LOCKSCREEN_ALPHA =
AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 03bc935..8a565fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -26,4 +26,8 @@
class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** */
fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
+
+ /** Check whether the flag for the receiver success state is enabled. */
+ fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
+ featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 358534c..889147b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -114,6 +114,9 @@
}
}
+ private var maxRippleWidth: Float = 0f
+ private var maxRippleHeight: Float = 0f
+
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -216,7 +219,7 @@
expandRipple(view.requireViewById(R.id.ripple))
}
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(getTranslationAmount().toFloat())
@@ -226,7 +229,14 @@
.alpha(0f)
.setDuration(ICON_ALPHA_ANIM_DURATION)
.start()
- (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
+
+ val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+ if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
+ mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
+ expandRippleToFull(rippleView, onAnimationEnd)
+ } else {
+ rippleView.collapseRipple(onAnimationEnd)
+ }
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -271,12 +281,19 @@
})
}
- private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+ private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
val windowBounds = windowManager.currentWindowMetrics.bounds
val height = windowBounds.height().toFloat()
val width = windowBounds.width().toFloat()
- rippleView.setMaxSize(width / 2f, height / 2f)
+ if (isFullScreen) {
+ maxRippleHeight = height * 2f
+ maxRippleWidth = width * 2f
+ } else {
+ maxRippleHeight = height / 2f
+ maxRippleWidth = width / 2f
+ }
+ rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
// Center the ripple on the bottom of the screen in the middle.
rippleView.setCenter(width * 0.5f, height)
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
@@ -286,6 +303,11 @@
private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon)
}
+
+ private fun expandRippleToFull(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
+ layoutRipple(rippleView, true)
+ rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+ }
}
val ICON_TRANSLATION_ANIM_DURATION = 30.frames
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 6e9fc5c..87b2528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -22,6 +22,7 @@
import android.util.AttributeSet
import com.android.systemui.surfaceeffects.ripple.RippleShader
import com.android.systemui.surfaceeffects.ripple.RippleView
+import kotlin.math.pow
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
@@ -59,4 +60,44 @@
})
animator.reverse()
}
+
+ // Expands the ripple to cover full screen.
+ fun expandToFull(newHeight: Float, onAnimationEnd: Runnable? = null) {
+ if (!isStarted) {
+ return
+ }
+ // Reset all listeners to animator.
+ animator.removeAllListeners()
+ animator.removeAllUpdateListeners()
+
+ // Only show the outline as ripple expands and disappears when animation ends.
+ setRippleFill(false)
+
+ val startingPercentage = calculateStartingPercentage(newHeight)
+ animator.addUpdateListener { updateListener ->
+ val now = updateListener.currentPlayTime
+ val progress = updateListener.animatedValue as Float
+ rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
+ rippleShader.distortionStrength = 1 - rippleShader.progress
+ rippleShader.pixelDensity = 1 - rippleShader.progress
+ rippleShader.time = now.toFloat()
+ invalidate()
+ }
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ animation?.let { visibility = GONE }
+ onAnimationEnd?.run()
+ isStarted = false
+ }
+ })
+ animator.start()
+ }
+
+ // Calculates the actual starting percentage according to ripple shader progress set method.
+ // Check calculations in [RippleShader.progress]
+ fun calculateStartingPercentage(newHeight: Float): Float {
+ val ratio = rippleShader.currentHeight / newHeight
+ val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
+ return 1 - remainingPercentage
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1709043..31543df 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -44,6 +44,7 @@
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import static java.lang.Float.isNaN;
@@ -139,6 +140,10 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -679,6 +684,12 @@
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
+ private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private boolean mIsDreamToLockscreenTransitionRunning = false;
+ private int mDreamingToLockscreenTransitionTranslationY;
+ private boolean mUnocclusionTransitionFlagEnabled = false;
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@@ -694,6 +705,12 @@
}
};
+ private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
+ (TransitionStep step) -> {
+ mIsDreamToLockscreenTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
+
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@Main Handler handler,
@@ -763,6 +780,8 @@
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
+ DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -778,6 +797,8 @@
mShadeLog = shadeLogger;
mShadeHeightLogger = shadeHeightLogger;
mGutsManager = gutsManager;
+ mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -923,6 +944,8 @@
mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
+ mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
+
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
@@ -1076,6 +1099,18 @@
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
+
+ if (mUnocclusionTransitionFlagEnabled) {
+ collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
+ dreamingToLockscreenTransitionAlpha(mNotificationStackScrollLayoutController));
+
+ collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
+ mDreamingToLockscreenTransitionTranslationY),
+ dreamingToLockscreenTransitionY(mNotificationStackScrollLayoutController));
+
+ collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
+ mDreamingToLockscreenTransition);
+ }
}
@VisibleForTesting
@@ -1110,6 +1145,8 @@
mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize(
R.dimen.split_shade_scrim_transition_distance);
+ mDreamingToLockscreenTransitionTranslationY = mResources.getDimensionPixelSize(
+ R.dimen.dreaming_to_lockscreen_transition_lockscreen_translation_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1773,10 +1810,14 @@
}
private void updateClock() {
+ if (mIsDreamToLockscreenTransitionRunning) {
+ return;
+ }
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
mKeyguardStatusViewController.setAlpha(alpha);
mKeyguardStatusViewController
- .setTranslationYExcludingMedia(mKeyguardOnlyTransitionTranslationY);
+ .setTranslationY(mKeyguardOnlyTransitionTranslationY, /* excludeMedia= */true);
+
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setAlpha(alpha);
}
@@ -2660,7 +2701,9 @@
} else if (statusBarState == KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
mKeyguardBottomArea.setVisibility(View.VISIBLE);
- mKeyguardBottomArea.setAlpha(1f);
+ if (!mIsDreamToLockscreenTransitionRunning) {
+ mKeyguardBottomArea.setAlpha(1f);
+ }
} else {
mKeyguardBottomArea.setVisibility(View.GONE);
}
@@ -3527,6 +3570,9 @@
}
private void updateNotificationTranslucency() {
+ if (mIsDreamToLockscreenTransitionRunning) {
+ return;
+ }
float alpha = 1f;
if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
&& !mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -3582,6 +3628,9 @@
}
private void updateKeyguardBottomAreaAlpha() {
+ if (mIsDreamToLockscreenTransitionRunning) {
+ return;
+ }
// There are two possible panel expansion behaviors:
// • User dragging up to unlock: we want to fade out as quick as possible
// (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
@@ -3614,7 +3663,9 @@
}
private void onExpandingFinished() {
- mScrimController.onExpandingFinished();
+ if (!mUnocclusionTransitionFlagEnabled) {
+ mScrimController.onExpandingFinished();
+ }
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
@@ -5809,6 +5860,32 @@
mCurrentPanelState = state;
}
+ private Consumer<Float> dreamingToLockscreenTransitionAlpha(
+ NotificationStackScrollLayoutController stackScroller) {
+ return (Float alpha) -> {
+ mKeyguardStatusViewController.setAlpha(alpha);
+ stackScroller.setAlpha(alpha);
+
+ mKeyguardBottomAreaInteractor.setAlpha(alpha);
+ mLockIconViewController.setAlpha(alpha);
+
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setAlpha(alpha);
+ }
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setAlpha(alpha);
+ }
+ };
+ }
+
+ private Consumer<Float> dreamingToLockscreenTransitionY(
+ NotificationStackScrollLayoutController stackScroller) {
+ return (Float translationY) -> {
+ mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false);
+ stackScroller.setTranslationY(translationY);
+ };
+ }
+
@VisibleForTesting
StatusBarStateController getStatusBarStateController() {
return mStatusBarStateController;
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 4bcc0b6..c2c38a7 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
@@ -916,6 +916,11 @@
return mView.getTranslationX();
}
+ /** Set view y-translation */
+ public void setTranslationY(float translationY) {
+ mView.setTranslationY(translationY);
+ }
+
public int indexOfChild(View view) {
return mView.indexOfChild(view);
}
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 d500f99..ee8b861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -232,7 +232,6 @@
private boolean mExpansionAffectsAlpha = true;
private boolean mAnimateChange;
private boolean mUpdatePending;
- private boolean mTracking;
private long mAnimationDuration = -1;
private long mAnimationDelay;
private Animator.AnimatorListener mAnimatorListener;
@@ -526,7 +525,6 @@
}
public void onTrackingStarted() {
- mTracking = true;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
mAnimatingPanelExpansionOnUnlock = false;
@@ -534,7 +532,6 @@
}
public void onExpandingFinished() {
- mTracking = false;
setUnocclusionAnimationRunning(false);
}
@@ -1450,8 +1447,6 @@
pw.print(" expansionProgress=");
pw.println(mTransitionToLockScreenFullShadeNotificationsProgress);
- pw.print(" mTracking=");
- pw.println(mTracking);
pw.print(" mDefaultScrimAlpha=");
pw.println(mDefaultScrimAlpha);
pw.print(" mPanelExpansionFraction=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3e3ce77..61ddf8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -137,7 +137,7 @@
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final BouncerView mPrimaryBouncerView;
- private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
+ private final Lazy<ShadeController> mShadeController;
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
@@ -255,6 +255,7 @@
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
private boolean mIsModernAlternateBouncerEnabled;
+ private boolean mIsUnoccludeTransitionFlagEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -335,6 +336,7 @@
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
}
@Override
@@ -891,8 +893,10 @@
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
- if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
- mCentralSurfaces.animateKeyguardUnoccluding();
+ if (!mIsUnoccludeTransitionFlagEnabled) {
+ if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
+ mCentralSurfaces.animateKeyguardUnoccluding();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 532fbaa..ad48e21 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -331,7 +331,7 @@
return
}
- removeViewFromWindow(displayInfo)
+ removeViewFromWindow(displayInfo, removalReason)
// Prune anything that's already timed out before determining if we should re-display a
// different chipbar.
@@ -358,14 +358,14 @@
removeViewFromWindow(displayInfo)
}
- private fun removeViewFromWindow(displayInfo: DisplayInfo) {
+ private fun removeViewFromWindow(displayInfo: DisplayInfo, removalReason: String? = null) {
val view = displayInfo.view
if (view == null) {
logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
return
}
displayInfo.view = null // Need other places??
- animateViewOut(view) {
+ animateViewOut(view, removalReason) {
windowManager.removeView(view)
displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
}
@@ -428,7 +428,11 @@
*
* @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
*/
- internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ internal open fun animateViewOut(
+ view: ViewGroup,
+ removalReason: String? = null,
+ onAnimationEnd: Runnable
+ ) {
onAnimationEnd.run()
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index fd2c705..52980c3 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -211,7 +211,7 @@
)
}
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
ViewHierarchyAnimator.animateRemoval(
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 74295f0..45cb11e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -56,6 +56,7 @@
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.user.utils.MultiUserActionsEvent
+import com.android.systemui.user.utils.MultiUserActionsEventHelper
import com.android.systemui.util.kotlin.pairwise
import java.io.PrintWriter
import javax.inject.Inject
@@ -372,6 +373,9 @@
if (LegacyUserDataHelper.isUser(record)) {
// It's safe to use checkNotNull around record.info because isUser only returns true
// if record.info is not null.
+ uiEventLogger.log(
+ MultiUserActionsEventHelper.userSwitchMetric(checkNotNull(record.info))
+ )
selectUser(checkNotNull(record.info).id, dialogShower)
} else {
executeAction(LegacyUserDataHelper.toUserActionModel(record), dialogShower)
diff --git a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
index bfedac9..ec79b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
@@ -23,7 +23,13 @@
@UiEvent(doc = "Add User tap from User Switcher.") CREATE_USER_FROM_USER_SWITCHER(1257),
@UiEvent(doc = "Add Guest tap from User Switcher.") CREATE_GUEST_FROM_USER_SWITCHER(1258),
@UiEvent(doc = "Add Restricted User tap from User Switcher.")
- CREATE_RESTRICTED_USER_FROM_USER_SWITCHER(1259);
+ CREATE_RESTRICTED_USER_FROM_USER_SWITCHER(1259),
+ @UiEvent(doc = "Switch to User tap from User Switcher.")
+ SWITCH_TO_USER_FROM_USER_SWITCHER(1266),
+ @UiEvent(doc = "Switch to Guest User tap from User Switcher.")
+ SWITCH_TO_GUEST_FROM_USER_SWITCHER(1267),
+ @UiEvent(doc = "Switch to Restricted User tap from User Switcher.")
+ SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER(1268);
override fun getId(): Int {
return value
diff --git a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEventHelper.kt b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEventHelper.kt
new file mode 100644
index 0000000..d12a788
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEventHelper.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.user.utils
+
+import android.content.pm.UserInfo
+
+class MultiUserActionsEventHelper {
+ /** Return MultiUserActionsEvent based on UserInfo. */
+ companion object {
+ fun userSwitchMetric(userInfo: UserInfo): MultiUserActionsEvent {
+ if (userInfo.isGuest) return MultiUserActionsEvent.SWITCH_TO_GUEST_FROM_USER_SWITCHER
+ if (userInfo.isRestricted)
+ return MultiUserActionsEvent.SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER
+ return MultiUserActionsEvent.SWITCH_TO_USER_FROM_USER_SWITCHER
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index c94c97c..be4bbdf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -25,6 +25,7 @@
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -59,6 +60,8 @@
@Mock
DozeParameters mDozeParameters;
@Mock
+ FeatureFlags mFeatureFlags;
+ @Mock
ScreenOffAnimationController mScreenOffAnimationController;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -77,6 +80,7 @@
mKeyguardUpdateMonitor,
mConfigurationController,
mDozeParameters,
+ mFeatureFlags,
mScreenOffAnimationController);
}
@@ -96,9 +100,9 @@
public void setTranslationYExcludingMedia_forwardsCallToView() {
float translationY = 123f;
- mController.setTranslationYExcludingMedia(translationY);
+ mController.setTranslationY(translationY, /* excludeMedia= */true);
- verify(mKeyguardStatusView).setChildrenTranslationYExcludingMediaView(translationY);
+ verify(mKeyguardStatusView).setChildrenTranslationY(translationY, /* excludeMedia= */true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index ce44f4d..508aea5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -37,19 +37,23 @@
fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
val translationY = 1234f
- keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+ keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */true)
assertThat(mediaView.translationY).isEqualTo(0)
- }
-
- @Test
- fun setChildrenTranslationYExcludingMediaView_childrenAreTranslated() {
- val translationY = 1234f
-
- keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
childrenExcludingMedia.forEach {
assertThat(it.translationY).isEqualTo(translationY)
}
}
-}
\ No newline at end of file
+
+ @Test
+ fun setChildrenTranslationYIncludeMediaView() {
+ val translationY = 1234f
+
+ keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */false)
+
+ statusViewContainer.children.forEach {
+ assertThat(it.translationY).isEqualTo(translationY)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 81950f1..c54e456 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -19,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -28,6 +29,8 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -65,10 +68,9 @@
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of
- // the overall animation time
- assertThat(values.size).isEqualTo(2)
+ // Only 3 values should be present, since the dream overlay runs for a small fraction
+ // of the overall animation time
+ assertThat(values.size).isEqualTo(3)
assertThat(values[0])
.isEqualTo(
EMPHASIZED_ACCELERATE.getInterpolation(
@@ -81,6 +83,12 @@
animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
) * pixels
)
+ assertThat(values[2])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
+ ) * pixels
+ )
job.cancel()
}
@@ -98,8 +106,7 @@
repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction
- // of
- // the overall animation time
+ // of the overall animation time
assertThat(values.size).isEqualTo(2)
assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
@@ -107,6 +114,77 @@
job.cancel()
}
+ @Test
+ fun lockscreenFadeIn() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ // Should start running here...
+ repository.sendTransitionStep(step(0.2f))
+ repository.sendTransitionStep(step(0.3f))
+ // ...up to here
+ repository.sendTransitionStep(step(1f))
+
+ // Only two values should be present, since the dream overlay runs for a small fraction
+ // of the overall animation time
+ assertThat(values.size).isEqualTo(2)
+ assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
+ assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+
+ job.cancel()
+ }
+
+ @Test
+ fun lockscreenTranslationY() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ assertThat(values[0])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(0f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[1])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[2])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[3])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(1f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+
+ job.cancel()
+ }
+
private fun animValue(stepValue: Float, params: AnimationParams): Float {
val totalDuration = TO_LOCKSCREEN_DURATION
val startValue = (params.startTime / totalDuration).toFloat()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index 07a3109..9c4e849 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -66,7 +66,7 @@
wakeLockBuilder,
systemClock,
) {
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
onAnimationEnd.run()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 03ba3d3..cefc742 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -98,6 +98,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
+ whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 17eb6e2..bbd62c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -147,8 +147,8 @@
setUserProfiles(0);
setShowUserVisibleJobs(true);
- UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
- UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+ UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", null, 0);
+ UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", null, 1);
Assert.assertEquals(0, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
Assert.assertEquals(1, mFmc.getNumRunningPackages());
@@ -167,8 +167,8 @@
Binder b1 = new Binder();
Binder b2 = new Binder();
- UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
- UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", 1);
+ UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", null, 0);
+ UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", null, 1);
Assert.assertEquals(0, mFmc.getNumRunningPackages());
mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
Assert.assertEquals(1, mFmc.getNumRunningPackages());
@@ -359,8 +359,8 @@
// pkg1 has only job
// pkg2 has both job and fgs
// pkg3 has only fgs
- UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
- UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+ UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", null, 0);
+ UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", null, 1);
Binder b2 = new Binder();
Binder b3 = new Binder();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 4843c76..0586a0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -105,6 +105,8 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -288,6 +290,8 @@
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+ @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private MotionEvent mDownMotionEvent;
@Captor
private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
@@ -505,6 +509,8 @@
mKeyguardBottomAreaViewModel,
mKeyguardBottomAreaInteractor,
mAlternateBouncerInteractor,
+ mDreamingToLockscreenTransitionViewModel,
+ mKeyguardTransitionInteractor,
mDumpManager);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index b9a5bd7..4ef4e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -64,7 +64,7 @@
wakeLockBuilder,
systemClock,
) {
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
onAnimationEnd.run()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index ffa4e2d..9534575 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -175,6 +175,8 @@
underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+ verify(uiEventLogger, times(1))
+ .log(MultiUserActionsEvent.SWITCH_TO_USER_FROM_USER_SWITCHER)
verify(dialogShower).dismiss()
verify(activityManager).switchUser(userInfos[1].id)
Unit
@@ -190,6 +192,33 @@
underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+ verify(uiEventLogger, times(1))
+ .log(MultiUserActionsEvent.SWITCH_TO_GUEST_FROM_USER_SWITCHER)
+ verify(activityManager).switchUser(userInfos.last().id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - switch to restricted user`() =
+ runBlocking(IMMEDIATE) {
+ var userInfos = createUserInfos(count = 2, includeGuest = false).toMutableList()
+ userInfos.add(
+ UserInfo(
+ 60,
+ "Restricted user",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_RESTRICTED,
+ )
+ )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+ verify(uiEventLogger, times(1))
+ .log(MultiUserActionsEvent.SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER)
verify(activityManager).switchUser(userInfos.last().id)
Unit
}
@@ -206,6 +235,8 @@
underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+ verify(uiEventLogger, times(1))
+ .log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
verify(dialogShower).dismiss()
verify(manager).createGuest(any())
Unit
@@ -221,6 +252,8 @@
underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+ verify(uiEventLogger, times(1))
+ .log(MultiUserActionsEvent.CREATE_RESTRICTED_USER_FROM_USER_SWITCHER)
verify(dialogShower, never()).dismiss()
verify(activityStarter).startActivity(any(), anyBoolean())
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
new file mode 100644
index 0000000..7ea1e6c
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.datatransfer.contextsync;
+
+import android.telecom.Call;
+import android.telecom.InCallService;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+/** In-call service to sync call metadata across a user's devices. */
+public class CallMetadataSyncInCallService extends InCallService {
+
+ @VisibleForTesting
+ final Set<CrossDeviceCall> mCurrentCalls = new HashSet<>();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mCurrentCalls.addAll(getCalls().stream().map(CrossDeviceCall::new).toList());
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ onCallAdded(new CrossDeviceCall(call));
+ }
+
+ @VisibleForTesting
+ void onCallAdded(CrossDeviceCall call) {
+ mCurrentCalls.add(call);
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ mCurrentCalls.removeIf(crossDeviceCall -> crossDeviceCall.getCall().equals(call));
+ }
+
+ /** Data holder for a telecom call and additional metadata. */
+ public static final class CrossDeviceCall {
+ private static final AtomicLong sNextId = new AtomicLong(1);
+
+ private final Call mCall;
+ private final long mId;
+
+ public CrossDeviceCall(Call call) {
+ mCall = call;
+ mId = sNextId.getAndIncrement();
+ }
+
+ public Call getCall() {
+ return mCall;
+ }
+
+ public long getId() {
+ return mId;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 59db686..f149fda 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -45,6 +45,7 @@
import android.util.Slog;
import com.android.internal.telephony.IMms;
+import com.android.internal.telephony.TelephonyPermissions;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -337,6 +338,14 @@
throws RemoteException {
Slog.d(TAG, "sendMessage() by " + callingPkg);
mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
+
+ // Check if user is associated with the subscription
+ if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
+ Binder.getCallingUserHandle())) {
+ // TODO(b/258629881): Display error dialog.
+ return;
+ }
+
if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
callingPkg, attributionTag, null) != AppOpsManager.MODE_ALLOWED) {
Slog.e(TAG, callingPkg + " is not allowed to call sendMessage()");
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index f35931ca..99d6228 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -35,6 +35,7 @@
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.AudioPlaybackConfiguration.FormatInfo;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
import android.media.AudioSystem;
import android.media.IPlaybackConfigDispatcher;
@@ -75,7 +76,7 @@
public final class PlaybackActivityMonitor
implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
- public static final String TAG = "AudioService.PlaybackActivityMonitor";
+ public static final String TAG = "AS.PlaybackActivityMon";
/*package*/ static final boolean DEBUG = false;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
@@ -343,7 +344,7 @@
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_L_UPDATE_PORT_EVENT, eventValue, piid));
+ mEventHandler.obtainMessage(MSG_II_UPDATE_PORT_EVENT, eventValue, piid));
return;
} else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
for (Integer uidInteger: mBannedUids) {
@@ -399,7 +400,7 @@
}
if (DEBUG) {
- Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
+ Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
}
@@ -427,7 +428,12 @@
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) {
mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_L_UPDATE_PLAYER_MUTED_EVENT, piid,
+ mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid,
+ portId,
+ extras));
+ } else if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) {
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_FORMAT, piid,
portId,
extras));
}
@@ -457,7 +463,7 @@
// remove all port ids mapped to the released player
mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
+ mEventHandler.obtainMessage(MSG_I_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
if (change && mDoNotLogPiidList.contains(piid)) {
// do not dispatch a change for a "do not log" player
@@ -1352,6 +1358,21 @@
}
}
+ private static final class PlayerFormatEvent extends EventLogger.Event {
+ private final int mPlayerIId;
+ private final AudioPlaybackConfiguration.FormatInfo mFormat;
+
+ PlayerFormatEvent(int piid, AudioPlaybackConfiguration.FormatInfo format) {
+ mPlayerIId = piid;
+ mFormat = format;
+ }
+
+ @Override
+ public String eventToString() {
+ return new String("player piid:" + mPlayerIId + " format update:" + mFormat);
+ }
+ }
+
static final EventLogger
sEventLogger = new EventLogger(100,
"playback activity as reported through PlayerBase");
@@ -1478,7 +1499,7 @@
* msg.arg1: port id
* msg.arg2: piid
*/
- private static final int MSG_L_UPDATE_PORT_EVENT = 2;
+ private static final int MSG_II_UPDATE_PORT_EVENT = 2;
/**
* event for player getting muted
@@ -1488,14 +1509,24 @@
* msg.obj: extras describing the mute reason
* type: PersistableBundle
*/
- private static final int MSG_L_UPDATE_PLAYER_MUTED_EVENT = 3;
+ private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 3;
/**
* clear all ports assigned to a given piid
* args:
* msg.arg1: the piid
*/
- private static final int MSG_L_CLEAR_PORTS_FOR_PIID = 4;
+ private static final int MSG_I_CLEAR_PORTS_FOR_PIID = 4;
+
+ /**
+ * event for player reporting playback format and spatialization status
+ * args:
+ * msg.arg1: piid
+ * msg.arg2: port id
+ * msg.obj: extras describing the sample rate, channel mask, spatialized
+ * type: PersistableBundle
+ */
+ private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 5;
private void initEventHandler() {
mEventThread = new HandlerThread(TAG);
@@ -1513,12 +1544,13 @@
}
mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
break;
- case MSG_L_UPDATE_PORT_EVENT:
+
+ case MSG_II_UPDATE_PORT_EVENT:
synchronized (mPlayerLock) {
mPortIdToPiid.put(/*portId*/msg.arg1, /*piid*/msg.arg2);
}
break;
- case MSG_L_UPDATE_PLAYER_MUTED_EVENT:
+ case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT:
// TODO: replace PersistableBundle with own struct
PersistableBundle extras = (PersistableBundle) msg.obj;
if (extras == null) {
@@ -1533,14 +1565,18 @@
sEventLogger.enqueue(
new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
- final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ final AudioPlaybackConfiguration apc;
+ synchronized (mPlayerLock) {
+ apc = mPlayers.get(piid);
+ }
if (apc == null || !apc.handleMutedEvent(eventValue)) {
break; // do not dispatch
}
dispatchPlaybackChange(/* iplayerReleased= */false);
}
break;
- case MSG_L_CLEAR_PORTS_FOR_PIID:
+
+ case MSG_I_CLEAR_PORTS_FOR_PIID:
int piid = msg.arg1;
if (piid == AudioPlaybackConfiguration.PLAYER_PIID_INVALID) {
Log.w(TAG, "Received clear ports with invalid piid");
@@ -1554,6 +1590,34 @@
}
}
break;
+
+ case MSG_IIL_UPDATE_PLAYER_FORMAT:
+ final PersistableBundle formatExtras = (PersistableBundle) msg.obj;
+ if (formatExtras == null) {
+ Log.w(TAG, "Received format event with no extras");
+ break;
+ }
+ final boolean spatialized = formatExtras.getBoolean(
+ AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_SPATIALIZED, false);
+ final int sampleRate = formatExtras.getInt(
+ AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_SAMPLE_RATE, 0);
+ final int nativeChannelMask = formatExtras.getInt(
+ AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_CHANNEL_MASK, 0);
+ final FormatInfo format =
+ new FormatInfo(spatialized, nativeChannelMask, sampleRate);
+
+ sEventLogger.enqueue(new PlayerFormatEvent(msg.arg1, format));
+
+ final AudioPlaybackConfiguration apc;
+ synchronized (mPlayerLock) {
+ apc = mPlayers.get(msg.arg1);
+ }
+ if (apc == null || !apc.handleFormatEvent(format)) {
+ break; // do not dispatch
+ }
+ // TODO optimize for no dispatch to non-privileged listeners
+ dispatchPlaybackChange(/* iplayerReleased= */false);
+ break;
default:
break;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index fe1d1a6..b7b7031 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -180,6 +180,15 @@
public static final int FLAG_DEVICE_DISPLAY_GROUP = 1 << 18;
/**
+ * Flag: Indicates that the display should not become the top focused display by stealing the
+ * top focus from another display.
+ *
+ * @see Display#FLAG_STEAL_TOP_FOCUS_DISABLED
+ * @hide
+ */
+ public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 19;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -649,6 +658,9 @@
if ((flags & FLAG_OWN_FOCUS) != 0) {
msg.append(", FLAG_OWN_FOCUS");
}
+ if ((flags & FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
+ msg.append(", FLAG_STEAL_TOP_FOCUS_DISABLED");
+ }
return msg.toString();
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index ad426b5..dfdbce5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -361,6 +361,9 @@
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
}
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
+ mBaseDisplayInfo.flags |= Display.FLAG_STEAL_TOP_FOCUS_DISABLED;
+ }
Rect maskingInsets = getMaskingInsets(deviceInfo);
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 7c647cf..5a5be4e 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -28,6 +28,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
@@ -35,6 +36,7 @@
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
@@ -526,6 +528,17 @@
+ "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
}
}
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0
+ && (mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+ mInfo.flags |= FLAG_STEAL_TOP_FOCUS_DISABLED;
+ } else {
+ Slog.w(TAG,
+ "Ignoring VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED as it "
+ + "requires VIRTUAL_DISPLAY_FLAG_OWN_FOCUS which requires "
+ + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+ }
+ }
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 96e7b03..6303bdc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -899,10 +899,16 @@
@ServiceThreadOnly
void startArcAction(boolean enabled) {
+ startArcAction(enabled, null);
+ }
+
+ @ServiceThreadOnly
+ void startArcAction(boolean enabled, IHdmiControlCallback callback) {
assertRunOnServiceThread();
HdmiDeviceInfo info = getAvrDeviceInfo();
if (info == null) {
Slog.w(TAG, "Failed to start arc action; No AVR device.");
+ invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
return;
}
if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
@@ -910,19 +916,37 @@
if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
}
+ invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+ return;
+ }
+ if (enabled && mService.earcBlocksArcConnection()) {
+ Slog.i(TAG,
+ "ARC connection blocked because eARC connection is established or being "
+ + "established.");
+ invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
return;
}
- // Terminate opposite action and start action if not exist.
+ // Terminate opposite action and create an action with callback.
if (enabled) {
removeAction(RequestArcTerminationAction.class);
- if (!hasAction(RequestArcInitiationAction.class)) {
- addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
+ if (hasAction(RequestArcInitiationAction.class)) {
+ RequestArcInitiationAction existingInitiationAction =
+ getActions(RequestArcInitiationAction.class).get(0);
+ existingInitiationAction.addCallback(callback);
+ } else {
+ addAndStartAction(
+ new RequestArcInitiationAction(this, info.getLogicalAddress(), callback));
}
} else {
removeAction(RequestArcInitiationAction.class);
- if (!hasAction(RequestArcTerminationAction.class)) {
- addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
+ if (hasAction(RequestArcTerminationAction.class)) {
+ RequestArcTerminationAction existingTerminationAction =
+ getActions(RequestArcTerminationAction.class).get(0);
+ existingTerminationAction.addCallback(callback);
+ } else {
+ addAndStartAction(
+ new RequestArcTerminationAction(this, info.getLogicalAddress(), callback));
}
}
}
@@ -1010,6 +1034,13 @@
protected int handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
+ if (mService.earcBlocksArcConnection()) {
+ Slog.i(TAG,
+ "ARC connection blocked because eARC connection is established or being "
+ + "established.");
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
+ }
+
if (!canStartArcUpdateAction(message.getSource(), true)) {
HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
if (avrDeviceInfo == null) {
@@ -1023,9 +1054,8 @@
return Constants.ABORT_REFUSED;
}
- // In case where <Initiate Arc> is started by <Request ARC Initiation>
- // need to clean up RequestArcInitiationAction.
- removeAction(RequestArcInitiationAction.class);
+ // In case where <Initiate Arc> is started by <Request ARC Initiation>, this message is
+ // handled in RequestArcInitiationAction as well.
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getSource(), true);
addAndStartAction(action);
@@ -1059,9 +1089,8 @@
return Constants.HANDLED;
}
// Do not check ARC configuration since the AVR might have been already removed.
- // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
- // <Request ARC Termination>.
- removeAction(RequestArcTerminationAction.class);
+ // In case where <Terminate Arc> is started by <Request ARC Termination>, this
+ // message is handled in RequestArcTerminationAction as well.
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getSource(), false);
addAndStartAction(action);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 85bd16b..f66f8ea 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -26,6 +26,7 @@
import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static com.android.server.hdmi.Constants.DISABLED;
import static com.android.server.hdmi.Constants.ENABLED;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
@@ -392,7 +393,7 @@
// and the eARC HAL is present.
@GuardedBy("mLock")
@VisibleForTesting
- protected boolean mEarcSupported;
+ private boolean mEarcSupported;
// Set to true while the eARC feature is enabled.
@GuardedBy("mLock")
@@ -725,7 +726,7 @@
if (isEarcEnabled()) {
initializeEarc(INITIATED_BY_BOOT_UP);
} else {
- setEarcEnabledInHal(false);
+ setEarcEnabledInHal(false, false);
}
}
@@ -3236,6 +3237,9 @@
}
private void invokeCallback(IHdmiControlCallback callback, int result) {
+ if (callback == null) {
+ return;
+ }
try {
callback.onComplete(result);
} catch (RemoteException e) {
@@ -3518,7 +3522,7 @@
}
initializeEarc(startReason);
} else {
- setEarcEnabledInHal(false);
+ setEarcEnabledInHal(false, false);
}
}
// TODO: Initialize MHL local devices.
@@ -4414,8 +4418,17 @@
private void initializeEarc(int initiatedBy) {
Slog.i(TAG, "eARC initialized, reason = " + initiatedBy);
- setEarcEnabledInHal(true);
initializeEarcLocalDevice(initiatedBy);
+
+ if (initiatedBy == INITIATED_BY_ENABLE_EARC) {
+ // Since ARC and eARC cannot be connected simultaneously, we need to terminate ARC
+ // before even enabling eARC.
+ setEarcEnabledInHal(true, true);
+ } else {
+ // On boot, wake-up, and hotplug in, eARC will always be attempted before ARC.
+ // So there is no need to explicitly terminate ARC before enabling eARC.
+ setEarcEnabledInHal(true, false);
+ }
}
@ServiceThreadOnly
@@ -4464,13 +4477,14 @@
@ServiceThreadOnly
private void onEnableEarc() {
+ // This will terminate ARC as well.
initializeEarc(INITIATED_BY_ENABLE_EARC);
}
@ServiceThreadOnly
private void onDisableEarc() {
disableEarcLocalDevice();
- setEarcEnabledInHal(false);
+ setEarcEnabledInHal(false, false);
clearEarcLocalDevice();
}
@@ -4504,12 +4518,28 @@
@ServiceThreadOnly
@VisibleForTesting
- protected void setEarcEnabledInHal(boolean enabled) {
+ protected void setEarcEnabledInHal(boolean enabled, boolean terminateArcFirst) {
assertRunOnServiceThread();
- mEarcController.setEarcEnabled(enabled);
- mCecController.setHpdSignalType(
- enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL,
- mEarcPortId);
+ if (terminateArcFirst) {
+ startArcAction(false, new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) throws RemoteException {
+ // Independently of the result (i.e. independently of whether the ARC RX device
+ // responded with <Terminate ARC> or not), we always end up terminating ARC in
+ // the HAL. As soon as we do that, we can enable eARC in the HAL.
+ mEarcController.setEarcEnabled(enabled);
+ mCecController.setHpdSignalType(
+ enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT
+ : Constants.HDMI_HPD_TYPE_PHYSICAL,
+ mEarcPortId);
+ }
+ });
+ } else {
+ mEarcController.setEarcEnabled(enabled);
+ mCecController.setHpdSignalType(
+ enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL,
+ mEarcPortId);
+ }
}
@ServiceThreadOnly
@@ -4540,4 +4570,21 @@
mEarcLocalDevice.handleEarcCapabilitiesReported(rawCapabilities);
}
}
+
+ protected boolean earcBlocksArcConnection() {
+ if (mEarcLocalDevice == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ return mEarcLocalDevice.mEarcStatus != HDMI_EARC_STATUS_ARC_PENDING;
+ }
+ }
+
+ protected void startArcAction(boolean enabled, IHdmiControlCallback callback) {
+ if (!isTvDeviceEnabled()) {
+ invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+ } else {
+ tv().startArcAction(enabled, callback);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
index c7c7702..abb8439 100644
--- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
@@ -18,6 +18,7 @@
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -69,23 +70,36 @@
HdmiEarcLocalDeviceTx(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_TV);
+ synchronized (mLock) {
+ mEarcStatus = HDMI_EARC_STATUS_EARC_PENDING;
+ }
mReportCapsHandler = new Handler(service.getServiceLooper());
mReportCapsRunnable = new ReportCapsRunnable();
}
protected void handleEarcStateChange(@Constants.EarcStatus int status) {
+ int oldEarcStatus;
synchronized (mLock) {
HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus,
status);
+ oldEarcStatus = mEarcStatus;
mEarcStatus = status;
}
mReportCapsHandler.removeCallbacksAndMessages(null);
if (status == HDMI_EARC_STATUS_IDLE) {
notifyEarcStatusToAudioService(false, new ArrayList<>());
+ mService.startArcAction(false, null);
} else if (status == HDMI_EARC_STATUS_ARC_PENDING) {
notifyEarcStatusToAudioService(false, new ArrayList<>());
+ mService.startArcAction(true, null);
+ } else if (status == HDMI_EARC_STATUS_EARC_PENDING
+ && oldEarcStatus == HDMI_EARC_STATUS_ARC_PENDING) {
+ mService.startArcAction(false, null);
} else if (status == HDMI_EARC_STATUS_EARC_CONNECTED) {
+ if (oldEarcStatus == HDMI_EARC_STATUS_ARC_PENDING) {
+ mService.startArcAction(false, null);
+ }
mReportCapsHandler.postDelayed(mReportCapsRunnable, REPORT_CAPS_MAX_DELAY_MS);
}
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 3d9a290..54c8c00 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -16,7 +16,9 @@
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
/**
* Base feature action class for <Request ARC Initiation>/<Request ARC Termination>.
@@ -35,41 +37,19 @@
*
* @param source {@link HdmiCecLocalDevice} instance
* @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
+ * @param callback callback to inform about the status of the action
* @throws IllegalArgumentException if device type of sourceAddress and avrAddress
* is invalid
*/
- RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
- super(source);
+ RequestArcAction(HdmiCecLocalDevice source, int avrAddress, IHdmiControlCallback callback) {
+ super(source, callback);
HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV);
HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mAvrAddress = avrAddress;
}
- @Override
- boolean processCommand(HdmiCecMessage cmd) {
- if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
- || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
- return false;
- }
- int opcode = cmd.getOpcode();
- switch (opcode) {
- // Handles only <Feature Abort> here and, both <Initiate ARC> and <Terminate ARC>
- // are handled in HdmiControlService itself because both can be
- // received without <Request ARC Initiation> or <Request ARC Termination>.
- case Constants.MESSAGE_FEATURE_ABORT:
- int originalOpcode = cmd.getParams()[0] & 0xFF;
- if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) {
- disableArcTransmission();
- finish();
- return true;
- } else if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) {
- tv().disableArc();
- finish();
- return true;
- }
- return false;
- }
- return false;
+ RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
+ this(source, avrAddress, null);
}
protected final void disableArcTransmission() {
@@ -86,6 +66,6 @@
}
HdmiLogger.debug("[T] RequestArcAction.");
disableArcTransmission();
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
}
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index 3b7f1dd..db21a33 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
/**
@@ -35,6 +37,16 @@
super(source, avrAddress);
}
+ /**
+ * @Constructor
+ *
+ * For more details look at {@link RequestArcAction#RequestArcAction}.
+ */
+ RequestArcInitiationAction(HdmiCecLocalDevice source, int avrAddress,
+ IHdmiControlCallback callback) {
+ super(source, avrAddress, callback);
+ }
+
@Override
boolean start() {
// Seq #38
@@ -49,10 +61,34 @@
if (error != SendMessageResult.SUCCESS) {
// Turn off ARC status if <Request ARC Initiation> fails.
tv().disableArc();
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
}
});
return true;
}
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
+ || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
+ return false;
+ }
+ int opcode = cmd.getOpcode();
+ switch (opcode) {
+ case Constants.MESSAGE_FEATURE_ABORT:
+ int originalOpcode = cmd.getParams()[0] & 0xFF;
+ if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) {
+ tv().disableArc();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ return true;
+ }
+ return false;
+ case Constants.MESSAGE_INITIATE_ARC:
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ // This message still needs to be handled in HdmiCecLocalDeviceTv as well.
+ return false;
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
index 8b5a2931..85128b6 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
/**
@@ -35,6 +37,16 @@
super(source, avrAddress);
}
+ /**
+ * @Constructor
+ *
+ * @see RequestArcAction#RequestArcAction
+ */
+ RequestArcTerminationAction(HdmiCecLocalDevice source, int avrAddress,
+ IHdmiControlCallback callback) {
+ super(source, avrAddress, callback);
+ }
+
@Override
boolean start() {
mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
@@ -49,10 +61,34 @@
// If failed to send <Request ARC Termination>, start "Disabled" ARC
// transmission action.
disableArcTransmission();
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
}
});
return true;
}
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
+ || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
+ return false;
+ }
+ int opcode = cmd.getOpcode();
+ switch (opcode) {
+ case Constants.MESSAGE_FEATURE_ABORT:
+ int originalOpcode = cmd.getParams()[0] & 0xFF;
+ if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) {
+ disableArcTransmission();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ return true;
+ }
+ return false;
+ case Constants.MESSAGE_TERMINATE_ARC:
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ // This message still needs to be handled in HdmiCecLocalDeviceTv as well.
+ return false;
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/AuthSecretHidlAdapter.java b/services/core/java/com/android/server/locksettings/AuthSecretHidlAdapter.java
new file mode 100644
index 0000000..3b5f340
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/AuthSecretHidlAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.locksettings;
+
+import android.hardware.authsecret.IAuthSecret;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+
+/**
+ * Adapt the legacy HIDL interface to present the AIDL interface.
+ */
+class AuthSecretHidlAdapter implements IAuthSecret {
+ // private final String TAG = "AuthSecretHidlAdapter";
+ private final android.hardware.authsecret.V1_0.IAuthSecret mImpl;
+
+ AuthSecretHidlAdapter(android.hardware.authsecret.V1_0.IAuthSecret impl) {
+ mImpl = impl;
+ }
+
+ @Override
+ public void setPrimaryUserCredential(byte[] secret) throws RemoteException {
+ final ArrayList<Byte> secretAsArrayList = new ArrayList<>(secret.length);
+ for (int i = 0; i < secret.length; ++i) {
+ secretAsArrayList.add(secret[i]);
+ }
+ mImpl.primaryUserCredential(secretAsArrayList);
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ // Supports only V1
+ return 1;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("AuthSecretHidlAdapter does not support asBinder");
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ throw new UnsupportedOperationException(
+ "AuthSecretHidlAdapter does not support getInterfaceHash");
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 2253a9a..89bc495a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -267,8 +267,7 @@
protected boolean mHasSecureLockScreen;
protected IGateKeeperService mGateKeeperService;
- protected IAuthSecret mAuthSecretServiceAidl;
- protected android.hardware.authsecret.V1_0.IAuthSecret mAuthSecretServiceHidl;
+ protected IAuthSecret mAuthSecretService;
private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
@@ -837,16 +836,19 @@
}
private void getAuthSecretHal() {
- mAuthSecretServiceAidl = IAuthSecret.Stub.asInterface(ServiceManager.
- waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
- if (mAuthSecretServiceAidl == null) {
- Slog.i(TAG, "Device doesn't implement AuthSecret HAL(aidl), try to get hidl version");
-
+ mAuthSecretService =
+ IAuthSecret.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
+ if (mAuthSecretService != null) {
+ Slog.i(TAG, "Device implements AIDL AuthSecret HAL");
+ } else {
try {
- mAuthSecretServiceHidl =
- android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+ android.hardware.authsecret.V1_0.IAuthSecret authSecretServiceHidl =
+ android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+ mAuthSecretService = new AuthSecretHidlAdapter(authSecretServiceHidl);
+ Slog.i(TAG, "Device implements HIDL AuthSecret HAL");
} catch (NoSuchElementException e) {
- Slog.i(TAG, "Device doesn't implement AuthSecret HAL(hidl)");
+ Slog.i(TAG, "Device doesn't implement AuthSecret HAL");
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get AuthSecret HAL(hidl)", e);
}
@@ -1243,7 +1245,8 @@
private int getFrpCredentialType() {
PersistentData data = mStorage.readPersistentDataBlock();
- if (data.type != PersistentData.TYPE_SP && data.type != PersistentData.TYPE_SP_WEAVER) {
+ if (data.type != PersistentData.TYPE_SP_GATEKEEPER &&
+ data.type != PersistentData.TYPE_SP_WEAVER) {
return CREDENTIAL_TYPE_NONE;
}
int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
@@ -2577,25 +2580,16 @@
// If the given user is the primary user, pass the auth secret to the HAL. Only the system
// user can be primary. Check for the system user ID before calling getUserInfo(), as other
// users may still be under construction.
+ if (mAuthSecretService == null) {
+ return;
+ }
if (userId == UserHandle.USER_SYSTEM &&
mUserManager.getUserInfo(userId).isPrimary()) {
- final byte[] rawSecret = sp.deriveVendorAuthSecret();
- if (mAuthSecretServiceAidl != null) {
- try {
- mAuthSecretServiceAidl.setPrimaryUserCredential(rawSecret);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(aidl)", e);
- }
- } else if (mAuthSecretServiceHidl != null) {
- try {
- final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
- for (int i = 0; i < rawSecret.length; ++i) {
- secret.add(rawSecret[i]);
- }
- mAuthSecretServiceHidl.primaryUserCredential(secret);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(hidl)", e);
- }
+ final byte[] secret = sp.deriveVendorAuthSecret();
+ try {
+ mAuthSecretService.setPrimaryUserCredential(secret);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 473c4b6..2c28af1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -587,7 +587,7 @@
static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
public static final int TYPE_NONE = 0;
- public static final int TYPE_SP = 1;
+ public static final int TYPE_SP_GATEKEEPER = 1;
public static final int TYPE_SP_WEAVER = 2;
public static final PersistentData NONE = new PersistentData(TYPE_NONE,
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 67d1c71..ad2fa22 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -900,7 +900,7 @@
protectorSecret = transformUnderSecdiscardable(stretchedLskf,
createSecdiscardable(protectorId, userId));
// No need to pass in quality since the credential type already encodes sufficient info
- synchronizeFrpPassword(pwd, 0, userId);
+ synchronizeGatekeeperFrpPassword(pwd, 0, userId);
}
if (!credential.isNone()) {
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
@@ -916,7 +916,7 @@
LockscreenCredential userCredential,
ICheckCredentialProgressCallback progressCallback) {
PersistentData persistentData = mStorage.readPersistentDataBlock();
- if (persistentData.type == PersistentData.TYPE_SP) {
+ if (persistentData.type == PersistentData.TYPE_SP_GATEKEEPER) {
PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
byte[] stretchedLskf = stretchLskf(userCredential, pwd);
@@ -941,7 +941,7 @@
return weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)).stripPayload();
} else {
- Slog.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+ Slog.e(TAG, "persistentData.type must be TYPE_SP_GATEKEEPER or TYPE_SP_WEAVER, but is "
+ persistentData.type);
return VerifyCredentialResponse.ERROR;
}
@@ -960,7 +960,7 @@
if (weaverSlot != INVALID_WEAVER_SLOT) {
synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
} else {
- synchronizeFrpPassword(pwd, requestedQuality, userInfo.id);
+ synchronizeGatekeeperFrpPassword(pwd, requestedQuality, userInfo.id);
}
}
}
@@ -994,13 +994,13 @@
return true;
}
- private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
+ private void synchronizeGatekeeperFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
int userId) {
if (shouldSynchronizeFrpCredential(pwd, userId)) {
Slogf.d(TAG, "Syncing Gatekeeper-based FRP credential tied to user %d", userId);
if (!isNoneCredential(pwd)) {
- mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
- pwd.toBytes());
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_GATEKEEPER, userId,
+ requestedQuality, pwd.toBytes());
} else {
mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
}
@@ -1224,7 +1224,7 @@
pwd.credentialType = credential.getType();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
syncState(userId);
- synchronizeFrpPassword(pwd, 0, userId);
+ synchronizeGatekeeperFrpPassword(pwd, 0, userId);
} else {
Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
// continue the flow anyway
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 4fddc9c..0362ddd 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,15 +1470,9 @@
}
// Then make sure none of the activities have more than the max number of shortcuts.
- int total = 0;
for (int i = counts.size() - 1; i >= 0; i--) {
- int count = counts.valueAt(i);
- service.enforceMaxActivityShortcuts(count);
- total += count;
+ service.enforceMaxActivityShortcuts(counts.valueAt(i));
}
-
- // Finally make sure that the app doesn't have more than the max number of shortcuts.
- service.enforceMaxAppShortcuts(total);
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 12a33ee..83720f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,9 +181,6 @@
static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
- static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 60;
-
- @VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@VisibleForTesting
@@ -260,11 +257,6 @@
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
- * Key name for the max dynamic shortcuts per app. (int)
- */
- String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
-
- /**
* Key name for icon compression quality, 0-100.
*/
String KEY_ICON_QUALITY = "icon_quality";
@@ -337,14 +329,9 @@
new SparseArray<>();
/**
- * Max number of dynamic + manifest shortcuts that each activity can have at a time.
- */
- private int mMaxShortcutsPerActivity;
-
- /**
* Max number of dynamic + manifest shortcuts that each application can have at a time.
*/
- private int mMaxShortcutsPerApp;
+ private int mMaxShortcuts;
/**
* Max number of updating API calls that each application can make during the interval.
@@ -817,12 +804,9 @@
mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
- mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
+ mMaxShortcuts = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
- mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
- ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
-
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1762,33 +1746,16 @@
* {@link #getMaxActivityShortcuts()}.
*/
void enforceMaxActivityShortcuts(int numShortcuts) {
- if (numShortcuts > mMaxShortcutsPerActivity) {
+ if (numShortcuts > mMaxShortcuts) {
throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
}
}
/**
- * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
- * {@link #getMaxAppShortcuts()}.
- */
- void enforceMaxAppShortcuts(int numShortcuts) {
- if (numShortcuts > mMaxShortcutsPerApp) {
- throw new IllegalArgumentException("Max number of dynamic shortcuts per app exceeded");
- }
- }
-
- /**
* Return the max number of dynamic + manifest shortcuts for each launcher icon.
*/
int getMaxActivityShortcuts() {
- return mMaxShortcutsPerActivity;
- }
-
- /**
- * Return the max number of dynamic + manifest shortcuts for each launcher icon.
- */
- int getMaxAppShortcuts() {
- return mMaxShortcutsPerApp;
+ return mMaxShortcuts;
}
/**
@@ -2221,8 +2188,6 @@
ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
fillInDefaultActivity(Arrays.asList(shortcut));
- enforceMaxAppShortcuts(ps.getShortcutCount());
-
if (!shortcut.hasRank()) {
shortcut.setRank(0);
}
@@ -2610,7 +2575,7 @@
throws RemoteException {
verifyCaller(packageName, userId);
- return mMaxShortcutsPerActivity;
+ return mMaxShortcuts;
}
@Override
@@ -4759,7 +4724,7 @@
pw.print(" maxUpdatesPerInterval: ");
pw.println(mMaxUpdatesPerInterval);
pw.print(" maxShortcutsPerActivity: ");
- pw.println(mMaxShortcutsPerActivity);
+ pw.println(mMaxShortcuts);
pw.println();
mStatLogger.dump(pw, " ");
@@ -5246,7 +5211,7 @@
@VisibleForTesting
int getMaxShortcutsForTest() {
- return mMaxShortcutsPerActivity;
+ return mMaxShortcuts;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f88adab..fe0fe29 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -617,7 +617,6 @@
private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
= new LogDecelerateInterpolator(100, 0);
- private boolean mPerDisplayFocusEnabled = false;
private volatile int mTopFocusedDisplayId = INVALID_DISPLAY;
private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS;
@@ -2118,9 +2117,6 @@
mHandleVolumeKeysInWM = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeKeysInWindowManager);
- mPerDisplayFocusEnabled = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
mWakeUpToLastStateTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_wakeUpToLastStateTimeoutMillis);
@@ -4314,23 +4310,20 @@
wakeUpFromWakeKey(event);
}
- if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
- && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
- // If the key event is targeted to a specific display, then the user is interacting with
- // that display. Therefore, give focus to the display that the user is interacting with,
- // unless that display maintains its own focus.
- Display display = mDisplayManager.getDisplay(displayId);
- if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
- // An event is targeting a non-focused display. Move the display to top so that
- // it can become the focused display to interact with the user.
- // This should be done asynchronously, once the focus logic is fully moved to input
- // from windowmanager. Currently, we need to ensure the setInputWindows completes,
- // which would force the focus event to be queued before the current key event.
- // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
- Log.i(TAG, "Moving non-focused display " + displayId + " to top "
- + "because a key is targeting it");
- mWindowManagerFuncs.moveDisplayToTop(displayId);
- }
+ // If the key event is targeted to a specific display, then the user is interacting with
+ // that display. Therefore, try to give focus to the display that the user is interacting
+ // with.
+ if ((result & ACTION_PASS_TO_USER) != 0 && displayId != INVALID_DISPLAY
+ && displayId != mTopFocusedDisplayId) {
+ // An event is targeting a non-focused display. Move the display to top so that
+ // it can become the focused display to interact with the user.
+ // This should be done asynchronously, once the focus logic is fully moved to input
+ // from windowmanager. Currently, we need to ensure the setInputWindows completes,
+ // which would force the focus event to be queued before the current key event.
+ // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
+ Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+ + "because a key is targeting it");
+ mWindowManagerFuncs.moveDisplayToTopIfAllowed(displayId);
}
return result;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 88ec691..94fb840 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -330,8 +330,11 @@
/**
* Hint to window manager that the user is interacting with a display that should be treated
* as the top display.
+ *
+ * Calling this method does not guarantee that the display will be moved to top. The window
+ * manager will make the final decision whether or not to move the display.
*/
- void moveDisplayToTop(int displayId);
+ void moveDisplayToTopIfAllowed(int displayId);
/**
* Return whether the app transition state is idle.
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 4fef2a8..e2ab216 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -141,7 +141,7 @@
private final SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
new SparseArray<>();
private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
- private int mFocusedDisplay = -1;
+ private int mFocusedDisplay = Display.INVALID_DISPLAY;
private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
// Set to true if initializing window population complete.
private boolean mAllObserversInitialized = true;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index eb3fdca..ff50fd4 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1663,6 +1663,12 @@
pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent,
UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
resultToUid /*visible*/, true /*direct*/);
+ } else if (mStartActivity.mShareIdentity) {
+ final PackageManagerInternal pmInternal =
+ mService.getPackageManagerInternalLocked();
+ pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent,
+ UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
+ r.launchedFromUid /*visible*/, true /*direct*/);
}
final Task startedTask = mStartActivity.getTask();
if (newTask) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d324df0..044357b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -790,6 +790,12 @@
}
};
+ /**
+ * A lambda function to find the focused window of the given window.
+ *
+ * <p>The lambda returns true if a focused window was found, false otherwise. If a focused
+ * window is found it will be stored in <code>mTmpWindow</code>.
+ */
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
final ActivityRecord focusedApp = mFocusedApp;
ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
@@ -3719,6 +3725,14 @@
}
/**
+ * @see Display#FLAG_STEAL_TOP_FOCUS_DISABLED
+ * @return True if this display can become the top focused display, false otherwise.
+ */
+ boolean canStealTopFocus() {
+ return (mDisplayInfo.flags & Display.FLAG_STEAL_TOP_FOCUS_DISABLED) == 0;
+ }
+
+ /**
* Looking for the focused window on this display if the top focused display hasn't been
* found yet (topFocusedDisplayId is INVALID_DISPLAY) or per-display focused was allowed.
*
@@ -3730,9 +3744,15 @@
? findFocusedWindow() : null;
}
+ /**
+ * Find the focused window of this DisplayContent. The search takes the state of the display
+ * content into account
+ * @return The focused window, null if none was found.
+ */
WindowState findFocusedWindow() {
mTmpWindow = null;
+ // mFindFocusedWindow will populate mTmpWindow with the new focused window when found.
forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
if (mTmpWindow == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index b735b30..6d47eeb 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -291,7 +291,7 @@
boolean dontMoveToTop = settings.mDontMoveToTop != null
? settings.mDontMoveToTop : false;
- dc.mDontMoveToTop = dontMoveToTop;
+ dc.mDontMoveToTop = !dc.canStealTopFocus() || dontMoveToTop;
if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 13da96f..fd5c5eb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -442,10 +442,14 @@
mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
}
+ /**
+ * Updates the children's focused window and the top focused display if needed.
+ */
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
mTopFocusedAppByProcess.clear();
boolean changed = false;
int topFocusedDisplayId = INVALID_DISPLAY;
+ // Go through the children in z-order starting at the top-most
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent dc = mChildren.get(i);
changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 70bedc7..b83f423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -563,7 +563,7 @@
/** Mapping from an InputWindowHandle token to the server's Window object. */
final HashMap<IBinder, WindowState> mInputToWindowMap = new HashMap<>();
- /** Global service lock used by the package the owns this service. */
+ /** Global service lock used by the package that owns this service. */
final WindowManagerGlobalLock mGlobalLock;
/**
@@ -3165,15 +3165,41 @@
}
@Override
- public void moveDisplayToTop(int displayId) {
+ public void moveDisplayToTopIfAllowed(int displayId) {
+ moveDisplayToTopInternal(displayId);
+ syncInputTransactions(true /* waitForAnimations */);
+ }
+
+ /**
+ * Moves the given display to the top. If it cannot be moved to the top this method does
+ * nothing.
+ */
+ void moveDisplayToTopInternal(int displayId) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && mRoot.getTopChild() != displayContent) {
+ // Check whether anything prevents us from moving the display to the top.
+ if (!displayContent.canStealTopFocus()) {
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
+ "Not moving display (displayId=%d) to top. Top focused displayId=%d. "
+ + "Reason: FLAG_STEAL_TOP_FOCUS_DISABLED",
+ displayId, mRoot.getTopFocusedDisplayContent().getDisplayId());
+ return;
+ }
+
+ if (mPerDisplayFocusEnabled) {
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
+ "Not moving display (displayId=%d) to top. Top focused displayId=%d. "
+ + "Reason: config_perDisplayFocusEnabled", displayId,
+ mRoot.getTopFocusedDisplayContent().getDisplayId());
+ return;
+ }
+
+ // Nothing prevented us from moving the display to the top. Let's do it!
displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
displayContent, true /* includingParents */);
}
}
- syncInputTransactions(true /* waitForAnimations */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 746f983..ae31ee8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -6238,10 +6238,7 @@
@Override
public void handleTapOutsideFocusInsideSelf() {
final DisplayContent displayContent = getDisplayContent();
- if (!displayContent.isOnTop()) {
- displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
- true /* includingParents */);
- }
+ mWmService.moveDisplayToTopInternal(getDisplayId());
mWmService.handleTaskFocusChange(getTask(), mActivityRecord);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7100020..8c2065e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19572,6 +19572,11 @@
}
admin.mtePolicy = flags;
saveSettingsLocked(caller.getUserId());
+
+ DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
+ .setInt(flags)
+ .setAdmin(admin.info.getPackageName())
+ .write();
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 0dfad43..79fbc87 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -839,7 +839,7 @@
private static JobStatus createJob(int uid, int jobId, @Nullable String sourcePackageName) {
return JobStatus.createFromJobInfo(
new JobInfo.Builder(jobId, new ComponentName("foo", "bar")).build(), uid,
- sourcePackageName, UserHandle.getUserId(uid), "JobConcurrencyManagerTest");
+ sourcePackageName, UserHandle.getUserId(uid), "JobConcurrencyManagerTest", null);
}
private static final class TypeConfig {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 6374c66..c4ff4f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -197,7 +197,7 @@
private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
int callingUid) {
return JobStatus.createFromJobInfo(
- jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
+ jobInfoBuilder.build(), callingUid, "com.android.test", 0, "JSSTest", testTag);
}
private void grantRunLongJobsPermission(boolean grant) {
@@ -1111,7 +1111,7 @@
i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
assertEquals("Got unexpected result for schedule #" + (i + 1),
expected,
- mService.scheduleAsPackage(job, null, 10123, null, 0, ""));
+ mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
}
}
@@ -1132,7 +1132,7 @@
for (int i = 0; i < 500; ++i) {
assertEquals("Got unexpected result for schedule #" + (i + 1),
JobScheduler.RESULT_SUCCESS,
- mService.scheduleAsPackage(job, null, 10123, null, 0, ""));
+ mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
}
}
@@ -1153,7 +1153,8 @@
for (int i = 0; i < 500; ++i) {
assertEquals("Got unexpected result for schedule #" + (i + 1),
JobScheduler.RESULT_SUCCESS,
- mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, ""));
+ mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, "JSSTest",
+ ""));
}
}
@@ -1177,7 +1178,7 @@
assertEquals("Got unexpected result for schedule #" + (i + 1),
expected,
mService.scheduleAsPackage(job, null, 10123, job.getService().getPackageName(),
- 0, ""));
+ 0, "JSSTest", ""));
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 7c435be..f334a6a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -194,7 +194,7 @@
private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, "BCTest", testTag);
js.serviceProcessName = "testProcess";
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 42e22f3..32e5c83 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -1316,7 +1316,7 @@
private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
- return new JobStatus(job.build(), uid, null, -1, 0, null,
+ return new JobStatus(job.build(), uid, null, -1, 0, null, null,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 398acb8..1e65b38 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -195,7 +195,7 @@
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
JobInfo jobInfo = job.build();
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
return js;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 7f522b0..d2ee9ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -920,12 +920,12 @@
long latestRunTimeElapsedMillis) {
final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
- return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis,
+ return new JobStatus(job, 0, null, -1, 0, null, null, earliestRunTimeElapsedMillis,
latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
}
private static JobStatus createJobStatus(JobInfo job) {
- JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
+ JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest", null);
jobStatus.serviceProcessName = "testProcess";
return jobStatus;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index b949b3b..fb59ea2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -184,7 +184,7 @@
private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, "PCTest", testTag);
js.serviceProcessName = "testProcess";
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9aef674..6f713e0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -383,7 +383,7 @@
private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, "QCTest", testTag);
js.serviceProcessName = "testProcess";
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
index 51d641b..b111757 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
@@ -137,7 +137,7 @@
.setMinimumLatency(Math.abs(jobId) + 1)
.build();
return JobStatus.createFromJobInfo(
- jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, "SCTest", testTag);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
index d64c1b3..27efcfa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
@@ -137,7 +137,7 @@
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
JobInfo jobInfo = job.build();
return JobStatus.createFromJobInfo(
- jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "TCTest", testTag);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index 9067285..02fdfad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -328,6 +328,6 @@
private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
return JobStatus.createFromJobInfo(
- jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, "TSRTest", testTag);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 58cff94..579621c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -15,19 +15,6 @@
*/
package com.android.server.pm;
-import static android.os.UserHandle.USER_NULL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
-import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
-import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
-import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
-
-import org.junit.Test;
-
/**
* Tests for {@link UserVisibilityMediator} tests for devices that support concurrent Multiple
* Users on Multiple Displays (A.K.A {@code MUMD}).
@@ -35,208 +22,10 @@
* <p>Run as
* {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorMUMDTest}
*/
-public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
+public final class UserVisibilityMediatorMUMDTest
+ extends UserVisibilityMediatorVisibleBackgroundUserTestCase {
public UserVisibilityMediatorMUMDTest() throws Exception {
super(/* usersOnSecondaryDisplaysEnabled= */ true);
}
-
- @Test
- public void testStartFgUser_onDefaultDisplay() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(USER_ID));
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
- DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(USER_ID);
- expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
- expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
- expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- expectVisibleUsers(USER_ID);
-
- expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
- expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
- expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
-
- expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
-
- listener.verify();
- }
-
- @Test
- public void testSwitchFgUser_onDefaultDisplay() throws Exception {
- int previousCurrentUserId = OTHER_USER_ID;
- int currentUserId = USER_ID;
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(previousCurrentUserId),
- onInvisible(previousCurrentUserId),
- onVisible(currentUserId));
- startForegroundUser(previousCurrentUserId);
-
- int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
- DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(currentUserId);
- expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
- expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
- expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
- expectVisibleUsers(currentUserId);
-
- expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
- expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
- expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
-
- expectUserIsNotVisibleAtAll(previousCurrentUserId);
- expectNoDisplayAssignedToUser(previousCurrentUserId);
-
- listener.verify();
- }
-
- @Test
- public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(PARENT_USER_ID),
- onVisible(PROFILE_USER_ID));
- startForegroundUser(PARENT_USER_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
- BG_VISIBLE, DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(PROFILE_USER_ID);
- expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
- expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
- expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
- expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
- listener.verify();
- }
-
- @Test
- public void testStartFgUser_onInvalidDisplay() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForNoEvents();
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
-
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
- listener.verify();
- }
-
- @Test
- public void testStartBgUser_onInvalidDisplay() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForNoEvents();
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
- INVALID_DISPLAY);
-
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
- expectUserIsNotVisibleAtAll(USER_ID);
-
- listener.verify();
- }
-
- @Test
- public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
- SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(USER_ID);
- expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
- expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
- expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
-
- expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
- expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
-
- listener.verify();
- }
-
- @Test
- public void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
- throws Exception {
- startDefaultProfile();
-
- // Make sure they were visible before
- expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
- SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testStartBgUser_onSecondaryDisplay_displayAlreadyAssigned() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
- startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
- SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
- expectUserIsNotVisibleAtAll(USER_ID);
- expectNoDisplayAssignedToUser(USER_ID);
- expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
-
- listener.verify();
- }
-
- @Test
- public void testStartBgUser_onSecondaryDisplay_userAlreadyAssigned() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
- startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
- SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
- expectUserIsVisible(USER_ID);
- expectUserIsVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
- expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
- expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
- expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
-
- expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
- expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
-
- listener.verify();
- }
-
- @Test
- public void testStartBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
- throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
- startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
- BG_VISIBLE, DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
-
- expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
- expectNoDisplayAssignedToUser(PROFILE_USER_ID);
- expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
-
- listener.verify();
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 3d64c29..88709e1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -61,7 +61,7 @@
expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
- expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+ expectNoDisplayAssignedToUser(USER_NULL);
listener.verify();
}
@@ -99,7 +99,8 @@
}
@Test
- public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+ public void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+ throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(PARENT_USER_ID),
@@ -123,7 +124,7 @@
}
@Test
- public void testStartBgUser_onSecondaryDisplay() throws Exception {
+ public void testStartVisibleBgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
@@ -133,7 +134,7 @@
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
- expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+ expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 0ac0f57..e4664d2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -149,7 +149,7 @@
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
- expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+ expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
@@ -163,13 +163,17 @@
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
- expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+ expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
@Test
- public final void testStartBgUser_onDefaultDisplay_visible() throws Exception {
+ public final void testStartVisibleBgUser_onDefaultDisplay() throws Exception {
+ visibleBgUserCannotBeStartedOnDefaultDisplayTest();
+ }
+
+ protected final void visibleBgUserCannotBeStartedOnDefaultDisplayTest() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
@@ -183,7 +187,7 @@
}
@Test
- public final void testStartBgUser_onSecondaryDisplay_invisible() throws Exception {
+ public final void testStartBgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
@@ -217,7 +221,7 @@
}
@Test
- public final void testStopVisibleProfile() throws Exception {
+ public final void testStopVisibleBgProfile() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(PARENT_USER_ID),
@@ -235,7 +239,8 @@
}
@Test
- public final void testVisibleProfileBecomesInvisibleWhenParentIsSwitchedOut() throws Exception {
+ public final void testVisibleBgProfileBecomesInvisibleWhenParentIsSwitchedOut()
+ throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(PARENT_USER_ID),
@@ -255,7 +260,7 @@
}
@Test
- public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
+ public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsNotStarted()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
@@ -270,7 +275,7 @@
}
@Test
- public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
+ public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
startBackgroundUser(PARENT_USER_ID);
@@ -282,14 +287,14 @@
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
- expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+ expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
// Not supported - profiles can only be started on default display
@Test
- public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
+ public final void testStartVisibleBgProfile_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
@@ -298,13 +303,13 @@
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
- expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+ expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
@Test
- public final void testStartBgProfile_onSecondaryDisplay_invisible() throws Exception {
+ public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
@@ -313,7 +318,7 @@
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
- expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+ expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
@@ -329,7 +334,7 @@
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
- expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+ expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
@@ -344,7 +349,7 @@
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
- expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+ expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
@@ -477,7 +482,7 @@
}
protected void expectUserIsVisible(@UserIdInt int userId) {
- expectWithMessage("mediator.isUserVisible(%s)", userId)
+ expectWithMessage("isUserVisible(%s)", userId)
.that(mMediator.isUserVisible(userId))
.isTrue();
}
@@ -490,13 +495,13 @@
}
protected void expectUserIsVisibleOnDisplay(@UserIdInt int userId, int displayId) {
- expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+ expectWithMessage("isUserVisible(%s, %s)", userId, displayId)
.that(mMediator.isUserVisible(userId, displayId))
.isTrue();
}
protected void expectUserIsNotVisibleOnDisplay(@UserIdInt int userId, int displayId) {
- expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+ expectWithMessage("isUserVisible(%s, %s)", userId, displayId)
.that(mMediator.isUserVisible(userId, displayId))
.isFalse();
}
@@ -504,13 +509,13 @@
protected void expectUserIsNotVisibleOnDisplay(String when, @UserIdInt int userId,
int displayId) {
String suffix = TextUtils.isEmpty(when) ? "" : " on " + when;
- expectWithMessage("mediator.isUserVisible(%s, %s)%s", userId, displayId, suffix)
+ expectWithMessage("isUserVisible(%s, %s)%s", userId, displayId, suffix)
.that(mMediator.isUserVisible(userId, displayId))
.isFalse();
}
protected void expectUserIsNotVisibleAtAll(@UserIdInt int userId) {
- expectWithMessage("mediator.isUserVisible(%s)", userId)
+ expectWithMessage("isUserVisible(%s)", userId)
.that(mMediator.isUserVisible(userId))
.isFalse();
expectUserIsNotVisibleOnDisplay(userId, DEFAULT_DISPLAY);
@@ -534,7 +539,7 @@
.that(mMediator.getUserAssignedToDisplay(displayId)).isEqualTo(userId);
}
- protected void expectNoUserAssignedToDisplay(int displayId) {
+ protected void expectInitialCurrentUserAssignedToDisplay(int displayId) {
expectWithMessage("getUserAssignedToDisplay(%s)", displayId)
.that(mMediator.getUserAssignedToDisplay(displayId))
.isEqualTo(INITIAL_CURRENT_USER_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
new file mode 100644
index 0000000..66d7eb6
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2022 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.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
+
+import android.annotation.UserIdInt;
+
+import org.junit.Test;
+
+/**
+ * Base class for {@link UserVisibilityMediator} test classe on devices that support starting
+ * background users on visible displays (as defined by
+ * {@link android.os.UserManagerInternal#isVisibleBackgroundUsersSupported}).
+ */
+abstract class UserVisibilityMediatorVisibleBackgroundUserTestCase
+ extends UserVisibilityMediatorTestCase {
+
+ UserVisibilityMediatorVisibleBackgroundUserTestCase(boolean backgroundUsersOnDisplaysEnabled)
+ throws Exception {
+ super(backgroundUsersOnDisplaysEnabled);
+ }
+
+ @Test
+ public final void testStartFgUser_onDefaultDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(USER_ID));
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testSwitchFgUser_onDefaultDisplay() throws Exception {
+ int previousCurrentUserId = OTHER_USER_ID;
+ int currentUserId = USER_ID;
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(previousCurrentUserId),
+ onInvisible(previousCurrentUserId),
+ onVisible(currentUserId));
+ startForegroundUser(previousCurrentUserId);
+
+ int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(currentUserId);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(currentUserId);
+
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+ expectUserIsNotVisibleAtAll(previousCurrentUserId);
+ expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID));
+ startForegroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(PROFILE_USER_ID);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+ expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartFgUser_onInvalidDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
+
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartVisibleBgUser_onInvalidDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ INVALID_DISPLAY);
+
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartVisibleBgUser_onSecondaryDisplay_displayAvailable()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
+ throws Exception {
+ startDefaultProfile();
+
+ // Make sure they were visible before
+ expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public final void testStartVisibleBgUser_onSecondaryDisplay_displayAlreadyAssigned()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
+ startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(USER_ID);
+ expectNoDisplayAssignedToUser(USER_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartVisibleBgUser_onSecondaryDisplay_userAlreadyAssigned()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+ startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void
+ testStartVisibleBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+ listener.verify();
+ }
+
+ private void currentUserVisibilityWhenNoDisplayIsAssignedTest(@UserIdInt int currentUserId) {
+ // Conditions below are asserted on other tests, but they're explicitly checked in the 2
+ // tests below as well
+ expectUserIsVisible(currentUserId);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(currentUserId, OTHER_SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, currentUserId);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+ }
+
+ @Test
+ public final void testCurrentUserVisibilityWhenNoDisplayIsAssigned_onBoot() throws Exception {
+ currentUserVisibilityWhenNoDisplayIsAssignedTest(INITIAL_CURRENT_USER_ID);
+ }
+
+ @Test
+ public final void testCurrentUserVisibilityWhenNoDisplayIsAssigned_afterSwitch()
+ throws Exception {
+ startForegroundUser(USER_ID);
+
+ currentUserVisibilityWhenNoDisplayIsAssignedTest(USER_ID);
+ expectUserIsNotVisibleAtAll(INITIAL_CURRENT_USER_ID);
+ expectDisplayAssignedToUser(INITIAL_CURRENT_USER_ID, INVALID_DISPLAY);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 5246107..5dd29fd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -39,6 +39,7 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioManager;
import android.os.Looper;
@@ -96,6 +97,7 @@
private int mTvPhysicalAddress;
private int mTvLogicalAddress;
private boolean mWokenUp;
+ private boolean mEarcBlocksArc;
private List<DeviceEventListener> mDeviceEventListeners = new ArrayList<>();
private class DeviceEventListener {
@@ -156,6 +158,11 @@
}
@Override
+ boolean isPowerStandbyOrTransient() {
+ return false;
+ }
+
+ @Override
AudioManager getAudioManager() {
return mAudioManager;
}
@@ -164,6 +171,11 @@
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceEventListeners.add(new DeviceEventListener(device, status));
}
+
+ @Override
+ protected boolean earcBlocksArcConnection() {
+ return mEarcBlocksArc;
+ }
};
mHdmiControlService.setIoLooper(mMyLooper);
@@ -175,16 +187,18 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false, false);
hdmiPortInfos[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mTvPhysicalAddress = 0x0000;
+ mEarcBlocksArc = false;
mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
@@ -196,6 +210,20 @@
mNativeWrapper.clearResultMessages();
}
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+
+ private int getResult() {
+ assertThat(mCallbackResult.size()).isEqualTo(1);
+ return mCallbackResult.get(0);
+ }
+ }
+
@Test
public void initialPowerStateIsStandby() {
assertThat(mHdmiCecLocalDeviceTv.getPowerStatus()).isEqualTo(
@@ -426,9 +454,10 @@
public void startArcAction_enable_portDoesNotSupportArc() {
// Emulate Audio device on port 0x1000 (does not support ARC)
mNativeWrapper.setPortConnectionStatus(1, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mHdmiCecLocalDeviceTv.startArcAction(true);
HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
@@ -445,9 +474,10 @@
public void startArcAction_disable_portDoesNotSupportArc() {
// Emulate Audio device on port 0x1000 (does not support ARC)
mNativeWrapper.setPortConnectionStatus(1, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mHdmiCecLocalDeviceTv.startArcAction(false);
HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
@@ -464,9 +494,10 @@
public void startArcAction_enable_portSupportsArc() {
// Emulate Audio device on port 0x2000 (supports ARC)
mNativeWrapper.setPortConnectionStatus(2, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv.startArcAction(true);
@@ -485,9 +516,10 @@
public void startArcAction_disable_portSupportsArc() {
// Emulate Audio device on port 0x2000 (supports ARC)
mNativeWrapper.setPortConnectionStatus(2, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv.startArcAction(false);
@@ -522,9 +554,10 @@
public void handleInitiateArc_portDoesNotSupportArc() {
// Emulate Audio device on port 0x1000 (does not support ARC)
mNativeWrapper.setPortConnectionStatus(1, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
ADDR_AUDIO_SYSTEM,
@@ -544,9 +577,10 @@
public void handleInitiateArc_portSupportsArc() {
// Emulate Audio device on port 0x2000 (supports ARC)
mNativeWrapper.setPortConnectionStatus(2, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
@@ -575,6 +609,66 @@
}
@Test
+ public void handleTerminateArc_noAudioDevice() {
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(terminateArc);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+ }
+
+ @Test
+ public void handleTerminateArc_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(terminateArc);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+ }
+
+ @Test
+ public void handleTerminateArc_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(terminateArc);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+ }
+
+ @Test
public void supportsRecordTvScreen() {
HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_RECORDER_1, mTvLogicalAddress,
Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
@@ -595,9 +689,10 @@
HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
// Emulate Audio device on port 0x1000 (does not support ARC)
mNativeWrapper.setPortConnectionStatus(1, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
HdmiCecFeatureAction systemAudioAutoInitiationAction =
@@ -636,9 +731,9 @@
public void hotplugDetectionAction_discoversDeviceAfterMessageReceived() {
// Playback 1 sends a message before ACKing a poll
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildActiveSource(
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
ADDR_PLAYBACK_1, ADDR_TV);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mNativeWrapper.onCecMessage(activeSource);
mTestLooper.dispatchAll();
// Playback 1 begins ACKing polls, allowing detection by HotplugDetectionAction
@@ -812,12 +907,12 @@
mTestLooper.dispatchAll();
// <Feature Abort>[Not in correct mode] not sent
- HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
ADDR_TV,
ADDR_PLAYBACK_1,
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
Constants.ABORT_NOT_IN_CORRECT_MODE);
- assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
// <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
@@ -838,12 +933,12 @@
mTestLooper.dispatchAll();
// <Feature Abort>[Not in correct mode] sent
- HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
ADDR_TV,
ADDR_PLAYBACK_1,
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
Constants.ABORT_NOT_IN_CORRECT_MODE);
- assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
// AudioManager not notified of volume change
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
@@ -853,11 +948,11 @@
@Test
public void tvSendRequestArcTerminationOnSleep() {
// Emulate Audio device on port 0x2000 (supports ARC)
-
mNativeWrapper.setPortConnectionStatus(2, true);
- HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mNativeWrapper.onCecMessage(hdmiCecMessage);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv.startArcAction(true);
@@ -898,4 +993,560 @@
assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
}
+ @Test
+ public void startArcAction_enable_earcBlocksArc() {
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = true;
+
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_enable_earcDoesNotBlockArc() {
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = false;
+
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_disable_earcBlocksArc() {
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = true;
+
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
+
+ @Test
+ public void handleInitiateArc_earcBlocksArc() {
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = true;
+
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM,
+ Constants.MESSAGE_INITIATE_ARC,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+ }
+
+ @Test
+ public void handleInitiateArc_earcDoesNotBlockArc() {
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = false;
+
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ // <Report ARC Initiated> should only be sent after SAD querying is done
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+
+ // Finish querying SADs
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+ }
+
+ @Test
+ public void handleTerminateArc_earcBlocksArc() {
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = true;
+
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(terminateArc);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+ }
+
+ @Test
+ public void startArcAction_initiation_noAvr() {
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void startArcAction_initiation_portNotConnected() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+ // Emulate port disconnect
+ mNativeWrapper.setPortConnectionStatus(2, false);
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+ }
+
+ @Test
+ public void startArcAction_initiation_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (Doesn´t support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+ }
+
+ @Test
+ public void startArcAction_initiation_indirectPhysicalAddress() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2320, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+ }
+
+ @Test
+ public void startArcAction_initiation_earcBlocksArc() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ mEarcBlocksArc = true;
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+ }
+
+ @Test
+ public void startArcAction_initiation_messageNotAcked() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setMessageSendResult(
+ Constants.MESSAGE_REQUEST_ARC_INITIATION, SendMessageResult.NACK);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void startArcAction_initiation_timeout() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
+ }
+
+ @Test
+ public void startArcAction_initiation_featureAbort() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV,
+ Constants.MESSAGE_REQUEST_ARC_INITIATION,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ mNativeWrapper.onCecMessage(featureAbort);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void startArcAction_initiation_success() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+ mNativeWrapper.onCecMessage(initiateArc);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void startArcAction_termination_noAvr() {
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void startArcAction_termination_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (Doesn´t support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+ }
+
+ @Test
+ public void startArcAction_termination_messageNotAcked() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setMessageSendResult(
+ Constants.MESSAGE_REQUEST_ARC_TERMINATION, SendMessageResult.NACK);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void startArcAction_termination_timeout() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
+ }
+
+ @Test
+ public void startArcAction_termination_featureAbort() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV,
+ Constants.MESSAGE_REQUEST_ARC_TERMINATION,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ mNativeWrapper.onCecMessage(featureAbort);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void startArcAction_termination_success() {
+ // Emulate Audio device on port 0x2000 (Supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+ mNativeWrapper.onCecMessage(terminateArc);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void enableEarc_terminateArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(initiateArc);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ // <Report ARC Initiated> should only be sent after SAD querying is done
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+
+ // Finish querying SADs
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 2b555a0..aa49a62 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -30,9 +30,11 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -143,6 +145,7 @@
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiControlServiceSpy.setAudioManager(mAudioManager);
+ mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
}
@@ -1087,7 +1090,6 @@
@Test
public void disableEarc_clearEarcLocalDevice() {
- mHdmiControlServiceSpy.setEarcSupported(true);
mHdmiControlServiceSpy.clearEarcLocalDevice();
mHdmiControlServiceSpy.addEarcLocalDevice(
new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy));
@@ -1100,7 +1102,6 @@
@Test
public void disableCec_doNotClearEarcLocalDevice() {
- mHdmiControlServiceSpy.setEarcSupported(true);
mHdmiControlServiceSpy.clearEarcLocalDevice();
mHdmiControlServiceSpy.addEarcLocalDevice(
new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy));
@@ -1113,7 +1114,6 @@
@Test
public void enableCec_initializeCecLocalDevices() {
- mHdmiControlServiceSpy.setEarcSupported(true);
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
@@ -1125,7 +1125,6 @@
@Test
public void enableEarc_initializeEarcLocalDevices() {
- mHdmiControlServiceSpy.setEarcSupported(true);
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
@@ -1137,7 +1136,6 @@
@Test
public void disableCec_DoNotInformHalAboutEarc() {
- mHdmiControlServiceSpy.setEarcSupported(true);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
@@ -1147,7 +1145,7 @@
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean());
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean(), anyBoolean());
}
@Test
@@ -1155,15 +1153,14 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false);
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true);
+ verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false);
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
}
@Test
@@ -1171,14 +1168,13 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean());
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean(), anyBoolean());
}
@Test
@@ -1186,15 +1182,14 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true);
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false);
+ verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, true);
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean());
}
@Test
@@ -1202,13 +1197,15 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.initService();
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true);
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false);
+ verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, false);
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean());
}
@Test
@@ -1216,13 +1213,12 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.initService();
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false);
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true);
+ verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false);
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
}
@Test
@@ -1230,13 +1226,12 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true);
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false);
+ verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, false);
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean());
}
@Test
@@ -1244,13 +1239,106 @@
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
- mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
- verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false);
- verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true);
+ verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false);
+ verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
+ }
+
+ @Test
+ public void earcIdle_blocksArcConnection() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue();
+ }
+
+ @Test
+ public void earcPending_blocksArcConnection() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue();
+ }
+
+ @Test
+ public void earcEnabled_blocksArcConnection() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue();
+ }
+
+ @Test
+ public void arcPending_doesNotBlockArcConnection() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isFalse();
+ }
+
+ @Test
+ public void earcStatusBecomesIdle_terminateArc() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE);
+ verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any());
+ }
+
+ @Test
+ public void earcStatusBecomesEnabled_doNothing() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED);
+ verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+ }
+
+ @Test
+ public void earcStatusBecomesPending_doNothing() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING);
+ verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+ }
+
+ @Test
+ public void earcStatusBecomesNotEnabled_initiateArc() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+ verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any());
+ }
+
+ @Test
+ public void earcStateWasArcPending_becomesEarcPending_terminateArc() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+ mTestLooper.dispatchAll();
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING);
+ verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any());
+ }
+
+ @Test
+ public void earcStateWasArcPending_becomesEarcEnabled_terminateArc() {
+ mHdmiControlServiceSpy.clearEarcLocalDevice();
+ HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+ mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+ mTestLooper.dispatchAll();
+ localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED);
+ verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any());
}
protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
diff --git a/services/tests/servicestests/src/com/android/server/job/JobSetTest.java b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java
index 62cc111..baa5421 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobSetTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java
@@ -79,7 +79,7 @@
.setRequiresCharging(true)
.build();
return JobStatus.createFromJobInfo(jobInfo, callingUid, mContext.getPackageName(),
- mContext.getUserId(), "Test");
+ mContext.getUserId(), "Namespace", "Test");
}
@Test
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 0589b3a..d90f53a 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -135,8 +135,8 @@
.build();
final int uid1 = SOME_UID;
final int uid2 = uid1 + 1;
- final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
- final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
runWritingJobsToDisk(JobStatus1, JobStatus2);
// Remove 1 job
@@ -188,8 +188,8 @@
.build();
final int uid1 = SOME_UID;
final int uid2 = uid1 + 1;
- final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
- final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
runWritingJobsToDisk(JobStatus1, JobStatus2);
// Remove all jobs
@@ -265,7 +265,7 @@
.setMinimumLatency(runFromMillis)
.setPersisted(true)
.build();
- final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
+ final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null, null);
ts.addInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
mTaskStoreUnderTest.add(ts);
waitForPendingIo();
@@ -308,8 +308,10 @@
.build();
final int uid1 = SOME_UID;
final int uid2 = uid1 + 1;
- final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
- final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ final JobStatus taskStatus1 =
+ JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+ final JobStatus taskStatus2 =
+ JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
runWritingJobsToDisk(taskStatus1, taskStatus2);
}
@@ -364,7 +366,7 @@
extras.putInt("into", 3);
b.setExtras(extras);
final JobInfo task = b.build();
- JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
+ JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -384,7 +386,7 @@
.setRequiresCharging(true)
.setPersisted(true);
JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID,
- "com.google.android.gms", 0, null);
+ "com.android.test.app", 0, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -406,7 +408,8 @@
.setPeriodic(5*60*60*1000, 1*60*60*1000)
.setRequiresCharging(true)
.setPersisted(true);
- JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -435,7 +438,7 @@
invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period).
final Pair<Long, Long> persistedExecutionTimesUTC = new Pair<>(rtcNow, rtcNow + ONE_HOUR);
final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage",
- 0 /* sourceUserId */, 0, "someTag",
+ 0 /* sourceUserId */, 0, "someNamespace", "someTag",
invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */);
@@ -464,7 +467,7 @@
.setOverrideDeadline(5000)
.setBias(42)
.setPersisted(true);
- final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(js);
waitForPendingIo();
@@ -475,13 +478,30 @@
}
@Test
+ public void testNamespacePersisted() throws Exception {
+ final String namespace = "my.test.namespace";
+ JobInfo.Builder b = new Builder(93, mComponent)
+ .setRequiresBatteryNotLow(true)
+ .setPersisted(true);
+ final JobStatus js =
+ JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, namespace, null);
+ mTaskStoreUnderTest.add(js);
+ waitForPendingIo();
+
+ final JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+ assertEquals("Namespace not correctly persisted.", namespace, loaded.getNamespace());
+ }
+
+ @Test
public void testPriorityPersisted() throws Exception {
final JobInfo job = new Builder(92, mComponent)
.setOverrideDeadline(5000)
.setPriority(JobInfo.PRIORITY_MIN)
.setPersisted(true)
.build();
- final JobStatus js = JobStatus.createFromJobInfo(job, SOME_UID, null, -1, null);
+ final JobStatus js = JobStatus.createFromJobInfo(job, SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(js);
waitForPendingIo();
@@ -500,12 +520,14 @@
JobInfo.Builder b = new Builder(42, mComponent)
.setOverrideDeadline(10000)
.setPersisted(false);
- JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(jsNonPersisted);
b = new Builder(43, mComponent)
.setOverrideDeadline(10000)
.setPersisted(true);
- JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(jsPersisted);
waitForPendingIo();
@@ -593,7 +615,8 @@
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPersisted(true);
- JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -612,7 +635,8 @@
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresCharging(true)
.setPersisted(true);
- JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -631,7 +655,8 @@
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresStorageNotLow(true)
.setPersisted(true);
- JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -650,7 +675,8 @@
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresBatteryNotLow(true)
.setPersisted(true);
- JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+ SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(taskStatus);
waitForPendingIo();
@@ -670,7 +696,7 @@
*/
private void assertPersistedEquals(JobInfo firstInfo) throws Exception {
mTaskStoreUnderTest.clear();
- JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null);
+ JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null);
mTaskStoreUnderTest.add(first);
waitForPendingIo();
@@ -693,6 +719,8 @@
assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
+ assertEquals(expected.getNamespace(), actual.getNamespace());
+
assertEquals("Internal flags not equal",
expected.getInternalFlags(), actual.getInternalFlags());
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index b7faf22..3268df2 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -28,7 +28,7 @@
import android.platform.test.annotations.LargeTest;
import android.util.ArraySet;
import android.util.Log;
-import android.util.SparseArray;
+import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
@@ -54,8 +54,13 @@
private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
int callingUid) {
+ return createJobStatus(testTag, jobInfoBuilder, callingUid, "PJQTest");
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
+ int callingUid, String namespace) {
return JobStatus.createFromJobInfo(
- jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
+ jobInfoBuilder.build(), callingUid, "com.android.test", 0, namespace, testTag);
}
@Test
@@ -373,12 +378,12 @@
jobQueue.add(rC10);
jobQueue.add(eC11);
- checkPendingJobInvariants(jobQueue);
JobStatus job;
final JobStatus[] expectedPureOrder = new JobStatus[]{
eC3, rD4, eE5, eB6, rB2, eA7, rA1, rH8, eF9, rF8, eC11, rC10, rG12, rG13, eE14};
int idx = 0;
jobQueue.setOptimizeIteration(false);
+ checkPendingJobInvariants(jobQueue);
jobQueue.resetIterator();
while ((job = jobQueue.next()) != null) {
assertEquals("List wasn't correctly sorted @ index " + idx,
@@ -390,6 +395,93 @@
eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13};
idx = 0;
jobQueue.setOptimizeIteration(true);
+ checkPendingJobInvariants(jobQueue);
+ jobQueue.resetIterator();
+ while ((job = jobQueue.next()) != null) {
+ assertEquals("Optimized list wasn't correctly sorted @ index " + idx,
+ expectedOptimizedOrder[idx].getJobId(), job.getJobId());
+ idx++;
+ }
+ }
+
+ @Test
+ public void testPendingJobSorting_namespacing() {
+ PendingJobQueue jobQueue = new PendingJobQueue();
+
+ // First letter in job variable name indicate regular (r) or expedited (e).
+ // Capital letters in job variable name indicate the app/UID.
+ // Third letter (x, y, z) indicates the namespace.
+ // Numbers in job variable name indicate the enqueue time.
+ // Expected sort order:
+ // eCx3 > rDx4 > eBy6 > rBy2 > eAy7 > rAx1 > eCy8 > rEz9 > rEz5
+ // Intentions:
+ // * A jobs test expedited is before regular, regardless of namespace
+ // * B jobs test expedited is before regular, in the same namespace
+ // * C jobs test sorting by priority with different namespaces
+ // * E jobs test sorting by priority in the same namespace
+ final String namespaceX = null;
+ final String namespaceY = "y";
+ final String namespaceZ = "z";
+ JobStatus rAx1 = createJobStatus("testPendingJobSorting",
+ createJobInfo(1), 1, namespaceX);
+ JobStatus rBy2 = createJobStatus("testPendingJobSorting",
+ createJobInfo(2), 2, namespaceY);
+ JobStatus eCx3 = createJobStatus("testPendingJobSorting",
+ createJobInfo(3).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH),
+ 3, namespaceX);
+ JobStatus rDx4 = createJobStatus("testPendingJobSorting",
+ createJobInfo(4), 4, namespaceX);
+ JobStatus rEz5 = createJobStatus("testPendingJobSorting",
+ createJobInfo(5).setPriority(JobInfo.PRIORITY_LOW), 5, namespaceZ);
+ JobStatus eBy6 = createJobStatus("testPendingJobSorting",
+ createJobInfo(6).setExpedited(true), 2, namespaceY);
+ JobStatus eAy7 = createJobStatus("testPendingJobSorting",
+ createJobInfo(7).setExpedited(true), 1, namespaceY);
+ JobStatus eCy8 = createJobStatus("testPendingJobSorting",
+ createJobInfo(8).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX),
+ 3, namespaceY);
+ JobStatus rEz9 = createJobStatus("testPendingJobSorting",
+ createJobInfo(9).setPriority(JobInfo.PRIORITY_HIGH), 5, namespaceZ);
+
+ rAx1.enqueueTime = 10;
+ rBy2.enqueueTime = 20;
+ eCx3.enqueueTime = 30;
+ rDx4.enqueueTime = 40;
+ rEz5.enqueueTime = 50;
+ eBy6.enqueueTime = 60;
+ eAy7.enqueueTime = 70;
+ eCy8.enqueueTime = 80;
+ rEz9.enqueueTime = 90;
+
+ // Add in random order so sorting is apparent.
+ jobQueue.add(rEz9);
+ jobQueue.add(eCy8);
+ jobQueue.add(rDx4);
+ jobQueue.add(rEz5);
+ jobQueue.add(rBy2);
+ jobQueue.add(rAx1);
+ jobQueue.add(eCx3);
+ jobQueue.add(eBy6);
+ jobQueue.add(eAy7);
+
+ JobStatus job;
+ final JobStatus[] expectedPureOrder = new JobStatus[]{
+ eCx3, rDx4, eBy6, rBy2, eAy7, rAx1, eCy8, rEz9, rEz5};
+ int idx = 0;
+ jobQueue.setOptimizeIteration(false);
+ checkPendingJobInvariants(jobQueue);
+ jobQueue.resetIterator();
+ while ((job = jobQueue.next()) != null) {
+ assertEquals("List wasn't correctly sorted @ index " + idx,
+ expectedPureOrder[idx].getJobId(), job.getJobId());
+ idx++;
+ }
+
+ final JobStatus[] expectedOptimizedOrder = new JobStatus[]{
+ eCx3, eCy8, rDx4, eBy6, rBy2, eAy7, rAx1, rEz9, rEz5};
+ idx = 0;
+ jobQueue.setOptimizeIteration(true);
+ checkPendingJobInvariants(jobQueue);
jobQueue.resetIterator();
while ((job = jobQueue.next()) != null) {
assertEquals("Optimized list wasn't correctly sorted @ index " + idx,
@@ -414,6 +506,22 @@
}
@Test
+ public void testPendingJobSorting_Random_namespacing() {
+ PendingJobQueue jobQueue = new PendingJobQueue();
+ Random random = new Random(1); // Always use the same series of pseudo random values.
+
+ for (int i = 0; i < 5000; ++i) {
+ JobStatus job = createJobStatus("testPendingJobSorting_Random",
+ createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250),
+ "namespace" + random.nextInt(5));
+ job.enqueueTime = random.nextInt(1_000_000);
+ jobQueue.add(job);
+ }
+
+ checkPendingJobInvariants(jobQueue);
+ }
+
+ @Test
public void testPendingJobSortingTransitivity() {
PendingJobQueue jobQueue = new PendingJobQueue();
// Always use the same series of pseudo random values.
@@ -546,10 +654,11 @@
private void checkPendingJobInvariants(PendingJobQueue jobQueue) {
final SparseBooleanArray regJobSeen = new SparseBooleanArray();
- // Latest priority enqueue times seen for each priority for each app.
- final SparseArray<SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
- new SparseArray<>();
- final SparseArray<SparseLongArray> latestPriorityEjEnqueueTimesPerUid = new SparseArray<>();
+ // Latest priority enqueue times seen for each priority+namespace for each app.
+ final SparseArrayMap<String, SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
+ new SparseArrayMap();
+ final SparseArrayMap<String, SparseLongArray> latestPriorityEjEnqueueTimesPerUid =
+ new SparseArrayMap<>();
final int noEntry = -1;
int prevOverrideState = noEntry;
@@ -579,11 +688,12 @@
}
final int priority = job.getEffectivePriority();
- final SparseArray<SparseLongArray> latestPriorityEnqueueTimesPerUid =
+ final SparseArrayMap<String, SparseLongArray> latestPriorityEnqueueTimesPerUid =
job.isRequestedExpeditedJob()
? latestPriorityEjEnqueueTimesPerUid
: latestPriorityRegEnqueueTimesPerUid;
- SparseLongArray latestPriorityEnqueueTimes = latestPriorityEnqueueTimesPerUid.get(uid);
+ SparseLongArray latestPriorityEnqueueTimes =
+ latestPriorityEnqueueTimesPerUid.get(uid, job.getNamespace());
if (latestPriorityEnqueueTimes != null) {
// Invariant 2
for (int p = priority - 1; p >= JobInfo.PRIORITY_MIN; --p) {
@@ -603,7 +713,8 @@
}
} else {
latestPriorityEnqueueTimes = new SparseLongArray();
- latestPriorityEnqueueTimesPerUid.put(uid, latestPriorityEnqueueTimes);
+ latestPriorityEnqueueTimesPerUid.add(
+ uid, job.getNamespace(), latestPriorityEnqueueTimes);
}
latestPriorityEnqueueTimes.put(priority, job.enqueueTime);
@@ -618,7 +729,7 @@
}
private static String testJobToString(JobStatus job) {
- return "testJob " + job.getSourceUid() + "/" + job.getJobId()
+ return "testJob " + job.getSourceUid() + "/" + job.getNamespace() + "/" + job.getJobId()
+ "/o" + job.overrideState
+ "/p" + job.getEffectivePriority()
+ "/b" + job.lastEvaluatedBias
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index d3b647d..f0f0632 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -154,7 +154,7 @@
storageManager, spManager, gsiService, recoverableKeyStoreManager,
userManagerInternal, deviceStateCache));
mGateKeeperService = gatekeeper;
- mAuthSecretServiceAidl = authSecretService;
+ mAuthSecretService = authSecretService;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 95d0e15..03d5b17 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -389,11 +389,11 @@
@Test
public void testPersistentData_serializeUnserialize() {
- byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP, SOME_USER_ID,
+ byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID,
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
PersistentData deserialized = PersistentData.fromBytes(serialized);
- assertEquals(PersistentData.TYPE_SP, deserialized.type);
+ assertEquals(PersistentData.TYPE_SP_GATEKEEPER, deserialized.type);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, deserialized.qualityForUi);
assertArrayEquals(PAYLOAD, deserialized.payload);
}
@@ -424,13 +424,13 @@
// the wire format in the future.
byte[] serializedVersion1 = new byte[] {
1, /* PersistentData.VERSION_1 */
- 1, /* PersistentData.TYPE_SP */
+ 1, /* PersistentData.TYPE_SP_GATEKEEPER */
0x00, 0x00, 0x04, 0x0A, /* SOME_USER_ID */
0x00, 0x03, 0x00, 0x00, /* PASSWORD_NUMERIC_COMPLEX */
1, 2, -1, -2, 33, /* PAYLOAD */
};
PersistentData deserialized = PersistentData.fromBytes(serializedVersion1);
- assertEquals(PersistentData.TYPE_SP, deserialized.type);
+ assertEquals(PersistentData.TYPE_SP_GATEKEEPER, deserialized.type);
assertEquals(SOME_USER_ID, deserialized.userId);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
deserialized.qualityForUi);
@@ -438,7 +438,7 @@
// Make sure the constants we use on the wire do not change.
assertEquals(0, PersistentData.TYPE_NONE);
- assertEquals(1, PersistentData.TYPE_SP);
+ assertEquals(1, PersistentData.TYPE_SP_GATEKEEPER);
assertEquals(2, PersistentData.TYPE_SP_WEAVER);
}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index fdf69430..f90eabc 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
@@ -822,4 +823,35 @@
}
return Integer.MAX_VALUE;
}
+
+ /**
+ * Check if calling user is associated with the given subscription.
+ * @param context Context
+ * @param subId subscription ID
+ * @param callerUserHandle caller user handle
+ * @return false if user is not associated with the subscription.
+ */
+ public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
+ @NonNull UserHandle callerUserHandle) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ // No subscription on device, return true.
+ return true;
+ }
+
+ SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if ((subManager != null) &&
+ (!subManager.isSubscriptionAssociatedWithUser(subId, callerUserHandle))) {
+ // If subId is not associated with calling user, return false.
+ Log.e(LOG_TAG,"User[User ID:" + callerUserHandle.getIdentifier()
+ + "] is not associated with Subscription ID:" + subId);
+ return false;
+
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index c53b463..e6f1349 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2349,6 +2349,7 @@
RESULT_SMS_SEND_RETRY_FAILED,
RESULT_REMOTE_EXCEPTION,
RESULT_NO_DEFAULT_SMS_APP,
+ RESULT_USER_NOT_ALLOWED,
RESULT_RIL_RADIO_NOT_AVAILABLE,
RESULT_RIL_SMS_SEND_FAIL_RETRY,
RESULT_RIL_NETWORK_REJECT,
@@ -2543,6 +2544,13 @@
*/
public static final int RESULT_NO_DEFAULT_SMS_APP = 32;
+ /**
+ * User is not associated with the subscription.
+ * TODO(b/263279115): Make this error code public.
+ * @hide
+ */
+ public static final int RESULT_USER_NOT_ALLOWED = 33;
+
// Radio Error results
/**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4afc943..0638189 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4418,4 +4418,69 @@
}
return null;
}
+
+ /**
+ * Check if subscription and user are associated with each other.
+ *
+ * @param subscriptionId the subId of the subscription
+ * @param userHandle user handle of the user
+ * @return {@code true} if subscription is associated with user
+ * {code true} if there are no subscriptions on device
+ * else {@code false} if subscription is not associated with user.
+ *
+ * @throws IllegalArgumentException if subscription is invalid.
+ * @throws SecurityException if the caller doesn't have permissions required.
+ * @throws IllegalStateException if subscription service is not available.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+ public boolean isSubscriptionAssociatedWithUser(int subscriptionId,
+ @NonNull UserHandle userHandle) {
+ if (!isValidSubscriptionId(subscriptionId)) {
+ throw new IllegalArgumentException("[isSubscriptionAssociatedWithUser]: "
+ + "Invalid subscriptionId: " + subscriptionId);
+ }
+
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ return iSub.isSubscriptionAssociatedWithUser(subscriptionId, userHandle);
+ } else {
+ throw new IllegalStateException("[isSubscriptionAssociatedWithUser]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ return false;
+ }
+
+ /**
+ * Get list of subscriptions associated with user.
+ *
+ * @param userHandle user handle of the user
+ * @return list of subscriptionInfo associated with the user.
+ *
+ * @throws SecurityException if the caller doesn't have permissions required.
+ * @throws IllegalStateException if subscription service is not available.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+ public @NonNull List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
+ @NonNull UserHandle userHandle) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ return iSub.getSubscriptionInfoListAssociatedWithUser(userHandle);
+ } else {
+ throw new IllegalStateException("[getSubscriptionInfoListAssociatedWithUser]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ return new ArrayList<>();
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index c5f6902..25a714a 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -326,4 +326,34 @@
* @throws IllegalArgumentException if subId is invalid.
*/
UserHandle getSubscriptionUserHandle(int subId);
+
+ /**
+ * Check if subscription and user are associated with each other.
+ *
+ * @param subscriptionId the subId of the subscription
+ * @param userHandle user handle of the user
+ * @return {@code true} if subscription is associated with user
+ * {code true} if there are no subscriptions on device
+ * else {@code false} if subscription is not associated with user.
+ *
+ * @throws IllegalArgumentException if subscription is invalid.
+ * @throws SecurityException if the caller doesn't have permissions required.
+ * @throws IllegalStateException if subscription service is not available.
+ *
+ * @hide
+ */
+ boolean isSubscriptionAssociatedWithUser(int subscriptionId, in UserHandle userHandle);
+
+ /**
+ * Get list of subscriptions associated with user.
+ *
+ * @param userHandle user handle of the user
+ * @return list of subscriptionInfo associated with the user.
+ *
+ * @throws SecurityException if the caller doesn't have permissions required.
+ * @throws IllegalStateException if subscription service is not available.
+ *
+ * @hide
+ */
+ List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(in UserHandle userHandle);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index f9a245a..f26ce25 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -22,6 +22,8 @@
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.IComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
/**
* Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
@@ -202,13 +204,15 @@
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
* of the SF trace
*/
-fun FlickerTest.statusBarLayerPositionAtStart() {
+fun FlickerTest.statusBarLayerPositionAtStart(
+ wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
+) {
+ // collect navbar position for the equivalent WM state
+ val state = wmTrace?.firstOrNull() ?: error("WM state missing in $this")
+ val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
+ val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display)
assertLayersStart {
- val display =
- this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
- this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
- .coversExactly(WindowUtils.getStatusBarPosition(display))
+ this.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(navBarPosition)
}
}
@@ -216,13 +220,15 @@
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
* the SF trace
*/
-fun FlickerTest.statusBarLayerPositionAtEnd() {
+fun FlickerTest.statusBarLayerPositionAtEnd(
+ wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
+) {
+ // collect navbar position for the equivalent WM state
+ val state = wmTrace?.lastOrNull() ?: error("WM state missing in $this")
+ val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
+ val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display)
assertLayersEnd {
- val display =
- this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
- this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
- .coversExactly(WindowUtils.getStatusBarPosition(display))
+ this.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(navBarPosition)
}
}
diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
index afaeca1..00fc498 100644
--- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
+++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
@@ -208,6 +208,6 @@
.setPersisted(true)
.build();
return JobStatus.createFromJobInfo(
- jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, null, testTag);
}
}