diff options
author | Matthew Williams <mjwilliams@google.com> | 2014-05-01 10:47:00 -0700 |
---|---|---|
committer | Matthew Williams <mjwilliams@google.com> | 2014-05-12 15:41:43 -0700 |
commit | 6de79e2b17fa0796ea4d39fd9555b563c484248d (patch) | |
tree | 50f27d1131c1e639dfcda3dff9eada8ddb3b98ef | |
parent | cae6873161fd0794a794ef487c40074ee7e815aa (diff) | |
download | frameworks_base-6de79e2b17fa0796ea4d39fd9555b563c484248d.zip frameworks_base-6de79e2b17fa0796ea4d39fd9555b563c484248d.tar.gz frameworks_base-6de79e2b17fa0796ea4d39fd9555b563c484248d.tar.bz2 |
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
11 files changed, 932 insertions, 33 deletions
diff --git a/api/current.txt b/api/current.txt index 5a22eb1..3ceea24 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java index e2eafd8..0351082 100644 --- a/core/java/android/app/task/TaskParams.java +++ b/core/java/android/app/task/TaskParams.java @@ -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>() { diff --git a/core/java/android/content/Task.java b/core/java/android/content/Task.java index ed5ed88..407880f 100644 --- a/core/java/android/content/Task.java +++ b/core/java/android/content/Task.java @@ -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. diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java new file mode 100644 index 0000000..a87bf95 --- /dev/null +++ b/services/core/java/com/android/server/task/StateChangedListener.java @@ -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); +} diff --git a/services/core/java/com/android/server/task/TaskList.java b/services/core/java/com/android/server/task/TaskList.java new file mode 100644 index 0000000..d2b8440 --- /dev/null +++ b/services/core/java/com/android/server/task/TaskList.java @@ -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>(); + } +} diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java new file mode 100644 index 0000000..5df4b2a --- /dev/null +++ b/services/core/java/com/android/server/task/TaskManagerService.java @@ -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) { + + } +} diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java new file mode 100644 index 0000000..65c6fa5 --- /dev/null +++ b/services/core/java/com/android/server/task/TaskServiceContext.java @@ -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; + } +} diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java new file mode 100644 index 0000000..5cca77c --- /dev/null +++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java @@ -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); + } + } + }; +} diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java new file mode 100644 index 0000000..e1cd662 --- /dev/null +++ b/services/core/java/com/android/server/task/controllers/StateController.java @@ -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); + +} diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java new file mode 100644 index 0000000..230b049 --- /dev/null +++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java @@ -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))); + } +} diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java new file mode 100644 index 0000000..6d97a53 --- /dev/null +++ b/services/core/java/com/android/server/task/controllers/TimeController.java @@ -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(); + } + } + }; +} |