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
This commit is contained in:
@@ -34,14 +34,17 @@ interface ITaskCallback {
|
||||
* Immediate callback to the system after sending a start signal, used to quickly detect ANR.
|
||||
*
|
||||
* @param taskId Unique integer used to identify this task.
|
||||
* @param ongoing True to indicate that the client is processing the task. False if the task is
|
||||
* complete
|
||||
*/
|
||||
void acknowledgeStartMessage(int taskId);
|
||||
void acknowledgeStartMessage(int taskId, boolean ongoing);
|
||||
/**
|
||||
* Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
|
||||
*
|
||||
* @param taskId Unique integer used to identify this task.
|
||||
* @param rescheulde Whether or not to reschedule this task.
|
||||
*/
|
||||
void acknowledgeStopMessage(int taskId);
|
||||
void acknowledgeStopMessage(int taskId, boolean reschedule);
|
||||
/*
|
||||
* Tell the task manager that the client is done with its execution, so that it can go on to
|
||||
* the next one and stop attributing wakelock time to us etc.
|
||||
|
||||
@@ -18,7 +18,6 @@ package android.app.task;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
@@ -124,22 +123,20 @@ public abstract class TaskService extends Service {
|
||||
switch (msg.what) {
|
||||
case MSG_EXECUTE_TASK:
|
||||
try {
|
||||
TaskService.this.onStartTask(params);
|
||||
boolean workOngoing = TaskService.this.onStartTask(params);
|
||||
ackStartMessage(params, workOngoing);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error while executing task: " + params.getTaskId());
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
maybeAckMessageReceived(params, MSG_EXECUTE_TASK);
|
||||
}
|
||||
break;
|
||||
case MSG_STOP_TASK:
|
||||
try {
|
||||
TaskService.this.onStopTask(params);
|
||||
boolean ret = TaskService.this.onStopTask(params);
|
||||
ackStopMessage(params, ret);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Application unable to handle onStopTask.", e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
maybeAckMessageReceived(params, MSG_STOP_TASK);
|
||||
}
|
||||
break;
|
||||
case MSG_TASK_FINISHED:
|
||||
@@ -162,30 +159,34 @@ public abstract class TaskService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages come in on the application's main thread, so rather than run the risk of
|
||||
* waiting for an app that may be doing something foolhardy, we ack to the system after
|
||||
* processing a message. This allows us to throw up an ANR dialogue as quickly as possible.
|
||||
* @param params id of the task we're acking.
|
||||
* @param state Information about what message we're acking.
|
||||
*/
|
||||
private void maybeAckMessageReceived(TaskParams params, int state) {
|
||||
private void ackStartMessage(TaskParams params, boolean workOngoing) {
|
||||
final ITaskCallback callback = params.getCallback();
|
||||
final int taskId = params.getTaskId();
|
||||
if (callback != null) {
|
||||
try {
|
||||
if (state == MSG_EXECUTE_TASK) {
|
||||
callback.acknowledgeStartMessage(taskId);
|
||||
} else if (state == MSG_STOP_TASK) {
|
||||
callback.acknowledgeStopMessage(taskId);
|
||||
}
|
||||
callback.acknowledgeStartMessage(taskId, workOngoing);
|
||||
} catch(RemoteException e) {
|
||||
Log.e(TAG, "System unreachable for starting task.");
|
||||
}
|
||||
} else {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, state + ": Attempting to ack a task that has already been" +
|
||||
"processed.");
|
||||
Log.d(TAG, "Attempting to ack a task that has already been processed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ackStopMessage(TaskParams params, boolean reschedule) {
|
||||
final ITaskCallback callback = params.getCallback();
|
||||
final int taskId = params.getTaskId();
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.acknowledgeStopMessage(taskId, reschedule);
|
||||
} catch(RemoteException e) {
|
||||
Log.e(TAG, "System unreachable for stopping task.");
|
||||
}
|
||||
} else {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Attempting to ack a task that has already been processed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,12 +204,14 @@ public abstract class TaskService extends Service {
|
||||
*
|
||||
* @param params Parameters specifying info about this task, including the extras bundle you
|
||||
* optionally provided at task-creation time.
|
||||
* @return True if your service needs to process the work (on a separate thread). False if
|
||||
* there's no more work to be done for this task.
|
||||
*/
|
||||
public abstract void onStartTask(TaskParams params);
|
||||
public abstract boolean onStartTask(TaskParams params);
|
||||
|
||||
/**
|
||||
* This method is called if your task should be stopped even before you've called
|
||||
* {@link #taskFinished(TaskParams, boolean)}.
|
||||
* This method is called if the system has determined that you must stop execution of your task
|
||||
* even before you've had a chance to call {@link #taskFinished(TaskParams, boolean)}.
|
||||
*
|
||||
* <p>This will happen if the requirements specified at schedule time are no longer met. For
|
||||
* example you may have requested WiFi with
|
||||
@@ -217,33 +220,27 @@ public abstract class TaskService extends Service {
|
||||
* {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
|
||||
* idle maintenance window. You are solely responsible for the behaviour of your application
|
||||
* upon receipt of this message; your app will likely start to misbehave if you ignore it. One
|
||||
* repercussion is that the system will cease to hold a wakelock for you.</p>
|
||||
*
|
||||
* <p>After you've done your clean-up you are still expected to call
|
||||
* {@link #taskFinished(TaskParams, boolean)} this will inform the TaskManager that all is well, and
|
||||
* allow you to reschedule your task as it is probably uncompleted. Until you call
|
||||
* taskFinished() you will not receive any newly scheduled tasks with the given task id as the
|
||||
* TaskManager will consider the task to be in an error state.</p>
|
||||
* immediate repercussion is that the system will cease holding a wakelock for you.</p>
|
||||
*
|
||||
* @param params Parameters specifying info about this task.
|
||||
* @return True to indicate to the TaskManager whether you'd like to reschedule this task based
|
||||
* on the criteria provided at task creation-time. False to drop the task. Regardless of the
|
||||
* value returned, your task must stop executing.
|
||||
* on the retry criteria provided at task creation-time. False to drop the task. Regardless of
|
||||
* the value returned, your task must stop executing.
|
||||
*/
|
||||
public abstract boolean onStopTask(TaskParams params);
|
||||
|
||||
/**
|
||||
* Callback to inform the TaskManager you have completed execution. This can be called from any
|
||||
* Callback to inform the TaskManager you've finished executing. This can be called from any
|
||||
* thread, as it will ultimately be run on your application's main thread. When the system
|
||||
* receives this message it will release the wakelock being held.
|
||||
* <p>
|
||||
* You can specify post-execution behaviour to the scheduler here with <code>needsReschedule
|
||||
* </code>. This will apply a back-off timer to your task based on the default, or what was
|
||||
* set with {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The
|
||||
* original requirements are always honoured even for a backed-off task.
|
||||
* Note that a task running in idle mode will not be backed-off. Instead what will happen
|
||||
* is the task will be re-added to the queue and re-executed within a future idle
|
||||
* maintenance window.
|
||||
* You can specify post-execution behaviour to the scheduler here with
|
||||
* <code>needsReschedule </code>. This will apply a back-off timer to your task based on
|
||||
* the default, or what was set with
|
||||
* {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The original
|
||||
* requirements are always honoured even for a backed-off task. Note that a task running in
|
||||
* idle mode will not be backed-off. Instead what will happen is the task will be re-added
|
||||
* to the queue and re-executed within a future idle maintenance window.
|
||||
* </p>
|
||||
*
|
||||
* @param params Parameters specifying system-provided info about this task, this was given to
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -66,22 +69,43 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
break;
|
||||
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,20 +128,19 @@ 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()) {
|
||||
|
||||
} else {
|
||||
if (mPendingTaskServices.get(taskStatus.getServiceToken()) != null) {
|
||||
// The task is either pending or being executed, which we have to cancel.
|
||||
}
|
||||
}
|
||||
postCheckTasksMessage();
|
||||
|
||||
}
|
||||
|
||||
@@ -125,4 +148,60 @@ public class TaskManagerService extends com.android.server.SystemService
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
public void stopTask() {
|
||||
|
||||
}
|
||||
|
||||
public void startTask(Task task) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void taskFinished(int taskId, boolean reschedule) {
|
||||
|
||||
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acknowledgeStopMessage(int taskId) {
|
||||
|
||||
public void acknowledgeStopMessage(int taskId, boolean reschedule) {
|
||||
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acknowledgeStartMessage(int taskId) {
|
||||
public void acknowledgeStartMessage(int taskId, boolean ongoing) {
|
||||
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,12 +77,27 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user