Merge "Add OnNetworkActive to TaskManager and simplify locking." into lmp-preview-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5553aeb2ae
@@ -20,6 +20,7 @@ package android.app;
|
||||
import android.app.task.ITaskManager;
|
||||
import android.app.task.Task;
|
||||
import android.app.task.TaskManager;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,26 +38,35 @@ public class TaskManagerImpl extends TaskManager {
|
||||
|
||||
@Override
|
||||
public int schedule(Task task) {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
try {
|
||||
return mBinder.schedule(task);
|
||||
} catch (RemoteException e) {
|
||||
return TaskManager.RESULT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(int taskId) {
|
||||
// TODO Auto-generated method stub
|
||||
try {
|
||||
mBinder.cancel(taskId);
|
||||
} catch (RemoteException e) {}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelAll() {
|
||||
// TODO Auto-generated method stub
|
||||
try {
|
||||
mBinder.cancelAll();
|
||||
} catch (RemoteException e) {}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getAllPendingTasks() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
try {
|
||||
return mBinder.getAllPendingTasks();
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ public class Task implements Parcelable {
|
||||
* @hide
|
||||
*/
|
||||
public static final int DEFAULT_BACKOFF_POLICY = BackoffPolicy.EXPONENTIAL;
|
||||
/**
|
||||
* Maximum backoff we allow for a job, in milliseconds.
|
||||
* @hide
|
||||
*/
|
||||
public static final long MAX_BACKOFF_DELAY_MILLIS = 24 * 60 * 60 * 1000; // 24 hours.
|
||||
|
||||
/**
|
||||
* Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1
|
||||
@@ -185,7 +190,7 @@ public class Task implements Parcelable {
|
||||
private Task(Parcel in) {
|
||||
taskId = in.readInt();
|
||||
extras = in.readPersistableBundle();
|
||||
service = ComponentName.readFromParcel(in);
|
||||
service = in.readParcelable(null);
|
||||
requireCharging = in.readInt() == 1;
|
||||
requireDeviceIdle = in.readInt() == 1;
|
||||
networkCapabilities = in.readInt();
|
||||
@@ -201,7 +206,7 @@ public class Task implements Parcelable {
|
||||
|
||||
private Task(Task.Builder b) {
|
||||
taskId = b.mTaskId;
|
||||
extras = new PersistableBundle(b.mExtras);
|
||||
extras = b.mExtras;
|
||||
service = b.mTaskService;
|
||||
requireCharging = b.mRequiresCharging;
|
||||
requireDeviceIdle = b.mRequiresDeviceIdle;
|
||||
@@ -225,7 +230,7 @@ public class Task implements Parcelable {
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(taskId);
|
||||
out.writePersistableBundle(extras);
|
||||
ComponentName.writeToParcel(service, out);
|
||||
out.writeParcelable(service, flags);
|
||||
out.writeInt(requireCharging ? 1 : 0);
|
||||
out.writeInt(requireDeviceIdle ? 1 : 0);
|
||||
out.writeInt(networkCapabilities);
|
||||
|
||||
@@ -35,5 +35,5 @@ public interface StateChangedListener {
|
||||
* it must be run immediately.
|
||||
* @param taskStatus The state of the task which is to be run immediately.
|
||||
*/
|
||||
public void onTaskDeadlineExpired(TaskStatus taskStatus);
|
||||
public void onRunTaskNow(TaskStatus taskStatus);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ 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.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
@@ -33,6 +36,7 @@ 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;
|
||||
|
||||
@@ -53,9 +57,8 @@ import java.util.LinkedList;
|
||||
* 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, and
|
||||
* similarly for {@link #mActiveServices}. If both locks need to be held take mTasksSet first and then
|
||||
* mActiveService afterwards.
|
||||
* 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
|
||||
@@ -65,12 +68,6 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
/** The number of concurrent tasks we run at one time. */
|
||||
private static final int MAX_TASK_CONTEXTS_COUNT = 3;
|
||||
static final String TAG = "TaskManager";
|
||||
/**
|
||||
* When a task fails, it gets rescheduled according to its backoff policy. To be nice, we allow
|
||||
* this amount of time from the rescheduled time by which the retry must occur.
|
||||
*/
|
||||
private static final long RESCHEDULE_WINDOW_SLOP_MILLIS = 5000L;
|
||||
|
||||
/** Master list of tasks. */
|
||||
private final TaskStore mTasks;
|
||||
|
||||
@@ -109,18 +106,42 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
|
||||
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 will add the task to the
|
||||
* 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
|
||||
* of this task.
|
||||
* @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;
|
||||
}
|
||||
@@ -137,36 +158,33 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
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 cancelTaskForUid(int uid) {
|
||||
public void cancelTasksForUid(int uid) {
|
||||
// Remove from master list.
|
||||
synchronized (mTasks) {
|
||||
if (!mTasks.removeAllByUid(uid)) {
|
||||
// If it's not in the master list, it's nowhere.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Remove from pending queue.
|
||||
synchronized (mPendingTasks) {
|
||||
Iterator<TaskStatus> it = mPendingTasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
TaskStatus ts = it.next();
|
||||
if (ts.getUid() == uid) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel if running.
|
||||
synchronized (mActiveServices) {
|
||||
for (TaskServiceContext tsc : mActiveServices) {
|
||||
if (tsc.getRunningTask().getUid() == uid) {
|
||||
tsc.cancelExecutingTask();
|
||||
List<TaskStatus> tasksForUid = mTasks.getTasksByUid(uid);
|
||||
for (TaskStatus toRemove : tasksForUid) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Cancelling: " + toRemove);
|
||||
}
|
||||
cancelTaskLocked(toRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,34 +197,24 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
* @param taskId Id of the task, provided at schedule-time.
|
||||
*/
|
||||
public void cancelTask(int uid, int taskId) {
|
||||
TaskStatus toCancel;
|
||||
synchronized (mTasks) {
|
||||
if (!mTasks.remove(uid, taskId)) {
|
||||
// If it's not in the master list, it's nowhere.
|
||||
return;
|
||||
}
|
||||
}
|
||||
synchronized (mPendingTasks) {
|
||||
Iterator<TaskStatus> it = mPendingTasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
TaskStatus ts = it.next();
|
||||
if (ts.getUid() == uid && ts.getTaskId() == taskId) {
|
||||
it.remove();
|
||||
// If we got it from pending, it didn't make it to active so return.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (mActiveServices) {
|
||||
for (TaskServiceContext tsc : mActiveServices) {
|
||||
if (tsc.getRunningTask().getUid() == uid &&
|
||||
tsc.getRunningTask().getTaskId() == taskId) {
|
||||
tsc.cancelExecutingTask();
|
||||
return;
|
||||
}
|
||||
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>
|
||||
@@ -218,7 +226,13 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
*/
|
||||
public TaskManagerService(Context context) {
|
||||
super(context);
|
||||
mTasks = TaskStore.initAndGet(this);
|
||||
// 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".
|
||||
@@ -226,12 +240,7 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
mActiveServices.add(
|
||||
new TaskServiceContext(this, context.getMainLooper()));
|
||||
}
|
||||
// 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));
|
||||
mTasks = TaskStore.initAndGet(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,18 +248,35 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
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) {
|
||||
mTasks.add(taskStatus);
|
||||
update = mTasks.add(taskStatus);
|
||||
}
|
||||
for (StateController controller : mControllers) {
|
||||
if (update) {
|
||||
controller.maybeStopTrackingTask(taskStatus);
|
||||
}
|
||||
controller.maybeStartTrackingTask(taskStatus);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,16 +298,15 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
return removed;
|
||||
}
|
||||
|
||||
private boolean cancelTaskOnServiceContext(TaskStatus ts) {
|
||||
synchronized (mActiveServices) {
|
||||
for (TaskServiceContext tsc : mActiveServices) {
|
||||
if (tsc.getRunningTask() == ts) {
|
||||
tsc.cancelExecutingTask();
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,15 +314,14 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
* @return Whether or not the task represented by the status object is currently being run or
|
||||
* is pending.
|
||||
*/
|
||||
private boolean isCurrentlyActive(TaskStatus ts) {
|
||||
synchronized (mActiveServices) {
|
||||
for (TaskServiceContext serviceContext : mActiveServices) {
|
||||
if (serviceContext.getRunningTask() == ts) {
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,13 +350,14 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
|
||||
}
|
||||
case Task.BackoffPolicy.EXPONENTIAL:
|
||||
newEarliestRuntimeElapsed += Math.pow(initialBackoffMillis, backoffAttempt);
|
||||
newEarliestRuntimeElapsed +=
|
||||
Math.pow(initialBackoffMillis * 0.001, backoffAttempt) * 1000;
|
||||
break;
|
||||
}
|
||||
long newLatestRuntimeElapsed = failureToReschedule.hasIdleConstraint() ? Long.MAX_VALUE
|
||||
: newEarliestRuntimeElapsed + RESCHEDULE_WINDOW_SLOP_MILLIS;
|
||||
newEarliestRuntimeElapsed =
|
||||
Math.min(newEarliestRuntimeElapsed, Task.MAX_BACKOFF_DELAY_MILLIS);
|
||||
return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed,
|
||||
newLatestRuntimeElapsed, backoffAttempt);
|
||||
TaskStatus.NO_LATEST_RUNTIME, backoffAttempt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,6 +397,9 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
*/
|
||||
@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 " +
|
||||
@@ -405,8 +433,8 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskDeadlineExpired(TaskStatus taskStatus) {
|
||||
mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus);
|
||||
public void onRunTaskNow(TaskStatus taskStatus) {
|
||||
mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,7 +447,7 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
|
||||
synchronized (mTasks) {
|
||||
for (TaskStatus ts : tasks) {
|
||||
if (mTasks.contains(ts)) {
|
||||
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.
|
||||
@@ -440,7 +468,12 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MSG_TASK_EXPIRED:
|
||||
final TaskStatus expired = (TaskStatus) message.obj; // Unused for now.
|
||||
synchronized (mTasks) {
|
||||
TaskStatus runNow = (TaskStatus) message.obj;
|
||||
if (!mPendingTasks.contains(runNow)) {
|
||||
mPendingTasks.add(runNow);
|
||||
}
|
||||
}
|
||||
queueReadyTasksForExecutionH();
|
||||
break;
|
||||
case MSG_CHECK_TASKS:
|
||||
@@ -448,7 +481,7 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
maybeQueueReadyTasksForExecutionH();
|
||||
break;
|
||||
}
|
||||
maybeRunNextPendingTaskH();
|
||||
maybeRunPendingTasksH();
|
||||
// Don't remove TASK_EXPIRED in case one came along while processing the queue.
|
||||
removeMessages(MSG_CHECK_TASKS);
|
||||
}
|
||||
@@ -460,14 +493,10 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
private void queueReadyTasksForExecutionH() {
|
||||
synchronized (mTasks) {
|
||||
for (TaskStatus ts : mTasks.getTasks()) {
|
||||
final boolean criteriaSatisfied = ts.isReady();
|
||||
final boolean isRunning = isCurrentlyActive(ts);
|
||||
if (criteriaSatisfied && !isRunning) {
|
||||
synchronized (mPendingTasks) {
|
||||
mPendingTasks.add(ts);
|
||||
}
|
||||
} else if (!criteriaSatisfied && isRunning) {
|
||||
cancelTaskOnServiceContext(ts);
|
||||
if (isReadyToBeExecutedLocked(ts)) {
|
||||
mPendingTasks.add(ts);
|
||||
} else if (isReadyToBeCancelledLocked(ts)) {
|
||||
stopTaskOnServiceContextLocked(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,62 +506,93 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
* 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.
|
||||
* 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()) {
|
||||
final boolean criteriaSatisfied = ts.isReady();
|
||||
final boolean isRunning = isCurrentlyActive(ts);
|
||||
if (criteriaSatisfied && !isRunning) {
|
||||
if (isReadyToBeExecutedLocked(ts)) {
|
||||
if (ts.getNumFailures() > 0) {
|
||||
backoffCount++;
|
||||
}
|
||||
if (ts.hasIdleConstraint()) {
|
||||
idleCount++;
|
||||
}
|
||||
if (ts.hasConnectivityConstraint() || ts.hasMeteredConstraint()) {
|
||||
if (ts.hasConnectivityConstraint() || ts.hasUnmeteredConstraint()) {
|
||||
connectivityCount++;
|
||||
}
|
||||
runnableTasks.add(ts);
|
||||
} else if (!criteriaSatisfied && isRunning) {
|
||||
cancelTaskOnServiceContext(ts);
|
||||
} else if (isReadyToBeCancelledLocked(ts)) {
|
||||
stopTaskOnServiceContextLocked(ts);
|
||||
}
|
||||
}
|
||||
if (idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT ||
|
||||
if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
|
||||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
|
||||
runnableTasks.size() >= MIN_READY_TASKS_COUNT) {
|
||||
for (TaskStatus ts : runnableTasks) {
|
||||
synchronized (mPendingTasks) {
|
||||
mPendingTasks.add(ts);
|
||||
}
|
||||
mPendingTasks.add(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the state of the pending queue against any available
|
||||
* {@link com.android.server.task.TaskServiceContext} that can run a new task.
|
||||
* {@link com.android.server.task.TaskServiceContext}.
|
||||
* 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 void maybeRunNextPendingTaskH() {
|
||||
TaskStatus nextPending;
|
||||
synchronized (mPendingTasks) {
|
||||
nextPending = mPendingTasks.poll();
|
||||
}
|
||||
if (nextPending == null) {
|
||||
return;
|
||||
}
|
||||
private boolean isReadyToBeExecutedLocked(TaskStatus ts) {
|
||||
return ts.isReady() && !mPendingTasks.contains(ts) && !isCurrentlyActiveLocked(ts);
|
||||
}
|
||||
|
||||
synchronized (mActiveServices) {
|
||||
for (TaskServiceContext tsc : mActiveServices) {
|
||||
if (tsc.isAvailable()) {
|
||||
if (tsc.executeRunnableTask(nextPending)) {
|
||||
return;
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +616,7 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
synchronized (mPersistCache) {
|
||||
Boolean cached = mPersistCache.get(callingUid);
|
||||
if (cached) {
|
||||
if (cached != null) {
|
||||
canPersist = cached.booleanValue();
|
||||
} else {
|
||||
// Persisting tasks is tantamount to running at boot, so we permit
|
||||
@@ -574,6 +634,9 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
// ITaskManager implementation
|
||||
@Override
|
||||
public int schedule(Task task) throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Scheduling task: " + task);
|
||||
}
|
||||
final boolean canPersist = canCallerPersistTasks();
|
||||
final int uid = Binder.getCallingUid();
|
||||
|
||||
@@ -603,7 +666,7 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
|
||||
long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
TaskManagerService.this.cancelTaskForUid(uid);
|
||||
TaskManagerService.this.cancelTasksForUid(uid);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
@@ -639,16 +702,36 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
|
||||
void dumpInternal(PrintWriter pw) {
|
||||
synchronized (mTasks) {
|
||||
pw.print("Registered tasks:");
|
||||
pw.println("Registered tasks:");
|
||||
if (mTasks.size() > 0) {
|
||||
for (TaskStatus ts : mTasks.getTasks()) {
|
||||
pw.println();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.WorkSource;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -54,7 +54,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
private static final int defaultMaxActiveTasksPerService =
|
||||
ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
|
||||
/** Amount of time a task is allowed to execute for before being considered timed-out. */
|
||||
private static final long EXECUTING_TIMESLICE_MILLIS = 5 * 60 * 1000;
|
||||
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. */
|
||||
private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
|
||||
/** String prefix for all wakelock names. */
|
||||
@@ -100,10 +100,14 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
/** Binder to the client service. */
|
||||
ITaskService service;
|
||||
|
||||
private final Object mAvailableLock = new Object();
|
||||
private final Object mLock = new Object();
|
||||
/** Whether this context is free. */
|
||||
@GuardedBy("mAvailableLock")
|
||||
@GuardedBy("mLock")
|
||||
private boolean mAvailable;
|
||||
/** Track start time. */
|
||||
private long mExecutionStartTimeElapsed;
|
||||
/** Track when job will timeout. */
|
||||
private long mTimeoutElapsed;
|
||||
|
||||
TaskServiceContext(TaskManagerService service, Looper looper) {
|
||||
this(service.getContext(), service, looper);
|
||||
@@ -114,46 +118,43 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
mContext = context;
|
||||
mCallbackHandler = new TaskServiceHandler(looper);
|
||||
mCompletedListener = completedListener;
|
||||
mAvailable = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a task 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 was accepted and is going to run.
|
||||
* @return True if the task is valid and is running. False if the task cannot be executed.
|
||||
*/
|
||||
boolean executeRunnableTask(TaskStatus ts) {
|
||||
synchronized (mAvailableLock) {
|
||||
synchronized (mLock) {
|
||||
if (!mAvailable) {
|
||||
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
|
||||
return false;
|
||||
}
|
||||
mAvailable = false;
|
||||
}
|
||||
|
||||
final PowerManager pm =
|
||||
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
TM_WAKELOCK_PREFIX + ts.getServiceComponent().getPackageName());
|
||||
mWakeLock.setWorkSource(new WorkSource(ts.getUid()));
|
||||
mWakeLock.setReferenceCounted(false);
|
||||
mRunningTask = ts;
|
||||
mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
|
||||
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
|
||||
|
||||
mRunningTask = ts;
|
||||
mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
|
||||
|
||||
mVerb = VERB_BINDING;
|
||||
final Intent intent = new Intent().setComponent(ts.getServiceComponent());
|
||||
boolean binding = mContext.bindServiceAsUser(intent, this,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
|
||||
new UserHandle(ts.getUserId()));
|
||||
if (!binding) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable.");
|
||||
mVerb = VERB_BINDING;
|
||||
final Intent intent = new Intent().setComponent(ts.getServiceComponent());
|
||||
boolean binding = mContext.bindServiceAsUser(intent, this,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
|
||||
new UserHandle(ts.getUserId()));
|
||||
if (!binding) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable.");
|
||||
}
|
||||
mRunningTask = null;
|
||||
mParams = null;
|
||||
mExecutionStartTimeElapsed = 0L;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
mAvailable = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Used externally to query the running task. Will return null if there is no task running. */
|
||||
@@ -170,11 +171,19 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
* @return Whether this context is available to handle incoming work.
|
||||
*/
|
||||
boolean isAvailable() {
|
||||
synchronized (mAvailableLock) {
|
||||
synchronized (mLock) {
|
||||
return mAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
long getExecutionStartTimeElapsed() {
|
||||
return mExecutionStartTimeElapsed;
|
||||
}
|
||||
|
||||
long getTimeoutElapsed() {
|
||||
return mTimeoutElapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void taskFinished(int taskId, boolean reschedule) {
|
||||
if (!verifyCallingUid()) {
|
||||
@@ -217,6 +226,12 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
this.service = ITaskService.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()));
|
||||
mWakeLock.setReferenceCounted(false);
|
||||
mWakeLock.acquire();
|
||||
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
|
||||
}
|
||||
@@ -263,7 +278,8 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
break;
|
||||
case MSG_CALLBACK:
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask);
|
||||
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask + " v:" +
|
||||
VERB_STRINGS[mVerb]);
|
||||
}
|
||||
removeMessages(MSG_TIMEOUT);
|
||||
|
||||
@@ -288,6 +304,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
break;
|
||||
case MSG_SHUTDOWN_EXECUTION:
|
||||
closeAndCleanupTaskH(true /* needsReschedule */);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unrecognised message: " + message);
|
||||
}
|
||||
@@ -423,7 +440,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
case VERB_EXECUTING:
|
||||
// Not an error - client ran out of time.
|
||||
Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
|
||||
" Reporting failure and asking for reschedule. " +
|
||||
" sending onStop. " +
|
||||
mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
|
||||
+ taskId);
|
||||
sendStopMessageH();
|
||||
@@ -452,7 +469,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
service.stopTask(mParams);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error sending onStopTask to client.", e);
|
||||
closeAndCleanupTaskH(false);
|
||||
closeAndCleanupTaskH(false /* reschedule */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,17 +481,17 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
*/
|
||||
private void closeAndCleanupTaskH(boolean reschedule) {
|
||||
removeMessages(MSG_TIMEOUT);
|
||||
mWakeLock.release();
|
||||
mContext.unbindService(TaskServiceContext.this);
|
||||
mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
|
||||
synchronized (mLock) {
|
||||
mWakeLock.release();
|
||||
mContext.unbindService(TaskServiceContext.this);
|
||||
mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
|
||||
|
||||
mWakeLock = null;
|
||||
mRunningTask = null;
|
||||
mParams = null;
|
||||
mVerb = -1;
|
||||
mCancelled.set(false);
|
||||
service = null;
|
||||
synchronized (mAvailableLock) {
|
||||
mWakeLock = null;
|
||||
mRunningTask = null;
|
||||
mParams = null;
|
||||
mVerb = -1;
|
||||
mCancelled.set(false);
|
||||
service = null;
|
||||
mAvailable = true;
|
||||
}
|
||||
}
|
||||
@@ -496,6 +513,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
|
||||
}
|
||||
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
|
||||
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
|
||||
mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
@@ -54,7 +55,6 @@ import org.xmlpull.v1.XmlSerializer;
|
||||
* - When a task is added, it will determine if the task requirements have changed (update) and
|
||||
* whether the controllers need to be updated.
|
||||
* - Persists Tasks, figures out when to to rewrite the Task to disk.
|
||||
* - Is threadsafe.
|
||||
* - 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.
|
||||
@@ -96,7 +96,7 @@ public class TaskStore {
|
||||
|
||||
@VisibleForTesting
|
||||
public static TaskStore initAndGetForTesting(Context context, File dataDir,
|
||||
TaskMapReadFinishedListener callback) {
|
||||
TaskMapReadFinishedListener callback) {
|
||||
return new TaskStore(context, dataDir, callback);
|
||||
}
|
||||
|
||||
@@ -126,14 +126,22 @@ public class TaskStore {
|
||||
if (taskStatus.isPersisted()) {
|
||||
maybeWriteStatusToDiskAsync();
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Added task status to store: " + taskStatus);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this taskStatus object already exists in the TaskStore.
|
||||
*/
|
||||
public boolean contains(TaskStatus taskStatus) {
|
||||
return mTasksSet.contains(taskStatus);
|
||||
public boolean containsTaskIdForUid(int taskId, int uId) {
|
||||
for (TaskStatus ts : mTasksSet) {
|
||||
if (ts.getUid() == uId && ts.getTaskId() == taskId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
@@ -162,49 +170,48 @@ public class TaskStore {
|
||||
maybeWriteStatusToDiskAsync();
|
||||
}
|
||||
|
||||
public List<TaskStatus> getTasksByUser(int userHandle) {
|
||||
List<TaskStatus> matchingTasks = new ArrayList<TaskStatus>();
|
||||
Iterator<TaskStatus> it = mTasksSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
TaskStatus ts = it.next();
|
||||
if (UserHandle.getUserId(ts.getUid()) == userHandle) {
|
||||
matchingTasks.add(ts);
|
||||
}
|
||||
}
|
||||
return matchingTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all TaskStatus objects for a given uid from the master list. Note that it is
|
||||
* possible to remove a task that is pending/active. This operation will succeed, and the
|
||||
* removal will take effect when the task has completed executing.
|
||||
* @param uid Uid of the requesting app.
|
||||
* @return True if at least one task was removed, false if nothing matching the provided uId
|
||||
* was found.
|
||||
* @return All TaskStatus objects for a given uid from the master list.
|
||||
*/
|
||||
public boolean removeAllByUid(int uid) {
|
||||
public List<TaskStatus> getTasksByUid(int uid) {
|
||||
List<TaskStatus> matchingTasks = new ArrayList<TaskStatus>();
|
||||
Iterator<TaskStatus> it = mTasksSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
TaskStatus ts = it.next();
|
||||
if (ts.getUid() == uid) {
|
||||
it.remove();
|
||||
maybeWriteStatusToDiskAsync();
|
||||
return true;
|
||||
matchingTasks.add(ts);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return matchingTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the TaskStatus that matches the provided uId and taskId. Note that it is possible
|
||||
* to remove a task that is pending/active. This operation will succeed, and the removal will
|
||||
* take effect when the task has completed executing.
|
||||
* @param uid Uid of the requesting app.
|
||||
* @param taskId Task id, specified at schedule-time.
|
||||
* @return true if a removal occurred, false if the provided parameters didn't match anything.
|
||||
* @return the TaskStatus that matches the provided uId and taskId, or null if none found.
|
||||
*/
|
||||
public boolean remove(int uid, int taskId) {
|
||||
boolean changed = false;
|
||||
public TaskStatus getTaskByUidAndTaskId(int uid, int taskId) {
|
||||
Iterator<TaskStatus> it = mTasksSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
TaskStatus ts = it.next();
|
||||
if (ts.getUid() == uid && ts.getTaskId() == taskId) {
|
||||
it.remove();
|
||||
changed = true;
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
maybeWriteStatusToDiskAsync();
|
||||
}
|
||||
return changed;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,7 +333,7 @@ public class TaskStore {
|
||||
*/
|
||||
private void writeConstraintsToXml(XmlSerializer out, TaskStatus taskStatus) throws IOException {
|
||||
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
|
||||
if (taskStatus.hasMeteredConstraint()) {
|
||||
if (taskStatus.hasUnmeteredConstraint()) {
|
||||
out.attribute(null, "unmetered", Boolean.toString(true));
|
||||
}
|
||||
if (taskStatus.hasConnectivityConstraint()) {
|
||||
@@ -393,9 +400,11 @@ public class TaskStore {
|
||||
public void run() {
|
||||
try {
|
||||
List<TaskStatus> tasks;
|
||||
FileInputStream fis = mTasksFile.openRead();
|
||||
synchronized (TaskStore.this) {
|
||||
tasks = readTaskMapImpl();
|
||||
tasks = readTaskMapImpl(fis);
|
||||
}
|
||||
fis.close();
|
||||
if (tasks != null) {
|
||||
mCallback.onTaskMapReadFinished(tasks);
|
||||
}
|
||||
@@ -414,8 +423,7 @@ public class TaskStore {
|
||||
}
|
||||
}
|
||||
|
||||
private List<TaskStatus> readTaskMapImpl() throws XmlPullParserException, IOException {
|
||||
FileInputStream fis = mTasksFile.openRead();
|
||||
private List<TaskStatus> readTaskMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(fis, null);
|
||||
|
||||
@@ -537,10 +545,10 @@ public class TaskStore {
|
||||
}
|
||||
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
|
||||
try {
|
||||
if (runtimes.first != TaskStatus.DEFAULT_EARLIEST_RUNTIME) {
|
||||
if (runtimes.first != TaskStatus.NO_EARLIEST_RUNTIME) {
|
||||
taskBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
|
||||
}
|
||||
if (runtimes.second != TaskStatus.DEFAULT_LATEST_RUNTIME) {
|
||||
if (runtimes.second != TaskStatus.NO_LATEST_RUNTIME) {
|
||||
taskBuilder.setOverrideDeadline(
|
||||
runtimes.second - SystemClock.elapsedRealtime());
|
||||
}
|
||||
@@ -632,8 +640,8 @@ public class TaskStore {
|
||||
final long nowWallclock = System.currentTimeMillis();
|
||||
final long nowElapsed = SystemClock.elapsedRealtime();
|
||||
|
||||
long earliestRunTimeElapsed = TaskStatus.DEFAULT_EARLIEST_RUNTIME;
|
||||
long latestRunTimeElapsed = TaskStatus.DEFAULT_LATEST_RUNTIME;
|
||||
long earliestRunTimeElapsed = TaskStatus.NO_EARLIEST_RUNTIME;
|
||||
long latestRunTimeElapsed = TaskStatus.NO_LATEST_RUNTIME;
|
||||
String val = parser.getAttributeValue(null, "deadline");
|
||||
if (val != null) {
|
||||
long latestRuntimeWallclock = Long.valueOf(val);
|
||||
@@ -652,4 +660,4 @@ public class TaskStore {
|
||||
return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import com.android.server.BatteryService;
|
||||
import com.android.server.task.StateChangedListener;
|
||||
import com.android.server.task.TaskManagerService;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -210,4 +211,9 @@ public class BatteryController extends StateController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpControllerState(PrintWriter pw) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,15 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.ConnectivityService;
|
||||
import com.android.server.task.StateChangedListener;
|
||||
import com.android.server.task.TaskManagerService;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -38,25 +40,28 @@ import java.util.List;
|
||||
* We are only interested in metered vs. unmetered networks, and we're interested in them on a
|
||||
* per-user basis.
|
||||
*/
|
||||
public class ConnectivityController extends StateController {
|
||||
private static final String TAG = "TaskManager.Connectivity";
|
||||
public class ConnectivityController extends StateController implements
|
||||
ConnectivityManager.OnNetworkActiveListener {
|
||||
private static final String TAG = "TaskManager.Conn";
|
||||
|
||||
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
|
||||
private final BroadcastReceiver mConnectivityChangedReceiver =
|
||||
new ConnectivityChangedReceiver();
|
||||
/** Singleton. */
|
||||
private static ConnectivityController mSingleton;
|
||||
|
||||
private static Object sCreationLock = new Object();
|
||||
/** Track whether the latest active network is metered. */
|
||||
private boolean mMetered;
|
||||
private boolean mNetworkUnmetered;
|
||||
/** Track whether the latest active network is connected. */
|
||||
private boolean mConnectivity;
|
||||
private boolean mNetworkConnected;
|
||||
|
||||
public static synchronized ConnectivityController get(TaskManagerService taskManager) {
|
||||
if (mSingleton == null) {
|
||||
mSingleton = new ConnectivityController(taskManager, taskManager.getContext());
|
||||
public static ConnectivityController get(TaskManagerService taskManager) {
|
||||
synchronized (sCreationLock) {
|
||||
if (mSingleton == null) {
|
||||
mSingleton = new ConnectivityController(taskManager, taskManager.getContext());
|
||||
}
|
||||
return mSingleton;
|
||||
}
|
||||
return mSingleton;
|
||||
}
|
||||
|
||||
private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
|
||||
@@ -66,39 +71,72 @@ public class ConnectivityController extends StateController {
|
||||
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
mContext.registerReceiverAsUser(
|
||||
mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void maybeStartTrackingTask(TaskStatus taskStatus) {
|
||||
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
|
||||
taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
|
||||
taskStatus.meteredConstraintSatisfied.set(mMetered);
|
||||
mTrackedTasks.add(taskStatus);
|
||||
ConnectivityService cs =
|
||||
(ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cs != null) {
|
||||
if (cs.getActiveNetworkInfo() != null) {
|
||||
mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
|
||||
}
|
||||
mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
|
||||
mTrackedTasks.remove(taskStatus);
|
||||
public void maybeStartTrackingTask(TaskStatus taskStatus) {
|
||||
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasUnmeteredConstraint()) {
|
||||
synchronized (mTrackedTasks) {
|
||||
taskStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
|
||||
taskStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
|
||||
mTrackedTasks.add(taskStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeStopTrackingTask(TaskStatus taskStatus) {
|
||||
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasUnmeteredConstraint()) {
|
||||
synchronized (mTrackedTasks) {
|
||||
mTrackedTasks.remove(taskStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userId Id of the user for whom we are updating the connectivity state.
|
||||
*/
|
||||
private void updateTrackedTasks(int userId) {
|
||||
boolean changed = false;
|
||||
for (TaskStatus ts : mTrackedTasks) {
|
||||
if (ts.getUserId() != userId) {
|
||||
continue;
|
||||
}
|
||||
boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
|
||||
boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
|
||||
if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
|
||||
synchronized (mTrackedTasks) {
|
||||
boolean changed = false;
|
||||
for (TaskStatus ts : mTrackedTasks) {
|
||||
if (ts.getUserId() != userId) {
|
||||
continue;
|
||||
}
|
||||
boolean prevIsConnected =
|
||||
ts.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
|
||||
boolean prevIsMetered = ts.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
|
||||
if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
mStateChangedListener.onControllerStateChanged();
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
mStateChangedListener.onControllerStateChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* We know the network has just come up. We want to run any tasks that are ready.
|
||||
*/
|
||||
public synchronized void onNetworkActive() {
|
||||
synchronized (mTrackedTasks) {
|
||||
for (TaskStatus ts : mTrackedTasks) {
|
||||
if (ts.isReady()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Running " + ts + " due to network activity.");
|
||||
}
|
||||
mStateChangedListener.onRunTaskNow(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +151,10 @@ public class ConnectivityController extends StateController {
|
||||
// TODO: Test whether this will be called twice for each user.
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
|
||||
+ context.getUserId());
|
||||
}
|
||||
final String action = intent.getAction();
|
||||
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
|
||||
final int networkType =
|
||||
@@ -122,14 +164,18 @@ public class ConnectivityController extends StateController {
|
||||
final ConnectivityManager connManager = (ConnectivityManager)
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
|
||||
final int userid = context.getUserId();
|
||||
// This broadcast gets sent a lot, only update if the active network has changed.
|
||||
if (activeNetwork != null && activeNetwork.getType() == networkType) {
|
||||
final int userid = context.getUserId();
|
||||
mMetered = false;
|
||||
mConnectivity =
|
||||
!intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
||||
if (mConnectivity) { // No point making the call if we know there's no conn.
|
||||
mMetered = connManager.isActiveNetworkMetered();
|
||||
if (activeNetwork == null) {
|
||||
mNetworkUnmetered = false;
|
||||
mNetworkConnected = false;
|
||||
updateTrackedTasks(userid);
|
||||
} else if (activeNetwork.getType() == networkType) {
|
||||
mNetworkUnmetered = false;
|
||||
mNetworkConnected = !intent.getBooleanExtra(
|
||||
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
||||
if (mNetworkConnected) { // No point making the call if we know there's no conn.
|
||||
mNetworkUnmetered = !connManager.isActiveNetworkMetered();
|
||||
}
|
||||
updateTrackedTasks(userid);
|
||||
}
|
||||
@@ -140,4 +186,15 @@ public class ConnectivityController extends StateController {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.task.controllers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
@@ -177,4 +178,9 @@ public class IdleController extends StateController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpControllerState(PrintWriter pw) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.content.Context;
|
||||
import com.android.server.task.StateChangedListener;
|
||||
import com.android.server.task.TaskManagerService;
|
||||
|
||||
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
|
||||
@@ -48,4 +50,6 @@ public abstract class StateController {
|
||||
*/
|
||||
public abstract void maybeStopTrackingTask(TaskStatus taskStatus);
|
||||
|
||||
public abstract void dumpControllerState(PrintWriter pw);
|
||||
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* @hide
|
||||
*/
|
||||
public class TaskStatus {
|
||||
public static final long DEFAULT_LATEST_RUNTIME = Long.MAX_VALUE;
|
||||
public static final long DEFAULT_EARLIEST_RUNTIME = 0L;
|
||||
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
|
||||
public static final long NO_EARLIEST_RUNTIME = 0L;
|
||||
|
||||
final Task task;
|
||||
final int uId;
|
||||
@@ -51,7 +51,7 @@ public class TaskStatus {
|
||||
final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
|
||||
final AtomicBoolean deadlineConstraintSatisfied = new AtomicBoolean();
|
||||
final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
|
||||
final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean();
|
||||
final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean();
|
||||
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
@@ -90,9 +90,9 @@ public class TaskStatus {
|
||||
latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
|
||||
} else {
|
||||
earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
|
||||
elapsedNow + task.getMinLatencyMillis() : DEFAULT_EARLIEST_RUNTIME;
|
||||
elapsedNow + task.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
|
||||
latestRunTimeElapsedMillis = task.hasLateConstraint() ?
|
||||
elapsedNow + task.getMaxExecutionDelayMillis() : DEFAULT_LATEST_RUNTIME;
|
||||
elapsedNow + task.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public class TaskStatus {
|
||||
return task.getNetworkCapabilities() == Task.NetworkType.ANY;
|
||||
}
|
||||
|
||||
public boolean hasMeteredConstraint() {
|
||||
public boolean hasUnmeteredConstraint() {
|
||||
return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
|
||||
}
|
||||
|
||||
@@ -161,11 +161,11 @@ public class TaskStatus {
|
||||
}
|
||||
|
||||
public boolean hasTimingDelayConstraint() {
|
||||
return earliestRunTimeElapsedMillis != DEFAULT_EARLIEST_RUNTIME;
|
||||
return earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME;
|
||||
}
|
||||
|
||||
public boolean hasDeadlineConstraint() {
|
||||
return latestRunTimeElapsedMillis != DEFAULT_LATEST_RUNTIME;
|
||||
return latestRunTimeElapsedMillis != NO_LATEST_RUNTIME;
|
||||
}
|
||||
|
||||
public boolean hasIdleConstraint() {
|
||||
@@ -190,12 +190,13 @@ public class TaskStatus {
|
||||
return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
|
||||
&& (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
|
||||
&& (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
|
||||
&& (!hasMeteredConstraint() || meteredConstraintSatisfied.get())
|
||||
&& (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get())
|
||||
&& (!hasIdleConstraint() || idleConstraintSatisfied.get())
|
||||
&& (!hasDeadlineConstraint() || deadlineConstraintSatisfied.get());
|
||||
// Also ready if the deadline has expired - special case.
|
||||
|| (hasDeadlineConstraint() && deadlineConstraintSatisfied.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
/*@Override
|
||||
public int hashCode() {
|
||||
int result = getServiceComponent().hashCode();
|
||||
result = 31 * result + task.getId();
|
||||
@@ -212,12 +213,24 @@ public class TaskStatus {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(hashCode()).substring(0, 3) + ".."
|
||||
+ ":[" + task.getService().getPackageName() + ",tId=" + task.getId()
|
||||
+ ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")"
|
||||
+ ",N=" + task.getNetworkCapabilities() + ",C=" + task.isRequireCharging()
|
||||
+ ",I=" + task.isRequireDeviceIdle() + ",F=" + numFailures
|
||||
+ (isReady() ? "(READY)" : "")
|
||||
+ "]";
|
||||
}
|
||||
// Dumpsys infrastructure
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix); pw.print("Task "); pw.println(task.getId());
|
||||
pw.print(prefix); pw.print("uid="); pw.println(uId);
|
||||
pw.print(prefix); pw.print("component="); pw.println(task.getService());
|
||||
pw.println(this.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,12 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
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 java.io.PrintWriter;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -39,14 +41,16 @@ import java.util.ListIterator;
|
||||
public class TimeController extends StateController {
|
||||
private static final String TAG = "TaskManager.Time";
|
||||
private static final String ACTION_TASK_EXPIRED =
|
||||
"android.content.taskmanager.TASK_EXPIRED";
|
||||
"android.content.taskmanager.TASK_DEADLINE_EXPIRED";
|
||||
private static final String ACTION_TASK_DELAY_EXPIRED =
|
||||
"android.content.taskmanager.TASK_DELAY_EXPIRED";
|
||||
|
||||
/** Set an alarm for the next task expiry. */
|
||||
private final PendingIntent mTaskExpiredAlarmIntent;
|
||||
private final PendingIntent mDeadlineExpiredAlarmIntent;
|
||||
/** Set an alarm for the next task 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 mNextDelayExpiredElapsedMillis;
|
||||
@@ -66,12 +70,14 @@ public class TimeController extends StateController {
|
||||
|
||||
private TimeController(StateChangedListener stateChangedListener, Context context) {
|
||||
super(stateChangedListener, context);
|
||||
mTaskExpiredAlarmIntent =
|
||||
mDeadlineExpiredAlarmIntent =
|
||||
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
|
||||
new Intent(ACTION_TASK_EXPIRED), 0);
|
||||
mNextDelayExpiredAlarmIntent =
|
||||
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
|
||||
new Intent(ACTION_TASK_DELAY_EXPIRED), 0);
|
||||
mNextTaskExpiredElapsedMillis = Long.MAX_VALUE;
|
||||
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
|
||||
|
||||
// Register BR for these intents.
|
||||
IntentFilter intentFilter = new IntentFilter(ACTION_TASK_EXPIRED);
|
||||
@@ -85,63 +91,36 @@ public class TimeController extends StateController {
|
||||
*/
|
||||
@Override
|
||||
public synchronized void maybeStartTrackingTask(TaskStatus task) {
|
||||
if (task.hasTimingDelayConstraint()) {
|
||||
if (task.hasTimingDelayConstraint() || task.hasDeadlineConstraint()) {
|
||||
maybeStopTrackingTask(task);
|
||||
ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
|
||||
while (it.hasPrevious()) {
|
||||
TaskStatus ts = it.previous();
|
||||
if (ts.equals(task)) {
|
||||
// Update
|
||||
it.remove();
|
||||
it.add(task);
|
||||
break;
|
||||
} else if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
|
||||
if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
|
||||
// Insert
|
||||
it.add(task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed());
|
||||
it.add(task);
|
||||
maybeUpdateAlarms(
|
||||
task.hasTimingDelayConstraint() ? task.getEarliestRunTime() : Long.MAX_VALUE,
|
||||
task.hasDeadlineConstraint() ? task.getLatestRunTimeElapsed() : Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the task passed in is being tracked, figure out if we need to update our alarms, and if
|
||||
* so, update them.
|
||||
* When we stop tracking a task, we only need to update our alarms if the task 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)) {
|
||||
if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
|
||||
handleTaskDelayExpired();
|
||||
}
|
||||
if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) {
|
||||
handleTaskDeadlineExpired();
|
||||
}
|
||||
checkExpiredDelaysAndResetAlarm();
|
||||
checkExpiredDeadlinesAndResetAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
|
||||
* delay will expire.
|
||||
* This alarm <b>will not</b> wake up the phone.
|
||||
*/
|
||||
private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) {
|
||||
ensureAlarmService();
|
||||
mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsedMillis,
|
||||
mNextDelayExpiredAlarmIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
|
||||
* deadline will expire.
|
||||
* This alarm <b>will</b> wake up the phone.
|
||||
*/
|
||||
private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) {
|
||||
ensureAlarmService();
|
||||
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsedMillis,
|
||||
mTaskExpiredAlarmIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -155,17 +134,6 @@ public class TimeController extends StateController {
|
||||
taskStatus.deadlineConstraintSatisfied.get());
|
||||
}
|
||||
|
||||
private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
|
||||
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
|
||||
mNextDelayExpiredElapsedMillis = delayExpiredElapsed;
|
||||
setDelayExpiredAlarm(mNextDelayExpiredElapsedMillis);
|
||||
}
|
||||
if (deadlineExpiredElapsed < mNextTaskExpiredElapsedMillis) {
|
||||
mNextTaskExpiredElapsedMillis = deadlineExpiredElapsed;
|
||||
setDeadlineExpiredAlarm(mNextTaskExpiredElapsedMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAlarmService() {
|
||||
if (mAlarmService == null) {
|
||||
mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
||||
@@ -173,38 +141,41 @@ public class TimeController extends StateController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles alarm that notifies that a task has expired. When this function is called at least
|
||||
* one task must be run.
|
||||
* Checks list of tasks for ones that have an expired deadline, sending them to the TaskManager
|
||||
* if so, removing them from this list, and updating the alarm for the next expiry time.
|
||||
*/
|
||||
private synchronized void handleTaskDeadlineExpired() {
|
||||
private synchronized void checkExpiredDeadlinesAndResetAlarm() {
|
||||
long nextExpiryTime = Long.MAX_VALUE;
|
||||
final long nowElapsedMillis = SystemClock.elapsedRealtime();
|
||||
|
||||
Iterator<TaskStatus> it = mTrackedTasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
TaskStatus ts = it.next();
|
||||
if (!ts.hasDeadlineConstraint()) {
|
||||
continue;
|
||||
}
|
||||
final long taskDeadline = ts.getLatestRunTimeElapsed();
|
||||
|
||||
if (taskDeadline <= nowElapsedMillis) {
|
||||
ts.deadlineConstraintSatisfied.set(true);
|
||||
mStateChangedListener.onTaskDeadlineExpired(ts);
|
||||
mStateChangedListener.onRunTaskNow(ts);
|
||||
it.remove();
|
||||
} else { // Sorted by expiry time, so take the next one and stop.
|
||||
nextExpiryTime = taskDeadline;
|
||||
break;
|
||||
}
|
||||
}
|
||||
maybeUpdateAlarms(Long.MAX_VALUE, nextExpiryTime);
|
||||
setDeadlineExpiredAlarm(nextExpiryTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private synchronized void handleTaskDelayExpired() {
|
||||
private synchronized void checkExpiredDelaysAndResetAlarm() {
|
||||
final long nowElapsedMillis = SystemClock.elapsedRealtime();
|
||||
long nextDelayTime = Long.MAX_VALUE;
|
||||
|
||||
boolean ready = false;
|
||||
Iterator<TaskStatus> it = mTrackedTasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
final TaskStatus ts = it.next();
|
||||
@@ -212,31 +183,107 @@ public class TimeController extends StateController {
|
||||
continue;
|
||||
}
|
||||
final long taskDelayTime = ts.getEarliestRunTime();
|
||||
if (taskDelayTime < nowElapsedMillis) {
|
||||
if (taskDelayTime <= nowElapsedMillis) {
|
||||
ts.timeDelayConstraintSatisfied.set(true);
|
||||
if (canStopTrackingTask(ts)) {
|
||||
it.remove();
|
||||
}
|
||||
if (ts.isReady()) {
|
||||
ready = true;
|
||||
}
|
||||
} else { // Keep going through list to get next delay time.
|
||||
if (nextDelayTime > taskDelayTime) {
|
||||
nextDelayTime = taskDelayTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
mStateChangedListener.onControllerStateChanged();
|
||||
maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
|
||||
if (ready) {
|
||||
mStateChangedListener.onControllerStateChanged();
|
||||
}
|
||||
setDelayExpiredAlarm(nextDelayTime);
|
||||
}
|
||||
|
||||
private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
|
||||
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
|
||||
setDelayExpiredAlarm(delayExpiredElapsed);
|
||||
}
|
||||
if (deadlineExpiredElapsed < mNextTaskExpiredElapsedMillis) {
|
||||
setDeadlineExpiredAlarm(deadlineExpiredElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
|
||||
* delay will expire.
|
||||
* This alarm <b>will not</b> wake up the phone.
|
||||
*/
|
||||
private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) {
|
||||
final long earliestWakeupTimeElapsed =
|
||||
SystemClock.elapsedRealtime() + MIN_WAKEUP_INTERVAL_MILLIS;
|
||||
if (alarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
|
||||
alarmTimeElapsedMillis = earliestWakeupTimeElapsed;
|
||||
}
|
||||
mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
|
||||
updateAlarmWithPendingIntent(mNextDelayExpiredAlarmIntent, mNextDelayExpiredElapsedMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
|
||||
* deadline will expire.
|
||||
* This alarm <b>will</b> wake up the phone.
|
||||
*/
|
||||
private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) {
|
||||
final long earliestWakeupTimeElapsed =
|
||||
SystemClock.elapsedRealtime() + MIN_WAKEUP_INTERVAL_MILLIS;
|
||||
if (alarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
|
||||
alarmTimeElapsedMillis = earliestWakeupTimeElapsed;
|
||||
}
|
||||
mNextTaskExpiredElapsedMillis = alarmTimeElapsedMillis;
|
||||
updateAlarmWithPendingIntent(mDeadlineExpiredAlarmIntent, mNextTaskExpiredElapsedMillis);
|
||||
}
|
||||
|
||||
private void updateAlarmWithPendingIntent(PendingIntent pi, long alarmTimeElapsed) {
|
||||
ensureAlarmService();
|
||||
if (alarmTimeElapsed == Long.MAX_VALUE) {
|
||||
mAlarmService.cancel(pi);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Setting " + pi.getIntent().getAction() + " for: " + alarmTimeElapsed);
|
||||
}
|
||||
mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsed, pi);
|
||||
}
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
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
|
||||
// notify our StateChangedListener.
|
||||
if (ACTION_TASK_EXPIRED.equals(intent.getAction())) {
|
||||
handleTaskDeadlineExpired();
|
||||
checkExpiredDeadlinesAndResetAlarm();
|
||||
} else if (ACTION_TASK_DELAY_EXPIRED.equals(intent.getAction())) {
|
||||
handleTaskDelayExpired();
|
||||
checkExpiredDelaysAndResetAlarm();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpControllerState(PrintWriter pw) {
|
||||
final long nowElapsed = SystemClock.elapsedRealtime();
|
||||
pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")");
|
||||
pw.println(
|
||||
"Next delay alarm in " + (mNextDelayExpiredElapsedMillis - nowElapsed)/1000 + "s");
|
||||
pw.println("Next deadline alarm in " + (mNextTaskExpiredElapsedMillis - nowElapsed)/1000
|
||||
+ "s");
|
||||
pw.println("Tracking:");
|
||||
for (TaskStatus ts : mTrackedTasks) {
|
||||
pw.println(String.valueOf(ts.hashCode()).substring(0, 3) + ".."
|
||||
+ ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A")
|
||||
+ ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A")
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class BatteryControllerTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskDeadlineExpired(TaskStatus taskStatus) {
|
||||
public void onRunTaskNow(TaskStatus taskStatus) {
|
||||
|
||||
}
|
||||
};
|
||||
@@ -63,4 +63,4 @@ public class BatteryControllerTest extends AndroidTestCase {
|
||||
assertTrue(mTrackerUnderTest.isOnStablePower());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user