summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kweku Adams <kwekua@google.com> 2022-11-01 19:15:37 +0000
committer Kweku Adams <kwekua@google.com> 2022-12-20 18:36:14 +0000
commit2ad1bf1a60d8d6d8df6b35ca9bc76f8e2d593b66 (patch)
tree7bea5684220541bc13d05974b376f5a0cc6ae274
parent8f4e50a55ae075f3bd95ded5b29764f5210618eb (diff)
Allow for job namespacing.
Make it possible for apps to put jobs into distinct namespaces. This can be used by libraries to ensure their jobs don't conflict with other libraries or the main app, by large apps that don't want to worry about different parts of the app interfering with each other, by different packages with a shared UID, or even by the system. Bug: 253649458 Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job Test: atest CtsJobSchedulerTestCases:JobParametersTest Test: atest CtsJobSchedulerTestCases:JobSchedulingTest Change-Id: I1bcb80640c9ef8cb7e8ece77b0f265c60d758957
-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);
}
}