Merge "Added priority to JobScheduler scheduling"
This commit is contained in:
committed by
Android (Google) Code Review
commit
17971e383f
@@ -91,6 +91,7 @@ public class JobInfo implements Parcelable {
|
||||
private final long flexMillis;
|
||||
private final long initialBackoffMillis;
|
||||
private final int backoffPolicy;
|
||||
private final int priority;
|
||||
|
||||
/**
|
||||
* Unique job id associated with this class. This is assigned to your job by the scheduler.
|
||||
@@ -113,6 +114,11 @@ public class JobInfo implements Parcelable {
|
||||
return service;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this job needs the device to be plugged in.
|
||||
*/
|
||||
@@ -237,6 +243,7 @@ public class JobInfo implements Parcelable {
|
||||
backoffPolicy = in.readInt();
|
||||
hasEarlyConstraint = in.readInt() == 1;
|
||||
hasLateConstraint = in.readInt() == 1;
|
||||
priority = in.readInt();
|
||||
}
|
||||
|
||||
private JobInfo(JobInfo.Builder b) {
|
||||
@@ -256,6 +263,7 @@ public class JobInfo implements Parcelable {
|
||||
backoffPolicy = b.mBackoffPolicy;
|
||||
hasEarlyConstraint = b.mHasEarlyConstraint;
|
||||
hasLateConstraint = b.mHasLateConstraint;
|
||||
priority = b.mPriority;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -281,6 +289,7 @@ public class JobInfo implements Parcelable {
|
||||
out.writeInt(backoffPolicy);
|
||||
out.writeInt(hasEarlyConstraint ? 1 : 0);
|
||||
out.writeInt(hasLateConstraint ? 1 : 0);
|
||||
out.writeInt(priority);
|
||||
}
|
||||
|
||||
public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
|
||||
@@ -305,6 +314,7 @@ public class JobInfo implements Parcelable {
|
||||
private int mJobId;
|
||||
private PersistableBundle mExtras = PersistableBundle.EMPTY;
|
||||
private ComponentName mJobService;
|
||||
private int mPriority;
|
||||
// Requirements.
|
||||
private boolean mRequiresCharging;
|
||||
private boolean mRequiresDeviceIdle;
|
||||
@@ -337,6 +347,14 @@ public class JobInfo implements Parcelable {
|
||||
mJobId = jobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Builder setPriority(int priority) {
|
||||
mPriority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set optional extras. This is persisted, so we only allow primitive types.
|
||||
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
|
||||
|
||||
@@ -28,10 +28,22 @@ import android.os.PersistableBundle;
|
||||
*/
|
||||
public class JobParameters implements Parcelable {
|
||||
|
||||
/** @hide */
|
||||
public static final int REASON_CANCELED = 0;
|
||||
/** @hide */
|
||||
public static final int REASON_CONSTRAINTS_NOT_SATISFIED = 1;
|
||||
/** @hide */
|
||||
public static final int REASON_PREEMPT = 2;
|
||||
/** @hide */
|
||||
public static final int REASON_TIMEOUT = 3;
|
||||
/** @hide */
|
||||
public static final int REASON_DEVICE_IDLE = 4;
|
||||
|
||||
private final int jobId;
|
||||
private final PersistableBundle extras;
|
||||
private final IBinder callback;
|
||||
private final boolean overrideDeadlineExpired;
|
||||
private int stopReason; // Default value of stopReason is REASON_CANCELED
|
||||
|
||||
/** @hide */
|
||||
public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
|
||||
@@ -49,6 +61,14 @@ public class JobParameters implements Parcelable {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason onStopJob() was called on this job.
|
||||
* @hide
|
||||
*/
|
||||
public int getStopReason() {
|
||||
return stopReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The extras you passed in when constructing this job with
|
||||
* {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
|
||||
@@ -78,6 +98,12 @@ public class JobParameters implements Parcelable {
|
||||
extras = in.readPersistableBundle();
|
||||
callback = in.readStrongBinder();
|
||||
overrideDeadlineExpired = in.readInt() == 1;
|
||||
stopReason = in.readInt();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setStopReason(int reason) {
|
||||
stopReason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,6 +117,7 @@ public class JobParameters implements Parcelable {
|
||||
dest.writePersistableBundle(extras);
|
||||
dest.writeStrongBinder(callback);
|
||||
dest.writeInt(overrideDeadlineExpired ? 1 : 0);
|
||||
dest.writeInt(stopReason);
|
||||
}
|
||||
|
||||
public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
|
||||
|
||||
@@ -16,14 +16,21 @@
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerNative;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.IUidObserver;
|
||||
import android.app.job.IJobScheduler;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.app.job.IJobScheduler;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -47,7 +54,6 @@ import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.DeviceIdleController;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.controllers.AppIdleController;
|
||||
@@ -58,15 +64,6 @@ import com.android.server.job.controllers.JobStatus;
|
||||
import com.android.server.job.controllers.StateController;
|
||||
import com.android.server.job.controllers.TimeController;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responsible for taking jobs representing work to be performed by a client app, and determining
|
||||
* based on the criteria specified when that job should be run against the client application's
|
||||
@@ -130,7 +127,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
*/
|
||||
final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
|
||||
|
||||
int[] mStartedUsers = EmptyArray.INT;
|
||||
final ArrayList<Integer> mStartedUsers = new ArrayList<>();
|
||||
|
||||
final JobHandler mHandler;
|
||||
final JobSchedulerStub mJobSchedulerStub;
|
||||
@@ -161,9 +158,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
Slog.d(TAG, "Receieved: " + action);
|
||||
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
|
||||
Slog.d(TAG, "Receieved: " + intent.getAction());
|
||||
if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
|
||||
// If this is an outright uninstall rather than the first half of an
|
||||
// app update sequence, cancel the jobs associated with the app.
|
||||
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||
@@ -173,21 +169,18 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
cancelJobsForUid(uidRemoved, true);
|
||||
}
|
||||
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
|
||||
} else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
|
||||
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Removing jobs for user: " + userId);
|
||||
}
|
||||
cancelJobsForUser(userId);
|
||||
} else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
|
||||
|| PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
|
||||
} else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
|
||||
|| PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
|
||||
updateIdleMode(mPowerManager != null
|
||||
? (mPowerManager.isDeviceIdleMode()
|
||||
|| mPowerManager.isLightDeviceIdleMode())
|
||||
|| mPowerManager.isLightDeviceIdleMode())
|
||||
: false);
|
||||
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
|
||||
// Kick off pending jobs for any apps that re-appeared
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -209,20 +202,14 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
|
||||
@Override
|
||||
public void onStartUser(int userHandle) {
|
||||
mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
|
||||
// Let's kick any outstanding jobs for this user.
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnlockUser(int userHandle) {
|
||||
mStartedUsers.add(userHandle);
|
||||
// Let's kick any outstanding jobs for this user.
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopUser(int userHandle) {
|
||||
mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
|
||||
mStartedUsers.remove(Integer.valueOf(userHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,7 +316,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
// Remove from pending queue.
|
||||
mPendingJobs.remove(cancelled);
|
||||
// Cancel if running.
|
||||
stopJobOnServiceContextLocked(cancelled);
|
||||
stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
|
||||
reportActive();
|
||||
}
|
||||
}
|
||||
@@ -357,7 +344,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
JobServiceContext jsc = mActiveServices.get(i);
|
||||
final JobStatus executing = jsc.getRunningJob();
|
||||
if (executing != null) {
|
||||
jsc.cancelExecutingJob();
|
||||
jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -382,7 +369,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
if (mPendingJobs.size() <= 0) {
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
JobServiceContext jsc = mActiveServices.get(i);
|
||||
if (!jsc.isAvailable()) {
|
||||
if (jsc.getRunningJob() != null) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
@@ -429,24 +416,17 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (PHASE_SYSTEM_SERVICES_READY == phase) {
|
||||
// Register for package removals and user removals.
|
||||
// Register br for package removals and user removals.
|
||||
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
|
||||
filter.addDataScheme("package");
|
||||
getContext().registerReceiverAsUser(
|
||||
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
|
||||
|
||||
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
|
||||
userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
|
||||
getContext().registerReceiverAsUser(
|
||||
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
|
||||
|
||||
final IntentFilter storageFilter = new IntentFilter();
|
||||
storageFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
|
||||
getContext().registerReceiverAsUser(
|
||||
mBroadcastReceiver, UserHandle.ALL, storageFilter, null, null);
|
||||
|
||||
mPowerManager = getContext().getSystemService(PowerManager.class);
|
||||
mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
|
||||
try {
|
||||
ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
|
||||
ActivityManager.UID_OBSERVER_IDLE);
|
||||
@@ -526,12 +506,12 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return removed;
|
||||
}
|
||||
|
||||
private boolean stopJobOnServiceContextLocked(JobStatus job) {
|
||||
private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
JobServiceContext jsc = mActiveServices.get(i);
|
||||
final JobStatus executing = jsc.getRunningJob();
|
||||
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
|
||||
jsc.cancelExecutingJob();
|
||||
jsc.cancelExecutingJob(reason);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -731,6 +711,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
*/
|
||||
private void queueReadyJobsForExecutionLockedH() {
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
mPendingJobs.clear();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "queuing all ready jobs for execution:");
|
||||
}
|
||||
@@ -741,8 +722,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
Slog.d(TAG, " queued " + job.toShortString());
|
||||
}
|
||||
mPendingJobs.add(job);
|
||||
} else if (isReadyToBeCancelledLocked(job)) {
|
||||
stopJobOnServiceContextLocked(job);
|
||||
} else if (areJobConstraintsNotSatisfied(job)) {
|
||||
stopJobOnServiceContextLocked(job,
|
||||
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
@@ -765,8 +747,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* TODO: It would be nice to consolidate these sort of high-level policies somewhere.
|
||||
*/
|
||||
private void maybeQueueReadyJobsForExecutionLockedH() {
|
||||
mPendingJobs.clear();
|
||||
int chargingCount = 0;
|
||||
int idleCount = 0;
|
||||
int idleCount = 0;
|
||||
int backoffCount = 0;
|
||||
int connectivityCount = 0;
|
||||
List<JobStatus> runnableJobs = null;
|
||||
@@ -801,8 +784,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
runnableJobs = new ArrayList<>();
|
||||
}
|
||||
runnableJobs.add(job);
|
||||
} else if (isReadyToBeCancelledLocked(job)) {
|
||||
stopJobOnServiceContextLocked(job);
|
||||
} else if (areJobConstraintsNotSatisfied(job)) {
|
||||
stopJobOnServiceContextLocked(job,
|
||||
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
|
||||
}
|
||||
}
|
||||
if (backoffCount > 0 ||
|
||||
@@ -821,11 +805,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
|
||||
connectivityCount + " charging=" + chargingCount + " tot=" +
|
||||
runnableJobs.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -834,31 +813,18 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* - It's not pending.
|
||||
* - It's not already running on a JSC.
|
||||
* - The user that requested the job is running.
|
||||
* - The component is enabled and runnable.
|
||||
*/
|
||||
private boolean isReadyToBeExecutedLocked(JobStatus job) {
|
||||
final boolean jobReady = job.isReady();
|
||||
final boolean jobPending = mPendingJobs.contains(job);
|
||||
final boolean jobActive = isCurrentlyActiveLocked(job);
|
||||
|
||||
final int userId = job.getUserId();
|
||||
final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
|
||||
final boolean componentPresent;
|
||||
try {
|
||||
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
|
||||
job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
|
||||
userId) != null);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
|
||||
final boolean userRunning = mStartedUsers.contains(job.getUserId());
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
|
||||
+ " ready=" + jobReady + " pending=" + jobPending
|
||||
+ " active=" + jobActive + " userStarted=" + userStarted
|
||||
+ " componentPresent=" + componentPresent);
|
||||
+ " active=" + jobActive + " userRunning=" + userRunning);
|
||||
}
|
||||
return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
|
||||
return userRunning && jobReady && !jobPending && !jobActive;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -866,7 +832,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* - It's not ready
|
||||
* - It's running on a JSC.
|
||||
*/
|
||||
private boolean isReadyToBeCancelledLocked(JobStatus job) {
|
||||
private boolean areJobConstraintsNotSatisfied(JobStatus job) {
|
||||
return !job.isReady() && isCurrentlyActiveLocked(job);
|
||||
}
|
||||
|
||||
@@ -881,45 +847,120 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
// If device is idle, we will not schedule jobs to run.
|
||||
return;
|
||||
}
|
||||
Iterator<JobStatus> it = mPendingJobs.iterator();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
|
||||
}
|
||||
while (it.hasNext()) {
|
||||
JobStatus nextPending = it.next();
|
||||
JobServiceContext availableContext = null;
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
JobServiceContext jsc = mActiveServices.get(i);
|
||||
final JobStatus running = jsc.getRunningJob();
|
||||
if (running != null && running.matches(nextPending.getUid(),
|
||||
nextPending.getJobId())) {
|
||||
// Already running this job for this uId, skip.
|
||||
availableContext = null;
|
||||
break;
|
||||
}
|
||||
if (jsc.isAvailable()) {
|
||||
availableContext = jsc;
|
||||
}
|
||||
}
|
||||
if (availableContext != null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "About to run job "
|
||||
+ nextPending.getJob().getService().toString());
|
||||
}
|
||||
if (!availableContext.executeRunnableJob(nextPending)) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Error executing " + nextPending);
|
||||
}
|
||||
mJobs.remove(nextPending);
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
assignJobsToContextsH();
|
||||
reportActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes jobs from pending queue and runs them on available contexts.
|
||||
* If no contexts are available, preempts lower priority jobs to
|
||||
* run higher priority ones.
|
||||
* Lock on mJobs before calling this function.
|
||||
*/
|
||||
private void assignJobsToContextsH() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, printPendingQueue());
|
||||
}
|
||||
|
||||
// This array essentially stores the state of mActiveServices array.
|
||||
// ith index stores the job present on the ith JobServiceContext.
|
||||
// We manipulate this array until we arrive at what jobs should be running on
|
||||
// what JobServiceContext.
|
||||
JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
|
||||
// Indicates whether we need to act on this jobContext id
|
||||
boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT];
|
||||
int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob();
|
||||
preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid();
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
|
||||
}
|
||||
Iterator<JobStatus> it = mPendingJobs.iterator();
|
||||
while (it.hasNext()) {
|
||||
JobStatus nextPending = it.next();
|
||||
|
||||
// If job is already running, go to next job.
|
||||
int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
|
||||
if (jobRunningContext != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a context for nextPending. The context should be available OR
|
||||
// it should have lowest priority among all running jobs
|
||||
// (sharing the same Uid as nextPending)
|
||||
int minPriority = Integer.MAX_VALUE;
|
||||
int minPriorityContextId = -1;
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
JobStatus job = contextIdToJobMap[i];
|
||||
int preferredUid = preferredUidForContext[i];
|
||||
if (job == null && (preferredUid == nextPending.getUid() ||
|
||||
preferredUid == JobServiceContext.NO_PREFERRED_UID) ) {
|
||||
minPriorityContextId = i;
|
||||
break;
|
||||
}
|
||||
if (job.getUid() != nextPending.getUid()) {
|
||||
continue;
|
||||
}
|
||||
if (job.getPriority() >= nextPending.getPriority()) {
|
||||
continue;
|
||||
}
|
||||
if (minPriority > nextPending.getPriority()) {
|
||||
minPriority = nextPending.getPriority();
|
||||
minPriorityContextId = i;
|
||||
}
|
||||
}
|
||||
if (minPriorityContextId != -1) {
|
||||
contextIdToJobMap[minPriorityContextId] = nextPending;
|
||||
act[minPriorityContextId] = true;
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
|
||||
}
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
boolean preservePreferredUid = false;
|
||||
if (act[i]) {
|
||||
JobStatus js = mActiveServices.get(i).getRunningJob();
|
||||
if (js != null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
|
||||
}
|
||||
// preferredUid will be set to uid of currently running job.
|
||||
mActiveServices.get(i).preemptExecutingJob();
|
||||
preservePreferredUid = true;
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "About to run job on context "
|
||||
+ String.valueOf(i) + ", job: " + contextIdToJobMap[i]);
|
||||
}
|
||||
if (!mActiveServices.get(i).executeRunnableJob(contextIdToJobMap[i])) {
|
||||
Slog.d(TAG, "Error executing " + contextIdToJobMap[i]);
|
||||
}
|
||||
mPendingJobs.remove(contextIdToJobMap[i]);
|
||||
}
|
||||
}
|
||||
if (!preservePreferredUid) {
|
||||
mActiveServices.get(i).clearPreferredUid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
|
||||
for (int i=0; i<map.length; i++) {
|
||||
if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binder stub trampoline implementation
|
||||
*/
|
||||
@@ -936,8 +977,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
final IPackageManager pm = AppGlobals.getPackageManager();
|
||||
final ComponentName service = job.getService();
|
||||
try {
|
||||
ServiceInfo si = pm.getServiceInfo(service,
|
||||
PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(uid));
|
||||
ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
|
||||
if (si == null) {
|
||||
throw new IllegalArgumentException("No such service " + service);
|
||||
}
|
||||
@@ -1050,12 +1090,41 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private String printContextIdToJobMap(JobStatus[] map, String initial) {
|
||||
StringBuilder s = new StringBuilder(initial + ": ");
|
||||
for (int i=0; i<map.length; i++) {
|
||||
s.append("(")
|
||||
.append(map[i] == null? -1: map[i].getJobId())
|
||||
.append(map[i] == null? -1: map[i].getUid())
|
||||
.append(")" );
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private String printPendingQueue() {
|
||||
StringBuilder s = new StringBuilder("Pending queue: ");
|
||||
Iterator<JobStatus> it = mPendingJobs.iterator();
|
||||
while (it.hasNext()) {
|
||||
JobStatus js = it.next();
|
||||
s.append("(")
|
||||
.append(js.getJob().getId())
|
||||
.append(", ")
|
||||
.append(js.getUid())
|
||||
.append(") ");
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
void dumpInternal(PrintWriter pw) {
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
synchronized (mJobs) {
|
||||
pw.println("Started users: " + Arrays.toString(mStartedUsers));
|
||||
pw.print("Started users: ");
|
||||
for (int i=0; i<mStartedUsers.size(); i++) {
|
||||
pw.print("u" + mStartedUsers.get(i) + " ");
|
||||
}
|
||||
pw.println();
|
||||
pw.println("Registered jobs:");
|
||||
if (mJobs.size() > 0) {
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
@@ -1071,15 +1140,12 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mControllers.get(i).dumpControllerState(pw);
|
||||
}
|
||||
pw.println();
|
||||
pw.println("Pending:");
|
||||
for (int i=0; i<mPendingJobs.size(); i++) {
|
||||
pw.println(mPendingJobs.get(i).hashCode());
|
||||
}
|
||||
pw.println(printPendingQueue());
|
||||
pw.println();
|
||||
pw.println("Active jobs:");
|
||||
for (int i=0; i<mActiveServices.size(); i++) {
|
||||
JobServiceContext jsc = mActiveServices.get(i);
|
||||
if (jsc.isAvailable()) {
|
||||
if (jsc.getRunningJob() == null) {
|
||||
continue;
|
||||
} else {
|
||||
final long timeout = jsc.getTimeoutElapsed();
|
||||
|
||||
@@ -59,7 +59,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* To mitigate this, tearing down the context removes all messages from the handler, including any
|
||||
* tardy {@link #MSG_CANCEL}s. Additionally, we avoid sending duplicate onStopJob()
|
||||
* calls to the client after they've specified jobFinished().
|
||||
*
|
||||
*/
|
||||
public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
|
||||
private static final boolean DEBUG = JobSchedulerService.DEBUG;
|
||||
@@ -95,6 +94,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
/** Shutdown the job. Used when the client crashes and we can't die gracefully.*/
|
||||
private static final int MSG_SHUTDOWN_EXECUTION = 4;
|
||||
|
||||
public static final int NO_PREFERRED_UID = -1;
|
||||
|
||||
private final Handler mCallbackHandler;
|
||||
/** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
|
||||
private final JobCompletedListener mCompletedListener;
|
||||
@@ -117,7 +118,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
* Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
|
||||
*/
|
||||
private JobStatus mRunningJob;
|
||||
/** Binder to the client service. */
|
||||
/** Used to store next job to run when current job is to be preempted. */
|
||||
private int mPreferredUid;
|
||||
IJobService service;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
@@ -138,17 +140,19 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
|
||||
@VisibleForTesting
|
||||
JobServiceContext(Context context, IBatteryStats batteryStats,
|
||||
JobCompletedListener completedListener, Looper looper) {
|
||||
JobCompletedListener completedListener, Looper looper) {
|
||||
mContext = context;
|
||||
mBatteryStats = batteryStats;
|
||||
mCallbackHandler = new JobServiceHandler(looper);
|
||||
mCompletedListener = completedListener;
|
||||
mAvailable = true;
|
||||
mVerb = VERB_FINISHED;
|
||||
mPreferredUid = NO_PREFERRED_UID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a job to this context for execution. Callers must first check {@link #isAvailable()}
|
||||
* to make sure this is a valid context.
|
||||
* Give a job to this context for execution. Callers must first check {@link #getRunningJob()}
|
||||
* and ensure it is null to make sure this is a valid context.
|
||||
* @param job The status of the job that we are going to run.
|
||||
* @return True if the job is valid and is running. False if the job cannot be executed.
|
||||
*/
|
||||
@@ -159,6 +163,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
return false;
|
||||
}
|
||||
|
||||
mPreferredUid = NO_PREFERRED_UID;
|
||||
|
||||
mRunningJob = job;
|
||||
final boolean isDeadlineExpired =
|
||||
job.hasDeadlineConstraint() &&
|
||||
@@ -206,17 +212,22 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
}
|
||||
|
||||
/** Called externally when a job that was scheduled for execution should be cancelled. */
|
||||
void cancelExecutingJob() {
|
||||
mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
|
||||
void cancelExecutingJob(int reason) {
|
||||
mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether this context is available to handle incoming work.
|
||||
*/
|
||||
boolean isAvailable() {
|
||||
synchronized (mLock) {
|
||||
return mAvailable;
|
||||
}
|
||||
void preemptExecutingJob() {
|
||||
Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
|
||||
m.arg1 = JobParameters.REASON_PREEMPT;
|
||||
m.sendToTarget();
|
||||
}
|
||||
|
||||
int getPreferredUid() {
|
||||
return mPreferredUid;
|
||||
}
|
||||
|
||||
void clearPreferredUid() {
|
||||
mPreferredUid = NO_PREFERRED_UID;
|
||||
}
|
||||
|
||||
long getExecutionStartTimeElapsed() {
|
||||
@@ -344,6 +355,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
}
|
||||
break;
|
||||
case MSG_CANCEL:
|
||||
mParams.setStopReason(message.arg1);
|
||||
if (message.arg1 == JobParameters.REASON_PREEMPT) {
|
||||
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
|
||||
NO_PREFERRED_UID;
|
||||
}
|
||||
handleCancelH();
|
||||
break;
|
||||
case MSG_TIMEOUT:
|
||||
@@ -481,6 +497,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
|
||||
/** Process MSG_TIMEOUT here. */
|
||||
private void handleOpTimeoutH() {
|
||||
mParams.setStopReason(JobParameters.REASON_TIMEOUT);
|
||||
switch (mVerb) {
|
||||
case VERB_BINDING:
|
||||
Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
|
||||
|
||||
@@ -312,7 +312,7 @@ public class JobStore {
|
||||
Slog.d(TAG, "Saving job " + jobStatus.getJobId());
|
||||
}
|
||||
out.startTag(null, "job");
|
||||
addIdentifierAttributesToJobTag(out, jobStatus);
|
||||
addAttributesToJobTag(out, jobStatus);
|
||||
writeConstraintsToXml(out, jobStatus);
|
||||
writeExecutionCriteriaToXml(out, jobStatus);
|
||||
writeBundleToXml(jobStatus.getExtras(), out);
|
||||
@@ -337,13 +337,16 @@ public class JobStore {
|
||||
}
|
||||
}
|
||||
|
||||
/** Write out a tag with data comprising the required fields of this job and its client. */
|
||||
private void addIdentifierAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
|
||||
/** Write out a tag with data comprising the required fields and priority of this job and
|
||||
* its client.
|
||||
*/
|
||||
private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
|
||||
throws IOException {
|
||||
out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
|
||||
out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
|
||||
out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
|
||||
out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
|
||||
out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
|
||||
}
|
||||
|
||||
private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
|
||||
@@ -361,9 +364,9 @@ public class JobStore {
|
||||
PersistableBundle copy = (PersistableBundle) bundle.clone();
|
||||
Set<String> keySet = bundle.keySet();
|
||||
for (String key: keySet) {
|
||||
PersistableBundle b = copy.getPersistableBundle(key);
|
||||
if (b != null) {
|
||||
PersistableBundle bCopy = deepCopyBundle(b, maxDepth-1);
|
||||
Object o = copy.get(key);
|
||||
if (o instanceof PersistableBundle) {
|
||||
PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
|
||||
copy.putPersistableBundle(key, bCopy);
|
||||
}
|
||||
}
|
||||
@@ -541,11 +544,16 @@ public class JobStore {
|
||||
JobInfo.Builder jobBuilder;
|
||||
int uid;
|
||||
|
||||
// Read out job identifier attributes.
|
||||
// Read out job identifier attributes and priority.
|
||||
try {
|
||||
jobBuilder = buildBuilderFromXml(parser);
|
||||
jobBuilder.setPersisted(true);
|
||||
uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
|
||||
|
||||
String priority = parser.getAttributeValue(null, "priority");
|
||||
if (priority != null) {
|
||||
jobBuilder.setPriority(Integer.valueOf(priority));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Slog.e(TAG, "Error parsing job's required fields, skipping");
|
||||
return null;
|
||||
|
||||
@@ -165,6 +165,10 @@ public class JobStatus {
|
||||
public PersistableBundle getExtras() {
|
||||
return job.getExtras();
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return job.getPriority();
|
||||
}
|
||||
|
||||
public boolean hasConnectivityConstraint() {
|
||||
return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
|
||||
|
||||
@@ -102,6 +102,8 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name="com.android.server.job.MockPriorityJobService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
|
||||
@@ -179,7 +179,21 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS);
|
||||
// Assert late runtime was clamped to be now + period*2.
|
||||
assertTrue("Early runtime wasn't correctly clamped.",
|
||||
loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS*2);
|
||||
loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS * 2);
|
||||
}
|
||||
|
||||
public void testPriorityPersisted() throws Exception {
|
||||
JobInfo.Builder b = new Builder(92, mComponent)
|
||||
.setOverrideDeadline(5000)
|
||||
.setPriority(42)
|
||||
.setPersisted(true);
|
||||
final JobStatus js = new JobStatus(b.build(), SOME_UID);
|
||||
mTaskStoreUnderTest.add(js);
|
||||
Thread.sleep(IO_WAIT);
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
JobStatus loaded = jobStatusSet.iterator().next();
|
||||
assertEquals("Priority not correctly persisted.", 42, loaded.getPriority());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@TargetApi(24)
|
||||
public class MockPriorityJobService extends JobService {
|
||||
private static final String TAG = "MockPriorityJobService";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.e(TAG, "Created test service.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
Log.i(TAG, "Test job executing: " + params.getJobId());
|
||||
TestEnvironment.getTestEnvironment().executedEvents.add(
|
||||
new TestEnvironment.Event(TestEnvironment.EVENT_START_JOB, params.getJobId()));
|
||||
return true; // Job not finished
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
Log.i(TAG, "Test job stop executing: " + params.getJobId());
|
||||
int reason = params.getStopReason();
|
||||
int event = TestEnvironment.EVENT_STOP_JOB;
|
||||
Log.d(TAG, "stop reason: " + String.valueOf(reason));
|
||||
if (reason == JobParameters.REASON_PREEMPT) {
|
||||
event = TestEnvironment.EVENT_PREEMPT_JOB;
|
||||
Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
|
||||
}
|
||||
TestEnvironment.getTestEnvironment().executedEvents
|
||||
.add(new TestEnvironment.Event(event, params.getJobId()));
|
||||
return false; // Do not reschedule
|
||||
}
|
||||
|
||||
public static class TestEnvironment {
|
||||
|
||||
public static final int EVENT_START_JOB = 0;
|
||||
public static final int EVENT_PREEMPT_JOB = 1;
|
||||
public static final int EVENT_STOP_JOB = 2;
|
||||
|
||||
private static TestEnvironment kTestEnvironment;
|
||||
|
||||
private ArrayList<Event> executedEvents = new ArrayList<Event>();
|
||||
|
||||
public static TestEnvironment getTestEnvironment() {
|
||||
if (kTestEnvironment == null) {
|
||||
kTestEnvironment = new TestEnvironment();
|
||||
}
|
||||
return kTestEnvironment;
|
||||
}
|
||||
|
||||
public static class Event {
|
||||
public int event;
|
||||
public int jobId;
|
||||
|
||||
public Event() {
|
||||
}
|
||||
|
||||
public Event(int event, int jobId) {
|
||||
this.event = event;
|
||||
this.jobId = jobId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof Event) {
|
||||
Event otherEvent = (Event) other;
|
||||
return otherEvent.event == event && otherEvent.jobId == jobId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setUp() {
|
||||
executedEvents.clear();
|
||||
}
|
||||
|
||||
public ArrayList<Event> getExecutedEvents() {
|
||||
return executedEvents;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.test.AndroidTestCase;
|
||||
import com.android.server.job.MockPriorityJobService.TestEnvironment;
|
||||
import com.android.server.job.MockPriorityJobService.TestEnvironment.Event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@TargetApi(24)
|
||||
public class PrioritySchedulingTest extends AndroidTestCase {
|
||||
/** Environment that notifies of JobScheduler callbacks. */
|
||||
static TestEnvironment kTestEnvironment = TestEnvironment.getTestEnvironment();
|
||||
/** Handle for the service which receives the execution callbacks from the JobScheduler. */
|
||||
static ComponentName kJobServiceComponent;
|
||||
JobScheduler mJobScheduler;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
kTestEnvironment.setUp();
|
||||
kJobServiceComponent = new ComponentName(getContext(), MockPriorityJobService.class);
|
||||
mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
mJobScheduler.cancelAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
mJobScheduler.cancelAll();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testLowerPriorityJobPreempted() throws Exception {
|
||||
JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent)
|
||||
.setPriority(1)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent)
|
||||
.setPriority(1)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent)
|
||||
.setPriority(1)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent)
|
||||
.setPriority(2)
|
||||
.setMinimumLatency(2000L)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
mJobScheduler.schedule(job1);
|
||||
mJobScheduler.schedule(job2);
|
||||
mJobScheduler.schedule(job3);
|
||||
mJobScheduler.schedule(job4);
|
||||
Thread.sleep(10000); // Wait for job 4 to preempt one of the lower priority jobs
|
||||
|
||||
Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444);
|
||||
ArrayList<Event> executedEvents = kTestEnvironment.getExecutedEvents();
|
||||
boolean wasJob4Executed = executedEvents.contains(job4Execution);
|
||||
boolean wasSomeJobPreempted = false;
|
||||
for (Event event: executedEvents) {
|
||||
if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
|
||||
wasSomeJobPreempted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue("No job was preempted.", wasSomeJobPreempted);
|
||||
assertTrue("Lower priority jobs were not preempted.", wasJob4Executed);
|
||||
}
|
||||
|
||||
public void testHigherPriorityJobNotPreempted() throws Exception {
|
||||
JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent)
|
||||
.setPriority(2)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent)
|
||||
.setPriority(2)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent)
|
||||
.setPriority(2)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent)
|
||||
.setPriority(1)
|
||||
.setMinimumLatency(2000L)
|
||||
.setOverrideDeadline(7000L)
|
||||
.build();
|
||||
mJobScheduler.schedule(job1);
|
||||
mJobScheduler.schedule(job2);
|
||||
mJobScheduler.schedule(job3);
|
||||
mJobScheduler.schedule(job4);
|
||||
Thread.sleep(10000); // Wait for job 4 to preempt one of the higher priority jobs
|
||||
|
||||
Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444);
|
||||
boolean wasJob4Executed = kTestEnvironment.getExecutedEvents().contains(job4Execution);
|
||||
assertFalse("Higher priority job was preempted.", wasJob4Executed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user