First implementation of the TaskManager internals.

Sketching out the TaskManagerService, and provide an implementation
of the Connectivity & Time controllers to use as a basis for how the
IdleMode controller will function.

Change-Id: I9163b8b9df1f8a027354720b469a0fb4c8e37194
This commit is contained in:
Matthew Williams
2014-05-01 10:47:00 -07:00
parent cae6873161
commit 6de79e2b17
11 changed files with 932 additions and 33 deletions

View File

@@ -7613,7 +7613,7 @@ package android.content {
method public long getMaxExecutionDelayMillis();
method public long getMinLatencyMillis();
method public int getNetworkCapabilities();
method public java.lang.String getServiceClassName();
method public android.content.ComponentName getService();
method public int getTaskId();
method public boolean isPeriodic();
method public boolean isRequireCharging();
@@ -7628,7 +7628,7 @@ package android.content {
}
public final class Task.Builder {
ctor public Task.Builder(int, java.lang.Class<android.app.task.TaskService>);
ctor public Task.Builder(int, android.content.ComponentName);
method public android.content.Task build();
method public android.content.Task.Builder setBackoffCriteria(long, int);
method public android.content.Task.Builder setExtras(android.os.Bundle);

View File

@@ -29,7 +29,14 @@ public class TaskParams implements Parcelable {
private final int taskId;
private final Bundle extras;
private final IBinder mCallback;
private final IBinder callback;
/** @hide */
public TaskParams(int taskId, Bundle extras, IBinder callback) {
this.taskId = taskId;
this.extras = extras;
this.callback = callback;
}
/**
* @return The unique id of this task, specified at creation time.
@@ -47,17 +54,15 @@ public class TaskParams implements Parcelable {
return extras;
}
/**
* @hide
*/
/** @hide */
public ITaskCallback getCallback() {
return ITaskCallback.Stub.asInterface(mCallback);
return ITaskCallback.Stub.asInterface(callback);
}
private TaskParams(Parcel in) {
taskId = in.readInt();
extras = in.readBundle();
mCallback = in.readStrongBinder();
callback = in.readStrongBinder();
}
@Override
@@ -69,7 +74,7 @@ public class TaskParams implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(taskId);
dest.writeBundle(extras);
dest.writeStrongBinder(mCallback);
dest.writeStrongBinder(callback);
}
public static final Creator<TaskParams> CREATOR = new Creator<TaskParams>() {

View File

@@ -42,6 +42,20 @@ public class Task implements Parcelable {
public final int EXPONENTIAL = 1;
}
private final int taskId;
// TODO: Change this to use PersistableBundle when that lands in master.
private final Bundle extras;
private final ComponentName service;
private final boolean requireCharging;
private final boolean requireDeviceIdle;
private final int networkCapabilities;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
private final long intervalMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
/**
* Unique task id associated with this class. This is assigned to your task by the scheduler.
*/
@@ -59,8 +73,8 @@ public class Task implements Parcelable {
/**
* Name of the service endpoint that will be called back into by the TaskManager.
*/
public String getServiceClassName() {
return serviceClassName;
public ComponentName getService() {
return service;
}
/**
@@ -132,24 +146,10 @@ public class Task implements Parcelable {
return backoffPolicy;
}
private final int taskId;
// TODO: Change this to use PersistableBundle when that lands in master.
private final Bundle extras;
private final String serviceClassName;
private final boolean requireCharging;
private final boolean requireDeviceIdle;
private final int networkCapabilities;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
private final long intervalMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
private Task(Parcel in) {
taskId = in.readInt();
extras = in.readBundle();
serviceClassName = in.readString();
service = ComponentName.readFromParcel(in);
requireCharging = in.readInt() == 1;
requireDeviceIdle = in.readInt() == 1;
networkCapabilities = in.readInt();
@@ -164,7 +164,7 @@ public class Task implements Parcelable {
private Task(Task.Builder b) {
taskId = b.mTaskId;
extras = new Bundle(b.mExtras);
serviceClassName = b.mTaskServiceClassName;
service = b.mTaskService;
requireCharging = b.mRequiresCharging;
requireDeviceIdle = b.mRequiresDeviceIdle;
networkCapabilities = b.mNetworkCapabilities;
@@ -185,7 +185,7 @@ public class Task implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeInt(taskId);
out.writeBundle(extras);
out.writeString(serviceClassName);
ComponentName.writeToParcel(service, out);
out.writeInt(requireCharging ? 1 : 0);
out.writeInt(requireDeviceIdle ? 1 : 0);
out.writeInt(networkCapabilities);
@@ -215,7 +215,7 @@ public class Task implements Parcelable {
public final class Builder {
private int mTaskId;
private Bundle mExtras;
private String mTaskServiceClassName;
private ComponentName mTaskService;
// Requirements.
private boolean mRequiresCharging;
private boolean mRequiresDeviceIdle;
@@ -236,11 +236,11 @@ public class Task implements Parcelable {
* @param taskId Application-provided id for this task. Subsequent calls to cancel, or
* tasks created with the same taskId, will update the pre-existing task with
* the same id.
* @param cls The endpoint that you implement that will receive the callback from the
* @param taskService The endpoint that you implement that will receive the callback from the
* TaskManager.
*/
public Builder(int taskId, Class<TaskService> cls) {
mTaskServiceClassName = cls.getClass().getName();
public Builder(int taskId, ComponentName taskService) {
mTaskService = taskService;
mTaskId = taskId;
}
@@ -296,7 +296,7 @@ public class Task implements Parcelable {
* period. You have no control over when within this interval this task will be executed,
* only the guarantee that it will be executed at most once within this interval.
* A periodic task will be repeated until the phone is turned off, however it will only be
* persisted if the client app has declared the
* persisted beyond boot if the client app has declared the
* {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
* periodic tasks without this permission, they simply will cease to exist after the phone
* restarts.

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.task;
import com.android.server.task.controllers.TaskStatus;
/**
* Interface through which a {@link StateController} informs the
* {@link com.android.server.task.TaskManagerService} that there are some tasks potentially ready
* to be run.
*/
public interface StateChangedListener {
/**
* Called by the controller to notify the TaskManager that it should check on the state of a
* task.
* @param taskStatus The state of the task which has changed.
*/
public void onTaskStateChanged(TaskStatus taskStatus);
/**
* Called by the controller to notify the TaskManager that regardless of the state of the task,
* it must be run immediately.
* @param taskStatus The state of the task which is to be run immediately.
*/
public void onTaskDeadlineExpired(TaskStatus taskStatus);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.task;
import android.content.ComponentName;
import android.content.Task;
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:
* - 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.
*/
public class TaskList {
final List<TaskStatus> mTasks;
TaskList() {
mTasks = intialiseTaskMapFromDisk();
}
/**
* Add a task to the master list, persisting it if necessary.
* @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.
*/
// TODO: implement this when i decide whether i want to key by TaskStatus
public boolean add(Task task, boolean persistable) {
return true;
}
/**
* Remove the provided task. Will also delete the task if it was persisted. Note that this
* function does not return the validity of the operation, as we assume a delete will always
* succeed.
* @param task Task to remove.
*/
public void remove(Task task) {
}
/**
*
* @return
*/
// TODO: Implement this.
private List<TaskStatus> intialiseTaskMapFromDisk() {
return new ArrayList<TaskStatus>();
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.task;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
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
* endpoint.
* @hide
*/
public class TaskManagerService extends com.android.server.SystemService
implements StateChangedListener {
/** Master list of tasks. */
private final TaskList mTaskList;
/**
* Track Services that have currently active or pending tasks. The index is provided by
* {@link TaskStatus#getServiceToken()}
*/
private final SparseArray<TaskServiceContext> mPendingTaskServices =
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);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_RUN_PENDING:
break;
case MSG_STOP_TASK:
break;
}
}
/**
* Helper to post a message to this handler that will run through the pending queue and
* start any tasks it can.
*/
void sendRunPendingTasksMessage() {
Message m = Message.obtain(this, MSG_RUN_PENDING);
m.sendToTarget();
}
void sendOnStopMessage(TaskStatus taskStatus) {
}
}
/**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
* and passes it to super.
* </p>
*
* @param context The system server context.
*/
public TaskManagerService(Context context) {
super(context);
mTaskList = new TaskList();
mHandler = new TaskHandler(context.getMainLooper());
}
@Override
public void onStart() {
}
/**
* Offboard work to our handler thread as quickly as possible, b/c this call is probably being
* made on the main thread.
* @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.
}
}
}
@Override
public void onTaskDeadlineExpired(TaskStatus taskStatus) {
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.task;
import android.app.task.ITaskCallback;
import android.app.task.ITaskService;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.content.Task;
import android.os.IBinder;
import com.android.server.task.controllers.TaskStatus;
/**
* 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.
* 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 {
final ComponentName component;
int uid;
ITaskService service;
/** Whether this service is actively bound. */
boolean mBound;
TaskServiceContext(Task task) {
this.component = task.getService();
}
public void stopTask() {
}
public void startTask(Task task) {
}
@Override
public void taskFinished(int taskId, boolean reschedule) {
}
@Override
public void acknowledgeStopMessage(int taskId) {
}
@Override
public void acknowledgeStartMessage(int taskId) {
}
/**
* @return true if this task is pending or active within this context.
*/
public boolean hasTaskPending(TaskStatus taskStatus) {
return true;
}
public boolean isBound() {
return mBound;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.controllers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.UserHandle;
import android.util.Log;
import com.android.server.task.TaskManagerService;
import java.util.LinkedList;
import java.util.List;
/**
*
*/
public class ConnectivityController extends StateController {
private static final String TAG = "TaskManager.Connectivity";
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
public ConnectivityController(TaskManagerService service) {
super(service);
// Register connectivity changed BR.
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiverAsUser(
mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
}
@Override
public void maybeTrackTaskState(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
mTrackedTasks.add(taskStatus);
}
}
@Override
public void removeTaskStateIfTracked(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) {
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) {
mStateChangedListener.onTaskStateChanged(ts);
}
}
}
class ConnectivityChangedReceiver extends BroadcastReceiver {
/**
* We'll receive connectivity changes for each user here, which we'll 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.
*/
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
final int networkType =
intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
ConnectivityManager.TYPE_NONE);
// Connectivity manager for THIS context - important!
final ConnectivityManager connManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
// 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 =
!intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (isConnected) { // No point making the call if we know there's no conn.
isMetered = connManager.isActiveNetworkMetered();
}
updateTrackedTasks(isConnected, isMetered, userid);
}
} else {
Log.w(TAG, "Unrecognised action in intent: " + action);
}
}
};
}

View File

@@ -0,0 +1,51 @@
/*
* 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.controllers;
import android.content.Context;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
/**
* 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
* are ready to run, or whether they must be stopped.
*/
public abstract class StateController {
protected Context mContext;
protected StateChangedListener mStateChangedListener;
public StateController(TaskManagerService service) {
mStateChangedListener = service;
mContext = service.getContext();
}
/**
* Implement the logic here to decide whether a task should be tracked by this controller.
* This logic is put here so the TaskManger can be completely agnostic of Controller logic.
* Also called when updating a task, so implementing controllers have to be aware of
* preexisting tasks.
*/
public abstract void maybeTrackTaskState(TaskStatus taskStatus);
/**
* Remove task - this will happen if the task is cancelled, completed, etc.
*/
public abstract void removeTaskStateIfTracked(TaskStatus taskStatus);
}

View File

@@ -0,0 +1,154 @@
/*
* 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.controllers;
import android.content.ComponentName;
import android.content.Task;
import android.os.SystemClock;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Uniquely identifies a task internally.
* Created from the public {@link android.content.Task} object when it lands on the scheduler.
* Contains current state of the requirements of the task, as well as a function to evaluate
* whether it's ready to run.
* This object is shared among the various controllers - hence why the different fields are atomic.
* This isn't strictly necessary because each controller is only interested in a specific field,
* and the receivers that are listening for global state change will all run on the main looper,
* but we don't enforce that so this is safer.
* @hide
*/
public class TaskStatus {
final int taskId;
final int userId;
ComponentName component;
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
private final boolean hasChargingConstraint;
private final boolean hasTimingConstraint;
private final boolean hasIdleConstraint;
private final boolean hasMeteredConstraint;
private final boolean hasConnectivityConstraint;
private long earliestRunTimeElapsedMillis;
private long latestRunTimeElapsedMillis;
/** Provide a unique handle to the service that this task will be run on. */
public int getServiceToken() {
return component.hashCode() + userId;
}
/** 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);
}
/** Set up the state of a newly scheduled task. */
TaskStatus(Task task, int userId) {
this.taskId = task.getTaskId();
this.userId = userId;
this.component = task.getService();
hasChargingConstraint = task.isRequireCharging();
hasIdleConstraint = task.isRequireDeviceIdle();
// Timing constraints
if (task.isPeriodic()) {
long elapsedNow = SystemClock.elapsedRealtime();
earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
hasTimingConstraint = true;
} else if (task.getMinLatencyMillis() != 0L || task.getMaxExecutionDelayMillis() != 0L) {
earliestRunTimeElapsedMillis = task.getMinLatencyMillis() > 0L ?
task.getMinLatencyMillis() : Long.MAX_VALUE;
latestRunTimeElapsedMillis = task.getMaxExecutionDelayMillis() > 0L ?
task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
hasTimingConstraint = true;
} else {
hasTimingConstraint = false;
}
// Networking constraints
hasMeteredConstraint = task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
}
boolean hasConnectivityConstraint() {
return hasConnectivityConstraint;
}
boolean hasMeteredConstraint() {
return hasMeteredConstraint;
}
boolean hasChargingConstraint() {
return hasChargingConstraint;
}
boolean hasTimingConstraint() {
return hasTimingConstraint;
}
boolean hasIdleConstraint() {
return hasIdleConstraint;
}
long getEarliestRunTime() {
return earliestRunTimeElapsedMillis;
}
long getLatestRunTime() {
return latestRunTimeElapsedMillis;
}
/**
* @return whether this task is ready to run, based on its requirements.
*/
public synchronized boolean isReady() {
return (!hasChargingConstraint || chargingConstraintSatisfied.get())
&& (!hasTimingConstraint || timeConstraintSatisfied.get())
&& (!hasConnectivityConstraint || connectivityConstraintSatisfied.get())
&& (!hasMeteredConstraint || meteredConstraintSatisfied.get())
&& (!hasIdleConstraint || idleConstraintSatisfied.get());
}
@Override
public int hashCode() {
int result = component.hashCode();
result = 31 * result + taskId;
result = 31 * result + userId;
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TaskStatus)) return false;
TaskStatus that = (TaskStatus) o;
return ((taskId == that.taskId)
&& (userId == that.userId)
&& (component.equals(that.component)));
}
}

View File

@@ -0,0 +1,230 @@
/*
* 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.controllers;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import android.util.Log;
import com.android.server.task.TaskManagerService;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* This class sets an alarm for the next expiring task, and determines whether a task's minimum
* delay has been satisfied.
*/
public class TimeController extends StateController {
private static final String TAG = "TaskManager.Time";
private static final String ACTION_TASK_EXPIRED =
"android.content.taskmanager.TASK_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;
/** Set an alarm for the next task delay expiry. This*/
private final PendingIntent mNextDelayExpiredAlarmIntent;
private long mNextTaskExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
private AlarmManager mAlarmService = null;
/** List of tracked tasks, sorted asc. by deadline */
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
public TimeController(TaskManagerService service) {
super(service);
mTaskExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_EXPIRED), 0);
mNextDelayExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_DELAY_EXPIRED), 0);
// Register BR for these intents.
IntentFilter intentFilter = new IntentFilter(ACTION_TASK_EXPIRED);
intentFilter.addAction(ACTION_TASK_DELAY_EXPIRED);
mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
}
/**
* Check if the task has a timing constraint, and if so determine where to insert it in our
* list.
*/
@Override
public synchronized void maybeTrackTaskState(TaskStatus task) {
if (task.hasTimingConstraint()) {
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.getLatestRunTime() < task.getLatestRunTime()) {
// Insert
it.add(task);
break;
}
}
maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTime());
}
}
/**
* If the task passed in is being tracked, figure out if we need to update our alarms, and if
* so, update them.
*/
@Override
public synchronized void removeTaskStateIfTracked(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
handleTaskDelayExpired();
}
if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTime()) {
handleTaskDeadlineExpired();
}
}
}
/**
* 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
* the task's deadline is fulfilled - unlike other controllers a time constraint can't toggle
* back and forth.
*/
private boolean canStopTrackingTask(TaskStatus taskStatus) {
final long elapsedNowMillis = SystemClock.elapsedRealtime();
return taskStatus.timeConstraintSatisfied.get() &&
(taskStatus.getLatestRunTime() == Long.MAX_VALUE ||
taskStatus.getLatestRunTime() < elapsedNowMillis);
}
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);
}
}
/**
* Handles alarm that notifies that a task has expired. When this function is called at least
* one task must be run.
*/
private synchronized void handleTaskDeadlineExpired() {
long nextExpiryTime = Long.MAX_VALUE;
final long nowElapsedMillis = SystemClock.elapsedRealtime();
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
final long taskDeadline = ts.getLatestRunTime();
if (taskDeadline <= nowElapsedMillis) {
ts.timeConstraintSatisfied.set(true);
mStateChangedListener.onTaskDeadlineExpired(ts);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = taskDeadline;
break;
}
}
maybeUpdateAlarms(Long.MAX_VALUE, 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() {
final long nowElapsedMillis = SystemClock.elapsedRealtime();
long nextDelayTime = Long.MAX_VALUE;
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
final TaskStatus ts = it.next();
final long taskDelayTime = ts.getEarliestRunTime();
if (taskDelayTime < nowElapsedMillis) {
ts.timeConstraintSatisfied.set(true);
mStateChangedListener.onTaskStateChanged(ts);
if (canStopTrackingTask(ts)) {
it.remove();
}
} else { // Keep going through list to get next delay time.
if (nextDelayTime > taskDelayTime) {
nextDelayTime = taskDelayTime;
}
}
}
maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
}
private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 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();
} else if (ACTION_TASK_DELAY_EXPIRED.equals(intent.getAction())) {
handleTaskDelayExpired();
}
}
};
}