summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java71
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl18
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java3
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java20
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java50
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java19
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java24
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java238
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java104
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java15
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java15
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobSetTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java133
-rw-r--r--tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java2
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);
}
}