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:
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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));
|
||||
}});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.task;
|
||||
package android.app.job;
|
||||
|
||||
parcelable Task;
|
||||
|
||||
parcelable JobInfo;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.task;
|
||||
package android.app.job;
|
||||
|
||||
parcelable TaskParams;
|
||||
parcelable JobParameters;
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
72
core/java/android/app/job/JobScheduler.java
Normal file
72
core/java/android/app/job/JobScheduler.java
Normal 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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)" : "")
|
||||
+ "]";
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
<service
|
||||
android:name=".service.TestJobService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="true"/>
|
||||
</application>
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user