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:
@@ -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);
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
78
services/core/java/com/android/server/task/TaskList.java
Normal file
78
services/core/java/com/android/server/task/TaskList.java
Normal 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>();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user