diff options
29 files changed, 699 insertions, 191 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index 4242cf843874..5d67c96213a5 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.JobScheduler; 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 @@ import java.util.List; 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 @@ public class JobSchedulerImpl extends JobScheduler { @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 @@ public class JobSchedulerImpl extends JobScheduler { @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 @@ public class JobSchedulerImpl extends JobScheduler { @Override public void cancel(int jobId) { try { - mBinder.cancel(jobId); + mBinder.cancel(mNamespace, jobId); } catch (RemoteException e) {} - } @Override public void cancelAll() { try { - mBinder.cancelAll(); + 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 @@ public class JobSchedulerImpl extends JobScheduler { @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 @@ public class JobSchedulerImpl extends JobScheduler { @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 c87a2aff7dde..a1f19542c4ab 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.JobInfo; 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 deb97a5873ab..3bbbb15ee32e 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 @@ public class JobInfo implements Parcelable { * 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 a5a7f93137d6..18ddffbaf885 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 @@ public class JobParameters implements Parcelable { @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 @@ public class JobParameters implements Parcelable { 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 @@ public class JobParameters implements Parcelable { this.mTriggeredContentUris = triggeredContentUris; this.mTriggeredContentAuthorities = triggeredContentAuthorities; this.network = network; + this.mJobNamespace = namespace; } /** @@ -322,6 +325,19 @@ public class JobParameters implements Parcelable { } /** + * 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 @@ public class JobParameters implements Parcelable { private JobParameters(Parcel in) { jobId = in.readInt(); + mJobNamespace = in.readString(); extras = in.readPersistableBundle(); transientExtras = in.readBundle(); if (in.readInt() != 0) { @@ -581,6 +598,7 @@ public class JobParameters implements Parcelable { @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 659db9f7b6e3..0604c5403dba 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 android.os.PersistableBundle; 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 @@ public abstract class JobScheduler { } /** + * 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 class JobScheduler { 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 class JobScheduler { 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 afcbe7d8eb3d..311a9b2e8155 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 @@ public class UserVisibleJobSummary implements Parcelable { 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 @@ public class UserVisibleJobSummary implements Parcelable { mCallingUid = in.readInt(); mSourceUserId = in.readInt(); mSourcePackageName = in.readString(); + mNamespace = in.readString(); mJobId = in.readInt(); } @@ -55,6 +62,10 @@ public class UserVisibleJobSummary implements Parcelable { return mJobId; } + public String getNamespace() { + return mNamespace; + } + public int getSourceUserId() { return mSourceUserId; } @@ -71,6 +82,7 @@ public class UserVisibleJobSummary implements Parcelable { return mCallingUid == that.mCallingUid && mSourceUserId == that.mSourceUserId && mSourcePackageName.equals(that.mSourcePackageName) + && Objects.equals(mNamespace, that.mNamespace) && mJobId == that.mJobId; } @@ -80,6 +92,9 @@ public class UserVisibleJobSummary implements Parcelable { 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 @@ public class UserVisibleJobSummary implements Parcelable { + "callingUid=" + mCallingUid + ", sourceUserId=" + mSourceUserId + ", sourcePackageName='" + mSourcePackageName + "'" + + ", namespace=" + mNamespace + ", jobId=" + mJobId + "}"; } @@ -104,6 +120,7 @@ public class UserVisibleJobSummary implements Parcelable { 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 397d2c4ca679..b806ef88f2d7 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 @@ class JobConcurrencyManager { 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 @@ class JobConcurrencyManager { @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 @@ class JobConcurrencyManager { */ @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 @@ class JobConcurrencyManager { */ @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 @@ class JobConcurrencyManager { 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 @@ class JobConcurrencyManager { 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 1898e499f7e5..eb9a3a231788 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.Log; 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.Arrays; 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 @@ public class JobSchedulerService extends com.android.server.SystemService @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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService } 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 @@ public class JobSchedulerService extends com.android.server.SystemService } } - 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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); - 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); + 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); + 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 @@ public class JobSchedulerService extends com.android.server.SystemService } } 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 @@ public class JobSchedulerService extends com.android.server.SystemService @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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService * 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 @@ public class JobSchedulerService extends com.android.server.SystemService } 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 @@ public class JobSchedulerService extends com.android.server.SystemService * @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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService /** 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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. @@ -3714,7 +3758,7 @@ public class JobSchedulerService extends com.android.server.SystemService // 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()); } @@ -3732,10 +3776,14 @@ public class JobSchedulerService extends com.android.server.SystemService validateJob(job, uid); + 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); } @@ -3743,7 +3791,7 @@ public class JobSchedulerService extends com.android.server.SystemService // 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); } @@ -3760,18 +3808,22 @@ public class JobSchedulerService extends com.android.server.SystemService validateJob(job, uid, work); + 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() @@ -3791,46 +3843,70 @@ public class JobSchedulerService extends com.android.server.SystemService validateJob(job, callerUid); + 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); } @@ -3853,12 +3929,29 @@ public class JobSchedulerService extends com.android.server.SystemService } @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); @@ -4036,8 +4129,9 @@ public class JobSchedulerService extends com.android.server.SystemService } // 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 { @@ -4048,7 +4142,7 @@ public class JobSchedulerService extends com.android.server.SystemService } 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; } @@ -4078,14 +4172,14 @@ public class JobSchedulerService extends com.android.server.SystemService // 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."); } @@ -4094,7 +4188,7 @@ public class JobSchedulerService extends com.android.server.SystemService } // 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); @@ -4122,7 +4216,8 @@ public class JobSchedulerService extends com.android.server.SystemService } } 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."); } } @@ -4169,8 +4264,8 @@ public class JobSchedulerService extends com.android.server.SystemService } } - 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); @@ -4182,9 +4277,10 @@ public class JobSchedulerService extends com.android.server.SystemService } 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); @@ -4194,8 +4290,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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(); @@ -4216,8 +4312,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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); @@ -4229,9 +4325,10 @@ public class JobSchedulerService extends com.android.server.SystemService } 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); @@ -4241,8 +4338,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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; @@ -4286,7 +4383,8 @@ public class JobSchedulerService extends com.android.server.SystemService } // 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); @@ -4298,11 +4396,17 @@ public class JobSchedulerService extends com.android.server.SystemService } 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; } @@ -4478,7 +4582,9 @@ public class JobSchedulerService extends com.android.server.SystemService } 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 36ba8dd10bd5..2eeb25e46532 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { } } - 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { boolean force = false; boolean satisfied = false; int userId = UserHandle.USER_SYSTEM; + String namespace = null; String opt; while ((opt = getNextOption()) != null) { @@ -169,6 +180,11 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { checkPermission("force timeout jobs"); int userId = UserHandle.USER_ALL; + String namespace = null; String opt; while ((opt = getNextOption()) != null) { @@ -216,6 +234,11 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { checkPermission("cancel jobs"); int userId = UserHandle.USER_SYSTEM; + String namespace = null; String opt; while ((opt = getNextOption()) != null) { @@ -251,6 +276,11 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { checkPermission("get estimated bytes"); int userId = UserHandle.USER_SYSTEM; + String namespace = null; String opt; while ((opt = getNextOption()) != null) { @@ -328,6 +360,11 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { checkPermission("get transferred bytes"); int userId = UserHandle.USER_SYSTEM; + String namespace = null; String opt; while ((opt = getNextOption()) != null) { @@ -377,6 +416,11 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { checkPermission("get job state"); int userId = UserHandle.USER_SYSTEM; + String namespace = null; String opt; while ((opt = getNextOption()) != null) { @@ -414,6 +460,11 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 15fc3c9a5ec6..285b9825d25b 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.EconomicPolicy; 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 @@ public final class JobServiceContext implements ServiceConnection { 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 @@ public final class JobServiceContext implements ServiceConnection { } @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 a1153e315954..5f5f447933f5 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.IOException; 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 @@ public final class JobStore { * @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 @@ public final class JobStore { 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 @@ public final class JobStore { } 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 @@ public final class JobStore { 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 @@ public final class JobStore { 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 0eacfd68e385..36a26f07e08d 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.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.PriorityQueue; /** @@ -280,12 +281,14 @@ class PendingJobQueue { 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 9b6186ea632d..571259943972 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 static com.android.server.job.controllers.FlexibilityController.SYSTEM_WI 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.io.PrintWriter; 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 @@ public final class JobStatus { final int sourceUserId; final int sourceUid; final String sourceTag; + @Nullable + private final String mNamespace; final String tag; @@ -515,6 +519,7 @@ public final class JobStatus { * @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 @@ public final class JobStatus { * @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 final class JobStatus { 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 @@ public final class JobStatus { * 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 @@ public final class JobStatus { 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 @@ public final class JobStatus { * 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 @@ public final class JobStatus { 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 final class JobStatus { } 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 @@ public final class JobStatus { return true; } + public String getNamespace() { + return mNamespace; + } + public String getSourceTag() { return sourceTag; } @@ -1362,7 +1379,8 @@ public final class JobStatus { public UserVisibleJobSummary getUserVisibleJobSummary() { if (mUserVisibleJobSummary == null) { mUserVisibleJobSummary = new UserVisibleJobSummary( - callingUid, getSourceUserId(), getSourcePackageName(), getJobId()); + callingUid, getSourceUserId(), getSourcePackageName(), + getNamespace(), getJobId()); } return mUserVisibleJobSummary; } @@ -1989,8 +2007,12 @@ public final class JobStatus { 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 @@ public final class JobStatus { 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 final class JobStatus { 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/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java index 17eb6e20172e..bbd62c7ae382 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 @@ public class FgsManagerControllerTest extends SysuiTestCase { 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 @@ public class FgsManagerControllerTest extends SysuiTestCase { 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 @@ public class FgsManagerControllerTest extends SysuiTestCase { // 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/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java index 0dfad43599de..79fbc877835c 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 @@ public final class JobConcurrencyManagerTest { 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 2c103298b557..9bf0a7a7912d 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 @@ public class JobSchedulerServiceTest { 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) { @@ -1115,7 +1115,7 @@ public class JobSchedulerServiceTest { 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", "")); } } @@ -1136,7 +1136,7 @@ public class JobSchedulerServiceTest { 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", "")); } } @@ -1157,7 +1157,8 @@ public class JobSchedulerServiceTest { 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", + "")); } } @@ -1181,7 +1182,7 @@ public class JobSchedulerServiceTest { 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 7c435be5ebdd..f334a6adad0a 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 @@ public class BatteryControllerTest { 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 42e22f3f7c2f..32e5c836ac3d 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 @@ public class ConnectivityControllerTest { 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 398acb81378d..1e65b380310f 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 @@ public class FlexibilityControllerTest { 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 7f522b0a1af5..d2ee9fffc2cd 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 @@ public class JobStatusTest { 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 b949b3b265af..fb59ea2bb63b 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 @@ public class PrefetchControllerTest { 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 9aef674ed986..6f713e0fad64 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 @@ public class QuotaControllerTest { 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 51d641bfb80d..b111757a6356 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 @@ public class StateControllerTest { .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 d64c1b31e343..27efcfabea0c 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 @@ public class TimeControllerTest { 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 90672851cdff..02fdfadb2d8a 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 @@ public class ThermalStatusRestrictionTest { 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/servicestests/src/com/android/server/job/JobSetTest.java b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java index 62cc111e4dae..baa5421597ff 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 @@ public class JobSetTest { .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 0589b3a91225..d90f53ad0e55 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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { .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 @@ public class JobStoreTest { } @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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { 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 @@ public class JobStoreTest { */ 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 @@ public class JobStoreTest { 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 b7faf22a45a2..3268df2f83e6 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.content.ComponentName; 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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { } @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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { } 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 @@ public class PendingJobQueueTest { } } else { latestPriorityEnqueueTimes = new SparseLongArray(); - latestPriorityEnqueueTimesPerUid.put(uid, latestPriorityEnqueueTimes); + latestPriorityEnqueueTimesPerUid.add( + uid, job.getNamespace(), latestPriorityEnqueueTimes); } latestPriorityEnqueueTimes.put(priority, job.enqueueTime); @@ -618,7 +729,7 @@ public class PendingJobQueueTest { } 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/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java index afaeca1f76ae..00fc4982a213 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 @@ public class JobStorePerfTests { .setPersisted(true) .build(); return JobStatus.createFromJobInfo( - jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); + jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, null, testTag); } } |