summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorMatthew Williams <mjwilliams@google.com>2014-05-12 15:33:09 -0700
committerMatthew Williams <mjwilliams@google.com>2014-05-16 10:33:48 -0700
commit691e93e82cf97338a760c5fbf900ef29ed9224c2 (patch)
tree44c31eb7e10aeca075c5b4a6f4969299cf95d5bf /services
parent8974f2490992b2921b636c0b65ebeb5b19dda89b (diff)
downloadframeworks_base-691e93e82cf97338a760c5fbf900ef29ed9224c2.zip
frameworks_base-691e93e82cf97338a760c5fbf900ef29ed9224c2.tar.gz
frameworks_base-691e93e82cf97338a760c5fbf900ef29ed9224c2.tar.bz2
TM TaskServiceContext implementation
Each task is run on the client from within a TaskServiceContext. TSC tracks the state of execution of each task on the client. Change-Id: I93c306a83c1115559f4e9675d9997dceae3f186a
Diffstat (limited to 'services')
-rw-r--r--services/core/java/com/android/server/task/TaskCompletedListener.java38
-rw-r--r--services/core/java/com/android/server/task/TaskManagerService.java131
-rw-r--r--services/core/java/com/android/server/task/TaskServiceContext.java459
-rw-r--r--services/core/java/com/android/server/task/TaskStore.java (renamed from services/core/java/com/android/server/task/TaskList.java)59
-rw-r--r--services/core/java/com/android/server/task/controllers/ConnectivityController.java33
-rw-r--r--services/core/java/com/android/server/task/controllers/TaskStatus.java34
6 files changed, 674 insertions, 80 deletions
diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java
new file mode 100644
index 0000000..0210442
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskCompletedListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Used for communication between {@link com.android.server.task.TaskServiceContext} and the
+ * {@link com.android.server.task.TaskManagerService}.
+ */
+public interface TaskCompletedListener {
+
+ /**
+ * Callback for when a task is completed.
+ * @param needsReschedule Whether the implementing class should reschedule this task.
+ */
+ public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);
+
+ /**
+ * Callback for when the implementing class needs to clean up the
+ * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
+ * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
+ * and it needs to clean up).
+ */
+ public void onAllTasksCompleted(int serviceToken);
+}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index 5df4b2a..1b3a927 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -17,16 +17,15 @@
package com.android.server.task;
import android.content.Context;
+import android.content.Task;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.Log;
import android.util.SparseArray;
import com.android.server.task.controllers.TaskStatus;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* 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
@@ -34,25 +33,29 @@ import java.util.List;
* @hide
*/
public class TaskManagerService extends com.android.server.SystemService
- implements StateChangedListener {
+ implements StateChangedListener, TaskCompletedListener {
+ static final String TAG = "TaskManager";
/** Master list of tasks. */
- private final TaskList mTaskList;
+ private final TaskStore mTasks;
+
+ /** Check the pending queue and start any tasks. */
+ static final int MSG_RUN_PENDING = 0;
+ /** Initiate the stop task flow. */
+ static final int MSG_STOP_TASK = 1;
+ /** */
+ static final int MSG_CHECK_TASKS = 2;
/**
* Track Services that have currently active or pending tasks. The index is provided by
* {@link TaskStatus#getServiceToken()}
*/
- private final SparseArray<TaskServiceContext> mPendingTaskServices =
+ private final SparseArray<TaskServiceContext> mActiveServices =
new SparseArray<TaskServiceContext>();
private final TaskHandler mHandler;
private class TaskHandler extends Handler {
- /** Check the pending queue and start any tasks. */
- static final int MSG_RUN_PENDING = 0;
- /** Initiate the stop task flow. */
- static final int MSG_STOP_TASK = 1;
public TaskHandler(Looper looper) {
super(looper);
@@ -67,21 +70,42 @@ public class TaskManagerService extends com.android.server.SystemService
case MSG_STOP_TASK:
break;
+ case MSG_CHECK_TASKS:
+ checkTasks();
+ break;
}
}
/**
- * Helper to post a message to this handler that will run through the pending queue and
- * start any tasks it can.
+ * Called when we need to run through the list of all tasks and start/stop executing one or
+ * more of them.
*/
- void sendRunPendingTasksMessage() {
- Message m = Message.obtain(this, MSG_RUN_PENDING);
- m.sendToTarget();
+ private void checkTasks() {
+ synchronized (mTasks) {
+ final SparseArray<TaskStatus> tasks = mTasks.getTasks();
+ for (int i = 0; i < tasks.size(); i++) {
+ TaskStatus ts = tasks.valueAt(i);
+ if (ts.isReady() && ! isCurrentlyActive(ts)) {
+ assignTaskToServiceContext(ts);
+ }
+ }
+ }
}
+ }
- void sendOnStopMessage(TaskStatus taskStatus) {
-
- }
+ /**
+ * Entry point from client to schedule the provided task.
+ * This will add the task to the
+ * @param task Task object containing execution parameters
+ * @param userId The id of the user this task is for.
+ * @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.
+ * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ */
+ public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
+ TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
+ return 0;
}
/**
@@ -95,7 +119,7 @@ public class TaskManagerService extends com.android.server.SystemService
*/
public TaskManagerService(Context context) {
super(context);
- mTaskList = new TaskList();
+ mTasks = new TaskStore(context);
mHandler = new TaskHandler(context.getMainLooper());
}
@@ -104,25 +128,80 @@ public class TaskManagerService extends com.android.server.SystemService
}
+ // StateChangedListener implementations.
+
/**
- * Offboard work to our handler thread as quickly as possible, b/c this call is probably being
+ * 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.
* @param taskStatus The state of the task which has changed.
*/
@Override
public void onTaskStateChanged(TaskStatus taskStatus) {
- if (taskStatus.isReady()) {
+ postCheckTasksMessage();
- } else {
- if (mPendingTaskServices.get(taskStatus.getServiceToken()) != null) {
- // The task is either pending or being executed, which we have to cancel.
- }
+ }
+
+ @Override
+ public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+
+ }
+
+ // 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 serviceToken key for the service context in {@link #mActiveServices}.
+ * @param taskId Id of the task that is complete.
+ * @param needsReschedule Whether the implementing class should reschedule this task.
+ */
+ @Override
+ public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
+ final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
+ if (serviceContext == null) {
+ Log.e(TAG, "Task completed for invalid service context; " + serviceToken);
+ return;
}
}
@Override
- public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+ public void onClientExecutionCompleted(int serviceToken) {
+
+ }
+ private void assignTaskToServiceContext(TaskStatus ts) {
+ TaskServiceContext serviceContext =
+ mActiveServices.get(ts.getServiceToken());
+ if (serviceContext == null) {
+ serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
+ mActiveServices.put(ts.getServiceToken(), serviceContext);
+ }
+ serviceContext.addPendingTask(ts);
+ }
+
+ /**
+ * @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 isCurrentlyActive(TaskStatus ts) {
+ TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
+ if (serviceContext == null) {
+ return false;
+ }
+ return serviceContext.hasTaskPending(ts);
+ }
+
+ /**
+ * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
+ * are eligible.
+ */
+ private void postCheckTasksMessage() {
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
}
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
index 65c6fa5..2d148d5 100644
--- a/services/core/java/com/android/server/task/TaskServiceContext.java
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -16,79 +16,500 @@
package com.android.server.task;
+import android.app.ActivityManager;
import android.app.task.ITaskCallback;
import android.app.task.ITaskService;
+import android.app.task.TaskParams;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.Task;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
import com.android.server.task.controllers.TaskStatus;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* Maintains information required to bind to a {@link android.app.task.TaskService}. This binding
- * can then be reused to start concurrent tasks on the TaskService. Information here is unique
- * within this service.
+ * is reused to start concurrent tasks on the TaskService. Information here is unique
+ * to the service.
* Functionality provided by this class:
* - Managages wakelock for the service.
* - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks.
* -
*/
public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
+ private static final String TAG = "TaskServiceContext";
+ /** Define the maximum # of tasks allowed to run on a service at once. */
+ 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;
+ /** 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. */
+ private static final String TM_WAKELOCK_PREFIX = "*task*/";
+
+ private static final String[] VERB_STRINGS = {
+ "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
+ };
+
+ // States that a task occupies while interacting with the client.
+ private static final int VERB_STARTING = 0;
+ private static final int VERB_EXECUTING = 1;
+ private static final int VERB_STOPPING = 2;
+ private static final int VERB_PENDING = 3;
+
+ // Messages that result from interactions with the client service.
+ /** System timed out waiting for a response. */
+ 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.*/
+ private static final int MSG_CHECK_PENDING = 2;
+ /** Cancel an active task. */
+ private static final int MSG_CANCEL = 3;
+ /** Add a pending task. */
+ private static final int MSG_ADD_PENDING = 4;
+ /** Client crashed, so we need to wind things down. */
+ private static final int MSG_SHUTDOWN = 5;
+ /** Used to identify this task service context when communicating with the TaskManager. */
+ final int token;
final ComponentName component;
- int uid;
+ final int userId;
ITaskService service;
+ private final Handler mCallbackHandler;
+ /** Tasks that haven't been sent to the client for execution yet. */
+ private final SparseArray<ActiveTask> mPending;
+ /** Used for service binding, etc. */
+ private final Context mContext;
+ /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
+ final private TaskCompletedListener mCompletedListener;
+ private final PowerManager.WakeLock mWakeLock;
/** Whether this service is actively bound. */
boolean mBound;
- TaskServiceContext(Task task) {
- this.component = task.getService();
+ TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) {
+ mContext = taskManager.getContext();
+ this.component = taskStatus.getServiceComponent();
+ this.token = taskStatus.getServiceToken();
+ this.userId = taskStatus.getUserId();
+ mCallbackHandler = new TaskServiceHandler(looper);
+ mPending = new SparseArray<ActiveTask>();
+ mCompletedListener = taskManager;
+ final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TM_WAKELOCK_PREFIX + component.getPackageName());
+ mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid()));
+ mWakeLock.setReferenceCounted(false);
}
- public void stopTask() {
-
+ @Override
+ public void taskFinished(int taskId, boolean reschedule) {
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
}
- public void startTask(Task task) {
-
+ @Override
+ public void acknowledgeStopMessage(int taskId, boolean reschedule) {
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
}
@Override
- public void taskFinished(int taskId, boolean reschedule) {
-
+ public void acknowledgeStartMessage(int taskId, boolean ongoing) {
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
}
- @Override
- public void acknowledgeStopMessage(int taskId) {
+ /**
+ * Queue up this task to run on the client. This will execute the task as quickly as possible.
+ * @param ts Status of the task to run.
+ */
+ public void addPendingTask(TaskStatus ts) {
+ final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
+ final ActiveTask newTask = new ActiveTask(params, VERB_PENDING);
+ mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget();
+ if (!mBound) {
+ Intent intent = new Intent().setComponent(component);
+ boolean binding = mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+ new UserHandle(userId));
+ if (!binding) {
+ Log.e(TAG, component.getShortClassName() + " unavailable.");
+ cancelPendingTask(ts);
+ }
+ }
+ }
+ /**
+ * Called externally when a task that was scheduled for execution should be cancelled.
+ * @param ts The status of the task to cancel.
+ */
+ public void cancelPendingTask(TaskStatus ts) {
+ mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */)
+ .sendToTarget();
}
- @Override
- public void acknowledgeStartMessage(int taskId) {
+ /**
+ * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask}
+ * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given
+ * ActiveTask.
+ * @param op Operation that is taking place.
+ */
+ private void scheduleOpTimeOut(ActiveTask op) {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT, op);
+ final long timeoutMillis = (op.verb == VERB_EXECUTING) ?
+ EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " +
+ op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
+ }
+ Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op);
+ mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
/**
* @return true if this task is pending or active within this context.
*/
public boolean hasTaskPending(TaskStatus taskStatus) {
- return true;
+ synchronized (mPending) {
+ return mPending.get(taskStatus.getTaskId()) != null;
+ }
}
public boolean isBound() {
return mBound;
}
+ /**
+ * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work
+ * we intend to send to the client - we stop sending work when the service is unbound so until
+ * then we keep the wakelock.
+ * @param name The concrete component name of the service that has
+ * been connected.
+ * @param service The IBinder of the Service's communication channel,
+ */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
-
mBound = true;
+ this.service = ITaskService.Stub.asInterface(service);
+ // Remove all timeouts. We've just connected to the client so there are no other
+ // MSG_TIMEOUTs at this point.
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+ mWakeLock.acquire();
+ mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget();
}
+ /**
+ * When the client service crashes we can have a couple tasks executing, in various stages of
+ * undress. We'll cancel all of them and request that they be rescheduled.
+ * @param name The concrete component name of the service whose
+ */
@Override
public void onServiceDisconnected(ComponentName name) {
- mBound = false;
+ // Service disconnected... probably client crashed.
+ startShutdown();
+ }
+
+ /**
+ * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more
+ * tasks, then we do the shutdown.
+ */
+ private void startShutdown() {
+ mCompletedListener.onClientExecutionCompleted(token);
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget();
+ }
+
+ /** Tracks a task across its various state changes. */
+ private static class ActiveTask {
+ final TaskParams params;
+ int verb;
+ AtomicBoolean cancelled = new AtomicBoolean();
+
+ ActiveTask(TaskParams params, int verb) {
+ this.params = params;
+ this.verb = verb;
+ }
+
+ @Override
+ public String toString() {
+ return params.getTaskId() + " " + VERB_STRINGS[verb];
+ }
+ }
+
+ /**
+ * Handles the lifecycle of the TaskService 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) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ADD_PENDING:
+ if (message.obj != null) {
+ ActiveTask pendingTask = (ActiveTask) message.obj;
+ mPending.put(pendingTask.params.getTaskId(), pendingTask);
+ }
+ // fall through.
+ case MSG_CHECK_PENDING:
+ checkPendingTasksH();
+ break;
+ case MSG_CALLBACK:
+ ActiveTask receivedCallback = mPending.get(message.arg1);
+ removeMessages(MSG_TIMEOUT, receivedCallback);
+
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback);
+ }
+
+ if (receivedCallback.verb == VERB_STARTING) {
+ final boolean workOngoing = message.arg2 == 1;
+ handleStartedH(receivedCallback, workOngoing);
+ } else if (receivedCallback.verb == VERB_EXECUTING ||
+ receivedCallback.verb == VERB_STOPPING) {
+ final boolean reschedule = message.arg2 == 1;
+ handleFinishedH(receivedCallback, reschedule);
+ } else {
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unrecognised callback: " + receivedCallback);
+ }
+ }
+ break;
+ case MSG_CANCEL:
+ ActiveTask cancelled = mPending.get(message.arg1);
+ handleCancelH(cancelled);
+ break;
+ case MSG_TIMEOUT:
+ // Timeout msgs have the ActiveTask ref so we can remove them easily.
+ handleOpTimeoutH((ActiveTask) message.obj);
+ break;
+ case MSG_SHUTDOWN:
+ handleShutdownH();
+ break;
+ default:
+ Log.e(TAG, "Unrecognised message: " + message);
+ }
+ }
+
+ /**
+ * State behaviours.
+ * VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout.
+ * _PENDING -> Error
+ * _EXECUTING -> Error
+ * _STOPPING -> Error
+ */
+ private void handleStartedH(ActiveTask started, boolean workOngoing) {
+ switch (started.verb) {
+ case VERB_STARTING:
+ started.verb = VERB_EXECUTING;
+ if (!workOngoing) {
+ // Task is finished already so fast-forward to handleFinished.
+ handleFinishedH(started, false);
+ return;
+ } else if (started.cancelled.get()) {
+ // Cancelled *while* waiting for acknowledgeStartMessage from client.
+ handleCancelH(started);
+ return;
+ } else {
+ scheduleOpTimeOut(started);
+ }
+ break;
+ default:
+ Log.e(TAG, "Handling started task but task wasn't starting! " + started);
+ return;
+ }
+ }
+
+ /**
+ * VERB_EXECUTING -> Client called taskFinished(), clean up and notify done.
+ * _STOPPING -> Successful finish, clean up and notify done.
+ * _STARTING -> Error
+ * _PENDING -> Error
+ */
+ private void handleFinishedH(ActiveTask executedTask, boolean reschedule) {
+ switch (executedTask.verb) {
+ case VERB_EXECUTING:
+ case VERB_STOPPING:
+ closeAndCleanupTaskH(executedTask, reschedule);
+ break;
+ default:
+ Log.e(TAG, "Got an execution complete message for a task that wasn't being" +
+ "executed. " + executedTask);
+ }
+ }
+
+ /**
+ * A task can be in various states when a cancel request comes in:
+ * VERB_PENDING -> Remove from queue.
+ * _STARTING -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}.
+ * _EXECUTING -> call {@link #sendStopMessageH}}.
+ * _ENDING -> No point in doing anything here, so we ignore.
+ */
+ private void handleCancelH(ActiveTask cancelledTask) {
+ switch (cancelledTask.verb) {
+ case VERB_PENDING:
+ mPending.remove(cancelledTask.params.getTaskId());
+ break;
+ case VERB_STARTING:
+ cancelledTask.cancelled.set(true);
+ break;
+ case VERB_EXECUTING:
+ cancelledTask.verb = VERB_STOPPING;
+ sendStopMessageH(cancelledTask);
+ break;
+ case VERB_STOPPING:
+ // Nada.
+ break;
+ default:
+ Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask);
+ break;
+ }
+ }
+
+ /**
+ * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue
+ * and reschedule them as if they had failed.
+ * Before posting this message, caller must invoke
+ * {@link com.android.server.task.TaskCompletedListener#onClientExecutionCompleted(int)}
+ */
+ private void handleShutdownH() {
+ for (int i = 0; i < mPending.size(); i++) {
+ ActiveTask at = mPending.valueAt(i);
+ closeAndCleanupTaskH(at, true /* needsReschedule */);
+ }
+ mWakeLock.release();
+ mContext.unbindService(TaskServiceContext.this);
+ service = null;
+ mBound = false;
+ }
+
+ /**
+ * MSG_TIMEOUT gets processed here.
+ * @param timedOutTask The task that timed out.
+ */
+ private void handleOpTimeoutH(ActiveTask timedOutTask) {
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : "
+ + timedOutTask.params.getTaskId());
+ }
+
+ final int taskId = timedOutTask.params.getTaskId();
+ switch (timedOutTask.verb) {
+ 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
+ // FINISHED/NO-RETRY.
+ Log.e(TAG, "No response from client for onStartTask '" +
+ component.getShortClassName() + "' tId: " + taskId);
+ closeAndCleanupTaskH(timedOutTask, 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, '" +
+ component.getShortClassName() + "' tId: " + taskId);
+ closeAndCleanupTaskH(timedOutTask, 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)." +
+ " Reporting failure and asking for reschedule. " +
+ component.getShortClassName() + "' tId: " + taskId);
+ sendStopMessageH(timedOutTask);
+ break;
+ default:
+ Log.e(TAG, "Handling timeout for an unknown active task state: "
+ + timedOutTask);
+ return;
+ }
+ }
+
+ /**
+ * Called on the handler thread. Checks the state of the pending queue and starts the task
+ * if it can. The task only starts if there is capacity on the service.
+ */
+ private void checkPendingTasksH() {
+ if (!mBound) {
+ return;
+ }
+ for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) {
+ ActiveTask at = mPending.valueAt(i);
+ if (at.verb != VERB_PENDING) {
+ continue;
+ }
+ sendStartMessageH(at);
+ }
+ }
+
+ /**
+ * Already running, need to stop. Rund on handler.
+ * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from
+ * VERB_EXECUTING -> VERB_STOPPING.
+ */
+ private void sendStopMessageH(ActiveTask stoppingTask) {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask);
+ if (stoppingTask.verb != VERB_EXECUTING) {
+ Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask);
+ // TODO: Handle error?
+ return;
+ }
+ try {
+ service.stopTask(stoppingTask.params);
+ stoppingTask.verb = VERB_STOPPING;
+ scheduleOpTimeOut(stoppingTask);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStopTask to client.", e);
+ closeAndCleanupTaskH(stoppingTask, false);
+ }
+ }
+
+ /** Start the task on the service. */
+ private void sendStartMessageH(ActiveTask pendingTask) {
+ if (pendingTask.verb != VERB_PENDING) {
+ Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask);
+ // TODO: Handle error?
+ }
+ try {
+ service.startTask(pendingTask.params);
+ pendingTask.verb = VERB_STARTING;
+ scheduleOpTimeOut(pendingTask);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName()
+ + "' ", e);
+ }
+ }
+
+ /**
+ * The provided task has finished, either by calling
+ * {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, 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(ActiveTask completedTask, boolean reschedule) {
+ removeMessages(MSG_TIMEOUT, completedTask);
+ mPending.remove(completedTask.params.getTaskId());
+ if (mPending.size() == 0) {
+ startShutdown();
+ }
+ mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule);
+ }
}
}
diff --git a/services/core/java/com/android/server/task/TaskList.java b/services/core/java/com/android/server/task/TaskStore.java
index d2b8440..3bfc8a5 100644
--- a/services/core/java/com/android/server/task/TaskList.java
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -16,16 +16,12 @@
package com.android.server.task;
-import android.content.ComponentName;
+import android.content.Context;
import android.content.Task;
+import android.util.SparseArray;
import com.android.server.task.controllers.TaskStatus;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
/**
* Maintain a list of classes, and accessor methods/logic for these tasks.
* This class offers the following functionality:
@@ -36,25 +32,39 @@ import java.util.Map;
* - 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.
+ * - This class is <strong>not</strong> thread-safe.
*/
-public class TaskList {
+public class TaskStore {
- final List<TaskStatus> mTasks;
+ /**
+ * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}.
+ */
+ final SparseArray<TaskStatus> mTasks;
+ final Context mContext;
- TaskList() {
+ TaskStore(Context context) {
mTasks = intialiseTaskMapFromDisk();
+ mContext = context;
}
/**
* Add a task to the master list, persisting it if necessary.
+ * Will first check to see if the task already exists. If so, it will replace it.
+ * {@link android.content.pm.PackageManager} is queried to see if the calling package has
+ * permission to
* @param task Task to add.
- * @param persistable true if the TaskQueue should persist this task to the disk.
- * @return true if this operation was successful. If false, this task was neither added nor
- * persisted.
+ * @return The initialised TaskStatus object if this operation was successful, null if it
+ * failed.
*/
- // TODO: implement this when i decide whether i want to key by TaskStatus
- public boolean add(Task task, boolean persistable) {
- return true;
+ public TaskStatus addNewTaskForUser(Task task, int userId, int uId,
+ boolean canPersistTask) {
+ TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId);
+ if (canPersistTask && task.isPeriodic()) {
+ if (writeStatusToDisk()) {
+ mTasks.put(taskStatus.hashCode(), taskStatus);
+ }
+ }
+ return taskStatus;
}
/**
@@ -68,11 +78,26 @@ public class TaskList {
}
/**
+ * Every time the state changes we write all the tasks in one swathe, instead of trying to
+ * track incremental changes.
+ */
+ private boolean writeStatusToDisk() {
+ return true;
+ }
+
+ /**
*
* @return
*/
// TODO: Implement this.
- private List<TaskStatus> intialiseTaskMapFromDisk() {
- return new ArrayList<TaskStatus>();
+ private SparseArray<TaskStatus> intialiseTaskMapFromDisk() {
+ return new SparseArray<TaskStatus>();
+ }
+
+ /**
+ * @return The live array of TaskStatus objects.
+ */
+ public SparseArray<TaskStatus> getTasks() {
+ return mTasks;
}
}
diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
index 5cca77c..fad41d9 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -41,6 +41,11 @@ public class ConnectivityController extends StateController {
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
+ /** Track whether the latest active network is metered. */
+ private boolean mMetered;
+ /** Track whether the latest active network is connected. */
+ private boolean mConnectivity;
+
public ConnectivityController(TaskManagerService service) {
super(service);
// Register connectivity changed BR.
@@ -51,31 +56,30 @@ public class ConnectivityController extends StateController {
}
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
+ taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
+ taskStatus.meteredConstraintSatisfied.set(mMetered);
mTrackedTasks.add(taskStatus);
}
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public void maybeStopTrackingTask(TaskStatus taskStatus) {
mTrackedTasks.remove(taskStatus);
}
/**
- * @param isConnected Whether the active network is connected for the given uid
- * @param isMetered Whether the active network is metered for the given uid. This is
- * necessarily false if <code>isConnected</code> is false.
* @param userId Id of the user for whom we are updating the connectivity state.
*/
- private void updateTrackedTasks(boolean isConnected, boolean isMetered, int userId) {
+ private void updateTrackedTasks(int userId) {
for (TaskStatus ts : mTrackedTasks) {
if (ts.userId != userId) {
continue;
}
- boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(isConnected);
- boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(isMetered);
- if (prevIsConnected != isConnected || prevIsMetered != isMetered) {
+ boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
+ boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
+ if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
mStateChangedListener.onTaskStateChanged(ts);
}
}
@@ -83,12 +87,13 @@ public class ConnectivityController extends StateController {
class ConnectivityChangedReceiver extends BroadcastReceiver {
/**
- * We'll receive connectivity changes for each user here, which we'll process independently.
+ * We'll receive connectivity changes for each user here, which we process independently.
* We are only interested in the active network here. We're only interested in the active
* network, b/c the end result of this will be for apps to try to hit the network.
* @param context The Context in which the receiver is running.
* @param intent The Intent being received.
*/
+ // TODO: Test whether this will be called twice for each user.
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -103,13 +108,13 @@ public class ConnectivityController extends StateController {
// This broadcast gets sent a lot, only update if the active network has changed.
if (activeNetwork.getType() == networkType) {
final int userid = context.getUserId();
- boolean isMetered = false;
- boolean isConnected =
+ mMetered = false;
+ mConnectivity =
!intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (isConnected) { // No point making the call if we know there's no conn.
- isMetered = connManager.isActiveNetworkMetered();
+ mMetered = connManager.isActiveNetworkMetered();
}
- updateTrackedTasks(isConnected, isMetered, userid);
+ updateTrackedTasks(userid);
}
} else {
Log.w(TAG, "Unrecognised action in intent: " + action);
diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java
index 230b049..d96fedc 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -18,6 +18,8 @@ package com.android.server.task.controllers;
import android.content.ComponentName;
import android.content.Task;
+import android.content.pm.PackageParser;
+import android.os.Bundle;
import android.os.SystemClock;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -36,7 +38,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class TaskStatus {
final int taskId;
final int userId;
- ComponentName component;
+ final int uId;
+ final ComponentName component;
+ final Bundle extras;
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
@@ -60,15 +64,17 @@ public class TaskStatus {
/** Generate a TaskStatus object for a given task and uid. */
// TODO: reimplement this to reuse these objects instead of creating a new one each time?
- static TaskStatus getForTaskAndUid(Task task, int uId) {
- return new TaskStatus(task, uId);
+ public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) {
+ return new TaskStatus(task, userId, uId);
}
/** Set up the state of a newly scheduled task. */
- TaskStatus(Task task, int userId) {
+ TaskStatus(Task task, int userId, int uId) {
this.taskId = task.getTaskId();
this.userId = userId;
this.component = task.getService();
+ this.extras = task.getExtras();
+ this.uId = uId;
hasChargingConstraint = task.isRequireCharging();
hasIdleConstraint = task.isRequireDeviceIdle();
@@ -94,6 +100,26 @@ public class TaskStatus {
hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
}
+ public int getTaskId() {
+ return taskId;
+ }
+
+ public ComponentName getServiceComponent() {
+ return component;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public int getUid() {
+ return uId;
+ }
+
+ public Bundle getExtras() {
+ return extras;
+ }
+
boolean hasConnectivityConstraint() {
return hasConnectivityConstraint;
}