am d8f71d34: Merge "Out with the old; in with the new" into lmp-preview-dev

* commit 'd8f71d34c3c7fb161a1586de8edb52161c32d672':
  Out with the old; in with the new
This commit is contained in:
Christopher Tate
2014-06-11 17:02:18 +00:00
committed by Android Git Automerger
38 changed files with 1739 additions and 1760 deletions

View File

@@ -77,9 +77,9 @@ LOCAL_SRC_FILES += \
core/java/android/app/ISearchManagerCallback.aidl \
core/java/android/app/IServiceConnection.aidl \
core/java/android/app/IStopUserCallback.aidl \
core/java/android/app/task/ITaskCallback.aidl \
core/java/android/app/task/ITaskManager.aidl \
core/java/android/app/task/ITaskService.aidl \
core/java/android/app/job/IJobCallback.aidl \
core/java/android/app/job/IJobScheduler.aidl \
core/java/android/app/job/IJobService.aidl \
core/java/android/app/IThumbnailRetriever.aidl \
core/java/android/app/ITransientNotification.aidl \
core/java/android/app/IUiAutomationConnection.aidl \

View File

@@ -197,3 +197,6 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/classes/android/app/task)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/app/task)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/classes/android/app/TaskManager)

View File

@@ -5372,23 +5372,9 @@ package android.app.backup {
}
package android.app.maintenance {
package android.app.job {
public abstract class IdleService extends android.app.Service {
ctor public IdleService();
method public final void finishIdle();
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract boolean onIdleStart();
method public abstract void onIdleStop();
field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_IDLE_SERVICE";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.idle.IdleService";
}
}
package android.app.task {
public class Task implements android.os.Parcelable {
public class JobInfo implements android.os.Parcelable {
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
@@ -5406,55 +5392,69 @@ package android.app.task {
field public static final android.os.Parcelable.Creator CREATOR;
}
public static abstract interface Task.BackoffPolicy {
public static abstract interface JobInfo.BackoffPolicy {
field public static final int EXPONENTIAL = 1; // 0x1
field public static final int LINEAR = 0; // 0x0
}
public static final class Task.Builder {
ctor public Task.Builder(int, android.content.ComponentName);
method public android.app.task.Task build();
method public android.app.task.Task.Builder setBackoffCriteria(long, int);
method public android.app.task.Task.Builder setExtras(android.os.PersistableBundle);
method public android.app.task.Task.Builder setMinimumLatency(long);
method public android.app.task.Task.Builder setOverrideDeadline(long);
method public android.app.task.Task.Builder setPeriodic(long);
method public android.app.task.Task.Builder setRequiredNetworkCapabilities(int);
method public android.app.task.Task.Builder setRequiresCharging(boolean);
method public android.app.task.Task.Builder setRequiresDeviceIdle(boolean);
public static final class JobInfo.Builder {
ctor public JobInfo.Builder(int, android.content.ComponentName);
method public android.app.job.JobInfo build();
method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
method public android.app.job.JobInfo.Builder setRequiredNetworkCapabilities(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
}
public static abstract interface Task.NetworkType {
public static abstract interface JobInfo.NetworkType {
field public static final int ANY = 1; // 0x1
field public static final int NONE = 0; // 0x0
field public static final int UNMETERED = 2; // 0x2
}
public abstract class TaskManager {
ctor public TaskManager();
method public abstract void cancel(int);
method public abstract void cancelAll();
method public abstract java.util.List<android.app.task.Task> getAllPendingTasks();
method public abstract int schedule(android.app.task.Task);
field public static final int RESULT_FAILURE = 0; // 0x0
field public static final int RESULT_SUCCESS = 1; // 0x1
}
public class TaskParams implements android.os.Parcelable {
public class JobParameters implements android.os.Parcelable {
method public int describeContents();
method public android.os.PersistableBundle getExtras();
method public int getTaskId();
method public int getJobId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
public abstract class TaskService extends android.app.Service {
ctor public TaskService();
public abstract class JobScheduler {
ctor public JobScheduler();
method public abstract void cancel(int);
method public abstract void cancelAll();
method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
method public abstract int schedule(android.app.job.JobInfo);
field public static final int RESULT_FAILURE = 0; // 0x0
field public static final int RESULT_SUCCESS = 1; // 0x1
}
public abstract class JobService extends android.app.Service {
ctor public JobService();
method public final void jobFinished(android.app.job.JobParameters, boolean);
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract boolean onStartTask(android.app.task.TaskParams);
method public abstract boolean onStopTask(android.app.task.TaskParams);
method public final void taskFinished(android.app.task.TaskParams, boolean);
field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_TASK_SERVICE";
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE";
}
}
package android.app.maintenance {
public abstract class IdleService extends android.app.Service {
ctor public IdleService();
method public final void finishIdle();
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract boolean onIdleStart();
method public abstract void onIdleStop();
field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_IDLE_SERVICE";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.idle.IdleService";
}
}
@@ -7023,6 +7023,7 @@ package android.content {
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
@@ -7044,7 +7045,6 @@ package android.content {
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String TASK_SERVICE = "task";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";

View File

@@ -135,7 +135,7 @@ import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.app.admin.DevicePolicyManager;
import android.app.task.ITaskManager;
import android.app.job.IJobScheduler;
import android.app.trust.TrustManager;
import com.android.internal.annotations.GuardedBy;
@@ -706,10 +706,10 @@ class ContextImpl extends Context {
return new UsageStatsManager(ctx.getOuterContext());
}});
registerService(TASK_SERVICE, new ServiceFetcher() {
registerService(JOB_SCHEDULER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(TASK_SERVICE);
return new TaskManagerImpl(ITaskManager.Stub.asInterface(b));
IBinder b = ServiceManager.getService(JOB_SCHEDULER_SERVICE);
return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
}});
}

View File

@@ -17,38 +17,38 @@
// in android.app so ContextImpl has package access
package android.app;
import android.app.task.ITaskManager;
import android.app.task.Task;
import android.app.task.TaskManager;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.IJobScheduler;
import android.os.RemoteException;
import java.util.List;
/**
* Concrete implementation of the TaskManager interface
* Concrete implementation of the JobScheduler interface
* @hide
*/
public class TaskManagerImpl extends TaskManager {
ITaskManager mBinder;
public class JobSchedulerImpl extends JobScheduler {
IJobScheduler mBinder;
/* package */ TaskManagerImpl(ITaskManager binder) {
/* package */ JobSchedulerImpl(IJobScheduler binder) {
mBinder = binder;
}
@Override
public int schedule(Task task) {
public int schedule(JobInfo job) {
try {
return mBinder.schedule(task);
return mBinder.schedule(job);
} catch (RemoteException e) {
return TaskManager.RESULT_FAILURE;
return JobScheduler.RESULT_FAILURE;
}
}
@Override
public void cancel(int taskId) {
public void cancel(int jobId) {
try {
mBinder.cancel(taskId);
mBinder.cancel(jobId);
} catch (RemoteException e) {}
}
@@ -62,9 +62,9 @@ public class TaskManagerImpl extends TaskManager {
}
@Override
public List<Task> getAllPendingTasks() {
public List<JobInfo> getAllPendingJobs() {
try {
return mBinder.getAllPendingTasks();
return mBinder.getAllPendingJobs();
} catch (RemoteException e) {
return null;
}

View File

@@ -14,43 +14,40 @@
* limitations under the License.
*/
package android.app.task;
import android.app.task.ITaskService;
import android.app.task.TaskParams;
package android.app.job;
/**
* The server side of the TaskManager IPC protocols. The app-side implementation
* The server side of the JobScheduler IPC protocols. The app-side implementation
* invokes on this interface to indicate completion of the (asynchronous) instructions
* issued by the server.
*
* In all cases, the 'who' parameter is the caller's service binder, used to track
* which Task Service instance is reporting.
* which Job Service instance is reporting.
*
* {@hide}
*/
interface ITaskCallback {
interface IJobCallback {
/**
* Immediate callback to the system after sending a start signal, used to quickly detect ANR.
*
* @param taskId Unique integer used to identify this task.
* @param ongoing True to indicate that the client is processing the task. False if the task is
* @param jobId Unique integer used to identify this job.
* @param ongoing True to indicate that the client is processing the job. False if the job is
* complete
*/
void acknowledgeStartMessage(int taskId, boolean ongoing);
void acknowledgeStartMessage(int jobId, boolean ongoing);
/**
* Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
*
* @param taskId Unique integer used to identify this task.
* @param rescheulde Whether or not to reschedule this task.
* @param jobId Unique integer used to identify this job.
* @param reschedule Whether or not to reschedule this job.
*/
void acknowledgeStopMessage(int taskId, boolean reschedule);
void acknowledgeStopMessage(int jobId, boolean reschedule);
/*
* Tell the task manager that the client is done with its execution, so that it can go on to
* Tell the job manager that the client is done with its execution, so that it can go on to
* the next one and stop attributing wakelock time to us etc.
*
* @param taskId Unique integer used to identify this task.
* @param reschedule Whether or not to reschedule this task.
* @param jobId Unique integer used to identify this job.
* @param reschedule Whether or not to reschedule this job.
*/
void taskFinished(int taskId, boolean reschedule);
void jobFinished(int jobId, boolean reschedule);
}

View File

@@ -14,17 +14,17 @@
* limitations under the License.
*/
package android.app.task;
package android.app.job;
import android.app.task.Task;
import android.app.job.JobInfo;
/**
* IPC interface that supports the app-facing {@link #TaskManager} api.
* IPC interface that supports the app-facing {@link #JobScheduler} api.
* {@hide}
*/
interface ITaskManager {
int schedule(in Task task);
void cancel(int taskId);
interface IJobScheduler {
int schedule(in JobInfo job);
void cancel(int jobId);
void cancelAll();
List<Task> getAllPendingTasks();
List<JobInfo> getAllPendingJobs();
}

View File

@@ -14,22 +14,19 @@
* limitations under the License.
*/
package android.app.task;
package android.app.job;
import android.app.task.ITaskCallback;
import android.app.task.TaskParams;
import android.os.Bundle;
import android.app.job.JobParameters;
/**
* Interface that the framework uses to communicate with application code that implements a
* TaskService. End user code does not implement this interface directly; instead, the app's
* service implementation will extend android.app.task.TaskService.
* JobService. End user code does not implement this interface directly; instead, the app's
* service implementation will extend android.app.job.JobService.
* {@hide}
*/
oneway interface ITaskService {
/** Begin execution of application's task. */
void startTask(in TaskParams taskParams);
oneway interface IJobService {
/** Begin execution of application's job. */
void startJob(in JobParameters jobParams);
/** Stop execution of application's task. */
void stopTask(in TaskParams taskParams);
void stopJob(in JobParameters jobParams);
}

View File

@@ -14,7 +14,6 @@
* limitations under the License.
*/
package android.app.task;
package android.app.job;
parcelable Task;
parcelable JobInfo;

View File

@@ -14,7 +14,7 @@
* limitations under the License
*/
package android.app.task;
package android.app.job;
import android.content.ComponentName;
import android.os.Bundle;
@@ -23,22 +23,22 @@ import android.os.Parcelable;
import android.os.PersistableBundle;
/**
* Container of data passed to the {@link android.app.task.TaskManager} fully encapsulating the
* Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the
* parameters required to schedule work against the calling application. These are constructed
* using the {@link Task.Builder}.
* using the {@link JobInfo.Builder}.
*/
public class Task implements Parcelable {
public class JobInfo implements Parcelable {
public interface NetworkType {
/** Default. */
public final int NONE = 0;
/** This task requires network connectivity. */
/** This job requires network connectivity. */
public final int ANY = 1;
/** This task requires network connectivity that is unmetered. */
/** This job requires network connectivity that is unmetered. */
public final int UNMETERED = 2;
}
/**
* Amount of backoff a task has initially by default, in milliseconds.
* Amount of backoff a job has initially by default, in milliseconds.
* @hide.
*/
public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 5000L;
@@ -63,7 +63,7 @@ public class Task implements Parcelable {
public final int EXPONENTIAL = 1;
}
private final int taskId;
private final int jobId;
// TODO: Change this to use PersistableBundle when that lands in master.
private final PersistableBundle extras;
private final ComponentName service;
@@ -80,10 +80,10 @@ public class Task implements Parcelable {
private final int backoffPolicy;
/**
* Unique task id associated with this class. This is assigned to your task by the scheduler.
* Unique job id associated with this class. This is assigned to your job by the scheduler.
*/
public int getId() {
return taskId;
return jobId;
}
/**
@@ -94,43 +94,43 @@ public class Task implements Parcelable {
}
/**
* Name of the service endpoint that will be called back into by the TaskManager.
* Name of the service endpoint that will be called back into by the JobScheduler.
*/
public ComponentName getService() {
return service;
}
/**
* Whether this task needs the device to be plugged in.
* Whether this job needs the device to be plugged in.
*/
public boolean isRequireCharging() {
return requireCharging;
}
/**
* Whether this task needs the device to be in an Idle maintenance window.
* Whether this job needs the device to be in an Idle maintenance window.
*/
public boolean isRequireDeviceIdle() {
return requireDeviceIdle;
}
/**
* See {@link android.app.task.Task.NetworkType} for a description of this value.
* See {@link android.app.job.JobInfo.NetworkType} for a description of this value.
*/
public int getNetworkCapabilities() {
return networkCapabilities;
}
/**
* Set for a task that does not recur periodically, to specify a delay after which the task
* will be eligible for execution. This value is not set if the task recurs periodically.
* Set for a job that does not recur periodically, to specify a delay after which the job
* will be eligible for execution. This value is not set if the job recurs periodically.
*/
public long getMinLatencyMillis() {
return minLatencyMillis;
}
/**
* See {@link Builder#setOverrideDeadline(long)}. This value is not set if the task recurs
* See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs
* periodically.
*/
public long getMaxExecutionDelayMillis() {
@@ -138,23 +138,23 @@ public class Task implements Parcelable {
}
/**
* Track whether this task will repeat with a given period.
* Track whether this job will repeat with a given period.
*/
public boolean isPeriodic() {
return isPeriodic;
}
/**
* Set to the interval between occurrences of this task. This value is <b>not</b> set if the
* task does not recur periodically.
* Set to the interval between occurrences of this job. This value is <b>not</b> set if the
* job does not recur periodically.
*/
public long getIntervalMillis() {
return intervalMillis;
}
/**
* The amount of time the TaskManager will wait before rescheduling a failed task. This value
* will be increased depending on the backoff policy specified at task creation time. Defaults
* The amount of time the JobScheduler will wait before rescheduling a failed job. This value
* will be increased depending on the backoff policy specified at job creation time. Defaults
* to 5 seconds.
*/
public long getInitialBackoffMillis() {
@@ -162,7 +162,7 @@ public class Task implements Parcelable {
}
/**
* See {@link android.app.task.Task.BackoffPolicy} for an explanation of the values this field
* See {@link android.app.job.JobInfo.BackoffPolicy} for an explanation of the values this field
* can take. This defaults to exponential.
*/
public int getBackoffPolicy() {
@@ -187,8 +187,8 @@ public class Task implements Parcelable {
return hasLateConstraint;
}
private Task(Parcel in) {
taskId = in.readInt();
private JobInfo(Parcel in) {
jobId = in.readInt();
extras = in.readPersistableBundle();
service = in.readParcelable(null);
requireCharging = in.readInt() == 1;
@@ -204,10 +204,10 @@ public class Task implements Parcelable {
hasLateConstraint = in.readInt() == 1;
}
private Task(Task.Builder b) {
taskId = b.mTaskId;
private JobInfo(JobInfo.Builder b) {
jobId = b.mJobId;
extras = b.mExtras;
service = b.mTaskService;
service = b.mJobService;
requireCharging = b.mRequiresCharging;
requireDeviceIdle = b.mRequiresDeviceIdle;
networkCapabilities = b.mNetworkCapabilities;
@@ -228,7 +228,7 @@ public class Task implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(taskId);
out.writeInt(jobId);
out.writePersistableBundle(extras);
out.writeParcelable(service, flags);
out.writeInt(requireCharging ? 1 : 0);
@@ -244,23 +244,23 @@ public class Task implements Parcelable {
out.writeInt(hasLateConstraint ? 1 : 0);
}
public static final Creator<Task> CREATOR = new Creator<Task>() {
public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
@Override
public Task createFromParcel(Parcel in) {
return new Task(in);
public JobInfo createFromParcel(Parcel in) {
return new JobInfo(in);
}
@Override
public Task[] newArray(int size) {
return new Task[size];
public JobInfo[] newArray(int size) {
return new JobInfo[size];
}
};
/** Builder class for constructing {@link Task} objects. */
/** Builder class for constructing {@link JobInfo} objects. */
public static final class Builder {
private int mTaskId;
private int mJobId;
private PersistableBundle mExtras = PersistableBundle.EMPTY;
private ComponentName mTaskService;
private ComponentName mJobService;
// Requirements.
private boolean mRequiresCharging;
private boolean mRequiresDeviceIdle;
@@ -280,15 +280,15 @@ public class Task implements Parcelable {
private boolean mBackoffPolicySet = false;
/**
* @param taskId Application-provided id for this task. Subsequent calls to cancel, or
* tasks created with the same taskId, will update the pre-existing task with
* @param jobId Application-provided id for this job. Subsequent calls to cancel, or
* jobs created with the same jobId, will update the pre-existing job with
* the same id.
* @param taskService The endpoint that you implement that will receive the callback from the
* TaskManager.
* @param jobService The endpoint that you implement that will receive the callback from the
* JobScheduler.
*/
public Builder(int taskId, ComponentName taskService) {
mTaskService = taskService;
mTaskId = taskId;
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
/**
@@ -302,10 +302,10 @@ public class Task implements Parcelable {
/**
* Set some description of the kind of network capabilities you would like to have. This
* will be a parameter defined in {@link android.app.task.Task.NetworkType}.
* will be a parameter defined in {@link android.app.job.JobInfo.NetworkType}.
* Not calling this function means the network is not necessary.
* Bear in mind that calling this function defines network as a strict requirement for your
* task if the network requested is not available your task will never run. See
* job if the network requested is not available your job will never run. See
* {@link #setOverrideDeadline(long)} to change this behaviour.
*/
public Builder setRequiredNetworkCapabilities(int networkCapabilities) {
@@ -313,10 +313,10 @@ public class Task implements Parcelable {
return this;
}
/*
* Specify that to run this task, the device needs to be plugged in. This defaults to
/**
* Specify that to run this job, the device needs to be plugged in. This defaults to
* false.
* @param requireCharging Whether or not the device is plugged in.
* @param requiresCharging Whether or not the device is plugged in.
*/
public Builder setRequiresCharging(boolean requiresCharging) {
mRequiresCharging = requiresCharging;
@@ -324,11 +324,11 @@ public class Task implements Parcelable {
}
/**
* Specify that to run, the task needs the device to be in idle mode. This defaults to
* Specify that to run, the job needs the device to be in idle mode. This defaults to
* false.
* <p>Idle mode is a loose definition provided by the system, which means that the device
* is not in use, and has not been in use for some time. As such, it is a good time to
* perform resource heavy tasks. Bear in mind that battery usage will still be attributed
* perform resource heavy jobs. Bear in mind that battery usage will still be attributed
* to your application, and surfaced to the user in battery stats.</p>
* @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
* window.
@@ -339,17 +339,17 @@ public class Task implements Parcelable {
}
/**
* Specify that this task should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this task will be executed,
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
* only the guarantee that it will be executed at most once within this interval.
* A periodic task will be repeated until the phone is turned off, however it will only be
* A periodic job will be repeated until the phone is turned off, however it will only be
* persisted beyond boot if the client app has declared the
* {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
* periodic tasks without this permission, they simply will cease to exist after the phone
* periodic jobs without this permission, they simply will cease to exist after the phone
* restarts.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this task will repeat.
* @param intervalMillis Millisecond interval for which this job will repeat.
*/
public Builder setPeriodic(long intervalMillis) {
mIsPeriodic = true;
@@ -359,11 +359,11 @@ public class Task implements Parcelable {
}
/**
* Specify that this task should be delayed by the provided amount of time.
* Because it doesn't make sense setting this property on a periodic task, doing so will
* Specify that this job should be delayed by the provided amount of time.
* Because it doesn't make sense setting this property on a periodic job, doing so will
* throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.task.Task.Builder#build()} is called.
* @param minLatencyMillis Milliseconds before which this task will not be considered for
* {@link android.app.job.JobInfo.Builder#build()} is called.
* @param minLatencyMillis Milliseconds before which this job will not be considered for
* execution.
*/
public Builder setMinimumLatency(long minLatencyMillis) {
@@ -373,11 +373,11 @@ public class Task implements Parcelable {
}
/**
* Set deadline which is the maximum scheduling latency. The task will be run by this
* Set deadline which is the maximum scheduling latency. The job will be run by this
* deadline even if other requirements are not met. Because it doesn't make sense setting
* this property on a periodic task, doing so will throw an
* this property on a periodic job, doing so will throw an
* {@link java.lang.IllegalArgumentException} when
* {@link android.app.task.Task.Builder#build()} is called.
* {@link android.app.job.JobInfo.Builder#build()} is called.
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
@@ -389,13 +389,13 @@ public class Task implements Parcelable {
* Set up the back-off/retry policy.
* This defaults to some respectable values: {5 seconds, Exponential}. We cap back-off at
* 1hr.
* Note that trying to set a backoff criteria for a task with
* Note that trying to set a backoff criteria for a job with
* {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
* This is because back-off typically does not make sense for these types of tasks. See
* {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)}
* for more description of the return value for the case of a task executing while in idle
* This is because back-off typically does not make sense for these types of jobs. See
* {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
* for more description of the return value for the case of a job executing while in idle
* mode.
* @param initialBackoffMillis Millisecond time interval to wait initially when task has
* @param initialBackoffMillis Millisecond time interval to wait initially when job has
* failed.
* @param backoffPolicy is one of {@link BackoffPolicy}
*/
@@ -407,25 +407,25 @@ public class Task implements Parcelable {
}
/**
* @return The task object to hand to the TaskManager. This object is immutable.
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public Task build() {
public JobInfo build() {
mExtras = new PersistableBundle(mExtras); // Make our own copy.
// Check that a deadline was not set on a periodic task.
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
"periodic task.");
"periodic job.");
}
if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
"periodic task");
"periodic job");
}
if (mBackoffPolicySet && mRequiresDeviceIdle) {
throw new IllegalArgumentException("An idle mode task will not respect any" +
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
return new Task(this);
return new JobInfo(this);
}
}

View File

@@ -14,6 +14,6 @@
* limitations under the License.
*/
package android.app.task;
package android.app.job;
parcelable TaskParams;
parcelable JobParameters;

View File

@@ -14,40 +14,42 @@
* limitations under the License
*/
package android.app.task;
package android.app.job;
import android.app.job.IJobCallback;
import android.app.job.IJobCallback.Stub;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
/**
* Contains the parameters used to configure/identify your task. You do not create this object
* Contains the parameters used to configure/identify your job. You do not create this object
* yourself, instead it is handed in to your application by the System.
*/
public class TaskParams implements Parcelable {
public class JobParameters implements Parcelable {
private final int taskId;
private final int jobId;
private final PersistableBundle extras;
private final IBinder callback;
/** @hide */
public TaskParams(int taskId, PersistableBundle extras, IBinder callback) {
this.taskId = taskId;
public JobParameters(int jobId, PersistableBundle extras, IBinder callback) {
this.jobId = jobId;
this.extras = extras;
this.callback = callback;
}
/**
* @return The unique id of this task, specified at creation time.
* @return The unique id of this job, specified at creation time.
*/
public int getTaskId() {
return taskId;
public int getJobId() {
return jobId;
}
/**
* @return The extras you passed in when constructing this task with
* {@link android.app.task.Task.Builder#setExtras(android.os.PersistableBundle)}. This will
* @return The extras you passed in when constructing this job with
* {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
* never be null. If you did not set any extras this will be an empty bundle.
*/
public PersistableBundle getExtras() {
@@ -55,12 +57,12 @@ public class TaskParams implements Parcelable {
}
/** @hide */
public ITaskCallback getCallback() {
return ITaskCallback.Stub.asInterface(callback);
public IJobCallback getCallback() {
return IJobCallback.Stub.asInterface(callback);
}
private TaskParams(Parcel in) {
taskId = in.readInt();
private JobParameters(Parcel in) {
jobId = in.readInt();
extras = in.readPersistableBundle();
callback = in.readStrongBinder();
}
@@ -72,20 +74,20 @@ public class TaskParams implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(taskId);
dest.writeInt(jobId);
dest.writePersistableBundle(extras);
dest.writeStrongBinder(callback);
}
public static final Creator<TaskParams> CREATOR = new Creator<TaskParams>() {
public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
@Override
public TaskParams createFromParcel(Parcel in) {
return new TaskParams(in);
public JobParameters createFromParcel(Parcel in) {
return new JobParameters(in);
}
@Override
public TaskParams[] newArray(int size) {
return new TaskParams[size];
public JobParameters[] newArray(int size) {
return new JobParameters[size];
}
};
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2014 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 android.app.job;
import java.util.List;
import android.content.Context;
/**
* Class for scheduling various types of jobs with the scheduling framework on the device.
*
* <p>You do not
* instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
*/
public abstract class JobScheduler {
/**
* Returned from {@link #schedule(JobInfo)} when an invalid parameter was supplied. This can occur
* if the run-time for your job is too short, or perhaps the system can't resolve the
* requisite {@link JobService} in your package.
*/
public static final int RESULT_FAILURE = 0;
/**
* Returned from {@link #schedule(JobInfo)} if this application has made too many requests for
* work over too short a time.
*/
// TODO: Determine if this is necessary.
public static final int RESULT_SUCCESS = 1;
/**
* @param job The job you wish scheduled. See
* {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
* you can schedule.
* @return If >0, this int returns the jobId of the successfully scheduled job.
* Otherwise you have to compare the return value to the error codes defined in this class.
*/
public abstract int schedule(JobInfo job);
/**
* Cancel a job that is pending in the JobScheduler.
* @param jobId unique identifier for this job. Obtain this value from the jobs returned by
* {@link #getAllPendingJobs()}.
* @return
*/
public abstract void cancel(int jobId);
/**
* Cancel all jobs that have been registered with the JobScheduler by this package.
*/
public abstract void cancelAll();
/**
* @return a list of all the jobs registered by this package that have not yet been executed.
*/
public abstract List<JobInfo> getAllPendingJobs();
}

View File

@@ -14,9 +14,12 @@
* limitations under the License
*/
package android.app.task;
package android.app.job;
import android.app.Service;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.IJobService.Stub;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -28,72 +31,72 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
/**
* <p>Entry point for the callback from the {@link android.app.task.TaskManager}.</p>
* <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
* <p>This is the base class that handles asynchronous requests that were previously scheduled. You
* are responsible for overriding {@link TaskService#onStartTask(TaskParams)}, which is where
* you will implement your task logic.</p>
* <p>This service executes each incoming task on a {@link android.os.Handler} running on your
* are responsible for overriding {@link JobService#onStartJob(JobParameters)}, which is where
* you will implement your job logic.</p>
* <p>This service executes each incoming job on a {@link android.os.Handler} running on your
* application's main thread. This means that you <b>must</b> offload your execution logic to
* another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
* in blocking any future callbacks from the TaskManager - specifically
* {@link #onStopTask(android.app.task.TaskParams)}, which is meant to inform you that the
* in blocking any future callbacks from the JobManager - specifically
* {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
* scheduling requirements are no longer being met.</p>
*/
public abstract class TaskService extends Service {
private static final String TAG = "TaskService";
public abstract class JobService extends Service {
private static final String TAG = "JobService";
/**
* Task services must be protected with this permission:
* Job services must be protected with this permission:
*
* <pre class="prettyprint">
* <service android:name="MyTaskService"
* android:permission="android.permission.BIND_TASK_SERVICE" >
* <service android:name="MyJobService"
* android:permission="android.permission.BIND_JOB_SERVICE" >
* ...
* </service>
* </pre>
*
* <p>If a task service is declared in the manifest but not protected with this
* <p>If a job service is declared in the manifest but not protected with this
* permission, that service will be ignored by the OS.
*/
public static final String PERMISSION_BIND =
"android.permission.BIND_TASK_SERVICE";
"android.permission.BIND_JOB_SERVICE";
/**
* Identifier for a message that will result in a call to
* {@link #onStartTask(android.app.task.TaskParams)}.
* {@link #onStartJob(android.app.job.JobParameters)}.
*/
private final int MSG_EXECUTE_TASK = 0;
private final int MSG_EXECUTE_JOB = 0;
/**
* Message that will result in a call to {@link #onStopTask(android.app.task.TaskParams)}.
* Message that will result in a call to {@link #onStopJob(android.app.job.JobParameters)}.
*/
private final int MSG_STOP_TASK = 1;
private final int MSG_STOP_JOB = 1;
/**
* Message that the client has completed execution of this task.
* Message that the client has completed execution of this job.
*/
private final int MSG_TASK_FINISHED = 2;
private final int MSG_JOB_FINISHED = 2;
/** Lock object for {@link #mHandler}. */
private final Object mHandlerLock = new Object();
/**
* Handler we post tasks to. Responsible for calling into the client logic, and handling the
* Handler we post jobs to. Responsible for calling into the client logic, and handling the
* callback to the system.
*/
@GuardedBy("mHandlerLock")
TaskHandler mHandler;
JobHandler mHandler;
/** Binder for this service. */
ITaskService mBinder = new ITaskService.Stub() {
IJobService mBinder = new IJobService.Stub() {
@Override
public void startTask(TaskParams taskParams) {
public void startJob(JobParameters jobParams) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_EXECUTE_TASK, taskParams);
Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);
m.sendToTarget();
}
@Override
public void stopTask(TaskParams taskParams) {
public void stopJob(JobParameters jobParams) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_STOP_TASK, taskParams);
Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);
m.sendToTarget();
}
};
@@ -102,7 +105,7 @@ public abstract class TaskService extends Service {
void ensureHandler() {
synchronized (mHandlerLock) {
if (mHandler == null) {
mHandler = new TaskHandler(getMainLooper());
mHandler = new JobHandler(getMainLooper());
}
}
}
@@ -112,45 +115,45 @@ public abstract class TaskService extends Service {
* (app-specified) mechanism.
* @hide
*/
class TaskHandler extends Handler {
TaskHandler(Looper looper) {
class JobHandler extends Handler {
JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
final TaskParams params = (TaskParams) msg.obj;
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
case MSG_EXECUTE_TASK:
case MSG_EXECUTE_JOB:
try {
boolean workOngoing = TaskService.this.onStartTask(params);
boolean workOngoing = JobService.this.onStartJob(params);
ackStartMessage(params, workOngoing);
} catch (Exception e) {
Log.e(TAG, "Error while executing task: " + params.getTaskId());
Log.e(TAG, "Error while executing job: " + params.getJobId());
throw new RuntimeException(e);
}
break;
case MSG_STOP_TASK:
case MSG_STOP_JOB:
try {
boolean ret = TaskService.this.onStopTask(params);
boolean ret = JobService.this.onStopJob(params);
ackStopMessage(params, ret);
} catch (Exception e) {
Log.e(TAG, "Application unable to handle onStopTask.", e);
Log.e(TAG, "Application unable to handle onStopJob.", e);
throw new RuntimeException(e);
}
break;
case MSG_TASK_FINISHED:
case MSG_JOB_FINISHED:
final boolean needsReschedule = (msg.arg2 == 1);
ITaskCallback callback = params.getCallback();
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
callback.taskFinished(params.getTaskId(), needsReschedule);
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting task finish to system: binder has gone" +
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
"away.");
}
} else {
Log.e(TAG, "finishTask() called for a nonexistent task id.");
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
default:
@@ -159,34 +162,34 @@ public abstract class TaskService extends Service {
}
}
private void ackStartMessage(TaskParams params, boolean workOngoing) {
final ITaskCallback callback = params.getCallback();
final int taskId = params.getTaskId();
private void ackStartMessage(JobParameters params, boolean workOngoing) {
final IJobCallback callback = params.getCallback();
final int jobId = params.getJobId();
if (callback != null) {
try {
callback.acknowledgeStartMessage(taskId, workOngoing);
callback.acknowledgeStartMessage(jobId, workOngoing);
} catch(RemoteException e) {
Log.e(TAG, "System unreachable for starting task.");
Log.e(TAG, "System unreachable for starting job.");
}
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Attempting to ack a task that has already been processed.");
Log.d(TAG, "Attempting to ack a job that has already been processed.");
}
}
}
private void ackStopMessage(TaskParams params, boolean reschedule) {
final ITaskCallback callback = params.getCallback();
final int taskId = params.getTaskId();
private void ackStopMessage(JobParameters params, boolean reschedule) {
final IJobCallback callback = params.getCallback();
final int jobId = params.getJobId();
if (callback != null) {
try {
callback.acknowledgeStopMessage(taskId, reschedule);
callback.acknowledgeStopMessage(jobId, reschedule);
} catch(RemoteException e) {
Log.e(TAG, "System unreachable for stopping task.");
Log.e(TAG, "System unreachable for stopping job.");
}
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Attempting to ack a task that has already been processed.");
Log.d(TAG, "Attempting to ack a job that has already been processed.");
}
}
}
@@ -198,59 +201,59 @@ public abstract class TaskService extends Service {
}
/**
* Override this method with the callback logic for your task. Any such logic needs to be
* Override this method with the callback logic for your job. Any such logic needs to be
* performed on a separate thread, as this function is executed on your application's main
* thread.
*
* @param params Parameters specifying info about this task, including the extras bundle you
* optionally provided at task-creation time.
* @param params Parameters specifying info about this job, including the extras bundle you
* optionally provided at job-creation time.
* @return True if your service needs to process the work (on a separate thread). False if
* there's no more work to be done for this task.
* there's no more work to be done for this job.
*/
public abstract boolean onStartTask(TaskParams params);
public abstract boolean onStartJob(JobParameters params);
/**
* This method is called if the system has determined that you must stop execution of your task
* even before you've had a chance to call {@link #taskFinished(TaskParams, boolean)}.
* This method is called if the system has determined that you must stop execution of your job
* even before you've had a chance to call {@link #jobFinished(JobParameters, boolean)}.
*
* <p>This will happen if the requirements specified at schedule time are no longer met. For
* example you may have requested WiFi with
* {@link android.app.task.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your
* task was executing the user toggled WiFi. Another example is if you had specified
* {@link android.app.task.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkCapabilities(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
* {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
* idle maintenance window. You are solely responsible for the behaviour of your application
* upon receipt of this message; your app will likely start to misbehave if you ignore it. One
* immediate repercussion is that the system will cease holding a wakelock for you.</p>
*
* @param params Parameters specifying info about this task.
* @return True to indicate to the TaskManager whether you'd like to reschedule this task based
* on the retry criteria provided at task creation-time. False to drop the task. Regardless of
* the value returned, your task must stop executing.
* @param params Parameters specifying info about this job.
* @return True to indicate to the JobManager whether you'd like to reschedule this job based
* on the retry criteria provided at job creation-time. False to drop the job. Regardless of
* the value returned, your job must stop executing.
*/
public abstract boolean onStopTask(TaskParams params);
public abstract boolean onStopJob(JobParameters params);
/**
* Callback to inform the TaskManager you've finished executing. This can be called from any
* Callback to inform the JobManager you've finished executing. This can be called from any
* thread, as it will ultimately be run on your application's main thread. When the system
* receives this message it will release the wakelock being held.
* <p>
* You can specify post-execution behaviour to the scheduler here with
* <code>needsReschedule </code>. This will apply a back-off timer to your task based on
* <code>needsReschedule </code>. This will apply a back-off timer to your job based on
* the default, or what was set with
* {@link android.app.task.Task.Builder#setBackoffCriteria(long, int)}. The original
* requirements are always honoured even for a backed-off task. Note that a task running in
* idle mode will not be backed-off. Instead what will happen is the task will be re-added
* {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original
* requirements are always honoured even for a backed-off job. Note that a job running in
* idle mode will not be backed-off. Instead what will happen is the job will be re-added
* to the queue and re-executed within a future idle maintenance window.
* </p>
*
* @param params Parameters specifying system-provided info about this task, this was given to
* your application in {@link #onStartTask(TaskParams)}.
* @param needsReschedule True if this task is complete, false if you want the TaskManager to
* @param params Parameters specifying system-provided info about this job, this was given to
* your application in {@link #onStartJob(JobParameters)}.
* @param needsReschedule True if this job is complete, false if you want the JobManager to
* reschedule you.
*/
public final void taskFinished(TaskParams params, boolean needsReschedule) {
public final void jobFinished(JobParameters params, boolean needsReschedule) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_TASK_FINISHED, params);
Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
m.arg2 = needsReschedule ? 1 : 0;
m.sendToTarget();
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright (C) 2014 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 android.app.task;
import java.util.List;
import android.content.Context;
/**
* Class for scheduling various types of tasks with the scheduling framework on the device.
*
* <p>You do not
* instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.TASK_SERVICE)}.
*/
public abstract class TaskManager {
/*
* Returned from {@link #schedule(Task)} when an invalid parameter was supplied. This can occur
* if the run-time for your task is too short, or perhaps the system can't resolve the
* requisite {@link TaskService} in your package.
*/
public static final int RESULT_FAILURE = 0;
/**
* Returned from {@link #schedule(Task)} if this application has made too many requests for
* work over too short a time.
*/
// TODO: Determine if this is necessary.
public static final int RESULT_SUCCESS = 1;
/**
* @param task The task you wish scheduled. See
* {@link android.app.task.Task.Builder Task.Builder} for more detail on the sorts of tasks
* you can schedule.
* @return If >0, this int returns the taskId of the successfully scheduled task.
* Otherwise you have to compare the return value to the error codes defined in this class.
*/
public abstract int schedule(Task task);
/**
* Cancel a task that is pending in the TaskManager.
* @param taskId unique identifier for this task. Obtain this value from the tasks returned by
* {@link #getAllPendingTasks()}.
* @return
*/
public abstract void cancel(int taskId);
/**
* Cancel all tasks that have been registered with the TaskManager by this package.
*/
public abstract void cancelAll();
/**
* @return a list of all the tasks registered by this package that have not yet been executed.
*/
public abstract List<Task> getAllPendingTasks();
}

View File

@@ -2059,7 +2059,7 @@ public abstract class Context {
PRINT_SERVICE,
MEDIA_SESSION_SERVICE,
BATTERY_SERVICE,
TASK_SERVICE,
JOB_SCHEDULER_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -2116,8 +2116,8 @@ public abstract class Context {
* <dd> A {@link android.app.DownloadManager} for requesting HTTP downloads
* <dt> {@link #BATTERY_SERVICE} ("batterymanager")
* <dd> A {@link android.os.BatteryManager} for managing battery state
* <dt> {@link #TASK_SERVICE} ("taskmanager")
* <dd> A {@link android.app.task.TaskManager} for managing scheduled tasks
* <dt> {@link #JOB_SCHEDULER_SERVICE} ("taskmanager")
* <dd> A {@link android.app.job.JobScheduler} for managing scheduled tasks
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -2171,8 +2171,8 @@ public abstract class Context {
* @see android.app.DownloadManager
* @see #BATTERY_SERVICE
* @see android.os.BatteryManager
* @see #TASK_SERVICE
* @see android.app.task.TaskManager
* @see #JOB_SCHEDULER_SERVICE
* @see android.app.job.JobScheduler
*/
public abstract Object getSystemService(@ServiceName @NonNull String name);
@@ -2769,12 +2769,12 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.app.task.TaskManager} instance for managing occasional
* android.app.job.JobScheduler} instance for managing occasional
* background tasks.
* @see #getSystemService
* @see android.app.task.TaskManager
* @see android.app.job.JobScheduler
*/
public static final String TASK_SERVICE = "task";
public static final String JOB_SCHEDULER_SERVICE = "jobscheduler";
/**
* Determine whether the given permission is allowed for a particular

View File

@@ -1770,11 +1770,11 @@
<!-- Allows the system to bind to an application's task services
@hide -->
<permission android:name="android.permission.BIND_TASK_SERVICE"
<permission android:name="android.permission.BIND_JOB_SERVICE"
android:protectionLevel="signature"
android:label="@string/permlab_bindTaskService"
android:description="@string/permdesc_bindTaskService" />
<uses-permission android:name="android.permission.BIND_TASK_SERVICE"/>
android:label="@string/permlab_bindJobService"
android:description="@string/permdesc_bindJobService" />
<uses-permission android:name="android.permission.BIND_JOB_SERVICE"/>
<!-- ========================================= -->
<!-- Permissions for special development tools -->
@@ -2888,8 +2888,8 @@
</service>
<service android:name="com.android.server.MountServiceIdler"
android:exported="false"
android:permission="android.permission.BIND_TASK_SERVICE" >
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
</application>

View File

@@ -1261,10 +1261,10 @@
permission that an application must be granted by the user. Instead, it
is part of a mechanism that applications use to indicate to the system
that they want to do scheduled background work. -->
<string name="permlab_bindTaskService">run the application\'s scheduled background work</string>
<string name="permlab_bindJobService">run the application\'s scheduled background work</string>
<!-- Description of an application permission, so that the user can understand
what is being done if they are curious. -->
<string name="permdesc_bindTaskService">This permission allows the Android system to run the application in the background when requested.</string>
<string name="permdesc_bindJobService">This permission allows the Android system to run the application in the background when requested.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_diagnostic">read/write to resources owned by diag</string>

View File

@@ -18,32 +18,32 @@ package com.android.server;
import java.util.Calendar;
import android.app.task.Task;
import android.app.task.TaskManager;
import android.app.task.TaskParams;
import android.app.task.TaskService;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.util.Slog;
public class MountServiceIdler extends TaskService {
public class MountServiceIdler extends JobService {
private static final String TAG = "MountServiceIdler";
private static ComponentName sIdleService =
new ComponentName(MountServiceIdler.class.getPackage().getName(),
MountServiceIdler.class.getName());
private static int MOUNT_TASK_ID = 808;
private static int MOUNT_JOB_ID = 808;
private boolean mStarted;
private TaskParams mTaskParams;
private JobParameters mJobParams;
private Runnable mFinishCallback = new Runnable() {
@Override
public void run() {
Slog.i(TAG, "Got mount service completion callback");
synchronized (mFinishCallback) {
if (mStarted) {
taskFinished(mTaskParams, false);
jobFinished(mJobParams, false);
mStarted = false;
}
}
@@ -53,12 +53,12 @@ public class MountServiceIdler extends TaskService {
};
@Override
public boolean onStartTask(TaskParams params) {
public boolean onStartJob(JobParameters params) {
// The mount service will run an fstrim operation asynchronously
// on a designated separate thread, so we provide it with a callback
// that lets us cleanly end our idle timeslice. It's safe to call
// finishIdle() from any thread.
mTaskParams = params;
mJobParams = params;
MountService ms = MountService.sSelf;
if (ms != null) {
synchronized (mFinishCallback) {
@@ -70,9 +70,9 @@ public class MountServiceIdler extends TaskService {
}
@Override
public boolean onStopTask(TaskParams params) {
public boolean onStopJob(JobParameters params) {
// Once we kick off the fstrim we aren't actually interruptible; just note
// that we don't need to call taskFinished(), and let everything happen in
// that we don't need to call jobFinished(), and let everything happen in
// the callback from the mount service.
synchronized (mFinishCallback) {
mStarted = false;
@@ -84,12 +84,12 @@ public class MountServiceIdler extends TaskService {
* Schedule the idle job that will ping the mount service
*/
public static void scheduleIdlePass(Context context) {
TaskManager tm = (TaskManager) context.getSystemService(Context.TASK_SERVICE);
JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
Calendar calendar = tomorrowMidnight();
final long timeToMidnight = calendar.getTimeInMillis() - System.currentTimeMillis();
Task.Builder builder = new Task.Builder(MOUNT_TASK_ID, sIdleService);
JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService);
builder.setRequiresDeviceIdle(true);
builder.setRequiresCharging(true);
builder.setMinimumLatency(timeToMidnight);

View File

@@ -14,19 +14,19 @@
* limitations under the License
*/
package com.android.server.task;
package com.android.server.job;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.job.controllers.JobStatus;
/**
* Used for communication between {@link com.android.server.task.TaskServiceContext} and the
* {@link com.android.server.task.TaskManagerService}.
* Used for communication between {@link com.android.server.job.JobServiceContext} and the
* {@link com.android.server.job.JobSchedulerService}.
*/
public interface TaskCompletedListener {
public interface JobCompletedListener {
/**
* Callback for when a task is completed.
* @param needsReschedule Whether the implementing class should reschedule this task.
* Callback for when a job is completed.
* @param needsReschedule Whether the implementing class should reschedule this job.
*/
public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule);
public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule);
}

View File

@@ -14,21 +14,21 @@
* limitations under the License
*/
package com.android.server.task;
package com.android.server.job;
import java.util.List;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.job.controllers.JobStatus;
/**
* Callback definition for I/O thread to let the TaskManagerService know when
* Callback definition for I/O thread to let the JobManagerService know when
* I/O read has completed. Done this way so we don't stall the main thread on
* boot.
*/
public interface TaskMapReadFinishedListener {
public interface JobMapReadFinishedListener {
/**
* Called by the {@link TaskStore} at boot, when the disk read is finished.
* Called by the {@link JobStore} at boot, when the disk read is finished.
*/
public void onTaskMapReadFinished(List<TaskStatus> tasks);
public void onJobMapReadFinished(List<JobStatus> jobs);
}

View File

@@ -0,0 +1,764 @@
/*
* Copyright (C) 2014 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 java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.app.job.JobInfo;
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;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.IdleController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
import com.android.server.job.controllers.TimeController;
import java.util.LinkedList;
/**
* 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
* endpoint.
* Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
* about constraints, or the state of active jobs. It receives callbacks from the various
* controllers and completed jobs and operates accordingly.
*
* Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
* Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
* @hide
*/
public class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener, JobMapReadFinishedListener {
// TODO: Switch this off for final version.
static final boolean DEBUG = true;
/** The number of concurrent jobs we run at one time. */
private static final int MAX_JOB_CONTEXTS_COUNT = 3;
static final String TAG = "JobManagerService";
/** Master list of jobs. */
private final JobStore mJobs;
static final int MSG_JOB_EXPIRED = 0;
static final int MSG_CHECK_JOB = 1;
// Policy constants
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
* early.
*/
private static final int MIN_IDLE_COUNT = 1;
/**
* Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
* things early.
*/
private static final int MIN_CONNECTIVITY_COUNT = 2;
/**
* Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
* some work early.
*/
private static final int MIN_READY_JOBS_COUNT = 4;
/**
* Track Services that have currently active or pending jobs. The index is provided by
* {@link JobStatus#getServiceToken()}
*/
private final List<JobServiceContext> mActiveServices = new LinkedList<JobServiceContext>();
/** List of controllers that will notify this service of updates to jobs. */
private List<StateController> mControllers;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
*/
private final LinkedList<JobStatus> mPendingJobs = new LinkedList<JobStatus>();
private final JobHandler mHandler;
private final JobSchedulerStub mJobSchedulerStub;
/**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
*/
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Slog.d(TAG, "Receieved: " + intent.getAction());
if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
cancelJobsForUid(uidRemoved);
} 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);
}
}
};
/**
* Entry point from client to schedule the provided job.
* This cancels the job if it's already been scheduled, and replaces it with the one provided.
* @param job JobInfo object containing execution parameters
* @param uId The package identifier of the application this job is for.
* @param canPersistJob Whether or not the client has the appropriate permissions for
* persisting this job.
* @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
*/
public int schedule(JobInfo job, int uId, boolean canPersistJob) {
JobStatus jobStatus = new JobStatus(job, uId, canPersistJob);
cancelJob(uId, job.getId());
startTrackingJob(jobStatus);
return JobScheduler.RESULT_SUCCESS;
}
public List<JobInfo> getPendingJobs(int uid) {
ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
synchronized (mJobs) {
for (JobStatus job : mJobs.getJobs()) {
if (job.getUid() == uid) {
outList.add(job.getJob());
}
}
}
return outList;
}
private void cancelJobsForUser(int userHandle) {
synchronized (mJobs) {
List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
for (JobStatus toRemove : jobsForUser) {
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + toRemove);
}
cancelJobLocked(toRemove);
}
}
}
/**
* Entry point from client to cancel all jobs originating from their uid.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
* @param uid To check against for removal of a job.
*/
public void cancelJobsForUid(int uid) {
// Remove from master list.
synchronized (mJobs) {
List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (JobStatus toRemove : jobsForUid) {
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + toRemove);
}
cancelJobLocked(toRemove);
}
}
}
/**
* Entry point from client to cancel the job corresponding to the jobId provided.
* 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 of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
public void cancelJob(int uid, int jobId) {
JobStatus toCancel;
synchronized (mJobs) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
cancelJobLocked(toCancel);
}
}
}
private void cancelJobLocked(JobStatus cancelled) {
// Remove from store.
stopTrackingJob(cancelled);
// Remove from pending queue.
mPendingJobs.remove(cancelled);
// Cancel if running.
stopJobOnServiceContextLocked(cancelled);
}
/**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
* and passes it to super.
* </p>
*
* @param context The system server context.
*/
public JobSchedulerService(Context context) {
super(context);
// Create the controllers.
mControllers = new LinkedList<StateController>();
mControllers.add(ConnectivityController.get(this));
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
mControllers.add(BatteryController.get(this));
mHandler = new JobHandler(context.getMainLooper());
mJobSchedulerStub = new JobSchedulerStub();
// Create the "runners".
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mActiveServices.add(
new JobServiceContext(this, context.getMainLooper()));
}
mJobs = JobStore.initAndGet(this);
}
@Override
public void onStart() {
publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
}
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
// 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);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
}
}
/**
* Called when we have a job status object that we need to insert in our
* {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
* about.
*/
private void startTrackingJob(JobStatus jobStatus) {
boolean update;
synchronized (mJobs) {
update = mJobs.add(jobStatus);
}
for (StateController controller : mControllers) {
if (update) {
controller.maybeStopTrackingJob(jobStatus);
}
controller.maybeStartTrackingJob(jobStatus);
}
}
/**
* Called when we want to remove a JobStatus object that we've finished executing. Returns the
* object removed.
*/
private boolean stopTrackingJob(JobStatus jobStatus) {
boolean removed;
synchronized (mJobs) {
// Remove from store as well as controllers.
removed = mJobs.remove(jobStatus);
}
if (removed) {
for (StateController controller : mControllers) {
controller.maybeStopTrackingJob(jobStatus);
}
}
return removed;
}
private boolean stopJobOnServiceContextLocked(JobStatus job) {
for (JobServiceContext jsc : mActiveServices) {
final JobStatus executing = jsc.getRunningJob();
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
jsc.cancelExecutingJob();
return true;
}
}
return false;
}
/**
* @param job JobStatus we are querying against.
* @return Whether or not the job represented by the status object is currently being run or
* is pending.
*/
private boolean isCurrentlyActiveLocked(JobStatus job) {
for (JobServiceContext serviceContext : mActiveServices) {
final JobStatus running = serviceContext.getRunningJob();
if (running != null && running.matches(job.getUid(), job.getJobId())) {
return true;
}
}
return false;
}
/**
* A job is rescheduled with exponential back-off if the client requests this from their
* execution logic.
* A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the
* timeliness of the reschedule. For an idle-mode job, no deadline is given.
* @param failureToReschedule Provided job status that we will reschedule.
* @return A newly instantiated JobStatus with the same constraints as the last job except
* with adjusted timing constraints.
*/
private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
final long elapsedNowMillis = SystemClock.elapsedRealtime();
final JobInfo job = failureToReschedule.getJob();
final long initialBackoffMillis = job.getInitialBackoffMillis();
final int backoffAttempt = failureToReschedule.getNumFailures() + 1;
long newEarliestRuntimeElapsed = elapsedNowMillis;
switch (job.getBackoffPolicy()) {
case JobInfo.BackoffPolicy.LINEAR:
newEarliestRuntimeElapsed += initialBackoffMillis * backoffAttempt;
break;
default:
if (DEBUG) {
Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
}
case JobInfo.BackoffPolicy.EXPONENTIAL:
newEarliestRuntimeElapsed +=
Math.pow(initialBackoffMillis * 0.001, backoffAttempt) * 1000;
break;
}
newEarliestRuntimeElapsed =
Math.min(newEarliestRuntimeElapsed, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
return new JobStatus(failureToReschedule, newEarliestRuntimeElapsed,
JobStatus.NO_LATEST_RUNTIME, backoffAttempt);
}
/**
* Called after a periodic has executed so we can to re-add it. We take the last execution time
* of the job to be the time of completion (i.e. the time at which this function is called).
* This could be inaccurate b/c the job can run for as long as
* {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
* to underscheduling at least, rather than if we had taken the last execution time to be the
* start of the execution.
* @return A new job representing the execution criteria for this instantiation of the
* recurring job.
*/
private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
final long elapsedNow = SystemClock.elapsedRealtime();
// Compute how much of the period is remaining.
long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
long newEarliestRunTimeElapsed = elapsedNow + runEarly;
long period = periodicToReschedule.getJob().getIntervalMillis();
long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
if (DEBUG) {
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
}
return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
newLatestRuntimeElapsed, 0 /* backoffAttempt */);
}
// JobCompletedListener implementations.
/**
* A job just finished executing. We fetch the
* {@link com.android.server.job.controllers.JobStatus} from the store and depending on
* whether we want to reschedule we readd it to the controllers.
* @param jobStatus Completed job.
* @param needsReschedule Whether the implementing class should reschedule this job.
*/
@Override
public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
if (DEBUG) {
Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
}
if (!stopTrackingJob(jobStatus)) {
if (DEBUG) {
Slog.e(TAG, "Error removing job: could not find job to remove. Was job " +
"removed while executing?");
}
return;
}
if (needsReschedule) {
JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
startTrackingJob(rescheduled);
} else if (jobStatus.getJob().isPeriodic()) {
JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
startTrackingJob(rescheduledPeriodic);
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
// StateChangedListener implementations.
/**
* Off-board work to our handler thread as quickly as possible, b/c this call is probably being
* made on the main thread.
* For now this takes the job and if it's ready to run it will run it. In future we might not
* provide the job, so that the StateChangedListener has to run through its list of jobs to
* see which are ready. This will further decouple the controllers from the execution logic.
*/
@Override
public void onControllerStateChanged() {
// Post a message to to run through the list of jobs and start/stop any that are eligible.
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
@Override
public void onRunJobNow(JobStatus jobStatus) {
mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
}
/**
* Disk I/O is finished, take the list of jobs we read from disk and add them to our
* {@link JobStore}.
* This is run on the {@link com.android.server.IoThread} instance, which is a separate thread,
* and is called once at boot.
*/
@Override
public void onJobMapReadFinished(List<JobStatus> jobs) {
synchronized (mJobs) {
for (JobStatus js : jobs) {
if (mJobs.containsJobIdForUid(js.getJobId(), js.getUid())) {
// An app with BOOT_COMPLETED *might* have decided to reschedule their job, in
// the same amount of time it took us to read it from disk. If this is the case
// we leave it be.
continue;
}
startTrackingJob(js);
}
}
}
private class JobHandler extends Handler {
public JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_JOB_EXPIRED:
synchronized (mJobs) {
JobStatus runNow = (JobStatus) message.obj;
if (!mPendingJobs.contains(runNow)) {
mPendingJobs.add(runNow);
}
}
queueReadyJobsForExecutionH();
break;
case MSG_CHECK_JOB:
// Check the list of jobs and run some of them if we feel inclined.
maybeQueueReadyJobsForExecutionH();
break;
}
maybeRunPendingJobsH();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_JOB);
}
/**
* Run through list of jobs and execute all possible - at least one is expired so we do
* as many as we can.
*/
private void queueReadyJobsForExecutionH() {
synchronized (mJobs) {
for (JobStatus job : mJobs.getJobs()) {
if (isReadyToBeExecutedLocked(job)) {
mPendingJobs.add(job);
} else if (isReadyToBeCancelledLocked(job)) {
stopJobOnServiceContextLocked(job);
}
}
}
}
/**
* The state of at least one job has changed. Here is where we could enforce various
* policies on when we want to execute jobs.
* Right now the policy is such:
* If >1 of the ready jobs is idle mode we send all of them off
* if more than 2 network connectivity jobs are ready we send them all off.
* If more than 4 jobs total are ready we send them all off.
* TODO: It would be nice to consolidate these sort of high-level policies somewhere.
*/
private void maybeQueueReadyJobsForExecutionH() {
synchronized (mJobs) {
int idleCount = 0;
int backoffCount = 0;
int connectivityCount = 0;
List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
for (JobStatus job : mJobs.getJobs()) {
if (isReadyToBeExecutedLocked(job)) {
if (job.getNumFailures() > 0) {
backoffCount++;
}
if (job.hasIdleConstraint()) {
idleCount++;
}
if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
connectivityCount++;
}
runnableJobs.add(job);
} else if (isReadyToBeCancelledLocked(job)) {
stopJobOnServiceContextLocked(job);
}
}
if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
for (JobStatus job : runnableJobs) {
mPendingJobs.add(job);
}
}
}
}
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a JSC.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
return job.isReady() && !mPendingJobs.contains(job) && !isCurrentlyActiveLocked(job);
}
/**
* Criteria for cancelling an active job:
* - It's not ready
* - It's running on a JSC.
*/
private boolean isReadyToBeCancelledLocked(JobStatus job) {
return !job.isReady() && isCurrentlyActiveLocked(job);
}
/**
* Reconcile jobs in the pending queue against available execution contexts.
* A controller can force a job into the pending queue even if it's already running, but
* here is where we decide whether to actually execute it.
*/
private void maybeRunPendingJobsH() {
synchronized (mJobs) {
Iterator<JobStatus> it = mPendingJobs.iterator();
while (it.hasNext()) {
JobStatus nextPending = it.next();
JobServiceContext availableContext = null;
for (JobServiceContext jsc : mActiveServices) {
final JobStatus running = jsc.getRunningJob();
if (running != null && running.matches(nextPending.getUid(),
nextPending.getJobId())) {
// Already running this tId for this uId, skip.
availableContext = null;
break;
}
if (jsc.isAvailable()) {
availableContext = jsc;
}
}
if (availableContext != null) {
if (!availableContext.executeRunnableJob(nextPending)) {
if (DEBUG) {
Slog.d(TAG, "Error executing " + nextPending);
}
mJobs.remove(nextPending);
}
it.remove();
}
}
}
}
}
/**
* Binder stub trampoline implementation
*/
final class JobSchedulerStub extends IJobScheduler.Stub {
/** Cache determination of whether a given app can persist jobs
* key is uid of the calling app; value is undetermined/true/false
*/
private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
// Enforce that only the app itself (or shared uid participant) can schedule a
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
private void enforceValidJobRequest(int uid, JobInfo job) {
final PackageManager pm = getContext().getPackageManager();
final ComponentName service = job.getService();
try {
ServiceInfo si = pm.getServiceInfo(service, 0);
if (si.applicationInfo.uid != uid) {
throw new IllegalArgumentException("uid " + uid +
" cannot schedule job in " + service.getPackageName());
}
if (!JobService.PERMISSION_BIND.equals(si.permission)) {
throw new IllegalArgumentException("Scheduled service " + service
+ " does not require android.permission.BIND_JOB_SERVICE permission");
}
} catch (NameNotFoundException e) {
throw new IllegalArgumentException("No such service: " + service);
}
}
private boolean canPersistJobs(int pid, int uid) {
// If we get this far we're good to go; all we need to do now is check
// whether the app is allowed to persist its scheduled work.
final boolean canPersist;
synchronized (mPersistCache) {
Boolean cached = mPersistCache.get(uid);
if (cached != null) {
canPersist = cached.booleanValue();
} else {
// Persisting jobs is tantamount to running at boot, so we permit
// it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
// permission
int result = getContext().checkPermission(
android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
canPersist = (result == PackageManager.PERMISSION_GRANTED);
mPersistCache.put(uid, canPersist);
}
}
return canPersist;
}
// IJobScheduler implementation
@Override
public int schedule(JobInfo job) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling job: " + job);
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
enforceValidJobRequest(uid, job);
final boolean canPersist = canPersistJobs(pid, uid);
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.schedule(job, uid, canPersist);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public List<JobInfo> getAllPendingJobs() throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.getPendingJobs(uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void cancelAll() throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void cancel(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJob(uid, jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
* "dumpsys" infrastructure
*/
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
long identityToken = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.dumpInternal(pw);
} finally {
Binder.restoreCallingIdentity(identityToken);
}
}
};
void dumpInternal(PrintWriter pw) {
synchronized (mJobs) {
pw.println("Registered jobs:");
if (mJobs.size() > 0) {
for (JobStatus job : mJobs.getJobs()) {
job.dump(pw, " ");
}
} else {
pw.println();
pw.println("No jobs scheduled.");
}
for (StateController controller : mControllers) {
pw.println();
controller.dumpControllerState(pw);
}
pw.println();
pw.println("Pending");
for (JobStatus jobStatus : mPendingJobs) {
pw.println(jobStatus.hashCode());
}
pw.println();
pw.println("Active jobs:");
for (JobServiceContext jsc : mActiveServices) {
if (jsc.isAvailable()) {
continue;
} else {
pw.println(jsc.getRunningJob().hashCode() + " for: " +
(SystemClock.elapsedRealtime()
- jsc.getExecutionStartTimeElapsed())/1000 + "s " +
"timeout: " + jsc.getTimeoutElapsed());
}
}
}
pw.println();
}
}

View File

@@ -14,12 +14,12 @@
* limitations under the License
*/
package com.android.server.task;
package com.android.server.job;
import android.app.ActivityManager;
import android.app.task.ITaskCallback;
import android.app.task.ITaskService;
import android.app.task.TaskParams;
import android.app.job.JobParameters;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -39,32 +39,32 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.job.controllers.JobStatus;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Handles client binding and lifecycle of a task. A task will only execute one at a time on an
* Handles client binding and lifecycle of a job. A job will only execute one at a time on an
* instance of this class.
*/
public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
private static final boolean DEBUG = true;
private static final String TAG = "TaskServiceContext";
/** Define the maximum # of tasks allowed to run on a service at once. */
private static final int defaultMaxActiveTasksPerService =
private static final String TAG = "JobServiceContext";
/** Define the maximum # of jobs allowed to run on a service at once. */
private static final int defaultMaxActiveJobsPerService =
ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
/** Amount of time a task is allowed to execute for before being considered timed-out. */
/** Amount of time a job is allowed to execute for before being considered timed-out. */
private static final long EXECUTING_TIMESLICE_MILLIS = 60 * 1000;
/** Amount of time the TaskManager will wait for a response from an app for a message. */
/** Amount of time the JobScheduler will wait for a response from an app for a message. */
private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
/** String prefix for all wakelock names. */
private static final String TM_WAKELOCK_PREFIX = "*task*/";
private static final String JS_WAKELOCK_PREFIX = "*job*/";
private static final String[] VERB_STRINGS = {
"VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
};
// States that a task occupies while interacting with the client.
// States that a job occupies while interacting with the client.
static final int VERB_BINDING = 0;
static final int VERB_STARTING = 1;
static final int VERB_EXECUTING = 2;
@@ -75,30 +75,30 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
private static final int MSG_TIMEOUT = 0;
/** Received a callback from client. */
private static final int MSG_CALLBACK = 1;
/** Run through list and start any ready tasks.*/
/** Run through list and start any ready jobs.*/
private static final int MSG_SERVICE_BOUND = 2;
/** Cancel a task. */
/** Cancel a job. */
private static final int MSG_CANCEL = 3;
/** Shutdown the Task. Used when the client crashes and we can't die gracefully.*/
/** Shutdown the job. Used when the client crashes and we can't die gracefully.*/
private static final int MSG_SHUTDOWN_EXECUTION = 4;
private final Handler mCallbackHandler;
/** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
private final TaskCompletedListener mCompletedListener;
/** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
private final JobCompletedListener mCompletedListener;
/** Used for service binding, etc. */
private final Context mContext;
private PowerManager.WakeLock mWakeLock;
// Execution state.
private TaskParams mParams;
private JobParameters mParams;
@VisibleForTesting
int mVerb;
private AtomicBoolean mCancelled = new AtomicBoolean();
/** All the information maintained about the task currently being executed. */
private TaskStatus mRunningTask;
/** All the information maintained about the job currently being executed. */
private JobStatus mRunningJob;
/** Binder to the client service. */
ITaskService service;
IJobService service;
private final Object mLock = new Object();
/** Whether this context is free. */
@@ -109,45 +109,45 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
/** Track when job will timeout. */
private long mTimeoutElapsed;
TaskServiceContext(TaskManagerService service, Looper looper) {
JobServiceContext(JobSchedulerService service, Looper looper) {
this(service.getContext(), service, looper);
}
@VisibleForTesting
TaskServiceContext(Context context, TaskCompletedListener completedListener, Looper looper) {
JobServiceContext(Context context, JobCompletedListener completedListener, Looper looper) {
mContext = context;
mCallbackHandler = new TaskServiceHandler(looper);
mCallbackHandler = new JobServiceHandler(looper);
mCompletedListener = completedListener;
mAvailable = true;
}
/**
* Give a task to this context for execution. Callers must first check {@link #isAvailable()}
* Give a job to this context for execution. Callers must first check {@link #isAvailable()}
* to make sure this is a valid context.
* @param ts The status of the task that we are going to run.
* @return True if the task is valid and is running. False if the task cannot be executed.
* @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.
*/
boolean executeRunnableTask(TaskStatus ts) {
boolean executeRunnableJob(JobStatus job) {
synchronized (mLock) {
if (!mAvailable) {
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
return false;
}
mRunningTask = ts;
mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
mRunningJob = job;
mParams = new JobParameters(job.getJobId(), job.getExtras(), this);
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
mVerb = VERB_BINDING;
final Intent intent = new Intent().setComponent(ts.getServiceComponent());
final Intent intent = new Intent().setComponent(job.getServiceComponent());
boolean binding = mContext.bindServiceAsUser(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
new UserHandle(ts.getUserId()));
new UserHandle(job.getUserId()));
if (!binding) {
if (DEBUG) {
Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable.");
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningTask = null;
mRunningJob = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
return false;
@@ -157,13 +157,13 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
}
}
/** Used externally to query the running task. Will return null if there is no task running. */
TaskStatus getRunningTask() {
return mRunningTask;
/** Used externally to query the running job. Will return null if there is no job running. */
JobStatus getRunningJob() {
return mRunningJob;
}
/** Called externally when a task that was scheduled for execution should be cancelled. */
void cancelExecutingTask() {
/** Called externally when a job that was scheduled for execution should be cancelled. */
void cancelExecutingJob() {
mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
}
@@ -185,29 +185,29 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
}
@Override
public void taskFinished(int taskId, boolean reschedule) {
public void jobFinished(int jobId, boolean reschedule) {
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
.sendToTarget();
}
@Override
public void acknowledgeStopMessage(int taskId, boolean reschedule) {
public void acknowledgeStopMessage(int jobId, boolean reschedule) {
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
.sendToTarget();
}
@Override
public void acknowledgeStartMessage(int taskId, boolean ongoing) {
public void acknowledgeStartMessage(int jobId, boolean ongoing) {
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
}
/**
@@ -219,25 +219,25 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (!name.equals(mRunningTask.getServiceComponent())) {
if (!name.equals(mRunningJob.getServiceComponent())) {
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
return;
}
this.service = ITaskService.Stub.asInterface(service);
this.service = IJobService.Stub.asInterface(service);
// Remove all timeouts.
mCallbackHandler.removeMessages(MSG_TIMEOUT);
final PowerManager pm =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
TM_WAKELOCK_PREFIX + mRunningTask.getServiceComponent().getPackageName());
mWakeLock.setWorkSource(new WorkSource(mRunningTask.getUid()));
JS_WAKELOCK_PREFIX + mRunningJob.getServiceComponent().getPackageName());
mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
/**
* If the client service crashes we reschedule this task and clean up.
* If the client service crashes we reschedule this job and clean up.
* @param name The concrete component name of the service whose
*/
@Override
@@ -251,7 +251,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* @return True if the binder calling is coming from the client we expect.
*/
private boolean verifyCallingUid() {
if (mRunningTask == null || Binder.getCallingUid() != mRunningTask.getUid()) {
if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
if (DEBUG) {
Slog.d(TAG, "Stale callback received, ignoring.");
}
@@ -261,12 +261,12 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
}
/**
* Handles the lifecycle of the TaskService binding/callbacks, etc. The convention within this
* Handles the lifecycle of the JobService binding/callbacks, etc. The convention within this
* class is to append 'H' to each function name that can only be called on this handler. This
* isn't strictly necessary because all of these functions are private, but helps clarity.
*/
private class TaskServiceHandler extends Handler {
TaskServiceHandler(Looper looper) {
private class JobServiceHandler extends Handler {
JobServiceHandler(Looper looper) {
super(looper);
}
@@ -278,7 +278,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
break;
case MSG_CALLBACK:
if (DEBUG) {
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask + " v:" +
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob + " v:" +
VERB_STRINGS[mVerb]);
}
removeMessages(MSG_TIMEOUT);
@@ -292,7 +292,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
handleFinishedH(reschedule);
} else {
if (DEBUG) {
Slog.d(TAG, "Unrecognised callback: " + mRunningTask);
Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
}
}
break;
@@ -303,42 +303,42 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
handleOpTimeoutH();
break;
case MSG_SHUTDOWN_EXECUTION:
closeAndCleanupTaskH(true /* needsReschedule */);
closeAndCleanupJobH(true /* needsReschedule */);
break;
default:
Log.e(TAG, "Unrecognised message: " + message);
}
}
/** Start the task on the service. */
/** Start the job on the service. */
private void handleServiceBoundH() {
if (mVerb != VERB_BINDING) {
Slog.e(TAG, "Sending onStartTask for a task that isn't pending. "
Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
+ VERB_STRINGS[mVerb]);
closeAndCleanupTaskH(false /* reschedule */);
closeAndCleanupJobH(false /* reschedule */);
return;
}
if (mCancelled.get()) {
if (DEBUG) {
Slog.d(TAG, "Task cancelled while waiting for bind to complete. "
+ mRunningTask);
Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
+ mRunningJob);
}
closeAndCleanupTaskH(true /* reschedule */);
closeAndCleanupJobH(true /* reschedule */);
return;
}
try {
mVerb = VERB_STARTING;
scheduleOpTimeOut();
service.startTask(mParams);
service.startJob(mParams);
} catch (RemoteException e) {
Log.e(TAG, "Error sending onStart message to '" +
mRunningTask.getServiceComponent().getShortClassName() + "' ", e);
mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
}
}
/**
* State behaviours.
* VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout.
* VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout.
* _PENDING -> Error
* _EXECUTING -> Error
* _STOPPING -> Error
@@ -348,7 +348,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
case VERB_STARTING:
mVerb = VERB_EXECUTING;
if (!workOngoing) {
// Task is finished already so fast-forward to handleFinished.
// Job is finished already so fast-forward to handleFinished.
handleFinishedH(false);
return;
}
@@ -360,14 +360,14 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
scheduleOpTimeOut();
break;
default:
Log.e(TAG, "Handling started task but task wasn't starting! Was "
Log.e(TAG, "Handling started job but job wasn't starting! Was "
+ VERB_STRINGS[mVerb] + ".");
return;
}
}
/**
* VERB_EXECUTING -> Client called taskFinished(), clean up and notify done.
* VERB_EXECUTING -> Client called jobFinished(), clean up and notify done.
* _STOPPING -> Successful finish, clean up and notify done.
* _STARTING -> Error
* _PENDING -> Error
@@ -376,20 +376,20 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
closeAndCleanupTaskH(reschedule);
closeAndCleanupJobH(reschedule);
break;
default:
Slog.e(TAG, "Got an execution complete message for a task that wasn't being" +
Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
"executed. Was " + VERB_STRINGS[mVerb] + ".");
}
}
/**
* A task can be in various states when a cancel request comes in:
* A job can be in various states when a cancel request comes in:
* VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
* {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
* _STARTING -> Mark as cancelled and wait for
* {@link TaskServiceContext#acknowledgeStartMessage(int, boolean)}
* {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
* _EXECUTING -> call {@link #sendStopMessageH}}.
* _ENDING -> No point in doing anything here, so we ignore.
*/
@@ -406,48 +406,48 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
// Nada.
break;
default:
Slog.e(TAG, "Cancelling a task without a valid verb: " + mVerb);
Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
break;
}
}
/** Process MSG_TIMEOUT here. */
private void handleOpTimeoutH() {
if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
if (Log.isLoggable(JobSchedulerService.TAG, Log.DEBUG)) {
Log.d(TAG, "MSG_TIMEOUT of " +
mRunningTask.getServiceComponent().getShortClassName() + " : "
+ mParams.getTaskId());
mRunningJob.getServiceComponent().getShortClassName() + " : "
+ mParams.getJobId());
}
final int taskId = mParams.getTaskId();
final int jobId = mParams.getJobId();
switch (mVerb) {
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
// know what happened so let's log it and notify the TaskManager
// know what happened so let's log it and notify the JobScheduler
// FINISHED/NO-RETRY.
Log.e(TAG, "No response from client for onStartTask '" +
mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ taskId);
closeAndCleanupTaskH(false /* needsReschedule */);
Log.e(TAG, "No response from client for onStartJob '" +
mRunningJob.getServiceComponent().getShortClassName() + "' tId: "
+ jobId);
closeAndCleanupJobH(false /* needsReschedule */);
break;
case VERB_STOPPING:
// At least we got somewhere, so fail but ask the TaskManager to reschedule.
Log.e(TAG, "No response from client for onStopTask, '" +
mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ taskId);
closeAndCleanupTaskH(true /* needsReschedule */);
// At least we got somewhere, so fail but ask the JobScheduler to reschedule.
Log.e(TAG, "No response from client for onStopJob, '" +
mRunningJob.getServiceComponent().getShortClassName() + "' tId: "
+ jobId);
closeAndCleanupJobH(true /* needsReschedule */);
break;
case VERB_EXECUTING:
// Not an error - client ran out of time.
Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
Log.i(TAG, "Client timed out while executing (no jobFinished received)." +
" sending onStop. " +
mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ taskId);
mRunningJob.getServiceComponent().getShortClassName() + "' tId: "
+ jobId);
sendStopMessageH();
break;
default:
Log.e(TAG, "Handling timeout for an unknown active task state: "
+ mRunningTask);
Log.e(TAG, "Handling timeout for an unknown active job state: "
+ mRunningJob);
return;
}
}
@@ -459,35 +459,35 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
private void sendStopMessageH() {
mCallbackHandler.removeMessages(MSG_TIMEOUT);
if (mVerb != VERB_EXECUTING) {
Log.e(TAG, "Sending onStopTask for a task that isn't started. " + mRunningTask);
closeAndCleanupTaskH(false /* reschedule */);
Log.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
closeAndCleanupJobH(false /* reschedule */);
return;
}
try {
mVerb = VERB_STOPPING;
scheduleOpTimeOut();
service.stopTask(mParams);
service.stopJob(mParams);
} catch (RemoteException e) {
Log.e(TAG, "Error sending onStopTask to client.", e);
closeAndCleanupTaskH(false /* reschedule */);
Log.e(TAG, "Error sending onStopJob to client.", e);
closeAndCleanupJobH(false /* reschedule */);
}
}
/**
* The provided task has finished, either by calling
* {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)}
* The provided job has finished, either by calling
* {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
* we want to clean up internally.
*/
private void closeAndCleanupTaskH(boolean reschedule) {
private void closeAndCleanupJobH(boolean reschedule) {
removeMessages(MSG_TIMEOUT);
synchronized (mLock) {
mWakeLock.release();
mContext.unbindService(TaskServiceContext.this);
mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
mContext.unbindService(JobServiceContext.this);
mCompletedListener.onJobCompleted(mRunningJob, reschedule);
mWakeLock = null;
mRunningTask = null;
mRunningJob = null;
mParams = null;
mVerb = -1;
mCancelled.set(false);
@@ -508,8 +508,8 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
if (DEBUG) {
Slog.d(TAG, "Scheduling time out for '" +
mRunningTask.getServiceComponent().getShortClassName() + "' tId: " +
mParams.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
mRunningJob.getServiceComponent().getShortClassName() + "' tId: " +
mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
}
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);

View File

@@ -14,10 +14,10 @@
* limitations under the License
*/
package com.android.server.task;
package com.android.server.job;
import android.content.ComponentName;
import android.app.task.Task;
import android.app.job.JobInfo;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
@@ -33,7 +33,7 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.IoThread;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.job.controllers.JobStatus;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -50,94 +50,94 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
/**
* Maintain a list of classes, and accessor methods/logic for these tasks.
* Maintain a list of classes, and accessor methods/logic for these jobs.
* This class offers the following functionality:
* - When a task is added, it will determine if the task requirements have changed (update) and
* - When a job is added, it will determine if the job requirements have changed (update) and
* whether the controllers need to be updated.
* - Persists Tasks, figures out when to to rewrite the Task to disk.
* - Handles rescheduling of tasks.
* - When a periodic task is executed and must be re-added.
* - When a task fails and the client requests that it be retried with backoff.
* - Persists JobInfos, figures out when to to rewrite the JobInfo to disk.
* - Handles rescheduling of jobs.
* - When a periodic job is executed and must be re-added.
* - When a job fails and the client requests that it be retried with backoff.
* - This class <strong>is not</strong> thread-safe.
*
* Note on locking:
* All callers to this class must <strong>lock on the class object they are calling</strong>.
* This is important b/c {@link com.android.server.task.TaskStore.WriteTasksMapToDiskRunnable}
* and {@link com.android.server.task.TaskStore.ReadTaskMapFromDiskRunnable} lock on that
* This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
* and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
* object.
*/
public class TaskStore {
private static final String TAG = "TaskManagerStore";
private static final boolean DEBUG = TaskManagerService.DEBUG;
public class JobStore {
private static final String TAG = "JobStore";
private static final boolean DEBUG = JobSchedulerService.DEBUG;
/** Threshold to adjust how often we want to write to the db. */
private static final int MAX_OPS_BEFORE_WRITE = 1;
final ArraySet<TaskStatus> mTasksSet;
final ArraySet<JobStatus> mJobSet;
final Context mContext;
private int mDirtyOperations;
private static final Object sSingletonLock = new Object();
private final AtomicFile mTasksFile;
private final AtomicFile mJobsFile;
/** Handler backed by IoThread for writing to disk. */
private final Handler mIoHandler = IoThread.getHandler();
private static TaskStore sSingleton;
private static JobStore sSingleton;
/** Used by the {@Link TaskManagerService} to instantiate the TaskStore. */
static TaskStore initAndGet(TaskManagerService taskManagerService) {
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) {
synchronized (sSingletonLock) {
if (sSingleton == null) {
sSingleton = new TaskStore(taskManagerService.getContext(),
Environment.getDataDirectory(), taskManagerService);
sSingleton = new JobStore(jobManagerService.getContext(),
Environment.getDataDirectory(), jobManagerService);
}
return sSingleton;
}
}
@VisibleForTesting
public static TaskStore initAndGetForTesting(Context context, File dataDir,
TaskMapReadFinishedListener callback) {
return new TaskStore(context, dataDir, callback);
public static JobStore initAndGetForTesting(Context context, File dataDir,
JobMapReadFinishedListener callback) {
return new JobStore(context, dataDir, callback);
}
private TaskStore(Context context, File dataDir, TaskMapReadFinishedListener callback) {
private JobStore(Context context, File dataDir, JobMapReadFinishedListener callback) {
mContext = context;
mDirtyOperations = 0;
File systemDir = new File(dataDir, "system");
File taskDir = new File(systemDir, "task");
taskDir.mkdirs();
mTasksFile = new AtomicFile(new File(taskDir, "tasks.xml"));
File jobDir = new File(systemDir, "job");
jobDir.mkdirs();
mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
mTasksSet = new ArraySet<TaskStatus>();
mJobSet = new ArraySet<JobStatus>();
readTaskMapFromDiskAsync(callback);
readJobMapFromDiskAsync(callback);
}
/**
* Add a task to the master list, persisting it if necessary. If the TaskStatus already exists,
* Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
* it will be replaced.
* @param taskStatus Task to add.
* @return Whether or not an equivalent TaskStatus was replaced by this operation.
* @param jobStatus Job to add.
* @return Whether or not an equivalent JobStatus was replaced by this operation.
*/
public boolean add(TaskStatus taskStatus) {
boolean replaced = mTasksSet.remove(taskStatus);
mTasksSet.add(taskStatus);
if (taskStatus.isPersisted()) {
public boolean add(JobStatus jobStatus) {
boolean replaced = mJobSet.remove(jobStatus);
mJobSet.add(jobStatus);
if (jobStatus.isPersisted()) {
maybeWriteStatusToDiskAsync();
}
if (DEBUG) {
Slog.d(TAG, "Added task status to store: " + taskStatus);
Slog.d(TAG, "Added job status to store: " + jobStatus);
}
return replaced;
}
/**
* Whether this taskStatus object already exists in the TaskStore.
* Whether this jobStatus object already exists in the JobStore.
*/
public boolean containsTaskIdForUid(int taskId, int uId) {
for (TaskStatus ts : mTasksSet) {
if (ts.getUid() == uId && ts.getTaskId() == taskId) {
public boolean containsJobIdForUid(int jobId, int uId) {
for (JobStatus ts : mJobSet) {
if (ts.getUid() == uId && ts.getJobId() == jobId) {
return true;
}
}
@@ -145,18 +145,18 @@ public class TaskStore {
}
public int size() {
return mTasksSet.size();
return mJobSet.size();
}
/**
* Remove the provided task. Will also delete the task if it was persisted.
* @return Whether or not the task existed to be removed.
* Remove the provided job. Will also delete the job if it was persisted.
* @return Whether or not the job existed to be removed.
*/
public boolean remove(TaskStatus taskStatus) {
boolean removed = mTasksSet.remove(taskStatus);
public boolean remove(JobStatus jobStatus) {
boolean removed = mJobSet.remove(jobStatus);
if (!removed) {
if (DEBUG) {
Slog.d(TAG, "Couldn't remove task: didn't exist: " + taskStatus);
Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
}
return false;
}
@@ -166,48 +166,48 @@ public class TaskStore {
@VisibleForTesting
public void clear() {
mTasksSet.clear();
mJobSet.clear();
maybeWriteStatusToDiskAsync();
}
public List<TaskStatus> getTasksByUser(int userHandle) {
List<TaskStatus> matchingTasks = new ArrayList<TaskStatus>();
Iterator<TaskStatus> it = mTasksSet.iterator();
public List<JobStatus> getJobsByUser(int userHandle) {
List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
Iterator<JobStatus> it = mJobSet.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
JobStatus ts = it.next();
if (UserHandle.getUserId(ts.getUid()) == userHandle) {
matchingTasks.add(ts);
matchingJobs.add(ts);
}
}
return matchingTasks;
return matchingJobs;
}
/**
* @param uid Uid of the requesting app.
* @return All TaskStatus objects for a given uid from the master list.
* @return All JobStatus objects for a given uid from the master list.
*/
public List<TaskStatus> getTasksByUid(int uid) {
List<TaskStatus> matchingTasks = new ArrayList<TaskStatus>();
Iterator<TaskStatus> it = mTasksSet.iterator();
public List<JobStatus> getJobsByUid(int uid) {
List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
Iterator<JobStatus> it = mJobSet.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
JobStatus ts = it.next();
if (ts.getUid() == uid) {
matchingTasks.add(ts);
matchingJobs.add(ts);
}
}
return matchingTasks;
return matchingJobs;
}
/**
* @param uid Uid of the requesting app.
* @param taskId Task id, specified at schedule-time.
* @return the TaskStatus that matches the provided uId and taskId, or null if none found.
* @param jobId Job id, specified at schedule-time.
* @return the JobStatus that matches the provided uId and jobId, or null if none found.
*/
public TaskStatus getTaskByUidAndTaskId(int uid, int taskId) {
Iterator<TaskStatus> it = mTasksSet.iterator();
public JobStatus getJobByUidAndJobId(int uid, int jobId) {
Iterator<JobStatus> it = mJobSet.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
if (ts.getUid() == uid && ts.getTaskId() == taskId) {
JobStatus ts = it.next();
if (ts.getUid() == uid && ts.getJobId() == jobid) {
return ts;
}
}
@@ -215,15 +215,15 @@ public class TaskStore {
}
/**
* @return The live array of TaskStatus objects.
* @return The live array of JobStatus objects.
*/
public ArraySet<TaskStatus> getTasks() {
return mTasksSet;
public ArraySet<JobStatus> getJobs() {
return mJobSet;
}
/** Version of the db schema. */
private static final int TASKS_FILE_VERSION = 0;
/** Tag corresponds to constraints this task needs. */
private static final int JOBS_FILE_VERSION = 0;
/** Tag corresponds to constraints this job needs. */
private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
/** Tag corresponds to execution parameters. */
private static final String XML_TAG_PERIODIC = "periodic";
@@ -231,7 +231,7 @@ public class TaskStore {
private static final String XML_TAG_EXTRAS = "extras";
/**
* Every time the state changes we write all the tasks in one swathe, instead of trying to
* Every time the state changes we write all the jobs in one swath, instead of trying to
* track incremental changes.
* @return Whether the operation was successful. This will only fail for e.g. if the system is
* low on storage. If this happens, we continue as normal
@@ -240,38 +240,38 @@ public class TaskStore {
mDirtyOperations++;
if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) {
if (DEBUG) {
Slog.v(TAG, "Writing tasks to disk.");
Slog.v(TAG, "Writing jobs to disk.");
}
mIoHandler.post(new WriteTasksMapToDiskRunnable());
mIoHandler.post(new WriteJobsMapToDiskRunnable());
}
}
private void readTaskMapFromDiskAsync(TaskMapReadFinishedListener callback) {
mIoHandler.post(new ReadTaskMapFromDiskRunnable(callback));
private void readJobMapFromDiskAsync(JobMapReadFinishedListener callback) {
mIoHandler.post(new ReadJobMapFromDiskRunnable(callback));
}
public void readTaskMapFromDisk(TaskMapReadFinishedListener callback) {
new ReadTaskMapFromDiskRunnable(callback).run();
public void readJobMapFromDisk(JobMapReadFinishedListener callback) {
new ReadJobMapFromDiskRunnable(callback).run();
}
/**
* Runnable that writes {@link #mTasksSet} out to xml.
* NOTE: This Runnable locks on TaskStore.this
* Runnable that writes {@link #mJobSet} out to xml.
* NOTE: This Runnable locks on JobStore.this
*/
private class WriteTasksMapToDiskRunnable implements Runnable {
private class WriteJobsMapToDiskRunnable implements Runnable {
@Override
public void run() {
final long startElapsed = SystemClock.elapsedRealtime();
synchronized (TaskStore.this) {
writeTasksMapImpl();
synchronized (JobStore.this) {
writeJobsMapImpl();
}
if (TaskManagerService.DEBUG) {
if (JobSchedulerService.DEBUG) {
Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
- startElapsed) + "ms");
}
}
private void writeTasksMapImpl() {
private void writeJobsMapImpl() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XmlSerializer out = new FastXmlSerializer();
@@ -279,31 +279,31 @@ public class TaskStore {
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, "task-info");
out.attribute(null, "version", Integer.toString(TASKS_FILE_VERSION));
for (int i = 0; i < mTasksSet.size(); i++) {
final TaskStatus taskStatus = mTasksSet.valueAt(i);
out.startTag(null, "job-info");
out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
for (int i = 0; i < mJobSet.size(); i++) {
final JobStatus jobStatus = mJobSet.valueAt(i);
if (DEBUG) {
Slog.d(TAG, "Saving task " + taskStatus.getTaskId());
Slog.d(TAG, "Saving job " + jobStatus.getJobId());
}
out.startTag(null, "task");
addIdentifierAttributesToTaskTag(out, taskStatus);
writeConstraintsToXml(out, taskStatus);
writeExecutionCriteriaToXml(out, taskStatus);
writeBundleToXml(taskStatus.getExtras(), out);
out.endTag(null, "task");
out.startTag(null, "job");
addIdentifierAttributesToJobTag(out, jobStatus);
writeConstraintsToXml(out, jobStatus);
writeExecutionCriteriaToXml(out, jobStatus);
writeBundleToXml(jobStatus.getExtras(), out);
out.endTag(null, "job");
}
out.endTag(null, "task-info");
out.endTag(null, "job-info");
out.endDocument();
// Write out to disk in one fell sweep.
FileOutputStream fos = mTasksFile.startWrite();
FileOutputStream fos = mJobsFile.startWrite();
fos.write(baos.toByteArray());
mTasksFile.finishWrite(fos);
mJobsFile.finishWrite(fos);
mDirtyOperations = 0;
} catch (IOException e) {
if (DEBUG) {
Slog.v(TAG, "Error writing out task data.", e);
Slog.v(TAG, "Error writing out job data.", e);
}
} catch (XmlPullParserException e) {
if (DEBUG) {
@@ -312,13 +312,13 @@ public class TaskStore {
}
}
/** Write out a tag with data comprising the required fields of this task and its client. */
private void addIdentifierAttributesToTaskTag(XmlSerializer out, TaskStatus taskStatus)
/** Write out a tag with data comprising the required fields of this job and its client. */
private void addIdentifierAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
throws IOException {
out.attribute(null, "taskid", Integer.toString(taskStatus.getTaskId()));
out.attribute(null, "package", taskStatus.getServiceComponent().getPackageName());
out.attribute(null, "class", taskStatus.getServiceComponent().getClassName());
out.attribute(null, "uid", Integer.toString(taskStatus.getUid()));
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()));
}
private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
@@ -328,57 +328,57 @@ public class TaskStore {
out.endTag(null, XML_TAG_EXTRAS);
}
/**
* Write out a tag with data identifying this tasks constraints. If the constraint isn't here
* Write out a tag with data identifying this job's constraints. If the constraint isn't here
* it doesn't apply.
*/
private void writeConstraintsToXml(XmlSerializer out, TaskStatus taskStatus) throws IOException {
private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
if (taskStatus.hasUnmeteredConstraint()) {
if (jobStatus.hasUnmeteredConstraint()) {
out.attribute(null, "unmetered", Boolean.toString(true));
}
if (taskStatus.hasConnectivityConstraint()) {
if (jobStatus.hasConnectivityConstraint()) {
out.attribute(null, "connectivity", Boolean.toString(true));
}
if (taskStatus.hasIdleConstraint()) {
if (jobStatus.hasIdleConstraint()) {
out.attribute(null, "idle", Boolean.toString(true));
}
if (taskStatus.hasChargingConstraint()) {
if (jobStatus.hasChargingConstraint()) {
out.attribute(null, "charging", Boolean.toString(true));
}
out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
}
private void writeExecutionCriteriaToXml(XmlSerializer out, TaskStatus taskStatus)
private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus)
throws IOException {
final Task task = taskStatus.getTask();
if (taskStatus.getTask().isPeriodic()) {
final JobInfo job = jobStatus.getJob();
if (jobStatus.getJob().isPeriodic()) {
out.startTag(null, XML_TAG_PERIODIC);
out.attribute(null, "period", Long.toString(task.getIntervalMillis()));
out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
} else {
out.startTag(null, XML_TAG_ONEOFF);
}
if (taskStatus.hasDeadlineConstraint()) {
if (jobStatus.hasDeadlineConstraint()) {
// Wall clock deadline.
final long deadlineWallclock = System.currentTimeMillis() +
(taskStatus.getLatestRunTimeElapsed() - SystemClock.elapsedRealtime());
(jobStatus.getLatestRunTimeElapsed() - SystemClock.elapsedRealtime());
out.attribute(null, "deadline", Long.toString(deadlineWallclock));
}
if (taskStatus.hasTimingDelayConstraint()) {
if (jobStatus.hasTimingDelayConstraint()) {
final long delayWallclock = System.currentTimeMillis() +
(taskStatus.getEarliestRunTime() - SystemClock.elapsedRealtime());
(jobStatus.getEarliestRunTime() - SystemClock.elapsedRealtime());
out.attribute(null, "delay", Long.toString(delayWallclock));
}
// Only write out back-off policy if it differs from the default.
// This also helps the case where the task is idle -> these aren't allowed to specify
// This also helps the case where the job is idle -> these aren't allowed to specify
// back-off.
if (taskStatus.getTask().getInitialBackoffMillis() != Task.DEFAULT_INITIAL_BACKOFF_MILLIS
|| taskStatus.getTask().getBackoffPolicy() != Task.DEFAULT_BACKOFF_POLICY) {
out.attribute(null, "backoff-policy", Integer.toString(task.getBackoffPolicy()));
out.attribute(null, "initial-backoff", Long.toString(task.getInitialBackoffMillis()));
if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS
|| jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) {
out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy()));
out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis()));
}
if (task.isPeriodic()) {
if (job.isPeriodic()) {
out.endTag(null, XML_TAG_PERIODIC);
} else {
out.endTag(null, XML_TAG_ONEOFF);
@@ -387,43 +387,43 @@ public class TaskStore {
}
/**
* Runnable that reads list of persisted task from xml.
* NOTE: This Runnable locks on TaskStore.this
* Runnable that reads list of persisted job from xml.
* NOTE: This Runnable locks on JobStore.this
*/
private class ReadTaskMapFromDiskRunnable implements Runnable {
private TaskMapReadFinishedListener mCallback;
public ReadTaskMapFromDiskRunnable(TaskMapReadFinishedListener callback) {
private class ReadJobMapFromDiskRunnable implements Runnable {
private JobMapReadFinishedListener mCallback;
public ReadJobMapFromDiskRunnable(JobMapReadFinishedListener callback) {
mCallback = callback;
}
@Override
public void run() {
try {
List<TaskStatus> tasks;
FileInputStream fis = mTasksFile.openRead();
synchronized (TaskStore.this) {
tasks = readTaskMapImpl(fis);
List<JobStatus> jobs;
FileInputStream fis = mJobsFile.openRead();
synchronized (JobStore.this) {
jobs = readJobMapImpl(fis);
}
fis.close();
if (tasks != null) {
mCallback.onTaskMapReadFinished(tasks);
if (jobs != null) {
mCallback.onJobMapReadFinished(jobs);
}
} catch (FileNotFoundException e) {
if (TaskManagerService.DEBUG) {
Slog.d(TAG, "Could not find tasks file, probably there was nothing to load.");
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
}
} catch (XmlPullParserException e) {
if (TaskManagerService.DEBUG) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Error parsing xml.", e);
}
} catch (IOException e) {
if (TaskManagerService.DEBUG) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Error parsing xml.", e);
}
}
}
private List<TaskStatus> readTaskMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
private List<JobStatus> readJobMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
@@ -435,66 +435,66 @@ public class TaskStore {
}
if (eventType == XmlPullParser.END_DOCUMENT) {
if (DEBUG) {
Slog.d(TAG, "No persisted tasks.");
Slog.d(TAG, "No persisted jobs.");
}
return null;
}
String tagName = parser.getName();
if ("task-info".equals(tagName)) {
final List<TaskStatus> tasks = new ArrayList<TaskStatus>();
if ("job-info".equals(tagName)) {
final List<JobStatus> jobs = new ArrayList<JobStatus>();
// Read in version info.
try {
int version = Integer.valueOf(parser.getAttributeValue(null, "version"));
if (version != TASKS_FILE_VERSION) {
Slog.d(TAG, "Invalid version number, aborting tasks file read.");
if (version != JOBS_FILE_VERSION) {
Slog.d(TAG, "Invalid version number, aborting jobs file read.");
return null;
}
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid version number, aborting tasks file read.");
Slog.e(TAG, "Invalid version number, aborting jobs file read.");
return null;
}
eventType = parser.next();
do {
// Read each <task/>
// Read each <job/>
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
// Start reading task.
if ("task".equals(tagName)) {
TaskStatus persistedTask = restoreTaskFromXml(parser);
if (persistedTask != null) {
// Start reading job.
if ("job".equals(tagName)) {
JobStatus persistedJob = restoreJobFromXml(parser);
if (persistedJob != null) {
if (DEBUG) {
Slog.d(TAG, "Read out " + persistedTask);
Slog.d(TAG, "Read out " + persistedJob);
}
tasks.add(persistedTask);
jobs.add(persistedJob);
} else {
Slog.d(TAG, "Error reading task from file.");
Slog.d(TAG, "Error reading job from file.");
}
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
return tasks;
return jobs;
}
return null;
}
/**
* @param parser Xml parser at the beginning of a "<task/>" tag. The next "parser.next()" call
* will take the parser into the body of the task tag.
* @return Newly instantiated task holding all the information we just read out of the xml tag.
* @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call
* will take the parser into the body of the job tag.
* @return Newly instantiated job holding all the information we just read out of the xml tag.
*/
private TaskStatus restoreTaskFromXml(XmlPullParser parser) throws XmlPullParserException,
private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
Task.Builder taskBuilder;
JobInfo.Builder jobBuilder;
int uid;
// Read out task identifier attributes.
// Read out job identifier attributes.
try {
taskBuilder = buildBuilderFromXml(parser);
jobBuilder = buildBuilderFromXml(parser);
uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
} catch (NumberFormatException e) {
Slog.e(TAG, "Error parsing task's required fields, skipping");
Slog.e(TAG, "Error parsing job's required fields, skipping");
return null;
}
@@ -510,7 +510,7 @@ public class TaskStore {
return null;
}
try {
buildConstraintsFromXml(taskBuilder, parser);
buildConstraintsFromXml(jobBuilder, parser);
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading constraints, skipping.");
return null;
@@ -538,22 +538,22 @@ public class TaskStore {
if (XML_TAG_PERIODIC.equals(parser.getName())) {
try {
String val = parser.getAttributeValue(null, "period");
taskBuilder.setPeriodic(Long.valueOf(val));
jobBuilder.setPeriodic(Long.valueOf(val));
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
return null;
}
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
try {
if (runtimes.first != TaskStatus.NO_EARLIEST_RUNTIME) {
taskBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
if (runtimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
jobBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
}
if (runtimes.second != TaskStatus.NO_LATEST_RUNTIME) {
taskBuilder.setOverrideDeadline(
if (runtimes.second != JobStatus.NO_LATEST_RUNTIME) {
jobBuilder.setOverrideDeadline(
runtimes.second - SystemClock.elapsedRealtime());
}
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading task execution criteria, skipping.");
Slog.d(TAG, "Error reading job execution criteria, skipping.");
return null;
}
} else {
@@ -563,7 +563,7 @@ public class TaskStore {
// Expecting a parameters start tag.
return null;
}
maybeBuildBackoffPolicyFromXml(taskBuilder, parser);
maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
parser.nextTag(); // Consume parameters end tag.
@@ -579,52 +579,52 @@ public class TaskStore {
}
PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
taskBuilder.setExtras(extras);
jobBuilder.setExtras(extras);
parser.nextTag(); // Consume </extras>
return new TaskStatus(taskBuilder.build(), uid, runtimes.first, runtimes.second);
return new JobStatus(jobBuilder.build(), uid, runtimes.first, runtimes.second);
}
private Task.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
// Pull out required fields from <task> attributes.
int taskId = Integer.valueOf(parser.getAttributeValue(null, "taskid"));
private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
// Pull out required fields from <job> attributes.
int jobId = Integer.valueOf(parser.getAttributeValue(null, "jobid"));
String packageName = parser.getAttributeValue(null, "package");
String className = parser.getAttributeValue(null, "class");
ComponentName cname = new ComponentName(packageName, className);
return new Task.Builder(taskId, cname);
return new JobInfo.Builder(jobId, cname);
}
private void buildConstraintsFromXml(Task.Builder taskBuilder, XmlPullParser parser) {
private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
String val = parser.getAttributeValue(null, "unmetered");
if (val != null) {
taskBuilder.setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED);
jobBuilder.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED);
}
val = parser.getAttributeValue(null, "connectivity");
if (val != null) {
taskBuilder.setRequiredNetworkCapabilities(Task.NetworkType.ANY);
jobBuilder.setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY);
}
val = parser.getAttributeValue(null, "idle");
if (val != null) {
taskBuilder.setRequiresDeviceIdle(true);
jobBuilder.setRequiresDeviceIdle(true);
}
val = parser.getAttributeValue(null, "charging");
if (val != null) {
taskBuilder.setRequiresCharging(true);
jobBuilder.setRequiresCharging(true);
}
}
/**
* Builds the back-off policy out of the params tag. These attributes may not exist, depending
* on whether the back-off was set when the task was first scheduled.
* on whether the back-off was set when the job was first scheduled.
*/
private void maybeBuildBackoffPolicyFromXml(Task.Builder taskBuilder, XmlPullParser parser) {
private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
String val = parser.getAttributeValue(null, "initial-backoff");
if (val != null) {
long initialBackoff = Long.valueOf(val);
val = parser.getAttributeValue(null, "backoff-policy");
int backoffPolicy = Integer.valueOf(val); // Will throw NFE which we catch higher up.
taskBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
}
}
@@ -640,8 +640,8 @@ public class TaskStore {
final long nowWallclock = System.currentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
long earliestRunTimeElapsed = TaskStatus.NO_EARLIEST_RUNTIME;
long latestRunTimeElapsed = TaskStatus.NO_LATEST_RUNTIME;
long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
String val = parser.getAttributeValue(null, "deadline");
if (val != null) {
long latestRuntimeWallclock = Long.valueOf(val);

View File

@@ -14,26 +14,26 @@
* limitations under the License
*/
package com.android.server.task;
package com.android.server.job;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.job.controllers.JobStatus;
/**
* Interface through which a {@link com.android.server.task.controllers.StateController} informs
* the {@link com.android.server.task.TaskManagerService} that there are some tasks potentially
* Interface through which a {@link com.android.server.job.controllers.StateController} informs
* the {@link com.android.server.job.JobSchedulerService} that there are some tasks potentially
* ready to be run.
*/
public interface StateChangedListener {
/**
* Called by the controller to notify the TaskManager that it should check on the state of a
* Called by the controller to notify the JobManager that it should check on the state of a
* task.
*/
public void onControllerStateChanged();
/**
* Called by the controller to notify the TaskManager that regardless of the state of the task,
* Called by the controller to notify the JobManager that regardless of the state of the task,
* it must be run immediately.
* @param taskStatus The state of the task which is to be run immediately.
* @param jobStatus The state of the task which is to be run immediately.
*/
public void onRunTaskNow(TaskStatus taskStatus);
public void onRunJobNow(JobStatus jobStatus);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License
*/
package com.android.server.task.controllers;
package com.android.server.job.controllers;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -32,8 +32,8 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.BatteryService;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -54,10 +54,10 @@ public class BatteryController extends StateController {
/** Wait this long after phone is plugged in before doing any work. */
private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
private List<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>();
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private ChargingTracker mChargeTracker;
public static BatteryController get(TaskManagerService taskManagerService) {
public static BatteryController get(JobSchedulerService taskManagerService) {
synchronized (sCreationLock) {
if (sController == null) {
sController = new BatteryController(taskManagerService,
@@ -85,7 +85,7 @@ public class BatteryController extends StateController {
}
@Override
public void maybeStartTrackingTask(TaskStatus taskStatus) {
public void maybeStartTrackingJob(JobStatus taskStatus) {
if (taskStatus.hasChargingConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.add(taskStatus);
@@ -96,7 +96,7 @@ public class BatteryController extends StateController {
}
@Override
public void maybeStopTrackingTask(TaskStatus taskStatus) {
public void maybeStopTrackingJob(JobStatus taskStatus) {
if (taskStatus.hasChargingConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.remove(taskStatus);
@@ -108,7 +108,7 @@ public class BatteryController extends StateController {
final boolean stablePower = mChargeTracker.isOnStablePower();
boolean reportChange = false;
synchronized (mTrackedTasks) {
for (TaskStatus ts : mTrackedTasks) {
for (JobStatus ts : mTrackedTasks) {
boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
if (previous != stablePower) {
reportChange = true;

View File

@@ -14,7 +14,7 @@
* limitations under the License
*/
package com.android.server.task.controllers;
package com.android.server.job.controllers;
import android.content.BroadcastReceiver;
@@ -28,8 +28,8 @@ import android.os.UserHandle;
import android.util.Slog;
import com.android.server.ConnectivityService;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
import java.util.LinkedList;
@@ -42,9 +42,9 @@ import java.util.List;
*/
public class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "TaskManager.Conn";
private static final String TAG = "JobScheduler.Conn";
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
/** Singleton. */
@@ -55,10 +55,10 @@ public class ConnectivityController extends StateController implements
/** Track whether the latest active network is connected. */
private boolean mNetworkConnected;
public static ConnectivityController get(TaskManagerService taskManager) {
public static ConnectivityController get(JobSchedulerService jms) {
synchronized (sCreationLock) {
if (mSingleton == null) {
mSingleton = new ConnectivityController(taskManager, taskManager.getContext());
mSingleton = new ConnectivityController(jms, jms.getContext());
}
return mSingleton;
}
@@ -82,21 +82,21 @@ public class ConnectivityController extends StateController implements
}
@Override
public void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasUnmeteredConstraint()) {
synchronized (mTrackedTasks) {
taskStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
taskStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
mTrackedTasks.add(taskStatus);
public void maybeStartTrackingJob(JobStatus jobStatus) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
synchronized (mTrackedJobs) {
jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
mTrackedJobs.add(jobStatus);
}
}
}
@Override
public void maybeStopTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasUnmeteredConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.remove(taskStatus);
public void maybeStopTrackingJob(JobStatus jobStatus) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
synchronized (mTrackedJobs) {
mTrackedJobs.remove(jobStatus);
}
}
}
@@ -104,16 +104,16 @@ public class ConnectivityController extends StateController implements
/**
* @param userId Id of the user for whom we are updating the connectivity state.
*/
private void updateTrackedTasks(int userId) {
synchronized (mTrackedTasks) {
private void updateTrackedJobs(int userId) {
synchronized (mTrackedJobs) {
boolean changed = false;
for (TaskStatus ts : mTrackedTasks) {
if (ts.getUserId() != userId) {
for (JobStatus js : mTrackedJobs) {
if (js.getUserId() != userId) {
continue;
}
boolean prevIsConnected =
ts.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
boolean prevIsMetered = ts.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
js.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
boolean prevIsMetered = js.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) {
changed = true;
}
@@ -125,16 +125,16 @@ public class ConnectivityController extends StateController implements
}
/**
* We know the network has just come up. We want to run any tasks that are ready.
* We know the network has just come up. We want to run any jobs that are ready.
*/
public synchronized void onNetworkActive() {
synchronized (mTrackedTasks) {
for (TaskStatus ts : mTrackedTasks) {
if (ts.isReady()) {
synchronized (mTrackedJobs) {
for (JobStatus js : mTrackedJobs) {
if (js.isReady()) {
if (DEBUG) {
Slog.d(TAG, "Running " + ts + " due to network activity.");
Slog.d(TAG, "Running " + js + " due to network activity.");
}
mStateChangedListener.onRunTaskNow(ts);
mStateChangedListener.onRunJobNow(js);
}
}
}
@@ -169,7 +169,7 @@ public class ConnectivityController extends StateController implements
if (activeNetwork == null) {
mNetworkUnmetered = false;
mNetworkConnected = false;
updateTrackedTasks(userid);
updateTrackedJobs(userid);
} else if (activeNetwork.getType() == networkType) {
mNetworkUnmetered = false;
mNetworkConnected = !intent.getBooleanExtra(
@@ -177,7 +177,7 @@ public class ConnectivityController extends StateController implements
if (mNetworkConnected) { // No point making the call if we know there's no conn.
mNetworkUnmetered = !connManager.isActiveNetworkMetered();
}
updateTrackedTasks(userid);
updateTrackedJobs(userid);
}
} else {
if (DEBUG) {
@@ -191,10 +191,10 @@ public class ConnectivityController extends StateController implements
public void dumpControllerState(PrintWriter pw) {
pw.println("Conn.");
pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
for (TaskStatus ts: mTrackedTasks) {
pw.println(String.valueOf(ts.hashCode()).substring(0, 3) + ".."
+ ": C=" + ts.hasConnectivityConstraint()
+ ", UM=" + ts.hasUnmeteredConstraint());
for (JobStatus js: mTrackedJobs) {
pw.println(String.valueOf(js.hashCode()).substring(0, 3) + ".."
+ ": C=" + js.hasConnectivityConstraint()
+ ", UM=" + js.hasUnmeteredConstraint());
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License
*/
package com.android.server.task.controllers;
package com.android.server.job.controllers;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -29,8 +29,8 @@ import android.content.IntentFilter;
import android.os.SystemClock;
import android.util.Slog;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
public class IdleController extends StateController {
private static final String TAG = "IdleController";
@@ -43,14 +43,14 @@ public class IdleController extends StateController {
private static final String ACTION_TRIGGER_IDLE =
"com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
final ArrayList<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>();
final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
IdlenessTracker mIdleTracker;
// Singleton factory
private static Object sCreationLock = new Object();
private static volatile IdleController sController;
public static IdleController get(TaskManagerService service) {
public static IdleController get(JobSchedulerService service) {
synchronized (sCreationLock) {
if (sController == null) {
sController = new IdleController(service, service.getContext());
@@ -68,7 +68,7 @@ public class IdleController extends StateController {
* StateController interface
*/
@Override
public void maybeStartTrackingTask(TaskStatus taskStatus) {
public void maybeStartTrackingJob(JobStatus taskStatus) {
if (taskStatus.hasIdleConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.add(taskStatus);
@@ -78,7 +78,7 @@ public class IdleController extends StateController {
}
@Override
public void maybeStopTrackingTask(TaskStatus taskStatus) {
public void maybeStopTrackingJob(JobStatus taskStatus) {
synchronized (mTrackedTasks) {
mTrackedTasks.remove(taskStatus);
}
@@ -89,7 +89,7 @@ public class IdleController extends StateController {
*/
void reportNewIdleState(boolean isIdle) {
synchronized (mTrackedTasks) {
for (TaskStatus task : mTrackedTasks) {
for (JobStatus task : mTrackedTasks) {
task.idleConstraintSatisfied.set(isIdle);
}
}

View File

@@ -14,9 +14,9 @@
* limitations under the License
*/
package com.android.server.task.controllers;
package com.android.server.job.controllers;
import android.app.task.Task;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.os.PersistableBundle;
import android.os.SystemClock;
@@ -26,9 +26,9 @@ import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Uniquely identifies a task internally.
* Created from the public {@link android.app.task.Task} object when it lands on the scheduler.
* Contains current state of the requirements of the task, as well as a function to evaluate
* Uniquely identifies a job internally.
* Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler.
* Contains current state of the requirements of the job, as well as a function to evaluate
* whether it's ready to run.
* This object is shared among the various controllers - hence why the different fields are atomic.
* This isn't strictly necessary because each controller is only interested in a specific field,
@@ -36,14 +36,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
* but we don't enforce that so this is safer.
* @hide
*/
public class TaskStatus {
public class JobStatus {
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
final Task task;
final JobInfo job;
final int uId;
/** At reschedule time we need to know whether to update task on disk. */
/** At reschedule time we need to know whether to update job on disk. */
final boolean persisted;
// Constraints.
@@ -55,77 +55,77 @@ public class TaskStatus {
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
/**
* Earliest point in the future at which this task will be eligible to run. A value of 0
* Earliest point in the future at which this job will be eligible to run. A value of 0
* indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
*/
private long earliestRunTimeElapsedMillis;
/**
* Latest point in the future at which this task must be run. A value of {@link Long#MAX_VALUE}
* Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE}
* indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
*/
private long latestRunTimeElapsedMillis;
/** How many times this task has failed, used to compute back-off. */
/** How many times this job has failed, used to compute back-off. */
private final int numFailures;
/** Provide a handle to the service that this task will be run on. */
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return uId;
}
private TaskStatus(Task task, int uId, boolean persisted, int numFailures) {
this.task = task;
private JobStatus(JobInfo job, int uId, boolean persisted, int numFailures) {
this.job = job;
this.uId = uId;
this.numFailures = numFailures;
this.persisted = persisted;
}
/** Create a newly scheduled task. */
public TaskStatus(Task task, int uId, boolean persisted) {
this(task, uId, persisted, 0);
/** Create a newly scheduled job. */
public JobStatus(JobInfo job, int uId, boolean persisted) {
this(job, uId, persisted, 0);
final long elapsedNow = SystemClock.elapsedRealtime();
if (task.isPeriodic()) {
if (job.isPeriodic()) {
earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
} else {
earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
elapsedNow + task.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = task.hasLateConstraint() ?
elapsedNow + task.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = job.hasLateConstraint() ?
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
}
/**
* Create a new TaskStatus that was loaded from disk. We ignore the provided
* {@link android.app.task.Task} time criteria because we can load a persisted periodic task
* from the {@link com.android.server.task.TaskStore} and still want to respect its
* Create a new JobStatus that was loaded from disk. We ignore the provided
* {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
* from the {@link com.android.server.job.JobStore} and still want to respect its
* wallclock runtime rather than resetting it on every boot.
* We consider a freshly loaded task to no longer be in back-off.
* We consider a freshly loaded job to no longer be in back-off.
*/
public TaskStatus(Task task, int uId, long earliestRunTimeElapsedMillis,
public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis) {
this(task, uId, true, 0);
this(job, uId, true, 0);
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
}
/** Create a new task to be rescheduled with the provided parameters. */
public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsedMillis,
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt) {
this(rescheduling.task, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt);
this(rescheduling.job, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt);
earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
}
public Task getTask() {
return task;
public JobInfo getJob() {
return job;
}
public int getTaskId() {
return task.getId();
public int getJobId() {
return job.getId();
}
public int getNumFailures() {
@@ -133,7 +133,7 @@ public class TaskStatus {
}
public ComponentName getServiceComponent() {
return task.getService();
return job.getService();
}
public int getUserId() {
@@ -145,19 +145,19 @@ public class TaskStatus {
}
public PersistableBundle getExtras() {
return task.getExtras();
return job.getExtras();
}
public boolean hasConnectivityConstraint() {
return task.getNetworkCapabilities() == Task.NetworkType.ANY;
return job.getNetworkCapabilities() == JobInfo.NetworkType.ANY;
}
public boolean hasUnmeteredConstraint() {
return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
return job.getNetworkCapabilities() == JobInfo.NetworkType.UNMETERED;
}
public boolean hasChargingConstraint() {
return task.isRequireCharging();
return job.isRequireCharging();
}
public boolean hasTimingDelayConstraint() {
@@ -169,7 +169,7 @@ public class TaskStatus {
}
public boolean hasIdleConstraint() {
return task.isRequireDeviceIdle();
return job.isRequireDeviceIdle();
}
public long getEarliestRunTime() {
@@ -184,7 +184,7 @@ public class TaskStatus {
return persisted;
}
/**
* @return Whether or not this task is ready to run, based on its requirements.
* @return Whether or not this job is ready to run, based on its requirements.
*/
public synchronized boolean isReady() {
return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
@@ -196,36 +196,17 @@ public class TaskStatus {
|| (hasDeadlineConstraint() && deadlineConstraintSatisfied.get());
}
/*@Override
public int hashCode() {
int result = getServiceComponent().hashCode();
result = 31 * result + task.getId();
result = 31 * result + uId;
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TaskStatus)) return false;
TaskStatus that = (TaskStatus) o;
return ((task.getId() == that.task.getId())
&& (uId == that.uId)
&& (getServiceComponent().equals(that.getServiceComponent())));
}*/
public boolean matches(int uid, int taskId) {
return this.task.getId() == taskId && this.uId == uid;
public boolean matches(int uid, int jobId) {
return this.job.getId() == jobId && this.uId == uid;
}
@Override
public String toString() {
return String.valueOf(hashCode()).substring(0, 3) + ".."
+ ":[" + task.getService().getPackageName() + ",tId=" + task.getId()
+ ":[" + job.getService().getPackageName() + ",jId=" + job.getId()
+ ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")"
+ ",N=" + task.getNetworkCapabilities() + ",C=" + task.isRequireCharging()
+ ",I=" + task.isRequireDeviceIdle() + ",F=" + numFailures
+ ",N=" + job.getNetworkCapabilities() + ",C=" + job.isRequireCharging()
+ ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
+ (isReady() ? "(READY)" : "")
+ "]";
}

View File

@@ -14,18 +14,18 @@
* limitations under the License
*/
package com.android.server.task.controllers;
package com.android.server.job.controllers;
import android.content.Context;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
/**
* Incorporates shared controller logic between the various controllers of the TaskManager.
* These are solely responsible for tracking a list of tasks, and notifying the TM when these
* Incorporates shared controller logic between the various controllers of the JobManager.
* These are solely responsible for tracking a list of jobs, and notifying the JM when these
* are ready to run, or whether they must be stopped.
*/
public abstract class StateController {
@@ -39,16 +39,16 @@ public abstract class StateController {
}
/**
* Implement the logic here to decide whether a task should be tracked by this controller.
* This logic is put here so the TaskManger can be completely agnostic of Controller logic.
* Implement the logic here to decide whether a job should be tracked by this controller.
* This logic is put here so the JobManger can be completely agnostic of Controller logic.
* Also called when updating a task, so implementing controllers have to be aware of
* preexisting tasks.
*/
public abstract void maybeStartTrackingTask(TaskStatus taskStatus);
public abstract void maybeStartTrackingJob(JobStatus jobStatus);
/**
* Remove task - this will happen if the task is cancelled, completed, etc.
*/
public abstract void maybeStopTrackingTask(TaskStatus taskStatus);
public abstract void maybeStopTrackingJob(JobStatus jobStatus);
public abstract void dumpControllerState(PrintWriter pw);

View File

@@ -14,7 +14,7 @@
* limitations under the License
*/
package com.android.server.task.controllers;
package com.android.server.job.controllers;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -25,8 +25,8 @@ import android.content.IntentFilter;
import android.os.SystemClock;
import android.util.Slog;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
import java.util.Iterator;
@@ -35,35 +35,35 @@ import java.util.List;
import java.util.ListIterator;
/**
* This class sets an alarm for the next expiring task, and determines whether a task's minimum
* This class sets an alarm for the next expiring job, and determines whether a job's minimum
* delay has been satisfied.
*/
public class TimeController extends StateController {
private static final String TAG = "TaskManager.Time";
private static final String ACTION_TASK_EXPIRED =
"android.content.taskmanager.TASK_DEADLINE_EXPIRED";
private static final String ACTION_TASK_DELAY_EXPIRED =
"android.content.taskmanager.TASK_DELAY_EXPIRED";
private static final String TAG = "JobScheduler.Time";
private static final String ACTION_JOB_EXPIRED =
"android.content.jobscheduler.JOB_DEADLINE_EXPIRED";
private static final String ACTION_JOB_DELAY_EXPIRED =
"android.content.jobscheduler.JOB_DELAY_EXPIRED";
/** Set an alarm for the next task expiry. */
/** Set an alarm for the next job expiry. */
private final PendingIntent mDeadlineExpiredAlarmIntent;
/** Set an alarm for the next task delay expiry. This*/
/** Set an alarm for the next job delay expiry. This*/
private final PendingIntent mNextDelayExpiredAlarmIntent;
/** Constant time determining how near in the future we'll set an alarm for. */
private static final long MIN_WAKEUP_INTERVAL_MILLIS = 15 * 1000;
private long mNextTaskExpiredElapsedMillis;
private long mNextJobExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
private AlarmManager mAlarmService = null;
/** List of tracked tasks, sorted asc. by deadline */
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
/** List of tracked jobs, sorted asc. by deadline */
private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
/** Singleton. */
private static TimeController mSingleton;
public static synchronized TimeController get(TaskManagerService taskManager) {
public static synchronized TimeController get(JobSchedulerService jms) {
if (mSingleton == null) {
mSingleton = new TimeController(taskManager, taskManager.getContext());
mSingleton = new TimeController(jms, jms.getContext());
}
return mSingleton;
}
@@ -72,66 +72,66 @@ public class TimeController extends StateController {
super(stateChangedListener, context);
mDeadlineExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_EXPIRED), 0);
new Intent(ACTION_JOB_EXPIRED), 0);
mNextDelayExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_DELAY_EXPIRED), 0);
mNextTaskExpiredElapsedMillis = Long.MAX_VALUE;
new Intent(ACTION_JOB_DELAY_EXPIRED), 0);
mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
// Register BR for these intents.
IntentFilter intentFilter = new IntentFilter(ACTION_TASK_EXPIRED);
intentFilter.addAction(ACTION_TASK_DELAY_EXPIRED);
IntentFilter intentFilter = new IntentFilter(ACTION_JOB_EXPIRED);
intentFilter.addAction(ACTION_JOB_DELAY_EXPIRED);
mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
}
/**
* Check if the task has a timing constraint, and if so determine where to insert it in our
* Check if the job has a timing constraint, and if so determine where to insert it in our
* list.
*/
@Override
public synchronized void maybeStartTrackingTask(TaskStatus task) {
if (task.hasTimingDelayConstraint() || task.hasDeadlineConstraint()) {
maybeStopTrackingTask(task);
ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
public synchronized void maybeStartTrackingJob(JobStatus job) {
if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
maybeStopTrackingJob(job);
ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
while (it.hasPrevious()) {
TaskStatus ts = it.previous();
if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
JobStatus ts = it.previous();
if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
// Insert
break;
}
}
it.add(task);
it.add(job);
maybeUpdateAlarms(
task.hasTimingDelayConstraint() ? task.getEarliestRunTime() : Long.MAX_VALUE,
task.hasDeadlineConstraint() ? task.getLatestRunTimeElapsed() : Long.MAX_VALUE);
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE);
}
}
/**
* When we stop tracking a task, we only need to update our alarms if the task we're no longer
* When we stop tracking a job, we only need to update our alarms if the job we're no longer
* tracking was the one our alarms were based off of.
* Really an == comparison should be enough, but why play with fate? We'll do <=.
*/
@Override
public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
public synchronized void maybeStopTrackingJob(JobStatus job) {
if (mTrackedJobs.remove(job)) {
checkExpiredDelaysAndResetAlarm();
checkExpiredDeadlinesAndResetAlarm();
}
}
/**
* Determines whether this controller can stop tracking the given task.
* The controller is no longer interested in a task once its time constraint is satisfied, and
* the task's deadline is fulfilled - unlike other controllers a time constraint can't toggle
* Determines whether this controller can stop tracking the given job.
* The controller is no longer interested in a job once its time constraint is satisfied, and
* the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle
* back and forth.
*/
private boolean canStopTrackingTask(TaskStatus taskStatus) {
return (!taskStatus.hasTimingDelayConstraint() ||
taskStatus.timeDelayConstraintSatisfied.get()) &&
(!taskStatus.hasDeadlineConstraint() ||
taskStatus.deadlineConstraintSatisfied.get());
private boolean canStopTrackingJob(JobStatus job) {
return (!job.hasTimingDelayConstraint() ||
job.timeDelayConstraintSatisfied.get()) &&
(!job.hasDeadlineConstraint() ||
job.deadlineConstraintSatisfied.get());
}
private void ensureAlarmService() {
@@ -141,27 +141,27 @@ public class TimeController extends StateController {
}
/**
* Checks list of tasks for ones that have an expired deadline, sending them to the TaskManager
* Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
* if so, removing them from this list, and updating the alarm for the next expiry time.
*/
private synchronized void checkExpiredDeadlinesAndResetAlarm() {
long nextExpiryTime = Long.MAX_VALUE;
final long nowElapsedMillis = SystemClock.elapsedRealtime();
Iterator<TaskStatus> it = mTrackedTasks.iterator();
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
if (!ts.hasDeadlineConstraint()) {
JobStatus job = it.next();
if (!job.hasDeadlineConstraint()) {
continue;
}
final long taskDeadline = ts.getLatestRunTimeElapsed();
final long jobDeadline = job.getLatestRunTimeElapsed();
if (taskDeadline <= nowElapsedMillis) {
ts.deadlineConstraintSatisfied.set(true);
mStateChangedListener.onRunTaskNow(ts);
if (jobDeadline <= nowElapsedMillis) {
job.deadlineConstraintSatisfied.set(true);
mStateChangedListener.onRunJobNow(job);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = taskDeadline;
nextExpiryTime = jobDeadline;
break;
}
}
@@ -169,31 +169,31 @@ public class TimeController extends StateController {
}
/**
* Handles alarm that notifies us that a task's delay has expired. Iterates through the list of
* tracked tasks and marks them as ready as appropriate.
* Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
* tracked jobs and marks them as ready as appropriate.
*/
private synchronized void checkExpiredDelaysAndResetAlarm() {
final long nowElapsedMillis = SystemClock.elapsedRealtime();
long nextDelayTime = Long.MAX_VALUE;
boolean ready = false;
Iterator<TaskStatus> it = mTrackedTasks.iterator();
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
final TaskStatus ts = it.next();
if (!ts.hasTimingDelayConstraint()) {
final JobStatus job = it.next();
if (!job.hasTimingDelayConstraint()) {
continue;
}
final long taskDelayTime = ts.getEarliestRunTime();
if (taskDelayTime <= nowElapsedMillis) {
ts.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingTask(ts)) {
final long jobDelayTime = job.getEarliestRunTime();
if (jobDelayTime <= nowElapsedMillis) {
job.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingJob(job)) {
it.remove();
}
if (ts.isReady()) {
if (job.isReady()) {
ready = true;
}
} else { // Keep going through list to get next delay time.
if (nextDelayTime > taskDelayTime) {
nextDelayTime = taskDelayTime;
if (nextDelayTime > jobDelayTime) {
nextDelayTime = jobDelayTime;
}
}
}
@@ -207,13 +207,13 @@ public class TimeController extends StateController {
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
setDelayExpiredAlarm(delayExpiredElapsed);
}
if (deadlineExpiredElapsed < mNextTaskExpiredElapsedMillis) {
if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
setDeadlineExpiredAlarm(deadlineExpiredElapsed);
}
}
/**
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
* delay will expire.
* This alarm <b>will not</b> wake up the phone.
*/
@@ -228,7 +228,7 @@ public class TimeController extends StateController {
}
/**
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
* deadline will expire.
* This alarm <b>will</b> wake up the phone.
*/
@@ -238,8 +238,8 @@ public class TimeController extends StateController {
if (alarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
alarmTimeElapsedMillis = earliestWakeupTimeElapsed;
}
mNextTaskExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithPendingIntent(mDeadlineExpiredAlarmIntent, mNextTaskExpiredElapsedMillis);
mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithPendingIntent(mDeadlineExpiredAlarmIntent, mNextJobExpiredElapsedMillis);
}
private void updateAlarmWithPendingIntent(PendingIntent pi, long alarmTimeElapsed) {
@@ -260,11 +260,11 @@ public class TimeController extends StateController {
if (DEBUG) {
Slog.d(TAG, "Just received alarm: " + intent.getAction());
}
// An task has just expired, so we run through the list of tasks that we have and
// A job has just expired, so we run through the list of jobs that we have and
// notify our StateChangedListener.
if (ACTION_TASK_EXPIRED.equals(intent.getAction())) {
if (ACTION_JOB_EXPIRED.equals(intent.getAction())) {
checkExpiredDeadlinesAndResetAlarm();
} else if (ACTION_TASK_DELAY_EXPIRED.equals(intent.getAction())) {
} else if (ACTION_JOB_DELAY_EXPIRED.equals(intent.getAction())) {
checkExpiredDelaysAndResetAlarm();
}
}
@@ -276,10 +276,10 @@ public class TimeController extends StateController {
pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")");
pw.println(
"Next delay alarm in " + (mNextDelayExpiredElapsedMillis - nowElapsed)/1000 + "s");
pw.println("Next deadline alarm in " + (mNextTaskExpiredElapsedMillis - nowElapsed)/1000
pw.println("Next deadline alarm in " + (mNextJobExpiredElapsedMillis - nowElapsed)/1000
+ "s");
pw.println("Tracking:");
for (TaskStatus ts : mTrackedTasks) {
for (JobStatus ts : mTrackedJobs) {
pw.println(String.valueOf(ts.hashCode()).substring(0, 3) + ".."
+ ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A")
+ ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A")

View File

@@ -1,764 +0,0 @@
/*
* Copyright (C) 2014 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.task;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.app.task.ITaskManager;
import android.app.task.Task;
import android.app.task.TaskManager;
import android.content.BroadcastReceiver;
import android.app.task.TaskService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import com.android.server.task.controllers.BatteryController;
import com.android.server.task.controllers.ConnectivityController;
import com.android.server.task.controllers.IdleController;
import com.android.server.task.controllers.StateController;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.task.controllers.TimeController;
import java.util.LinkedList;
/**
* Responsible for taking tasks representing work to be performed by a client app, and determining
* based on the criteria specified when that task should be run against the client application's
* endpoint.
* Implements logic for scheduling, and rescheduling tasks. The TaskManagerService knows nothing
* about constraints, or the state of active tasks. It receives callbacks from the various
* controllers and completed tasks and operates accordingly.
*
* Note on locking: Any operations that manipulate {@link #mTasks} need to lock on that object.
* Any function with the suffix 'Locked' also needs to lock on {@link #mTasks}.
* @hide
*/
public class TaskManagerService extends com.android.server.SystemService
implements StateChangedListener, TaskCompletedListener, TaskMapReadFinishedListener {
// TODO: Switch this off for final version.
static final boolean DEBUG = true;
/** The number of concurrent tasks we run at one time. */
private static final int MAX_TASK_CONTEXTS_COUNT = 3;
static final String TAG = "TaskManager";
/** Master list of tasks. */
private final TaskStore mTasks;
static final int MSG_TASK_EXPIRED = 0;
static final int MSG_CHECK_TASKS = 1;
// Policy constants
/**
* Minimum # of idle tasks that must be ready in order to force the TM to schedule things
* early.
*/
private static final int MIN_IDLE_COUNT = 1;
/**
* Minimum # of connectivity tasks that must be ready in order to force the TM to schedule
* things early.
*/
private static final int MIN_CONNECTIVITY_COUNT = 2;
/**
* Minimum # of tasks (with no particular constraints) for which the TM will be happy running
* some work early.
*/
private static final int MIN_READY_TASKS_COUNT = 4;
/**
* Track Services that have currently active or pending tasks. The index is provided by
* {@link TaskStatus#getServiceToken()}
*/
private final List<TaskServiceContext> mActiveServices = new LinkedList<TaskServiceContext>();
/** List of controllers that will notify this service of updates to tasks. */
private List<StateController> mControllers;
/**
* Queue of pending tasks. The TaskServiceContext class will receive tasks from this list
* when ready to execute them.
*/
private final LinkedList<TaskStatus> mPendingTasks = new LinkedList<TaskStatus>();
private final TaskHandler mHandler;
private final TaskManagerStub mTaskManagerStub;
/**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
*/
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Slog.d(TAG, "Receieved: " + intent.getAction());
if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
cancelTasksForUid(uidRemoved);
} 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);
}
cancelTasksForUser(userId);
}
}
};
/**
* Entry point from client to schedule the provided task.
* This cancels the task if it's already been scheduled, and replaces it with the one provided.
* @param task Task object containing execution parameters
* @param uId The package identifier of the application this task is for.
* @param canPersistTask Whether or not the client has the appropriate permissions for
* persisting this task.
* @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
*/
public int schedule(Task task, int uId, boolean canPersistTask) {
TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask);
cancelTask(uId, task.getId());
startTrackingTask(taskStatus);
return TaskManager.RESULT_SUCCESS;
}
public List<Task> getPendingTasks(int uid) {
ArrayList<Task> outList = new ArrayList<Task>();
synchronized (mTasks) {
for (TaskStatus ts : mTasks.getTasks()) {
if (ts.getUid() == uid) {
outList.add(ts.getTask());
}
}
}
return outList;
}
private void cancelTasksForUser(int userHandle) {
synchronized (mTasks) {
List<TaskStatus> tasksForUser = mTasks.getTasksByUser(userHandle);
for (TaskStatus toRemove : tasksForUser) {
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + toRemove);
}
cancelTaskLocked(toRemove);
}
}
}
/**
* Entry point from client to cancel all tasks originating from their uid.
* This will remove the task from the master list, and cancel the task if it was staged for
* execution or being executed.
* @param uid To check against for removal of a task.
*/
public void cancelTasksForUid(int uid) {
// Remove from master list.
synchronized (mTasks) {
List<TaskStatus> tasksForUid = mTasks.getTasksByUid(uid);
for (TaskStatus toRemove : tasksForUid) {
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + toRemove);
}
cancelTaskLocked(toRemove);
}
}
}
/**
* Entry point from client to cancel the task corresponding to the taskId provided.
* This will remove the task from the master list, and cancel the task if it was staged for
* execution or being executed.
* @param uid Uid of the calling client.
* @param taskId Id of the task, provided at schedule-time.
*/
public void cancelTask(int uid, int taskId) {
TaskStatus toCancel;
synchronized (mTasks) {
toCancel = mTasks.getTaskByUidAndTaskId(uid, taskId);
if (toCancel != null) {
cancelTaskLocked(toCancel);
}
}
}
private void cancelTaskLocked(TaskStatus cancelled) {
// Remove from store.
stopTrackingTask(cancelled);
// Remove from pending queue.
mPendingTasks.remove(cancelled);
// Cancel if running.
stopTaskOnServiceContextLocked(cancelled);
}
/**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
* and passes it to super.
* </p>
*
* @param context The system server context.
*/
public TaskManagerService(Context context) {
super(context);
// Create the controllers.
mControllers = new LinkedList<StateController>();
mControllers.add(ConnectivityController.get(this));
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
mControllers.add(BatteryController.get(this));
mHandler = new TaskHandler(context.getMainLooper());
mTaskManagerStub = new TaskManagerStub();
// Create the "runners".
for (int i = 0; i < MAX_TASK_CONTEXTS_COUNT; i++) {
mActiveServices.add(
new TaskServiceContext(this, context.getMainLooper()));
}
mTasks = TaskStore.initAndGet(this);
}
@Override
public void onStart() {
publishBinderService(Context.TASK_SERVICE, mTaskManagerStub);
}
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
// 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);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
}
}
/**
* Called when we have a task status object that we need to insert in our
* {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know
* about.
*/
private void startTrackingTask(TaskStatus taskStatus) {
boolean update;
synchronized (mTasks) {
update = mTasks.add(taskStatus);
}
for (StateController controller : mControllers) {
if (update) {
controller.maybeStopTrackingTask(taskStatus);
}
controller.maybeStartTrackingTask(taskStatus);
}
}
/**
* Called when we want to remove a TaskStatus object that we've finished executing. Returns the
* object removed.
*/
private boolean stopTrackingTask(TaskStatus taskStatus) {
boolean removed;
synchronized (mTasks) {
// Remove from store as well as controllers.
removed = mTasks.remove(taskStatus);
}
if (removed) {
for (StateController controller : mControllers) {
controller.maybeStopTrackingTask(taskStatus);
}
}
return removed;
}
private boolean stopTaskOnServiceContextLocked(TaskStatus ts) {
for (TaskServiceContext tsc : mActiveServices) {
final TaskStatus executing = tsc.getRunningTask();
if (executing != null && executing.matches(ts.getUid(), ts.getTaskId())) {
tsc.cancelExecutingTask();
return true;
}
}
return false;
}
/**
* @param ts TaskStatus we are querying against.
* @return Whether or not the task represented by the status object is currently being run or
* is pending.
*/
private boolean isCurrentlyActiveLocked(TaskStatus ts) {
for (TaskServiceContext serviceContext : mActiveServices) {
final TaskStatus running = serviceContext.getRunningTask();
if (running != null && running.matches(ts.getUid(), ts.getTaskId())) {
return true;
}
}
return false;
}
/**
* A task is rescheduled with exponential back-off if the client requests this from their
* execution logic.
* A caveat is for idle-mode tasks, for which the idle-mode constraint will usurp the
* timeliness of the reschedule. For an idle-mode task, no deadline is given.
* @param failureToReschedule Provided task status that we will reschedule.
* @return A newly instantiated TaskStatus with the same constraints as the last task except
* with adjusted timing constraints.
*/
private TaskStatus getRescheduleTaskForFailure(TaskStatus failureToReschedule) {
final long elapsedNowMillis = SystemClock.elapsedRealtime();
final Task task = failureToReschedule.getTask();
final long initialBackoffMillis = task.getInitialBackoffMillis();
final int backoffAttempt = failureToReschedule.getNumFailures() + 1;
long newEarliestRuntimeElapsed = elapsedNowMillis;
switch (task.getBackoffPolicy()) {
case Task.BackoffPolicy.LINEAR:
newEarliestRuntimeElapsed += initialBackoffMillis * backoffAttempt;
break;
default:
if (DEBUG) {
Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
}
case Task.BackoffPolicy.EXPONENTIAL:
newEarliestRuntimeElapsed +=
Math.pow(initialBackoffMillis * 0.001, backoffAttempt) * 1000;
break;
}
newEarliestRuntimeElapsed =
Math.min(newEarliestRuntimeElapsed, Task.MAX_BACKOFF_DELAY_MILLIS);
return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed,
TaskStatus.NO_LATEST_RUNTIME, backoffAttempt);
}
/**
* Called after a periodic has executed so we can to re-add it. We take the last execution time
* of the task to be the time of completion (i.e. the time at which this function is called).
* This could be inaccurate b/c the task can run for as long as
* {@link com.android.server.task.TaskServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
* to underscheduling at least, rather than if we had taken the last execution time to be the
* start of the execution.
* @return A new task representing the execution criteria for this instantiation of the
* recurring task.
*/
private TaskStatus getRescheduleTaskForPeriodic(TaskStatus periodicToReschedule) {
final long elapsedNow = SystemClock.elapsedRealtime();
// Compute how much of the period is remaining.
long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
long newEarliestRunTimeElapsed = elapsedNow + runEarly;
long period = periodicToReschedule.getTask().getIntervalMillis();
long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
if (DEBUG) {
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
}
return new TaskStatus(periodicToReschedule, newEarliestRunTimeElapsed,
newLatestRuntimeElapsed, 0 /* backoffAttempt */);
}
// TaskCompletedListener implementations.
/**
* A task just finished executing. We fetch the
* {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
* whether we want to reschedule we readd it to the controllers.
* @param taskStatus Completed task.
* @param needsReschedule Whether the implementing class should reschedule this task.
*/
@Override
public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule) {
if (DEBUG) {
Slog.d(TAG, "Completed " + taskStatus + ", reschedule=" + needsReschedule);
}
if (!stopTrackingTask(taskStatus)) {
if (DEBUG) {
Slog.e(TAG, "Error removing task: could not find task to remove. Was task " +
"removed while executing?");
}
return;
}
if (needsReschedule) {
TaskStatus rescheduled = getRescheduleTaskForFailure(taskStatus);
startTrackingTask(rescheduled);
} else if (taskStatus.getTask().isPeriodic()) {
TaskStatus rescheduledPeriodic = getRescheduleTaskForPeriodic(taskStatus);
startTrackingTask(rescheduledPeriodic);
}
mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
// StateChangedListener implementations.
/**
* Off-board work to our handler thread as quickly as possible, b/c this call is probably being
* made on the main thread.
* For now this takes the task and if it's ready to run it will run it. In future we might not
* provide the task, so that the StateChangedListener has to run through its list of tasks to
* see which are ready. This will further decouple the controllers from the execution logic.
*/
@Override
public void onControllerStateChanged() {
// Post a message to to run through the list of tasks and start/stop any that are eligible.
mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
@Override
public void onRunTaskNow(TaskStatus taskStatus) {
mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus).sendToTarget();
}
/**
* Disk I/O is finished, take the list of tasks we read from disk and add them to our
* {@link TaskStore}.
* This is run on the {@link com.android.server.IoThread} instance, which is a separate thread,
* and is called once at boot.
*/
@Override
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
synchronized (mTasks) {
for (TaskStatus ts : tasks) {
if (mTasks.containsTaskIdForUid(ts.getTaskId(), ts.getUid())) {
// An app with BOOT_COMPLETED *might* have decided to reschedule their task, in
// the same amount of time it took us to read it from disk. If this is the case
// we leave it be.
continue;
}
startTrackingTask(ts);
}
}
}
private class TaskHandler extends Handler {
public TaskHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_TASK_EXPIRED:
synchronized (mTasks) {
TaskStatus runNow = (TaskStatus) message.obj;
if (!mPendingTasks.contains(runNow)) {
mPendingTasks.add(runNow);
}
}
queueReadyTasksForExecutionH();
break;
case MSG_CHECK_TASKS:
// Check the list of tasks and run some of them if we feel inclined.
maybeQueueReadyTasksForExecutionH();
break;
}
maybeRunPendingTasksH();
// Don't remove TASK_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_TASKS);
}
/**
* Run through list of tasks and execute all possible - at least one is expired so we do
* as many as we can.
*/
private void queueReadyTasksForExecutionH() {
synchronized (mTasks) {
for (TaskStatus ts : mTasks.getTasks()) {
if (isReadyToBeExecutedLocked(ts)) {
mPendingTasks.add(ts);
} else if (isReadyToBeCancelledLocked(ts)) {
stopTaskOnServiceContextLocked(ts);
}
}
}
}
/**
* The state of at least one task has changed. Here is where we could enforce various
* policies on when we want to execute tasks.
* Right now the policy is such:
* If >1 of the ready tasks is idle mode we send all of them off
* if more than 2 network connectivity tasks are ready we send them all off.
* If more than 4 tasks total are ready we send them all off.
* TODO: It would be nice to consolidate these sort of high-level policies somewhere.
*/
private void maybeQueueReadyTasksForExecutionH() {
synchronized (mTasks) {
int idleCount = 0;
int backoffCount = 0;
int connectivityCount = 0;
List<TaskStatus> runnableTasks = new ArrayList<TaskStatus>();
for (TaskStatus ts : mTasks.getTasks()) {
if (isReadyToBeExecutedLocked(ts)) {
if (ts.getNumFailures() > 0) {
backoffCount++;
}
if (ts.hasIdleConstraint()) {
idleCount++;
}
if (ts.hasConnectivityConstraint() || ts.hasUnmeteredConstraint()) {
connectivityCount++;
}
runnableTasks.add(ts);
} else if (isReadyToBeCancelledLocked(ts)) {
stopTaskOnServiceContextLocked(ts);
}
}
if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
runnableTasks.size() >= MIN_READY_TASKS_COUNT) {
for (TaskStatus ts : runnableTasks) {
mPendingTasks.add(ts);
}
}
}
}
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a TSC.
*/
private boolean isReadyToBeExecutedLocked(TaskStatus ts) {
return ts.isReady() && !mPendingTasks.contains(ts) && !isCurrentlyActiveLocked(ts);
}
/**
* Criteria for cancelling an active job:
* - It's not ready
* - It's running on a TSC.
*/
private boolean isReadyToBeCancelledLocked(TaskStatus ts) {
return !ts.isReady() && isCurrentlyActiveLocked(ts);
}
/**
* Reconcile jobs in the pending queue against available execution contexts.
* A controller can force a task into the pending queue even if it's already running, but
* here is where we decide whether to actually execute it.
*/
private void maybeRunPendingTasksH() {
synchronized (mTasks) {
Iterator<TaskStatus> it = mPendingTasks.iterator();
while (it.hasNext()) {
TaskStatus nextPending = it.next();
TaskServiceContext availableContext = null;
for (TaskServiceContext tsc : mActiveServices) {
final TaskStatus running = tsc.getRunningTask();
if (running != null && running.matches(nextPending.getUid(),
nextPending.getTaskId())) {
// Already running this tId for this uId, skip.
availableContext = null;
break;
}
if (tsc.isAvailable()) {
availableContext = tsc;
}
}
if (availableContext != null) {
if (!availableContext.executeRunnableTask(nextPending)) {
if (DEBUG) {
Slog.d(TAG, "Error executing " + nextPending);
}
mTasks.remove(nextPending);
}
it.remove();
}
}
}
}
}
/**
* Binder stub trampoline implementation
*/
final class TaskManagerStub extends ITaskManager.Stub {
/** Cache determination of whether a given app can persist tasks
* key is uid of the calling app; value is undetermined/true/false
*/
private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
// Enforce that only the app itself (or shared uid participant) can schedule a
// task that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_TASK_SERVICE permission
private void enforceValidJobRequest(int uid, Task job) {
final PackageManager pm = getContext().getPackageManager();
final ComponentName service = job.getService();
try {
ServiceInfo si = pm.getServiceInfo(service, 0);
if (si.applicationInfo.uid != uid) {
throw new IllegalArgumentException("uid " + uid +
" cannot schedule job in " + service.getPackageName());
}
if (!TaskService.PERMISSION_BIND.equals(si.permission)) {
throw new IllegalArgumentException("Scheduled service " + service
+ " does not require android.permission.BIND_TASK_SERVICE permission");
}
} catch (NameNotFoundException e) {
throw new IllegalArgumentException("No such service: " + service);
}
}
private boolean canPersistJobs(int pid, int uid) {
// If we get this far we're good to go; all we need to do now is check
// whether the app is allowed to persist its scheduled work.
final boolean canPersist;
synchronized (mPersistCache) {
Boolean cached = mPersistCache.get(uid);
if (cached != null) {
canPersist = cached.booleanValue();
} else {
// Persisting tasks is tantamount to running at boot, so we permit
// it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
// permission
int result = getContext().checkPermission(
android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
canPersist = (result == PackageManager.PERMISSION_GRANTED);
mPersistCache.put(uid, canPersist);
}
}
return canPersist;
}
// ITaskManager implementation
@Override
public int schedule(Task task) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling task: " + task);
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
enforceValidJobRequest(uid, task);
final boolean canPersist = canPersistJobs(pid, uid);
long ident = Binder.clearCallingIdentity();
try {
return TaskManagerService.this.schedule(task, uid, canPersist);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public List<Task> getAllPendingTasks() throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
return TaskManagerService.this.getPendingTasks(uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void cancelAll() throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
TaskManagerService.this.cancelTasksForUid(uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void cancel(int taskId) throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
TaskManagerService.this.cancelTask(uid, taskId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
* "dumpsys" infrastructure
*/
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
long identityToken = Binder.clearCallingIdentity();
try {
TaskManagerService.this.dumpInternal(pw);
} finally {
Binder.restoreCallingIdentity(identityToken);
}
}
};
void dumpInternal(PrintWriter pw) {
synchronized (mTasks) {
pw.println("Registered tasks:");
if (mTasks.size() > 0) {
for (TaskStatus ts : mTasks.getTasks()) {
ts.dump(pw, " ");
}
} else {
pw.println();
pw.println("No tasks scheduled.");
}
for (StateController controller : mControllers) {
pw.println();
controller.dumpControllerState(pw);
}
pw.println();
pw.println("Pending");
for (TaskStatus taskStatus : mPendingTasks) {
pw.println(taskStatus.hashCode());
}
pw.println();
pw.println("Active jobs:");
for (TaskServiceContext tsc : mActiveServices) {
if (tsc.isAvailable()) {
continue;
} else {
pw.println(tsc.getRunningTask().hashCode() + " for: " +
(SystemClock.elapsedRealtime()
- tsc.getExecutionStartTimeElapsed())/1000 + "s " +
"timeout: " + tsc.getTimeoutElapsed());
}
}
}
pw.println();
}
}

View File

@@ -65,6 +65,7 @@ import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.input.InputManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
@@ -84,7 +85,6 @@ import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.search.SearchManagerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.task.TaskManagerService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.twilight.TwilightService;
@@ -132,8 +132,8 @@ public final class SystemServer {
"com.android.server.wifi.p2p.WifiP2pService";
private static final String ETHERNET_SERVICE_CLASS =
"com.android.server.ethernet.EthernetService";
private static final String TASK_SERVICE_CLASS =
"com.android.server.task.TaskManagerService";
private static final String JOB_SCHEDULER_SERVICE_CLASS =
"com.android.server.job.JobSchedulerService";
private final int mFactoryTestMode;
private Timer mProfilerSnapshotTimer;
@@ -826,7 +826,7 @@ public final class SystemServer {
mSystemServiceManager.startService(UiModeManagerService.class);
mSystemServiceManager.startService(TaskManagerService.class);
mSystemServiceManager.startService(JobSchedulerService.class);
if (!disableNonCoreServices) {
try {

View File

@@ -3,18 +3,20 @@ package com.android.server.task;
import android.content.ComponentName;
import android.content.Context;
import android.app.task.Task;
import android.app.task.Task.Builder;
import android.app.job.JobInfo;
import android.app.job.JobInfo.Builder;
import android.os.PersistableBundle;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.util.Log;
import com.android.server.task.controllers.TaskStatus;
import com.android.server.job.JobMapReadFinishedListener;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.JobStatus;
import java.util.List;
import static com.android.server.task.TaskStore.initAndGet;
import static com.android.server.job.JobStore.initAndGet;
/**
* Test reading and writing correctly from file.
*/
@@ -26,12 +28,12 @@ public class TaskStoreTest extends AndroidTestCase {
private ComponentName mComponent;
private static final long IO_WAIT = 600L;
TaskStore mTaskStoreUnderTest;
JobStore mTaskStoreUnderTest;
Context mTestContext;
TaskMapReadFinishedListener mTaskMapReadFinishedListenerStub =
new TaskMapReadFinishedListener() {
JobMapReadFinishedListener mTaskMapReadFinishedListenerStub =
new JobMapReadFinishedListener() {
@Override
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
public void onJobMapReadFinished(List<JobStatus> tasks) {
// do nothing.
}
};
@@ -40,7 +42,7 @@ public class TaskStoreTest extends AndroidTestCase {
public void setUp() throws Exception {
mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);
Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'");
mTaskStoreUnderTest = TaskStore.initAndGetForTesting(mTestContext,
mTaskStoreUnderTest = JobStore.initAndGetForTesting(mTestContext,
mTestContext.getFilesDir(), mTaskMapReadFinishedListenerStub);
mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
}
@@ -56,23 +58,23 @@ public class TaskStoreTest extends AndroidTestCase {
long runFromMillis = 2000L; // 2s
long initialBackoff = 10000L; // 10s
final Task task = new Builder(taskId, mComponent)
final JobInfo task = new Builder(taskId, mComponent)
.setRequiresCharging(true)
.setRequiredNetworkCapabilities(Task.NetworkType.ANY)
.setBackoffCriteria(initialBackoff, Task.BackoffPolicy.EXPONENTIAL)
.setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY)
.setBackoffCriteria(initialBackoff, JobInfo.BackoffPolicy.EXPONENTIAL)
.setOverrideDeadline(runByMillis)
.setMinimumLatency(runFromMillis)
.build();
final TaskStatus ts = new TaskStatus(task, SOME_UID, true /* persisted */);
final JobStatus ts = new JobStatus(task, SOME_UID, true /* persisted */);
mTaskStoreUnderTest.add(ts);
Thread.sleep(IO_WAIT);
// Manually load tasks from xml file.
mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() {
mTaskStoreUnderTest.readJobMapFromDisk(new JobMapReadFinishedListener() {
@Override
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
public void onJobMapReadFinished(List<JobStatus> tasks) {
assertEquals("Didn't get expected number of persisted tasks.", 1, tasks.size());
TaskStatus loadedTaskStatus = tasks.get(0);
assertTasksEqual(task, loadedTaskStatus.getTask());
JobStatus loadedTaskStatus = tasks.get(0);
assertTasksEqual(task, loadedTaskStatus.getJob());
assertEquals("Different uids.", SOME_UID, tasks.get(0).getUid());
compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
@@ -84,30 +86,30 @@ public class TaskStoreTest extends AndroidTestCase {
}
public void testWritingTwoFilesToDisk() throws Exception {
final Task task1 = new Builder(8, mComponent)
final JobInfo task1 = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
.setRequiresCharging(true)
.build();
final Task task2 = new Builder(12, mComponent)
final JobInfo task2 = new Builder(12, mComponent)
.setMinimumLatency(5000L)
.setBackoffCriteria(15000L, Task.BackoffPolicy.LINEAR)
.setBackoffCriteria(15000L, JobInfo.BackoffPolicy.LINEAR)
.setOverrideDeadline(30000L)
.setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED)
.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED)
.build();
final TaskStatus taskStatus1 = new TaskStatus(task1, SOME_UID, true /* persisted */);
final TaskStatus taskStatus2 = new TaskStatus(task2, SOME_UID, true /* persisted */);
final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, true /* persisted */);
final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, true /* persisted */);
mTaskStoreUnderTest.add(taskStatus1);
mTaskStoreUnderTest.add(taskStatus2);
Thread.sleep(IO_WAIT);
mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() {
mTaskStoreUnderTest.readJobMapFromDisk(new JobMapReadFinishedListener() {
@Override
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
public void onJobMapReadFinished(List<JobStatus> tasks) {
assertEquals("Incorrect # of persisted tasks.", 2, tasks.size());
TaskStatus loaded1 = tasks.get(0);
TaskStatus loaded2 = tasks.get(1);
assertTasksEqual(task1, loaded1.getTask());
assertTasksEqual(task2, loaded2.getTask());
JobStatus loaded1 = tasks.get(0);
JobStatus loaded2 = tasks.get(1);
assertTasksEqual(task1, loaded1.getJob());
assertTasksEqual(task2, loaded2.getJob());
// Check that the loaded task has the correct runtimes.
compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
@@ -124,7 +126,7 @@ public class TaskStoreTest extends AndroidTestCase {
}
public void testWritingTaskWithExtras() throws Exception {
Task.Builder b = new Builder(8, mComponent)
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
.setRequiresCharging(true);
@@ -134,17 +136,17 @@ public class TaskStoreTest extends AndroidTestCase {
extras.putString("hi", "there");
extras.putInt("into", 3);
b.setExtras(extras);
final Task task = b.build();
TaskStatus taskStatus = new TaskStatus(task, SOME_UID, true /* persisted */);
final JobInfo task = b.build();
JobStatus taskStatus = new JobStatus(task, SOME_UID, true /* persisted */);
mTaskStoreUnderTest.add(taskStatus);
Thread.sleep(IO_WAIT);
mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() {
mTaskStoreUnderTest.readJobMapFromDisk(new JobMapReadFinishedListener() {
@Override
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
public void onJobMapReadFinished(List<JobStatus> tasks) {
assertEquals("Incorrect # of persisted tasks.", 1, tasks.size());
TaskStatus loaded = tasks.get(0);
assertTasksEqual(task, loaded.getTask());
JobStatus loaded = tasks.get(0);
assertTasksEqual(task, loaded.getJob());
}
});
@@ -153,7 +155,7 @@ public class TaskStoreTest extends AndroidTestCase {
/**
* Helper function to throw an error if the provided task and TaskStatus objects are not equal.
*/
private void assertTasksEqual(Task first, Task second) {
private void assertTasksEqual(JobInfo first, JobInfo second) {
assertEquals("Different task ids.", first.getId(), second.getId());
assertEquals("Different components.", first.getService(), second.getService());
assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
@@ -168,11 +170,11 @@ public class TaskStoreTest extends AndroidTestCase {
assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
second.isRequireDeviceIdle());
assertEquals("Invalid unmetered constraint.",
first.getNetworkCapabilities() == Task.NetworkType.UNMETERED,
second.getNetworkCapabilities() == Task.NetworkType.UNMETERED);
first.getNetworkCapabilities() == JobInfo.NetworkType.UNMETERED,
second.getNetworkCapabilities() == JobInfo.NetworkType.UNMETERED);
assertEquals("Invalid connectivity constraint.",
first.getNetworkCapabilities() == Task.NetworkType.ANY,
second.getNetworkCapabilities() == Task.NetworkType.ANY);
first.getNetworkCapabilities() == JobInfo.NetworkType.ANY,
second.getNetworkCapabilities() == JobInfo.NetworkType.ANY);
assertEquals("Invalid deadline constraint.",
first.hasLateConstraint(),
second.hasLateConstraint());

View File

@@ -21,9 +21,11 @@ import android.content.ComponentName;
import android.content.Intent;
import android.test.AndroidTestCase;
import com.android.server.task.StateChangedListener;
import com.android.server.job.StateChangedListener;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.JobStatus;
import static com.android.server.task.controllers.BatteryController.getForTesting;
import static com.android.server.job.controllers.BatteryController.getForTesting;
import static org.mockito.Mockito.*;
@@ -40,7 +42,7 @@ public class BatteryControllerTest extends AndroidTestCase {
}
@Override
public void onRunTaskNow(TaskStatus taskStatus) {
public void onRunJobNow(JobStatus taskStatus) {
}
};

View File

@@ -25,6 +25,7 @@
<service
android:name=".service.TestJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
</application>

View File

@@ -17,8 +17,8 @@
package com.android.demo.jobSchedulerApp;
import android.app.Activity;
import android.app.task.Task;
import android.app.task.TaskParams;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Resources;
@@ -80,10 +80,10 @@ public class MainActivity extends Activity {
RadioButton mWiFiConnectivityRadioButton;
RadioButton mAnyConnectivityRadioButton;
ComponentName mServiceComponent;
/** Service object to interact scheduled tasks. */
/** Service object to interact scheduled jobs. */
TestJobService mTestService;
private static int kTaskId = 0;
private static int kJobId = 0;
Handler mHandler = new Handler(/* default looper */) {
@Override
@@ -112,7 +112,7 @@ public class MainActivity extends Activity {
}
/**
* UI onclick listener to schedule a task. What this task is is defined in
* UI onclick listener to schedule a job. What this job is is defined in
* TestJobService#scheduleJob()
*/
public void scheduleJob(View v) {
@@ -120,7 +120,7 @@ public class MainActivity extends Activity {
return;
}
Task.Builder builder = new Task.Builder(kTaskId++, mServiceComponent);
JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);
String delay = mDelayEditText.getText().toString();
if (delay != null && !TextUtils.isEmpty(delay)) {
@@ -133,9 +133,9 @@ public class MainActivity extends Activity {
boolean requiresUnmetered = mWiFiConnectivityRadioButton.isSelected();
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isSelected();
if (requiresUnmetered) {
builder.setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED);
builder.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED);
} else if (requiresAnyConnectivity) {
builder.setRequiredNetworkCapabilities(Task.NetworkType.ANY);
builder.setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY);
}
mTestService.scheduleJob(builder.build());
@@ -143,24 +143,24 @@ public class MainActivity extends Activity {
}
/**
* UI onclick listener to call taskFinished() in our service.
* UI onclick listener to call jobFinished() in our service.
*/
public void finishJob(View v) {
if (!ensureTestService()) {
return;
}
mTestService.callTaskFinished();
mTestService.callJobFinished();
mParamsTextView.setText("");
}
public void onReceivedStartTask(TaskParams params) {
public void onReceivedStartJob(JobParameters params) {
mShowStartView.setBackgroundColor(startJobColor);
Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START);
mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second.
mParamsTextView.setText("Executing: " + params.getTaskId() + " " + params.getExtras());
mParamsTextView.setText("Executing: " + params.getJobId() + " " + params.getExtras());
}
public void onReceivedStopTask() {
public void onReceivedStopJob() {
mShowStopView.setBackgroundColor(stopJobColor);
Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP);
mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second.

View File

@@ -16,28 +16,20 @@
package com.android.demo.jobSchedulerApp.service;
import android.app.Service;
import android.app.task.Task;
import android.app.task.TaskManager;
import android.app.task.TaskParams;
import android.app.task.TaskService;
import android.content.ComponentName;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Log;
import com.android.demo.jobSchedulerApp.MainActivity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
@@ -52,7 +44,7 @@ import java.util.List;
* lifecycle of our and provide a handle to said SyncAdapter to the OS on
* request.
*/
public class TestJobService extends TaskService {
public class TestJobService extends JobService {
private static final String TAG = "SyncService";
@Override
@@ -82,44 +74,44 @@ public class TestJobService extends TaskService {
}
@Override
public boolean onStartTask(TaskParams params) {
taskParamsMap.add(params);
public boolean onStartJob(JobParameters params) {
jobParamsMap.add(params);
if (mActivity != null) {
mActivity.onReceivedStartTask(params);
mActivity.onReceivedStartJob(params);
}
Log.i(TAG, "on start task: " + params.getTaskId());
Log.i(TAG, "on start job: " + params.getJobId());
return true;
}
@Override
public boolean onStopTask(TaskParams params) {
taskParamsMap.remove(params);
mActivity.onReceivedStopTask();
Log.i(TAG, "on stop task: " + params.getTaskId());
public boolean onStopJob(JobParameters params) {
jobParamsMap.remove(params);
mActivity.onReceivedStopJob();
Log.i(TAG, "on stop job: " + params.getJobId());
return true;
}
MainActivity mActivity;
private final LinkedList<TaskParams> taskParamsMap = new LinkedList<TaskParams>();
private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>();
public void setUiCallback(MainActivity activity) {
mActivity = activity;
}
/** Send job to the JobScheduler. */
public void scheduleJob(Task t) {
public void scheduleJob(JobInfo t) {
Log.d(TAG, "Scheduling job");
TaskManager tm =
(TaskManager) getSystemService(Context.TASK_SERVICE);
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.schedule(t);
}
public boolean callTaskFinished() {
TaskParams params = taskParamsMap.poll();
public boolean callJobFinished() {
JobParameters params = jobParamsMap.poll();
if (params == null) {
return false;
} else {
taskFinished(params, false);
jobFinished(params, false);
return true;
}
}