summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java22
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityStack.java108
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java43
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java44
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java10
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java4
-rw-r--r--services/core/java/com/android/server/task/StateChangedListener.java3
-rw-r--r--services/core/java/com/android/server/task/TaskCompletedListener.java12
-rw-r--r--services/core/java/com/android/server/task/TaskManagerService.java603
-rw-r--r--services/core/java/com/android/server/task/TaskServiceContext.java522
-rw-r--r--services/core/java/com/android/server/task/TaskStore.java140
-rw-r--r--services/core/java/com/android/server/task/controllers/ConnectivityController.java35
-rw-r--r--services/core/java/com/android/server/task/controllers/IdleController.java8
-rw-r--r--services/core/java/com/android/server/task/controllers/StateController.java4
-rw-r--r--services/core/java/com/android/server/task/controllers/TaskStatus.java166
-rw-r--r--services/core/java/com/android/server/task/controllers/TimeController.java43
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java50
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java44
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java55
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java2
-rw-r--r--services/java/com/android/server/SystemServer.java7
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java2
24 files changed, 1289 insertions, 651 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1e21e1c..5527528 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5606,16 +5606,23 @@ public class ConnectivityService extends IConnectivityManager.Stub {
boolean isNewDefault = false;
if (DBG) log("handleConnectionValidated for "+newNetwork.name());
// check if any NetworkRequest wants this NetworkAgent
- // first check if it satisfies the NetworkCapabilities
ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
if (VDBG) log(" new Network has: " + newNetwork.networkCapabilities);
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (newNetwork == currentNetwork) {
+ if (VDBG) log("Network " + newNetwork.name() + " was already satisfying" +
+ " request " + nri.request.requestId + ". No change.");
+ keep = true;
+ continue;
+ }
+
+ // check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities(
newNetwork.networkCapabilities)) {
// next check if it's better than any current network we're using for
// this request
- NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
if (VDBG) {
log("currentScore = " +
(currentNetwork != null ? currentNetwork.currentScore : 0) +
@@ -5744,12 +5751,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
if (state == NetworkInfo.State.CONNECTED) {
- // TODO - check if we want it (optimization)
try {
+ // This is likely caused by the fact that this network already
+ // exists. An example is when a network goes from CONNECTED to
+ // CONNECTING and back (like wifi on DHCP renew).
+ // TODO: keep track of which networks we've created, or ask netd
+ // to tell us whether we've already created this network or not.
mNetd.createNetwork(networkAgent.network.netId);
} catch (Exception e) {
- loge("Error creating Network " + networkAgent.network.netId);
+ loge("Error creating network " + networkAgent.network.netId + ": "
+ + e.getMessage());
+ return;
}
+
updateLinkProperties(networkAgent, null);
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index a0440cb..1804d03 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -30,10 +30,6 @@ import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY;
import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
-
import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
@@ -1067,6 +1063,40 @@ final class ActivityStack {
}
}
+ /**
+ * Determine if home should be visible below the passed record.
+ * @param record activity we are querying for.
+ * @return true if home is visible below the passed activity, false otherwise.
+ */
+ boolean isActivityOverHome(ActivityRecord record) {
+ // Start at record and go down, look for either home or a visible fullscreen activity.
+ final TaskRecord recordTask = record.task;
+ for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) {
+ TaskRecord task = mTaskHistory.get(taskNdx);
+ final ArrayList<ActivityRecord> activities = task.mActivities;
+ final int startNdx =
+ task == recordTask ? activities.indexOf(record) : activities.size() - 1;
+ for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ if (r.isHomeActivity()) {
+ return true;
+ }
+ if (!r.finishing && r.fullscreen) {
+ // Passed activity is over a fullscreen activity.
+ return false;
+ }
+ }
+ if (task.mOnTopOfHome) {
+ // Got to the bottom of a task on top of home without finding a visible fullscreen
+ // activity. Home is visible.
+ return true;
+ }
+ }
+ // Got to the bottom of this stack and still don't know. If this is over the home stack
+ // then record is over home. May not work if we ever get more than two layers.
+ return mStackSupervisor.isFrontStack(this);
+ }
+
private void setVisibile(ActivityRecord r, boolean visible) {
r.visible = visible;
mWindowManager.setAppVisibility(r.appToken, visible);
@@ -1096,8 +1126,7 @@ final class ActivityStack {
for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) {
final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks();
for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) {
- final TaskRecord task = tasks.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
+ final ArrayList<ActivityRecord> activities = tasks.get(taskNdx).mActivities;
for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) {
final ActivityRecord r = activities.get(activityNdx);
@@ -1108,7 +1137,7 @@ final class ActivityStack {
// - Full Screen Activity OR
// - On top of Home and our stack is NOT home
if (!r.finishing && r.visible && (r.fullscreen ||
- (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()))) {
+ (!isHomeStack() && r.frontOfTask && tasks.get(taskNdx).mOnTopOfHome))) {
return false;
}
}
@@ -1236,7 +1265,7 @@ final class ActivityStack {
// At this point, nothing else needs to be shown
if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
behindFullscreen = true;
- } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+ } else if (!isHomeStack() && r.frontOfTask && task.mOnTopOfHome) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
behindFullscreen = true;
}
@@ -1390,7 +1419,6 @@ final class ActivityStack {
final boolean userLeaving = mStackSupervisor.mUserLeaving;
mStackSupervisor.mUserLeaving = false;
- final TaskRecord prevTask = prev != null ? prev.task : null;
if (next == null) {
// There are no more activities! Let's just start up the
// Launcher...
@@ -1398,10 +1426,7 @@ final class ActivityStack {
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
// Only resume home if on home display
- final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
- HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
- return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
+ return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev);
}
next.delayedResume = false;
@@ -1420,24 +1445,22 @@ final class ActivityStack {
}
final TaskRecord nextTask = next.task;
+ final TaskRecord prevTask = prev != null ? prev.task : null;
if (prevTask != null && prevTask.stack == this &&
- prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
+ prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
if (prevTask == nextTask) {
prevTask.setFrontOfTask();
} else if (prevTask != topTask()) {
- // This task is going away but it was supposed to return to the home stack.
+ // This task is going away but it was supposed to return to the home task.
// Now the task above it has to return to the home task instead.
final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
- mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ mTaskHistory.get(taskNdx).mOnTopOfHome = true;
} else {
if (DEBUG_STATES && isOnHomeDisplay()) Slog.d(TAG,
"resumeTopActivityLocked: Launching home next");
// Only resume home if on home display
- final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
- HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
- return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
+ return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev);
}
}
@@ -1808,11 +1831,10 @@ final class ActivityStack {
ActivityStack lastStack = mStackSupervisor.getLastStack();
final boolean fromHome = lastStack.isHomeStack();
if (!isHomeStack() && (fromHome || topTask() != task)) {
- task.setTaskToReturnTo(fromHome ?
- lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE);
+ task.mOnTopOfHome = fromHome;
}
} else {
- task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+ task.mOnTopOfHome = false;
}
mTaskHistory.remove(task);
@@ -1860,7 +1882,7 @@ final class ActivityStack {
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
- r.userId, r.info.configChanges);
+ r.userId, r.info.configChanges, task.voiceSession != null);
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
}
@@ -1921,7 +1943,7 @@ final class ActivityStack {
mWindowManager.addAppToken(task.mActivities.indexOf(r),
r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
- r.info.configChanges);
+ r.info.configChanges, task.voiceSession != null);
boolean doShow = true;
if (newTask) {
// Even though this activity is starting fresh, we still need
@@ -1966,7 +1988,7 @@ final class ActivityStack {
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
- r.info.configChanges);
+ r.info.configChanges, task.voiceSession != null);
ActivityOptions.abort(options);
options = null;
}
@@ -2357,8 +2379,8 @@ final class ActivityStack {
ActivityRecord next = topRunningActivityLocked(null);
if (next != r) {
final TaskRecord task = r.task;
- if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo());
+ if (r.frontOfTask && task == topTask() && task.mOnTopOfHome) {
+ mStackSupervisor.moveHomeToTop();
}
}
ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
@@ -2842,9 +2864,8 @@ final class ActivityStack {
if (task != null && task.removeActivity(r)) {
if (DEBUG_STACK) Slog.i(TAG,
"removeActivityFromHistoryLocked: last activity removed from " + this);
- if (mStackSupervisor.isFrontStack(this) && task == topTask() &&
- task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo());
+ if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) {
+ mStackSupervisor.moveHomeToTop();
}
removeTask(task);
}
@@ -3159,13 +3180,12 @@ final class ActivityStack {
}
}
- void moveHomeStackTaskToTop(int homeStackTaskType) {
+ void moveHomeTaskToTop() {
final int top = mTaskHistory.size() - 1;
for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.taskType == homeStackTaskType) {
- if (DEBUG_TASKS || DEBUG_STACK)
- Slog.d(TAG, "moveHomeStackTaskToTop: moving " + task);
+ if (task.isHomeTask()) {
+ if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task);
mTaskHistory.remove(taskNdx);
mTaskHistory.add(top, task);
updateTaskMovement(task, true);
@@ -3277,12 +3297,12 @@ final class ActivityStack {
int numTasks = mTaskHistory.size();
for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.isOverHomeStack()) {
+ if (task.mOnTopOfHome) {
break;
}
if (taskNdx == 1) {
// Set the last task before tr to go to home.
- task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ task.mOnTopOfHome = true;
}
}
@@ -3303,10 +3323,9 @@ final class ActivityStack {
}
final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
- if (task == tr && tr.isOverHomeStack() || numTasks <= 1 && isOnHomeDisplay()) {
- final int taskToReturnTo = tr.getTaskToReturnTo();
- tr.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
- return mStackSupervisor.resumeHomeStackTask(taskToReturnTo, null);
+ if (task == tr && tr.mOnTopOfHome || numTasks <= 1 && isOnHomeDisplay()) {
+ tr.mOnTopOfHome = false;
+ return mStackSupervisor.resumeHomeActivity(null);
}
mStackSupervisor.resumeTopActivitiesLocked();
@@ -3747,11 +3766,8 @@ final class ActivityStack {
final int taskNdx = mTaskHistory.indexOf(task);
final int topTaskNdx = mTaskHistory.size() - 1;
- if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
- final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
- if (!nextTask.isOverHomeStack()) {
- nextTask.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
- }
+ if (task.mOnTopOfHome && taskNdx < topTaskNdx) {
+ mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
}
mTaskHistory.remove(task);
updateTaskMovement(task, true);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index f487536..0cc53d1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -31,9 +31,6 @@ import static com.android.server.am.ActivityManagerService.DEBUG_TASKS;
import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
import static com.android.server.am.ActivityManagerService.TAG;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import android.app.Activity;
import android.app.ActivityManager;
@@ -329,27 +326,18 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- void moveHomeStackTaskToTop(int homeStackTaskType) {
- if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
- mWindowManager.showRecentApps();
- return;
- }
+ void moveHomeToTop() {
moveHomeStack(true);
- mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
+ mHomeStack.moveHomeTaskToTop();
}
- boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev) {
- if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
- mWindowManager.showRecentApps();
- return false;
- }
- moveHomeStackTaskToTop(homeStackTaskType);
+ boolean resumeHomeActivity(ActivityRecord prev) {
+ moveHomeToTop();
if (prev != null) {
- prev.task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+ prev.task.mOnTopOfHome = false;
}
-
ActivityRecord r = mHomeStack.topRunningActivityLocked(null);
- if (r != null && (r.isHomeActivity() || r.isRecentsActivity())) {
+ if (r != null && r.isHomeActivity()) {
mService.setFocusedActivityLocked(r);
return resumeTopActivitiesLocked(mHomeStack, prev, null);
}
@@ -703,7 +691,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
void startHomeActivity(Intent intent, ActivityInfo aInfo) {
- moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE);
+ moveHomeToTop();
startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
null, false, null, null);
}
@@ -1641,7 +1629,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
// Caller wants to appear on home activity.
- intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ intentActivity.task.mOnTopOfHome = true;
}
options = null;
}
@@ -1826,11 +1814,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
voiceSession, voiceInteractor, true), null, true);
- if (sourceRecord == null) {
- // Launched from a service or notification or task that is finishing.
- r.task.setTaskToReturnTo(isFrontStack(mHomeStack) ?
- mHomeStack.topTask().taskType : RECENTS_ACTIVITY_TYPE);
- }
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1842,7 +1825,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
== (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
// Caller wants to appear on home activity, so before starting
// their own activity we will bring home to the front.
- r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ r.task.mOnTopOfHome = r.task.stack.isOnHomeDisplay();
}
}
} else if (sourceRecord != null) {
@@ -2193,7 +2176,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
// Caller wants the home activity moved with it. To accomplish this,
// we'll just indicate that this task returns to the home task.
- task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ task.mOnTopOfHome = true;
}
task.stack.moveTaskToFrontLocked(task, null, options);
if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
@@ -2300,11 +2283,11 @@ public final class ActivityStackSupervisor implements DisplayListener {
mWindowManager.addAppToken(0, r.appToken, taskId, stackId,
r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
- r.userId, r.info.configChanges);
+ r.userId, r.info.configChanges, task.voiceSession != null);
}
mWindowManager.addTask(taskId, stackId, false);
}
- resumeHomeStackTask(HOME_ACTIVITY_TYPE, null);
+ resumeHomeActivity(null);
}
void moveTaskToStack(int taskId, int stackId, boolean toTop) {
@@ -2566,7 +2549,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
} else {
// Stack was moved to another display while user was swapped out.
- resumeHomeStackTask(HOME_ACTIVITY_TYPE, null);
+ resumeHomeActivity(null);
}
return homeInFront;
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c07bc1e..ce83ae6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -17,9 +17,6 @@
package com.android.server.am;
import static com.android.server.am.ActivityManagerService.TAG;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
import android.app.Activity;
@@ -57,6 +54,7 @@ final class TaskRecord extends ThumbnailHolder {
private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_TASKTYPE = "task_type";
+ private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
@@ -106,11 +104,9 @@ final class TaskRecord extends ThumbnailHolder {
/** True if persistable, has changed, and has not yet been persisted */
boolean needsPersisting = false;
-
- /** Indication of what to run next when task exits. Use ActivityRecord types.
- * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the
- * task stack. */
- private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE;
+ /** Launch the home activity when leaving this task. Will be false for tasks that are not on
+ * Display.DEFAULT_DISPLAY. */
+ boolean mOnTopOfHome = false;
final ActivityManagerService mService;
@@ -127,8 +123,9 @@ final class TaskRecord extends ThumbnailHolder {
TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
String _affinity, ComponentName _realActivity, ComponentName _origActivity,
- boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
- String _lastDescription, ArrayList<ActivityRecord> activities, long lastTimeMoved) {
+ boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
+ int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
+ long lastTimeMoved) {
mService = service;
taskId = _taskId;
intent = _intent;
@@ -141,7 +138,7 @@ final class TaskRecord extends ThumbnailHolder {
rootWasReset = _rootWasReset;
askedCompatMode = _askedCompatMode;
taskType = _taskType;
- mTaskToReturnTo = HOME_ACTIVITY_TYPE;
+ mOnTopOfHome = _onTopOfHome;
userId = _userId;
lastDescription = _lastDescription;
mActivities = activities;
@@ -209,14 +206,6 @@ final class TaskRecord extends ThumbnailHolder {
}
}
- void setTaskToReturnTo(int taskToReturnTo) {
- mTaskToReturnTo = taskToReturnTo;
- }
-
- int getTaskToReturnTo() {
- return mTaskToReturnTo;
- }
-
void disposeThumbnail() {
super.disposeThumbnail();
for (int i=mActivities.size()-1; i>=0; i--) {
@@ -488,15 +477,11 @@ final class TaskRecord extends ThumbnailHolder {
}
boolean isHomeTask() {
- return taskType == HOME_ACTIVITY_TYPE;
+ return taskType == ActivityRecord.HOME_ACTIVITY_TYPE;
}
boolean isApplicationTask() {
- return taskType == APPLICATION_ACTIVITY_TYPE;
- }
-
- boolean isOverHomeStack() {
- return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE;
+ return taskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
}
public TaskAccessInfo getTaskAccessInfoLocked() {
@@ -638,6 +623,7 @@ final class TaskRecord extends ThumbnailHolder {
out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
out.attribute(null, ATTR_USERID, String.valueOf(userId));
out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
+ out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
if (lastDescription != null) {
out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
@@ -683,6 +669,7 @@ final class TaskRecord extends ThumbnailHolder {
boolean rootHasReset = false;
boolean askedCompatMode = false;
int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+ boolean onTopOfHome = true;
int userId = 0;
String lastDescription = null;
long lastTimeOnTop = 0;
@@ -710,6 +697,8 @@ final class TaskRecord extends ThumbnailHolder {
userId = Integer.valueOf(attrValue);
} else if (ATTR_TASKTYPE.equals(attrName)) {
taskType = Integer.valueOf(attrValue);
+ } else if (ATTR_ONTOPOFHOME.equals(attrName)) {
+ onTopOfHome = Boolean.valueOf(attrValue);
} else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
lastDescription = attrValue;
} else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
@@ -747,7 +736,8 @@ final class TaskRecord extends ThumbnailHolder {
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, realActivity, origActivity, rootHasReset,
- askedCompatMode, taskType, userId, lastDescription, activities, lastTimeOnTop);
+ askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
+ lastTimeOnTop);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
@@ -766,7 +756,7 @@ final class TaskRecord extends ThumbnailHolder {
pw.print(" userId="); pw.print(userId);
pw.print(" taskType="); pw.print(taskType);
pw.print(" numFullscreen="); pw.print(numFullscreen);
- pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+ pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome);
}
if (affinity != null) {
pw.print(prefix); pw.print("affinity="); pw.println(affinity);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index b94ea62..1b1fc8b 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -259,13 +259,17 @@ abstract public class ManagedServices {
userIds[i]));
}
- ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()];
+ ArrayList<ManagedServiceInfo> toRemove = new ArrayList<ManagedServiceInfo>();
final SparseArray<ArrayList<ComponentName>> toAdd
= new SparseArray<ArrayList<ComponentName>>();
synchronized (mMutex) {
- // unbind and remove all existing services
- toRemove = mServices.toArray(toRemove);
+ // Unbind automatically bound services, retain system services.
+ for (ManagedServiceInfo service : mServices) {
+ if (!service.isSystem) {
+ toRemove.add(service);
+ }
+ }
final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
final ArraySet<String> newPackages = new ArraySet<String>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b06b090..d505e81 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11775,8 +11775,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
if (removed.size() > 0) {
- for (int j=0; j<removed.size(); j++) {
- PreferredActivity pa = removed.get(i);
+ for (int r=0; r<removed.size(); r++) {
+ PreferredActivity pa = removed.get(r);
Slog.w(TAG, "Removing dangling preferred activity: "
+ pa.mPref.mComponent);
pir.removeFilter(pa);
diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java
index db2d4ee..b1a4636 100644
--- a/services/core/java/com/android/server/task/StateChangedListener.java
+++ b/services/core/java/com/android/server/task/StateChangedListener.java
@@ -27,9 +27,8 @@ 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);
+ public void onControllerStateChanged();
/**
* Called by the controller to notify the TaskManager that regardless of the state of the task,
diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java
index 0210442..c53f5ca 100644
--- a/services/core/java/com/android/server/task/TaskCompletedListener.java
+++ b/services/core/java/com/android/server/task/TaskCompletedListener.java
@@ -16,6 +16,8 @@
package com.android.server.task;
+import com.android.server.task.controllers.TaskStatus;
+
/**
* Used for communication between {@link com.android.server.task.TaskServiceContext} and the
* {@link com.android.server.task.TaskManagerService}.
@@ -26,13 +28,5 @@ public interface TaskCompletedListener {
* Callback for when a task is completed.
* @param needsReschedule Whether the implementing class should reschedule this task.
*/
- public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);
-
- /**
- * Callback for when the implementing class needs to clean up the
- * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
- * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
- * and it needs to clean up).
- */
- public void onAllTasksCompleted(int serviceToken);
+ public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule);
}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index 6d208ff..d5b70e6 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -16,15 +16,33 @@
package com.android.server.task;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import android.app.task.ITaskManager;
+import android.app.task.Task;
+import android.app.task.TaskManager;
import android.content.Context;
-import android.content.Task;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.util.Log;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
import android.util.SparseArray;
+import com.android.server.task.controllers.ConnectivityController;
+import com.android.server.task.controllers.IdleController;
+import com.android.server.task.controllers.StateController;
import com.android.server.task.controllers.TaskStatus;
+import com.android.server.task.controllers.TimeController;
+
+import java.util.LinkedList;
/**
* Responsible for taking tasks representing work to be performed by a client app, and determining
@@ -34,78 +52,151 @@ import com.android.server.task.controllers.TaskStatus;
*/
public class TaskManagerService extends com.android.server.SystemService
implements StateChangedListener, TaskCompletedListener {
+ // TODO: Switch this off for final version.
+ private static final boolean DEBUG = true;
+ /** The number of concurrent tasks we run at one time. */
+ private static final int MAX_TASK_CONTEXTS_COUNT = 3;
static final String TAG = "TaskManager";
+ /**
+ * When a task fails, it gets rescheduled according to its backoff policy. To be nice, we allow
+ * this amount of time from the rescheduled time by which the retry must occur.
+ */
+ private static final long RESCHEDULE_WINDOW_SLOP_MILLIS = 5000L;
/** Master list of tasks. */
private final TaskStore mTasks;
- /** Check the pending queue and start any tasks. */
- static final int MSG_RUN_PENDING = 0;
- /** Initiate the stop task flow. */
- static final int MSG_STOP_TASK = 1;
- /** */
- static final int MSG_CHECK_TASKS = 2;
+ static final int MSG_TASK_EXPIRED = 0;
+ static final int MSG_CHECK_TASKS = 1;
+
+ // Policy constants
+ /**
+ * Minimum # of idle tasks that must be ready in order to force the TM to schedule things
+ * early.
+ */
+ private static final int MIN_IDLE_COUNT = 1;
+ /**
+ * Minimum # of connectivity tasks that must be ready in order to force the TM to schedule
+ * things early.
+ */
+ private static final int MIN_CONNECTIVITY_COUNT = 2;
+ /**
+ * Minimum # of tasks (with no particular constraints) for which the TM will be happy running
+ * some work early.
+ */
+ private static final int MIN_READY_TASKS_COUNT = 4;
/**
* Track Services that have currently active or pending tasks. The index is provided by
* {@link TaskStatus#getServiceToken()}
*/
- private final SparseArray<TaskServiceContext> mActiveServices =
- new SparseArray<TaskServiceContext>();
+ private final List<TaskServiceContext> mActiveServices = new LinkedList<TaskServiceContext>();
+ /** List of controllers that will notify this service of updates to tasks. */
+ private List<StateController> mControllers;
+ /**
+ * Queue of pending tasks. The TaskServiceContext class will receive tasks from this list
+ * when ready to execute them.
+ */
+ private final LinkedList<TaskStatus> mPendingTasks = new LinkedList<TaskStatus>();
private final TaskHandler mHandler;
+ private final TaskManagerStub mTaskManagerStub;
- private class TaskHandler extends Handler {
+ /**
+ * Entry point from client to schedule the provided task.
+ * This will add the task to the
+ * @param task Task object containing execution parameters
+ * @param uId The package identifier of the application this task is for.
+ * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
+ * of this task.
+ * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ */
+ public int schedule(Task task, int uId, boolean canPersistTask) {
+ TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask);
+ return startTrackingTask(taskStatus) ?
+ TaskManager.RESULT_SUCCESS : TaskManager.RESULT_FAILURE;
+ }
- public TaskHandler(Looper looper) {
- super(looper);
+ public List<Task> getPendingTasks(int uid) {
+ ArrayList<Task> outList = new ArrayList<Task>();
+ synchronized (mTasks) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ if (ts.getUid() == uid) {
+ outList.add(ts.getTask());
+ }
+ }
}
+ return outList;
+ }
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_RUN_PENDING:
-
- break;
- case MSG_STOP_TASK:
-
- break;
- case MSG_CHECK_TASKS:
- checkTasks();
- break;
+ /**
+ * Entry point from client to cancel all tasks originating from their uid.
+ * This will remove the task from the master list, and cancel the task if it was staged for
+ * execution or being executed.
+ * @param uid To check against for removal of a task.
+ */
+ public void cancelTaskForUid(int uid) {
+ // Remove from master list.
+ synchronized (mTasks) {
+ if (!mTasks.removeAllByUid(uid)) {
+ // If it's not in the master list, it's nowhere.
+ return;
}
}
-
- /**
- * Called when we need to run through the list of all tasks and start/stop executing one or
- * more of them.
- */
- private void checkTasks() {
- synchronized (mTasks) {
- final SparseArray<TaskStatus> tasks = mTasks.getTasks();
- for (int i = 0; i < tasks.size(); i++) {
- TaskStatus ts = tasks.valueAt(i);
- if (ts.isReady() && ! isCurrentlyActive(ts)) {
- assignTaskToServiceContext(ts);
- }
+ // Remove from pending queue.
+ synchronized (mPendingTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid) {
+ it.remove();
+ }
+ }
+ }
+ // Cancel if running.
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask().getUid() == uid) {
+ tsc.cancelExecutingTask();
}
}
}
}
/**
- * Entry point from client to schedule the provided task.
- * This will add the task to the
- * @param task Task object containing execution parameters
- * @param userId The id of the user this task is for.
- * @param uId The package identifier of the application this task is for.
- * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
- * of this task.
- * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ * Entry point from client to cancel the task corresponding to the taskId provided.
+ * This will remove the task from the master list, and cancel the task if it was staged for
+ * execution or being executed.
+ * @param uid Uid of the calling client.
+ * @param taskId Id of the task, provided at schedule-time.
*/
- public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
- TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
- return 0;
+ public void cancelTask(int uid, int taskId) {
+ synchronized (mTasks) {
+ if (!mTasks.remove(uid, taskId)) {
+ // If it's not in the master list, it's nowhere.
+ return;
+ }
+ }
+ synchronized (mPendingTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid && ts.getTaskId() == taskId) {
+ it.remove();
+ // If we got it from pending, it didn't make it to active so return.
+ return;
+ }
+ }
+ }
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask().getUid() == uid &&
+ tsc.getRunningTask().getTaskId() == taskId) {
+ tsc.cancelExecutingTask();
+ return;
+ }
+ }
+ }
}
/**
@@ -121,31 +212,148 @@ public class TaskManagerService extends com.android.server.SystemService
super(context);
mTasks = new TaskStore(context);
mHandler = new TaskHandler(context.getMainLooper());
+ mTaskManagerStub = new TaskManagerStub();
+ // Create the "runners".
+ for (int i = 0; i < MAX_TASK_CONTEXTS_COUNT; i++) {
+ mActiveServices.add(
+ new TaskServiceContext(this, context.getMainLooper()));
+ }
+
+ mControllers = new LinkedList<StateController>();
+ mControllers.add(ConnectivityController.get(this));
+ mControllers.add(TimeController.get(this));
+ mControllers.add(IdleController.get(this));
+ // TODO: Add BatteryStateController when implemented.
}
@Override
public void onStart() {
+ publishBinderService(Context.TASK_SERVICE, mTaskManagerStub);
+ }
+ /**
+ * Called when we have a task status object that we need to insert in our
+ * {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know
+ * about.
+ */
+ private boolean startTrackingTask(TaskStatus taskStatus) {
+ boolean added = false;
+ synchronized (mTasks) {
+ added = mTasks.add(taskStatus);
+ }
+ if (added) {
+ for (StateController controller : mControllers) {
+ controller.maybeStartTrackingTask(taskStatus);
+ }
+ }
+ return added;
}
- // StateChangedListener implementations.
+ /**
+ * Called when we want to remove a TaskStatus object that we've finished executing. Returns the
+ * object removed.
+ */
+ private boolean stopTrackingTask(TaskStatus taskStatus) {
+ boolean removed;
+ synchronized (mTasks) {
+ // Remove from store as well as controllers.
+ removed = mTasks.remove(taskStatus);
+ }
+ if (removed) {
+ for (StateController controller : mControllers) {
+ controller.maybeStopTrackingTask(taskStatus);
+ }
+ }
+ return removed;
+ }
+
+ private boolean cancelTaskOnServiceContext(TaskStatus ts) {
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask() == ts) {
+ tsc.cancelExecutingTask();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
/**
- * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
- * made on the main thread.
- * For now this takes the task and if it's ready to run it will run it. In future we might not
- * provide the task, so that the StateChangedListener has to run through its list of tasks to
- * see which are ready. This will further decouple the controllers from the execution logic.
+ * @param ts TaskStatus we are querying against.
+ * @return Whether or not the task represented by the status object is currently being run or
+ * is pending.
*/
- @Override
- public void onTaskStateChanged(TaskStatus taskStatus) {
- postCheckTasksMessage();
+ private boolean isCurrentlyActive(TaskStatus ts) {
+ synchronized (mActiveServices) {
+ for (TaskServiceContext serviceContext : mActiveServices) {
+ if (serviceContext.getRunningTask() == ts) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ /**
+ * A task is rescheduled with exponential back-off if the client requests this from their
+ * execution logic.
+ * A caveat is for idle-mode tasks, for which the idle-mode constraint will usurp the
+ * timeliness of the reschedule. For an idle-mode task, no deadline is given.
+ * @param failureToReschedule Provided task status that we will reschedule.
+ * @return A newly instantiated TaskStatus with the same constraints as the last task except
+ * with adjusted timing constraints.
+ */
+ private TaskStatus getRescheduleTaskForFailure(TaskStatus failureToReschedule) {
+ final long elapsedNowMillis = SystemClock.elapsedRealtime();
+ final Task task = failureToReschedule.getTask();
+
+ final long initialBackoffMillis = task.getInitialBackoffMillis();
+ final int backoffAttempt = failureToReschedule.getNumFailures() + 1;
+ long newEarliestRuntimeElapsed = elapsedNowMillis;
+
+ switch (task.getBackoffPolicy()) {
+ case Task.BackoffPolicy.LINEAR:
+ newEarliestRuntimeElapsed += initialBackoffMillis * backoffAttempt;
+ break;
+ default:
+ if (DEBUG) {
+ Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
+ }
+ case Task.BackoffPolicy.EXPONENTIAL:
+ newEarliestRuntimeElapsed += Math.pow(initialBackoffMillis, backoffAttempt);
+ break;
+ }
+ long newLatestRuntimeElapsed = failureToReschedule.hasIdleConstraint() ? Long.MAX_VALUE
+ : newEarliestRuntimeElapsed + RESCHEDULE_WINDOW_SLOP_MILLIS;
+ return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed,
+ newLatestRuntimeElapsed, backoffAttempt);
}
- @Override
- public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+ /**
+ * Called after a periodic has executed so we can to re-add it. We take the last execution time
+ * of the task to be the time of completion (i.e. the time at which this function is called).
+ * This could be inaccurate b/c the task can run for as long as
+ * {@link com.android.server.task.TaskServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
+ * to underscheduling at least, rather than if we had taken the last execution time to be the
+ * start of the execution.
+ * @return A new task representing the execution criteria for this instantiation of the
+ * recurring task.
+ */
+ private TaskStatus getRescheduleTaskForPeriodic(TaskStatus periodicToReschedule) {
+ final long elapsedNow = SystemClock.elapsedRealtime();
+ // Compute how much of the period is remaining.
+ long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
+ long newEarliestRunTimeElapsed = elapsedNow + runEarly;
+ long period = periodicToReschedule.getTask().getIntervalMillis();
+ long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
+ if (DEBUG) {
+ Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
+ newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
+ }
+ return new TaskStatus(periodicToReschedule, newEarliestRunTimeElapsed,
+ newLatestRuntimeElapsed, 0 /* backoffAttempt */);
}
// TaskCompletedListener implementations.
@@ -154,53 +362,268 @@ public class TaskManagerService extends com.android.server.SystemService
* A task just finished executing. We fetch the
* {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
* whether we want to reschedule we readd it to the controllers.
- * @param serviceToken key for the service context in {@link #mActiveServices}.
- * @param taskId Id of the task that is complete.
+ * @param taskStatus Completed task.
* @param needsReschedule Whether the implementing class should reschedule this task.
*/
@Override
- public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
- final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
- if (serviceContext == null) {
- Log.e(TAG, "Task completed for invalid service context; " + serviceToken);
+ public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule) {
+ if (!stopTrackingTask(taskStatus)) {
+ if (DEBUG) {
+ Slog.e(TAG, "Error removing task: could not find task to remove. Was task" +
+ "removed while executing?");
+ }
return;
}
+ if (needsReschedule) {
+ TaskStatus rescheduled = getRescheduleTaskForFailure(taskStatus);
+ startTrackingTask(rescheduled);
+ } else if (taskStatus.getTask().isPeriodic()) {
+ TaskStatus rescheduledPeriodic = getRescheduleTaskForPeriodic(taskStatus);
+ startTrackingTask(rescheduledPeriodic);
+ }
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+ }
+
+ // StateChangedListener implementations.
+ /**
+ * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
+ * made on the main thread.
+ * For now this takes the task and if it's ready to run it will run it. In future we might not
+ * provide the task, so that the StateChangedListener has to run through its list of tasks to
+ * see which are ready. This will further decouple the controllers from the execution logic.
+ */
+ @Override
+ public void onControllerStateChanged() {
+ // Post a message to to run through the list of tasks and start/stop any that are eligible.
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
@Override
- public void onAllTasksCompleted(int serviceToken) {
-
+ public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+ mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus);
}
- private void assignTaskToServiceContext(TaskStatus ts) {
- TaskServiceContext serviceContext =
- mActiveServices.get(ts.getServiceToken());
- if (serviceContext == null) {
- serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
- mActiveServices.put(ts.getServiceToken(), serviceContext);
+ private class TaskHandler extends Handler {
+
+ public TaskHandler(Looper looper) {
+ super(looper);
}
- serviceContext.addPendingTask(ts);
- }
- /**
- * @param ts TaskStatus we are querying against.
- * @return Whether or not the task represented by the status object is currently being run or
- * is pending.
- */
- private boolean isCurrentlyActive(TaskStatus ts) {
- TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
- if (serviceContext == null) {
- return false;
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_TASK_EXPIRED:
+ final TaskStatus expired = (TaskStatus) message.obj; // Unused for now.
+ queueReadyTasksForExecutionH();
+ break;
+ case MSG_CHECK_TASKS:
+ // Check the list of tasks and run some of them if we feel inclined.
+ maybeQueueReadyTasksForExecutionH();
+ break;
+ }
+ maybeRunNextPendingTaskH();
+ // Don't remove TASK_EXPIRED in case one came along while processing the queue.
+ removeMessages(MSG_CHECK_TASKS);
+ }
+
+ /**
+ * Run through list of tasks and execute all possible - at least one is expired so we do
+ * as many as we can.
+ */
+ private void queueReadyTasksForExecutionH() {
+ synchronized (mTasks) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ final boolean criteriaSatisfied = ts.isReady();
+ final boolean isRunning = isCurrentlyActive(ts);
+ if (criteriaSatisfied && !isRunning) {
+ synchronized (mPendingTasks) {
+ mPendingTasks.add(ts);
+ }
+ } else if (!criteriaSatisfied && isRunning) {
+ cancelTaskOnServiceContext(ts);
+ }
+ }
+ }
+ }
+
+ /**
+ * The state of at least one task has changed. Here is where we could enforce various
+ * policies on when we want to execute tasks.
+ * Right now the policy is such:
+ * If >1 of the ready tasks is idle mode we send all of them off
+ * if more than 2 network connectivity tasks are ready we send them all off.
+ * If more than 4 tasks total are ready we send them all off.
+ * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
+ */
+ private void maybeQueueReadyTasksForExecutionH() {
+ synchronized (mTasks) {
+ int idleCount = 0;
+ int connectivityCount = 0;
+ List<TaskStatus> runnableTasks = new ArrayList<TaskStatus>();
+ for (TaskStatus ts : mTasks.getTasks()) {
+ final boolean criteriaSatisfied = ts.isReady();
+ final boolean isRunning = isCurrentlyActive(ts);
+ if (criteriaSatisfied && !isRunning) {
+ if (ts.hasIdleConstraint()) {
+ idleCount++;
+ }
+ if (ts.hasConnectivityConstraint() || ts.hasMeteredConstraint()) {
+ connectivityCount++;
+ }
+ runnableTasks.add(ts);
+ } else if (!criteriaSatisfied && isRunning) {
+ cancelTaskOnServiceContext(ts);
+ }
+ }
+ if (idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT ||
+ runnableTasks.size() >= MIN_READY_TASKS_COUNT) {
+ for (TaskStatus ts : runnableTasks) {
+ synchronized (mPendingTasks) {
+ mPendingTasks.add(ts);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks the state of the pending queue against any available
+ * {@link com.android.server.task.TaskServiceContext} that can run a new task.
+ * {@link com.android.server.task.TaskServiceContext}.
+ */
+ private void maybeRunNextPendingTaskH() {
+ TaskStatus nextPending;
+ synchronized (mPendingTasks) {
+ nextPending = mPendingTasks.poll();
+ }
+ if (nextPending == null) {
+ return;
+ }
+
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.isAvailable()) {
+ if (tsc.executeRunnableTask(nextPending)) {
+ return;
+ }
+ }
+ }
+ }
}
- return serviceContext.hasTaskPending(ts);
}
/**
- * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
- * are eligible.
+ * Binder stub trampoline implementation
*/
- private void postCheckTasksMessage() {
- mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+ final class TaskManagerStub extends ITaskManager.Stub {
+ /** Cache determination of whether a given app can persist tasks
+ * key is uid of the calling app; value is undetermined/true/false
+ */
+ private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
+
+ // Determine whether the caller is allowed to persist tasks, with a small cache
+ // because the lookup is expensive enough that we'd like to avoid repeating it.
+ // This must be called from within the calling app's binder identity!
+ private boolean canCallerPersistTasks() {
+ final boolean canPersist;
+ final int callingUid = Binder.getCallingUid();
+ synchronized (mPersistCache) {
+ Boolean cached = mPersistCache.get(callingUid);
+ if (cached) {
+ canPersist = cached.booleanValue();
+ } else {
+ // Persisting tasks is tantamount to running at boot, so we permit
+ // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
+ // permission
+ int result = getContext().checkCallingPermission(
+ android.Manifest.permission.RECEIVE_BOOT_COMPLETED);
+ canPersist = (result == PackageManager.PERMISSION_GRANTED);
+ mPersistCache.put(callingUid, canPersist);
+ }
+ }
+ return canPersist;
+ }
+
+ // ITaskManager implementation
+ @Override
+ public int schedule(Task task) throws RemoteException {
+ final boolean canPersist = canCallerPersistTasks();
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return TaskManagerService.this.schedule(task, uid, canPersist);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public List<Task> getAllPendingTasks() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return TaskManagerService.this.getPendingTasks(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void cancelAll() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.cancelTaskForUid(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void cancel(int taskId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.cancelTask(uid, taskId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * "dumpsys" infrastructure
+ */
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.dumpInternal(pw);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+ };
+
+ void dumpInternal(PrintWriter pw) {
+ synchronized (mTasks) {
+ pw.print("Registered tasks:");
+ if (mTasks.size() > 0) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ pw.println();
+ ts.dump(pw, " ");
+ }
+ } else {
+ pw.println();
+ pw.println("No tasks scheduled.");
+ }
+ }
+ pw.println();
}
}
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
index b51cbb3..75e9212 100644
--- a/services/core/java/com/android/server/task/TaskServiceContext.java
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -24,6 +24,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -36,20 +37,18 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.task.controllers.TaskStatus;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Maintains information required to bind to a {@link android.app.task.TaskService}. This binding
- * is reused to start concurrent tasks on the TaskService. Information here is unique
- * to the service.
- * Functionality provided by this class:
- * - Managages wakelock for the service.
- * - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks.
- * -
+ * Handles client binding and lifecycle of a task. A task will only execute one at a time on an
+ * instance of this class.
*/
public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
+ private static final boolean DEBUG = true;
private static final String TAG = "TaskServiceContext";
/** Define the maximum # of tasks allowed to run on a service at once. */
private static final int defaultMaxActiveTasksPerService =
@@ -66,10 +65,10 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
};
// States that a task occupies while interacting with the client.
- private static final int VERB_STARTING = 0;
- private static final int VERB_EXECUTING = 1;
- private static final int VERB_STOPPING = 2;
- private static final int VERB_PENDING = 3;
+ static final int VERB_BINDING = 0;
+ static final int VERB_STARTING = 1;
+ static final int VERB_EXECUTING = 2;
+ static final int VERB_STOPPING = 3;
// Messages that result from interactions with the client service.
/** System timed out waiting for a response. */
@@ -77,178 +76,173 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
/** Received a callback from client. */
private static final int MSG_CALLBACK = 1;
/** Run through list and start any ready tasks.*/
- private static final int MSG_CHECK_PENDING = 2;
- /** Cancel an active task. */
+ private static final int MSG_SERVICE_BOUND = 2;
+ /** Cancel a task. */
private static final int MSG_CANCEL = 3;
- /** Add a pending task. */
- private static final int MSG_ADD_PENDING = 4;
- /** Client crashed, so we need to wind things down. */
- private static final int MSG_SHUTDOWN = 5;
-
- /** Used to identify this task service context when communicating with the TaskManager. */
- final int token;
- final ComponentName component;
- final int userId;
- ITaskService service;
+ /** Shutdown the Task. Used when the client crashes and we can't die gracefully.*/
+ private static final int MSG_SHUTDOWN_EXECUTION = 4;
+
private final Handler mCallbackHandler;
- /** Tasks that haven't been sent to the client for execution yet. */
- private final SparseArray<ActiveTask> mPending;
+ /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
+ private final TaskCompletedListener mCompletedListener;
/** Used for service binding, etc. */
private final Context mContext;
- /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
- final private TaskCompletedListener mCompletedListener;
- private final PowerManager.WakeLock mWakeLock;
+ private PowerManager.WakeLock mWakeLock;
- /** Whether this service is actively bound. */
- boolean mBound;
+ // Execution state.
+ private TaskParams mParams;
+ @VisibleForTesting
+ int mVerb;
+ private AtomicBoolean mCancelled = new AtomicBoolean();
- TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) {
- mContext = taskManager.getContext();
- this.component = taskStatus.getServiceComponent();
- this.token = taskStatus.getServiceToken();
- this.userId = taskStatus.getUserId();
- mCallbackHandler = new TaskServiceHandler(looper);
- mPending = new SparseArray<ActiveTask>();
- mCompletedListener = taskManager;
- final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TM_WAKELOCK_PREFIX + component.getPackageName());
- mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid()));
- mWakeLock.setReferenceCounted(false);
- }
+ /** All the information maintained about the task currently being executed. */
+ private TaskStatus mRunningTask;
+ /** Binder to the client service. */
+ ITaskService service;
- @Override
- public void taskFinished(int taskId, boolean reschedule) {
- mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
- .sendToTarget();
- }
+ private final Object mAvailableLock = new Object();
+ /** Whether this context is free. */
+ @GuardedBy("mAvailableLock")
+ private boolean mAvailable;
- @Override
- public void acknowledgeStopMessage(int taskId, boolean reschedule) {
- mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
- .sendToTarget();
+ TaskServiceContext(TaskManagerService service, Looper looper) {
+ this(service.getContext(), service, looper);
}
- @Override
- public void acknowledgeStartMessage(int taskId, boolean ongoing) {
- mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
+ @VisibleForTesting
+ TaskServiceContext(Context context, TaskCompletedListener completedListener, Looper looper) {
+ mContext = context;
+ mCallbackHandler = new TaskServiceHandler(looper);
+ mCompletedListener = completedListener;
}
/**
- * Queue up this task to run on the client. This will execute the task as quickly as possible.
- * @param ts Status of the task to run.
+ * Give a task to this context for execution. Callers must first check {@link #isAvailable()}
+ * to make sure this is a valid context.
+ * @param ts The status of the task that we are going to run.
+ * @return True if the task was accepted and is going to run.
*/
- public void addPendingTask(TaskStatus ts) {
- final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
- final ActiveTask newTask = new ActiveTask(params, VERB_PENDING);
- mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget();
- if (!mBound) {
- Intent intent = new Intent().setComponent(component);
- boolean binding = mContext.bindServiceAsUser(intent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
- new UserHandle(userId));
- if (!binding) {
- Log.e(TAG, component.getShortClassName() + " unavailable.");
- cancelPendingTask(ts);
+ boolean executeRunnableTask(TaskStatus ts) {
+ synchronized (mAvailableLock) {
+ if (!mAvailable) {
+ Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
+ return false;
+ }
+ mAvailable = false;
+ }
+
+ final PowerManager pm =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TM_WAKELOCK_PREFIX + ts.getServiceComponent().getPackageName());
+ mWakeLock.setWorkSource(new WorkSource(ts.getUid()));
+ mWakeLock.setReferenceCounted(false);
+
+ mRunningTask = ts;
+ mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
+
+ mVerb = VERB_BINDING;
+ final Intent intent = new Intent().setComponent(ts.getServiceComponent());
+ boolean binding = mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+ new UserHandle(ts.getUserId()));
+ if (!binding) {
+ if (DEBUG) {
+ Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable.");
}
+ return false;
}
+
+ return true;
}
- /**
- * Called externally when a task that was scheduled for execution should be cancelled.
- * @param ts The status of the task to cancel.
- */
- public void cancelPendingTask(TaskStatus ts) {
- mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */)
- .sendToTarget();
+ /** Used externally to query the running task. Will return null if there is no task running. */
+ TaskStatus getRunningTask() {
+ return mRunningTask;
+ }
+
+ /** Called externally when a task that was scheduled for execution should be cancelled. */
+ void cancelExecutingTask() {
+ mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
}
/**
- * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask}
- * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given
- * ActiveTask.
- * @param op Operation that is taking place.
+ * @return Whether this context is available to handle incoming work.
*/
- private void scheduleOpTimeOut(ActiveTask op) {
- mCallbackHandler.removeMessages(MSG_TIMEOUT, op);
-
- final long timeoutMillis = (op.verb == VERB_EXECUTING) ?
- EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " +
- op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
+ boolean isAvailable() {
+ synchronized (mAvailableLock) {
+ return mAvailable;
}
- Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op);
- mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
- /**
- * @return true if this task is pending or active within this context.
- */
- public boolean hasTaskPending(TaskStatus taskStatus) {
- synchronized (mPending) {
- return mPending.get(taskStatus.getTaskId()) != null;
+ @Override
+ public void taskFinished(int taskId, boolean reschedule) {
+ if (!verifyCallingUid()) {
+ return;
+ }
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void acknowledgeStopMessage(int taskId, boolean reschedule) {
+ if (!verifyCallingUid()) {
+ return;
}
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
}
- public boolean isBound() {
- return mBound;
+ @Override
+ public void acknowledgeStartMessage(int taskId, boolean ongoing) {
+ if (!verifyCallingUid()) {
+ return;
+ }
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
}
/**
- * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work
+ * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
* then we keep the wakelock.
- * @param name The concrete component name of the service that has
- * been connected.
+ * @param name The concrete component name of the service that has been connected.
* @param service The IBinder of the Service's communication channel,
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mBound = true;
+ if (!name.equals(mRunningTask.getServiceComponent())) {
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
+ return;
+ }
this.service = ITaskService.Stub.asInterface(service);
- // Remove all timeouts. We've just connected to the client so there are no other
- // MSG_TIMEOUTs at this point.
+ // Remove all timeouts.
mCallbackHandler.removeMessages(MSG_TIMEOUT);
mWakeLock.acquire();
- mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget();
+ mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
/**
- * When the client service crashes we can have a couple tasks executing, in various stages of
- * undress. We'll cancel all of them and request that they be rescheduled.
+ * If the client service crashes we reschedule this task and clean up.
* @param name The concrete component name of the service whose
*/
@Override
public void onServiceDisconnected(ComponentName name) {
- // Service disconnected... probably client crashed.
- startShutdown();
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
}
/**
- * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more
- * tasks, then we do the shutdown.
+ * This class is reused across different clients, and passes itself in as a callback. Check
+ * whether the client exercising the callback is the client we expect.
+ * @return True if the binder calling is coming from the client we expect.
*/
- private void startShutdown() {
- mCompletedListener.onAllTasksCompleted(token);
- mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget();
- }
-
- /** Tracks a task across its various state changes. */
- private static class ActiveTask {
- final TaskParams params;
- int verb;
- AtomicBoolean cancelled = new AtomicBoolean();
-
- ActiveTask(TaskParams params, int verb) {
- this.params = params;
- this.verb = verb;
- }
-
- @Override
- public String toString() {
- return params.getTaskId() + " " + VERB_STRINGS[verb];
+ private boolean verifyCallingUid() {
+ if (mRunningTask == null || Binder.getCallingUid() != mRunningTask.getUid()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stale callback received, ignoring.");
+ }
+ return false;
}
+ return true;
}
/**
@@ -264,52 +258,67 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
@Override
public void handleMessage(Message message) {
switch (message.what) {
- case MSG_ADD_PENDING:
- if (message.obj != null) {
- ActiveTask pendingTask = (ActiveTask) message.obj;
- mPending.put(pendingTask.params.getTaskId(), pendingTask);
- }
- // fall through.
- case MSG_CHECK_PENDING:
- checkPendingTasksH();
+ case MSG_SERVICE_BOUND:
+ handleServiceBoundH();
break;
case MSG_CALLBACK:
- ActiveTask receivedCallback = mPending.get(message.arg1);
- removeMessages(MSG_TIMEOUT, receivedCallback);
-
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback);
+ if (DEBUG) {
+ Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask);
}
+ removeMessages(MSG_TIMEOUT);
- if (receivedCallback.verb == VERB_STARTING) {
+ if (mVerb == VERB_STARTING) {
final boolean workOngoing = message.arg2 == 1;
- handleStartedH(receivedCallback, workOngoing);
- } else if (receivedCallback.verb == VERB_EXECUTING ||
- receivedCallback.verb == VERB_STOPPING) {
+ handleStartedH(workOngoing);
+ } else if (mVerb == VERB_EXECUTING ||
+ mVerb == VERB_STOPPING) {
final boolean reschedule = message.arg2 == 1;
- handleFinishedH(receivedCallback, reschedule);
+ handleFinishedH(reschedule);
} else {
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "Unrecognised callback: " + receivedCallback);
+ if (DEBUG) {
+ Slog.d(TAG, "Unrecognised callback: " + mRunningTask);
}
}
break;
case MSG_CANCEL:
- ActiveTask cancelled = mPending.get(message.arg1);
- handleCancelH(cancelled);
+ handleCancelH();
break;
case MSG_TIMEOUT:
- // Timeout msgs have the ActiveTask ref so we can remove them easily.
- handleOpTimeoutH((ActiveTask) message.obj);
- break;
- case MSG_SHUTDOWN:
- handleShutdownH();
+ handleOpTimeoutH();
break;
+ case MSG_SHUTDOWN_EXECUTION:
+ closeAndCleanupTaskH(true /* needsReschedule */);
default:
Log.e(TAG, "Unrecognised message: " + message);
}
}
+ /** Start the task on the service. */
+ private void handleServiceBoundH() {
+ if (mVerb != VERB_BINDING) {
+ Slog.e(TAG, "Sending onStartTask for a task that isn't pending. "
+ + VERB_STRINGS[mVerb]);
+ closeAndCleanupTaskH(false /* reschedule */);
+ return;
+ }
+ if (mCancelled.get()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Task cancelled while waiting for bind to complete. "
+ + mRunningTask);
+ }
+ closeAndCleanupTaskH(true /* reschedule */);
+ return;
+ }
+ try {
+ mVerb = VERB_STARTING;
+ scheduleOpTimeOut();
+ service.startTask(mParams);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStart message to '" +
+ mRunningTask.getServiceComponent().getShortClassName() + "' ", e);
+ }
+ }
+
/**
* State behaviours.
* VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout.
@@ -317,24 +326,25 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* _EXECUTING -> Error
* _STOPPING -> Error
*/
- private void handleStartedH(ActiveTask started, boolean workOngoing) {
- switch (started.verb) {
+ private void handleStartedH(boolean workOngoing) {
+ switch (mVerb) {
case VERB_STARTING:
- started.verb = VERB_EXECUTING;
+ mVerb = VERB_EXECUTING;
if (!workOngoing) {
// Task is finished already so fast-forward to handleFinished.
- handleFinishedH(started, false);
+ handleFinishedH(false);
return;
- } else if (started.cancelled.get()) {
+ }
+ if (mCancelled.get()) {
// Cancelled *while* waiting for acknowledgeStartMessage from client.
- handleCancelH(started);
+ handleCancelH();
return;
- } else {
- scheduleOpTimeOut(started);
}
+ scheduleOpTimeOut();
break;
default:
- Log.e(TAG, "Handling started task but task wasn't starting! " + started);
+ Log.e(TAG, "Handling started task but task wasn't starting! Was "
+ + VERB_STRINGS[mVerb] + ".");
return;
}
}
@@ -345,155 +355,104 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* _STARTING -> Error
* _PENDING -> Error
*/
- private void handleFinishedH(ActiveTask executedTask, boolean reschedule) {
- switch (executedTask.verb) {
+ private void handleFinishedH(boolean reschedule) {
+ switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
- closeAndCleanupTaskH(executedTask, reschedule);
+ closeAndCleanupTaskH(reschedule);
break;
default:
- Log.e(TAG, "Got an execution complete message for a task that wasn't being" +
- "executed. " + executedTask);
+ Slog.e(TAG, "Got an execution complete message for a task that wasn't being" +
+ "executed. Was " + VERB_STRINGS[mVerb] + ".");
}
}
/**
* A task can be in various states when a cancel request comes in:
- * VERB_PENDING -> Remove from queue.
- * _STARTING -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}.
+ * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
+ * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
+ * _STARTING -> Mark as cancelled and wait for
+ * {@link TaskServiceContext#acknowledgeStartMessage(int, boolean)}
* _EXECUTING -> call {@link #sendStopMessageH}}.
* _ENDING -> No point in doing anything here, so we ignore.
*/
- private void handleCancelH(ActiveTask cancelledTask) {
- switch (cancelledTask.verb) {
- case VERB_PENDING:
- mPending.remove(cancelledTask.params.getTaskId());
- break;
+ private void handleCancelH() {
+ switch (mVerb) {
+ case VERB_BINDING:
case VERB_STARTING:
- cancelledTask.cancelled.set(true);
+ mCancelled.set(true);
break;
case VERB_EXECUTING:
- cancelledTask.verb = VERB_STOPPING;
- sendStopMessageH(cancelledTask);
+ sendStopMessageH();
break;
case VERB_STOPPING:
// Nada.
break;
default:
- Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask);
+ Slog.e(TAG, "Cancelling a task without a valid verb: " + mVerb);
break;
}
}
- /**
- * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue
- * and reschedule them as if they had failed.
- * Before posting this message, caller must invoke
- * {@link com.android.server.task.TaskCompletedListener#onAllTasksCompleted(int)}.
- */
- private void handleShutdownH() {
- for (int i = 0; i < mPending.size(); i++) {
- ActiveTask at = mPending.valueAt(i);
- closeAndCleanupTaskH(at, true /* needsReschedule */);
- }
- mWakeLock.release();
- mContext.unbindService(TaskServiceContext.this);
- service = null;
- mBound = false;
- }
-
- /**
- * MSG_TIMEOUT gets processed here.
- * @param timedOutTask The task that timed out.
- */
- private void handleOpTimeoutH(ActiveTask timedOutTask) {
+ /** Process MSG_TIMEOUT here. */
+ private void handleOpTimeoutH() {
if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : "
- + timedOutTask.params.getTaskId());
+ Log.d(TAG, "MSG_TIMEOUT of " +
+ mRunningTask.getServiceComponent().getShortClassName() + " : "
+ + mParams.getTaskId());
}
- final int taskId = timedOutTask.params.getTaskId();
- switch (timedOutTask.verb) {
+ final int taskId = mParams.getTaskId();
+ switch (mVerb) {
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
// know what happened so let's log it and notify the TaskManager
// FINISHED/NO-RETRY.
Log.e(TAG, "No response from client for onStartTask '" +
- component.getShortClassName() + "' tId: " + taskId);
- closeAndCleanupTaskH(timedOutTask, false /* needsReschedule */);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ closeAndCleanupTaskH(false /* needsReschedule */);
break;
case VERB_STOPPING:
// At least we got somewhere, so fail but ask the TaskManager to reschedule.
Log.e(TAG, "No response from client for onStopTask, '" +
- component.getShortClassName() + "' tId: " + taskId);
- closeAndCleanupTaskH(timedOutTask, true /* needsReschedule */);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ closeAndCleanupTaskH(true /* needsReschedule */);
break;
case VERB_EXECUTING:
// Not an error - client ran out of time.
Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
" Reporting failure and asking for reschedule. " +
- component.getShortClassName() + "' tId: " + taskId);
- sendStopMessageH(timedOutTask);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ sendStopMessageH();
break;
default:
Log.e(TAG, "Handling timeout for an unknown active task state: "
- + timedOutTask);
+ + mRunningTask);
return;
}
}
/**
- * Called on the handler thread. Checks the state of the pending queue and starts the task
- * if it can. The task only starts if there is capacity on the service.
- */
- private void checkPendingTasksH() {
- if (!mBound) {
- return;
- }
- for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) {
- ActiveTask at = mPending.valueAt(i);
- if (at.verb != VERB_PENDING) {
- continue;
- }
- sendStartMessageH(at);
- }
- }
-
- /**
- * Already running, need to stop. Rund on handler.
- * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from
- * VERB_EXECUTING -> VERB_STOPPING.
+ * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
+ * VERB_STOPPING.
*/
- private void sendStopMessageH(ActiveTask stoppingTask) {
- mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask);
- if (stoppingTask.verb != VERB_EXECUTING) {
- Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask);
- // TODO: Handle error?
+ private void sendStopMessageH() {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+ if (mVerb != VERB_EXECUTING) {
+ Log.e(TAG, "Sending onStopTask for a task that isn't started. " + mRunningTask);
+ closeAndCleanupTaskH(false /* reschedule */);
return;
}
try {
- service.stopTask(stoppingTask.params);
- stoppingTask.verb = VERB_STOPPING;
- scheduleOpTimeOut(stoppingTask);
+ mVerb = VERB_STOPPING;
+ scheduleOpTimeOut();
+ service.stopTask(mParams);
} catch (RemoteException e) {
Log.e(TAG, "Error sending onStopTask to client.", e);
- closeAndCleanupTaskH(stoppingTask, false);
- }
- }
-
- /** Start the task on the service. */
- private void sendStartMessageH(ActiveTask pendingTask) {
- if (pendingTask.verb != VERB_PENDING) {
- Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask);
- // TODO: Handle error?
- }
- try {
- service.startTask(pendingTask.params);
- pendingTask.verb = VERB_STARTING;
- scheduleOpTimeOut(pendingTask);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName()
- + "' ", e);
+ closeAndCleanupTaskH(false);
}
}
@@ -503,13 +462,42 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
* we want to clean up internally.
*/
- private void closeAndCleanupTaskH(ActiveTask completedTask, boolean reschedule) {
- removeMessages(MSG_TIMEOUT, completedTask);
- mPending.remove(completedTask.params.getTaskId());
- if (mPending.size() == 0) {
- startShutdown();
+ private void closeAndCleanupTaskH(boolean reschedule) {
+ removeMessages(MSG_TIMEOUT);
+ mWakeLock.release();
+ mContext.unbindService(TaskServiceContext.this);
+ mWakeLock = null;
+
+ mRunningTask = null;
+ mParams = null;
+ mVerb = -1;
+ mCancelled.set(false);
+
+ service = null;
+
+ mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
+ synchronized (mAvailableLock) {
+ mAvailable = true;
+ }
+ }
+
+ /**
+ * Called when sending a message to the client, over whose execution we have no control. If we
+ * haven't received a response in a certain amount of time, we want to give up and carry on
+ * with life.
+ */
+ private void scheduleOpTimeOut() {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+
+ final long timeoutMillis = (mVerb == VERB_EXECUTING) ?
+ EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling time out for '" +
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: " +
+ mParams.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
}
- mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule);
+ Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
+ mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
}
}
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
index 3bfc8a5..f72ab22 100644
--- a/services/core/java/com/android/server/task/TaskStore.java
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -16,12 +16,18 @@
package com.android.server.task;
+import android.app.task.Task;
import android.content.Context;
-import android.content.Task;
+import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.server.task.controllers.TaskStatus;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
/**
* Maintain a list of classes, and accessor methods/logic for these tasks.
* This class offers the following functionality:
@@ -35,53 +41,122 @@ import com.android.server.task.controllers.TaskStatus;
* - This class is <strong>not</strong> thread-safe.
*/
public class TaskStore {
-
- /**
- * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}.
- */
- final SparseArray<TaskStatus> mTasks;
+ private static final String TAG = "TaskManagerStore";
+ /** Threshold to adjust how often we want to write to the db. */
+ private static final int MAX_OPS_BEFORE_WRITE = 1;
+ final ArraySet<TaskStatus> mTasks;
final Context mContext;
+ private int mDirtyOperations;
+
TaskStore(Context context) {
- mTasks = intialiseTaskMapFromDisk();
+ mTasks = intialiseTasksFromDisk();
mContext = context;
+ mDirtyOperations = 0;
+ }
+
+ /**
+ * Add a task to the master list, persisting it if necessary. If the TaskStatus already exists,
+ * it will be replaced.
+ * @param taskStatus Task to add.
+ * @return true if the operation succeeded.
+ */
+ public boolean add(TaskStatus taskStatus) {
+ if (taskStatus.isPersisted()) {
+ if (!maybeWriteStatusToDisk()) {
+ return false;
+ }
+ }
+ mTasks.remove(taskStatus);
+ mTasks.add(taskStatus);
+ return true;
+ }
+
+ public int size() {
+ return mTasks.size();
+ }
+
+ /**
+ * Remove the provided task. Will also delete the task if it was persisted.
+ * @return The TaskStatus that was removed, or null if an invalid token was provided.
+ */
+ public boolean remove(TaskStatus taskStatus) {
+ boolean removed = mTasks.remove(taskStatus);
+ if (!removed) {
+ Slog.e(TAG, "Error removing task: " + taskStatus);
+ return false;
+ } else {
+ maybeWriteStatusToDisk();
+ }
+ return true;
}
/**
- * Add a task to the master list, persisting it if necessary.
- * Will first check to see if the task already exists. If so, it will replace it.
- * {@link android.content.pm.PackageManager} is queried to see if the calling package has
- * permission to
- * @param task Task to add.
- * @return The initialised TaskStatus object if this operation was successful, null if it
- * failed.
+ * Removes all TaskStatus objects for a given uid from the master list. Note that it is
+ * possible to remove a task that is pending/active. This operation will succeed, and the
+ * removal will take effect when the task has completed executing.
+ * @param uid Uid of the requesting app.
+ * @return True if at least one task was removed, false if nothing matching the provided uId
+ * was found.
*/
- public TaskStatus addNewTaskForUser(Task task, int userId, int uId,
- boolean canPersistTask) {
- TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId);
- if (canPersistTask && task.isPeriodic()) {
- if (writeStatusToDisk()) {
- mTasks.put(taskStatus.hashCode(), taskStatus);
+ public boolean removeAllByUid(int uid) {
+ Iterator<TaskStatus> it = mTasks.iterator();
+ boolean removed = false;
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid) {
+ it.remove();
+ removed = true;
}
}
- return taskStatus;
+ if (removed) {
+ maybeWriteStatusToDisk();
+ }
+ return removed;
}
/**
- * 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.
+ * Remove the TaskStatus that matches the provided uId and taskId. Note that it is possible
+ * to remove a task that is pending/active. This operation will succeed, and the removal will
+ * take effect when the task has completed executing.
+ * @param uid Uid of the requesting app.
+ * @param taskId Task id, specified at schedule-time.
+ * @return true if a removal occurred, false if the provided parameters didn't match anything.
*/
- public void remove(Task task) {
+ public boolean remove(int uid, int taskId) {
+ Iterator<TaskStatus> it = mTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid && ts.getTaskId() == taskId) {
+ it.remove();
+ maybeWriteStatusToDisk();
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * @return The live array of TaskStatus objects.
+ */
+ public Set<TaskStatus> getTasks() {
+ return mTasks;
}
/**
* Every time the state changes we write all the tasks in one swathe, instead of trying to
* track incremental changes.
+ * @return Whether the operation was successful. This will only fail for e.g. if the system is
+ * low on storage. If this happens, we continue as normal
*/
- private boolean writeStatusToDisk() {
+ private boolean maybeWriteStatusToDisk() {
+ mDirtyOperations++;
+ if (mDirtyOperations > MAX_OPS_BEFORE_WRITE) {
+ for (TaskStatus ts : mTasks) {
+ //
+ }
+ mDirtyOperations = 0;
+ }
return true;
}
@@ -90,14 +165,7 @@ public class TaskStore {
* @return
*/
// TODO: Implement this.
- private SparseArray<TaskStatus> intialiseTaskMapFromDisk() {
- return new SparseArray<TaskStatus>();
- }
-
- /**
- * @return The live array of TaskStatus objects.
- */
- public SparseArray<TaskStatus> getTasks() {
- return mTasks;
+ private ArraySet<TaskStatus> intialiseTasksFromDisk() {
+ return new ArraySet<TaskStatus>();
}
}
diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
index 6a4e1f3..474af8f 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -25,6 +25,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Slog;
import com.android.server.task.TaskManagerService;
@@ -32,21 +33,33 @@ import java.util.LinkedList;
import java.util.List;
/**
- *
+ * Handles changes in connectivity.
+ * We are only interested in metered vs. unmetered networks, and we're interested in them on a
+ * per-user basis.
*/
public class ConnectivityController extends StateController {
private static final String TAG = "TaskManager.Connectivity";
+ private static final boolean DEBUG = true;
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
+ /** Singleton. */
+ private static ConnectivityController mSingleton;
/** Track whether the latest active network is metered. */
private boolean mMetered;
/** Track whether the latest active network is connected. */
private boolean mConnectivity;
- public ConnectivityController(TaskManagerService service) {
+ public static synchronized ConnectivityController get(TaskManagerService taskManager) {
+ if (mSingleton == null) {
+ mSingleton = new ConnectivityController(taskManager);
+ }
+ return mSingleton;
+ }
+
+ private ConnectivityController(TaskManagerService service) {
super(service);
// Register connectivity changed BR.
IntentFilter intentFilter = new IntentFilter();
@@ -56,7 +69,7 @@ public class ConnectivityController extends StateController {
}
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public synchronized void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
taskStatus.meteredConstraintSatisfied.set(mMetered);
@@ -65,7 +78,7 @@ public class ConnectivityController extends StateController {
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
mTrackedTasks.remove(taskStatus);
}
@@ -73,16 +86,20 @@ public class ConnectivityController extends StateController {
* @param userId Id of the user for whom we are updating the connectivity state.
*/
private void updateTrackedTasks(int userId) {
+ boolean changed = false;
for (TaskStatus ts : mTrackedTasks) {
- if (ts.userId != userId) {
+ if (ts.getUserId() != userId) {
continue;
}
boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
- mStateChangedListener.onTaskStateChanged(ts);
+ changed = true;
}
}
+ if (changed) {
+ mStateChangedListener.onControllerStateChanged();
+ }
}
class ConnectivityChangedReceiver extends BroadcastReceiver {
@@ -106,7 +123,7 @@ public class ConnectivityController extends StateController {
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) {
+ if (activeNetwork != null && activeNetwork.getType() == networkType) {
final int userid = context.getUserId();
mMetered = false;
mConnectivity =
@@ -117,7 +134,9 @@ public class ConnectivityController extends StateController {
updateTrackedTasks(userid);
}
} else {
- Log.w(TAG, "Unrecognised action in intent: " + action);
+ if (DEBUG) {
+ Slog.d(TAG, "Unrecognised action in intent: " + action);
+ }
}
}
};
diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java
index a319a31..9489644 100644
--- a/services/core/java/com/android/server/task/controllers/IdleController.java
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -49,7 +49,7 @@ public class IdleController extends StateController {
private static Object sCreationLock = new Object();
private static volatile IdleController sController;
- public IdleController getController(TaskManagerService service) {
+ public static IdleController get(TaskManagerService service) {
synchronized (sCreationLock) {
if (sController == null) {
sController = new IdleController(service);
@@ -67,7 +67,7 @@ public class IdleController extends StateController {
* StateController interface
*/
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasIdleConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.add(taskStatus);
@@ -77,7 +77,7 @@ public class IdleController extends StateController {
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public void maybeStopTrackingTask(TaskStatus taskStatus) {
synchronized (mTrackedTasks) {
mTrackedTasks.remove(taskStatus);
}
@@ -90,9 +90,9 @@ public class IdleController extends StateController {
synchronized (mTrackedTasks) {
for (TaskStatus task : mTrackedTasks) {
task.idleConstraintSatisfied.set(isIdle);
- mStateChangedListener.onTaskStateChanged(task);
}
}
+ mStateChangedListener.onControllerStateChanged();
}
/**
diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java
index e1cd662..ed31eac 100644
--- a/services/core/java/com/android/server/task/controllers/StateController.java
+++ b/services/core/java/com/android/server/task/controllers/StateController.java
@@ -42,10 +42,10 @@ public abstract class StateController {
* Also called when updating a task, so implementing controllers have to be aware of
* preexisting tasks.
*/
- public abstract void maybeTrackTaskState(TaskStatus taskStatus);
+ public abstract void maybeStartTrackingTask(TaskStatus taskStatus);
/**
* Remove task - this will happen if the task is cancelled, completed, etc.
*/
- public abstract void removeTaskStateIfTracked(TaskStatus taskStatus);
+ public abstract void maybeStopTrackingTask(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
index d96fedc..b7f84ec 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -16,17 +16,18 @@
package com.android.server.task.controllers;
+import android.app.task.Task;
import android.content.ComponentName;
-import android.content.Task;
-import android.content.pm.PackageParser;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.UserHandle;
+import java.io.PrintWriter;
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.
+ * Created from the public {@link android.app.task.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.
@@ -36,80 +37,88 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @hide
*/
public class TaskStatus {
- final int taskId;
- final int userId;
+ final Task task;
final int uId;
- final ComponentName component;
- final Bundle extras;
+ /** At reschedule time we need to know whether to update task on disk. */
+ final boolean persisted;
+
+ // Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
- final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean deadlineConstraintSatisfied = 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;
-
+ /**
+ * Earliest point in the future at which this task will be eligible to run. A value of 0
+ * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
+ */
private long earliestRunTimeElapsedMillis;
+ /**
+ * Latest point in the future at which this task must be run. A value of {@link Long#MAX_VALUE}
+ * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
+ */
private long latestRunTimeElapsedMillis;
- /** Provide a unique handle to the service that this task will be run on. */
- public int getServiceToken() {
- return component.hashCode() + userId;
- }
+ private final int numFailures;
- /** 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?
- public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) {
- return new TaskStatus(task, userId, uId);
+ /** Provide a handle to the service that this task will be run on. */
+ public int getServiceToken() {
+ return uId;
}
- /** Set up the state of a newly scheduled task. */
- TaskStatus(Task task, int userId, int uId) {
- this.taskId = task.getTaskId();
- this.userId = userId;
- this.component = task.getService();
- this.extras = task.getExtras();
+ /** Create a newly scheduled task. */
+ public TaskStatus(Task task, int uId, boolean persisted) {
+ this.task = task;
this.uId = uId;
+ this.numFailures = 0;
+ this.persisted = persisted;
- hasChargingConstraint = task.isRequireCharging();
- hasIdleConstraint = task.isRequireDeviceIdle();
-
+ final long elapsedNow = SystemClock.elapsedRealtime();
// 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;
+ earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
+ elapsedNow + task.getMinLatencyMillis() : 0L;
+ latestRunTimeElapsedMillis = task.hasLateConstraint() ?
+ elapsedNow + task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
}
+ }
- // Networking constraints
- hasMeteredConstraint = task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
- hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
+ public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsed,
+ long newLatestRuntimeElapsed, int backoffAttempt) {
+ this.task = rescheduling.task;
+
+ this.uId = rescheduling.getUid();
+ this.persisted = rescheduling.isPersisted();
+ this.numFailures = backoffAttempt;
+
+ earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed;
+ latestRunTimeElapsedMillis = newLatestRuntimeElapsed;
+ }
+
+ public Task getTask() {
+ return task;
}
public int getTaskId() {
- return taskId;
+ return task.getId();
+ }
+
+ public int getNumFailures() {
+ return numFailures;
}
public ComponentName getServiceComponent() {
- return component;
+ return task.getService();
}
public int getUserId() {
- return userId;
+ return UserHandle.getUserId(uId);
}
public int getUid() {
@@ -117,53 +126,61 @@ public class TaskStatus {
}
public Bundle getExtras() {
- return extras;
+ return task.getExtras();
}
- boolean hasConnectivityConstraint() {
- return hasConnectivityConstraint;
+ public boolean hasConnectivityConstraint() {
+ return task.getNetworkCapabilities() == Task.NetworkType.ANY;
}
- boolean hasMeteredConstraint() {
- return hasMeteredConstraint;
+ public boolean hasMeteredConstraint() {
+ return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
}
- boolean hasChargingConstraint() {
- return hasChargingConstraint;
+ public boolean hasChargingConstraint() {
+ return task.isRequireCharging();
}
- boolean hasTimingConstraint() {
- return hasTimingConstraint;
+ public boolean hasTimingDelayConstraint() {
+ return earliestRunTimeElapsedMillis != 0L;
}
- boolean hasIdleConstraint() {
- return hasIdleConstraint;
+ public boolean hasDeadlineConstraint() {
+ return latestRunTimeElapsedMillis != Long.MAX_VALUE;
}
- long getEarliestRunTime() {
+ public boolean hasIdleConstraint() {
+ return task.isRequireDeviceIdle();
+ }
+
+ public long getEarliestRunTime() {
return earliestRunTimeElapsedMillis;
}
- long getLatestRunTime() {
+ public long getLatestRunTimeElapsed() {
return latestRunTimeElapsedMillis;
}
+ public boolean isPersisted() {
+ return persisted;
+ }
/**
- * @return whether this task is ready to run, based on its requirements.
+ * @return Whether or not 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());
+ return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
+ && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
+ && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
+ && (!hasMeteredConstraint() || meteredConstraintSatisfied.get())
+ && (!hasIdleConstraint() || idleConstraintSatisfied.get())
+ && (!hasDeadlineConstraint() || deadlineConstraintSatisfied.get());
}
@Override
public int hashCode() {
- int result = component.hashCode();
- result = 31 * result + taskId;
- result = 31 * result + userId;
+ int result = getServiceComponent().hashCode();
+ result = 31 * result + task.getId();
+ result = 31 * result + uId;
return result;
}
@@ -173,8 +190,15 @@ public class TaskStatus {
if (!(o instanceof TaskStatus)) return false;
TaskStatus that = (TaskStatus) o;
- return ((taskId == that.taskId)
- && (userId == that.userId)
- && (component.equals(that.component)));
+ return ((task.getId() == that.task.getId())
+ && (uId == that.uId)
+ && (getServiceComponent().equals(that.getServiceComponent())));
+ }
+
+ // Dumpsys infrastructure
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("Task "); pw.println(task.getId());
+ pw.print(prefix); pw.print("uid="); pw.println(uId);
+ pw.print(prefix); pw.print("component="); pw.println(task.getService());
}
}
diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java
index 6d97a53..72f312c 100644
--- a/services/core/java/com/android/server/task/controllers/TimeController.java
+++ b/services/core/java/com/android/server/task/controllers/TimeController.java
@@ -23,7 +23,6 @@ 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;
@@ -54,8 +53,17 @@ public class TimeController extends StateController {
private AlarmManager mAlarmService = null;
/** List of tracked tasks, sorted asc. by deadline */
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
+ /** Singleton. */
+ private static TimeController mSingleton;
- public TimeController(TaskManagerService service) {
+ public static synchronized TimeController get(TaskManagerService taskManager) {
+ if (mSingleton == null) {
+ mSingleton = new TimeController(taskManager);
+ }
+ return mSingleton;
+ }
+
+ private TimeController(TaskManagerService service) {
super(service);
mTaskExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
@@ -75,8 +83,8 @@ public class TimeController extends StateController {
* list.
*/
@Override
- public synchronized void maybeTrackTaskState(TaskStatus task) {
- if (task.hasTimingConstraint()) {
+ public synchronized void maybeStartTrackingTask(TaskStatus task) {
+ if (task.hasTimingDelayConstraint()) {
ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
while (it.hasPrevious()) {
TaskStatus ts = it.previous();
@@ -85,13 +93,13 @@ public class TimeController extends StateController {
it.remove();
it.add(task);
break;
- } else if (ts.getLatestRunTime() < task.getLatestRunTime()) {
+ } else if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
// Insert
it.add(task);
break;
}
}
- maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTime());
+ maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed());
}
}
@@ -100,12 +108,12 @@ public class TimeController extends StateController {
* so, update them.
*/
@Override
- public synchronized void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
handleTaskDelayExpired();
}
- if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTime()) {
+ if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) {
handleTaskDeadlineExpired();
}
}
@@ -140,10 +148,10 @@ public class TimeController extends StateController {
* 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);
+ return (!taskStatus.hasTimingDelayConstraint() ||
+ taskStatus.timeDelayConstraintSatisfied.get()) &&
+ (!taskStatus.hasDeadlineConstraint() ||
+ taskStatus.deadlineConstraintSatisfied.get());
}
private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
@@ -174,10 +182,10 @@ public class TimeController extends StateController {
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
- final long taskDeadline = ts.getLatestRunTime();
+ final long taskDeadline = ts.getLatestRunTimeElapsed();
if (taskDeadline <= nowElapsedMillis) {
- ts.timeConstraintSatisfied.set(true);
+ ts.deadlineConstraintSatisfied.set(true);
mStateChangedListener.onTaskDeadlineExpired(ts);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
@@ -199,10 +207,12 @@ public class TimeController extends StateController {
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
final TaskStatus ts = it.next();
+ if (!ts.hasTimingDelayConstraint()) {
+ continue;
+ }
final long taskDelayTime = ts.getEarliestRunTime();
if (taskDelayTime < nowElapsedMillis) {
- ts.timeConstraintSatisfied.set(true);
- mStateChangedListener.onTaskStateChanged(ts);
+ ts.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingTask(ts)) {
it.remove();
}
@@ -212,6 +222,7 @@ public class TimeController extends StateController {
}
}
}
+ mStateChangedListener.onControllerStateChanged();
maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index e2d2ac6..e007600 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -24,9 +24,7 @@ import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.IRemoteCallback;
-import android.os.SystemProperties;
import android.util.Slog;
-import android.view.View;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -117,7 +115,7 @@ public class AppTransition implements Dump {
private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
private static final int DEFAULT_APP_TRANSITION_DURATION = 250;
- private static final int THUMBNAIL_APP_TRANSITION_DURATION = 225;
+ private static final int THUMBNAIL_APP_TRANSITION_DURATION = 275;
private final Context mContext;
private final Handler mH;
@@ -299,7 +297,7 @@ public class AppTransition implements Dump {
return null;
}
- Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) {
+ Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) {
int anim = 0;
Context context = mContext;
if (animAttr >= 0) {
@@ -315,7 +313,19 @@ public class AppTransition implements Dump {
return null;
}
- private Animation loadAnimation(String packageName, int resId) {
+ Animation loadAnimationRes(WindowManager.LayoutParams lp, int resId) {
+ Context context = mContext;
+ if (resId >= 0) {
+ AttributeCache.Entry ent = getCachedAnimations(lp);
+ if (ent != null) {
+ context = ent.context;
+ }
+ return AnimationUtils.loadAnimation(context, resId);
+ }
+ return null;
+ }
+
+ private Animation loadAnimationRes(String packageName, int resId) {
int anim = 0;
Context context = mContext;
if (resId >= 0) {
@@ -695,11 +705,31 @@ public class AppTransition implements Dump {
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- int appWidth, int appHeight, int orientation,
- Rect containingFrame, Rect contentInsets, boolean isFullScreen) {
+ int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
+ boolean isFullScreen, boolean isVoiceInteraction) {
Animation a;
- if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- a = loadAnimation(mNextAppTransitionPackage, enter ?
+ if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
+ || transit == TRANSIT_TASK_OPEN
+ || transit == TRANSIT_TASK_TO_FRONT)) {
+ a = loadAnimationRes(lp, enter
+ ? com.android.internal.R.anim.voice_activity_open_enter
+ : com.android.internal.R.anim.voice_activity_open_exit);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "applyAnimation voice:"
+ + " anim=" + a + " transit=" + transit + " isEntrance=" + enter
+ + " Callers=" + Debug.getCallers(3));
+ } else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_CLOSE
+ || transit == TRANSIT_TASK_CLOSE
+ || transit == TRANSIT_TASK_TO_BACK)) {
+ a = loadAnimationRes(lp, enter
+ ? com.android.internal.R.anim.voice_activity_close_enter
+ : com.android.internal.R.anim.voice_activity_close_exit);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "applyAnimation voice:"
+ + " anim=" + a + " transit=" + transit + " isEntrance=" + enter
+ + " Callers=" + Debug.getCallers(3));
+ } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
+ a = loadAnimationRes(mNextAppTransitionPackage, enter ?
mNextAppTransitionEnter : mNextAppTransitionExit);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
@@ -782,7 +812,7 @@ public class AppTransition implements Dump {
: WindowAnimation_wallpaperIntraCloseExitAnimation;
break;
}
- a = animAttr != 0 ? loadAnimation(lp, animAttr) : null;
+ a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ca4ad8a..12c15e2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -50,6 +50,8 @@ class AppWindowToken extends WindowToken {
final WindowAnimator mAnimator;
+ final boolean voiceInteraction;
+
int groupId = -1;
boolean appFullscreen;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -107,11 +109,13 @@ class AppWindowToken extends WindowToken {
boolean mDeferRemoval;
- AppWindowToken(WindowManagerService _service, IApplicationToken _token) {
+ AppWindowToken(WindowManagerService _service, IApplicationToken _token,
+ boolean _voiceInteraction) {
super(_service, _token.asBinder(),
WindowManager.LayoutParams.TYPE_APPLICATION, true);
appWindowToken = this;
appToken = _token;
+ voiceInteraction = _voiceInteraction;
mInputApplicationHandle = new InputApplicationHandle(this);
mAnimator = service.mAnimator;
mAppAnimator = new AppWindowAnimator(this);
@@ -249,7 +253,7 @@ class AppWindowToken extends WindowToken {
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
if (appToken != null) {
- pw.print(prefix); pw.println("app=true");
+ pw.print(prefix); pw.print("app=true voiceInteraction="); pw.println(voiceInteraction);
}
if (allAppWindows.size() > 0) {
pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 266527d..6fdd535 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -36,6 +36,7 @@ import android.util.TimeUtils;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
+import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import com.android.server.wm.WindowManagerService.LayoutFields;
@@ -50,6 +51,9 @@ import java.util.ArrayList;
public class WindowAnimator {
private static final String TAG = "WindowAnimator";
+ /** How long to give statusbar to clear the private keyguard flag when animating out */
+ private static final long KEYGUARD_ANIM_TIMEOUT_MS = 1000;
+
final WindowManagerService mService;
final Context mContext;
final WindowManagerPolicy mPolicy;
@@ -82,6 +86,8 @@ public class WindowAnimator {
boolean mInitialized = false;
+ boolean mKeyguardGoingAway;
+
// forceHiding states.
static final int KEYGUARD_NOT_SHOWN = 0;
static final int KEYGUARD_ANIMATING_IN = 1;
@@ -213,6 +219,29 @@ public class WindowAnimator {
final WindowList windows = mService.getWindowListLocked(displayId);
ArrayList<WindowStateAnimator> unForceHiding = null;
boolean wallpaperInUnForceHiding = false;
+
+ if (mKeyguardGoingAway) {
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ WindowState win = windows.get(i);
+ if (!mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+ continue;
+ }
+ final WindowStateAnimator winAnimator = win.mWinAnimator;
+ if (mPolicy.doesForceHide(win.mAttrs)) {
+ if (!winAnimator.mAnimating) {
+ // Create a new animation to delay until keyguard is gone on its own.
+ winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f);
+ winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS);
+ winAnimator.mAnimationIsEntrance = false;
+ }
+ } else {
+ mKeyguardGoingAway = false;
+ winAnimator.clearAnimation();
+ }
+ break;
+ }
+ }
+
mForceHiding = KEYGUARD_NOT_SHOWN;
for (int i = windows.size() - 1; i >= 0; i--) {
@@ -239,7 +268,7 @@ public class WindowAnimator {
}
}
- if (mPolicy.doesForceHide(win, win.mAttrs)) {
+ if (mPolicy.doesForceHide(win.mAttrs)) {
if (!wasAnimating && nowAnimating) {
if (WindowManagerService.DEBUG_ANIM ||
WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
@@ -252,6 +281,11 @@ public class WindowAnimator {
getPendingLayoutChanges(displayId));
}
mService.mFocusMayChange = true;
+ } else if (mKeyguardGoingAway && !nowAnimating) {
+ // Timeout!!
+ Slog.e(TAG, "Timeout waiting for animation to startup");
+ mPolicy.startKeyguardExitAnimation(0);
+ mKeyguardGoingAway = false;
}
if (win.isReadyForDisplay()) {
if (nowAnimating) {
@@ -265,7 +299,7 @@ public class WindowAnimator {
}
}
if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
- "Force hide " + mForceHiding
+ "Force hide " + forceHidingToString()
+ " hasSurface=" + win.mHasSurface
+ " policyVis=" + win.mPolicyVisibility
+ " destroying=" + win.mDestroying
@@ -349,12 +383,18 @@ public class WindowAnimator {
// If we have windows that are being show due to them no longer
// being force-hidden, apply the appropriate animation to them.
if (unForceHiding != null) {
+ boolean startKeyguardExit = true;
for (int i=unForceHiding.size()-1; i>=0; i--) {
Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding);
if (a != null) {
final WindowStateAnimator winAnimator = unForceHiding.get(i);
winAnimator.setAnimation(a);
winAnimator.mAnimationIsEntrance = true;
+ if (startKeyguardExit) {
+ // Do one time only.
+ mPolicy.startKeyguardExitAnimation(a.getStartOffset());
+ startKeyguardExit = false;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c23d1ea..7382f4c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2215,6 +2215,11 @@ public class WindowManagerService extends IWindowManager.Stub
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
+ if (type == TYPE_VOICE_INTERACTION) {
+ Slog.w(TAG, "Attempted to add voice interaction window with unknown token "
+ + attrs.token + ". Aborting.");
+ return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+ }
if (type == TYPE_WALLPAPER) {
Slog.w(TAG, "Attempted to add wallpaper window with unknown token "
+ attrs.token + ". Aborting.");
@@ -2250,6 +2255,12 @@ public class WindowManagerService extends IWindowManager.Stub
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
+ } else if (type == TYPE_VOICE_INTERACTION) {
+ if (token.windowType != TYPE_VOICE_INTERACTION) {
+ Slog.w(TAG, "Attempted to add voice interaction window with bad token "
+ + attrs.token + ". Aborting.");
+ return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+ }
} else if (type == TYPE_WALLPAPER) {
if (token.windowType != TYPE_WALLPAPER) {
Slog.w(TAG, "Attempted to add wallpaper window with bad token "
@@ -3173,7 +3184,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
private boolean applyAnimationLocked(AppWindowToken atoken,
- WindowManager.LayoutParams lp, int transit, boolean enter) {
+ WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) {
// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
@@ -3203,7 +3214,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
- mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen);
+ mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen,
+ isVoiceInteraction);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -3423,7 +3435,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
- int configChanges) {
+ int configChanges, boolean voiceInteraction) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3449,7 +3461,7 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.w(TAG, "Attempted to add existing app token: " + token);
return;
}
- atoken = new AppWindowToken(this, token);
+ atoken = new AppWindowToken(this, token, voiceInteraction);
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
atoken.groupId = taskId;
atoken.appFullscreen = fullscreen;
@@ -4201,7 +4213,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
- boolean visible, int transit, boolean performLayout) {
+ boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
boolean delayed = false;
if (wtoken.clientHidden == visible) {
@@ -4222,7 +4234,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
wtoken.mAppAnimator.animation = null;
}
- if (applyAnimationLocked(wtoken, lp, transit, visible)) {
+ if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) {
delayed = runningAppAnimation = true;
}
WindowState window = wtoken.findMainWindow();
@@ -4400,7 +4412,7 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,
- true);
+ true, wtoken.voiceInteraction);
wtoken.updateReportedVisibilityLocked();
Binder.restoreCallingIdentity(origId);
}
@@ -4547,7 +4559,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken);
delayed = setTokenVisibilityLocked(wtoken, null, false,
- AppTransition.TRANSIT_UNSET, true);
+ AppTransition.TRANSIT_UNSET, true, wtoken.voiceInteraction);
wtoken.inPendingTransaction = false;
mOpeningApps.remove(wtoken);
wtoken.waitingToShow = false;
@@ -5127,6 +5139,18 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void keyguardGoingAway() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+ }
+ synchronized (mWindowMap) {
+ mAnimator.mKeyguardGoingAway = true;
+ requestTraversalLocked();
+ }
+ }
+
+ @Override
public void closeSystemDialogs(String reason) {
synchronized(mWindowMap) {
final int numDisplays = mDisplayContents.size();
@@ -7149,9 +7173,7 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int TAP_OUTSIDE_STACK = 31;
public static final int NOTIFY_ACTIVITY_DRAWN = 32;
- public static final int REMOVE_STARTING_TIMEOUT = 33;
-
- public static final int SHOW_DISPLAY_MASK = 34;
+ public static final int SHOW_DISPLAY_MASK = 33;
@Override
public void handleMessage(Message msg) {
@@ -8528,6 +8550,7 @@ public class WindowManagerService extends IWindowManager.Stub
LayoutParams animLp = null;
int bestAnimLayer = -1;
boolean fullscreenAnim = false;
+ boolean voiceInteraction = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New wallpaper target=" + mWallpaperTarget
@@ -8572,6 +8595,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ voiceInteraction |= wtoken.voiceInteraction;
+
if (wtoken.appFullscreen) {
WindowState ws = wtoken.findMainWindow();
if (ws != null) {
@@ -8644,7 +8669,7 @@ public class WindowManagerService extends IWindowManager.Stub
appAnimator.clearThumbnail();
wtoken.inPendingTransaction = false;
appAnimator.animation = null;
- setTokenVisibilityLocked(wtoken, animLp, true, transit, false);
+ setTokenVisibilityLocked(wtoken, animLp, true, transit, false, voiceInteraction);
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToShow = false;
@@ -8676,7 +8701,7 @@ public class WindowManagerService extends IWindowManager.Stub
wtoken.mAppAnimator.clearThumbnail();
wtoken.inPendingTransaction = false;
wtoken.mAppAnimator.animation = null;
- setTokenVisibilityLocked(wtoken, animLp, false, transit, false);
+ setTokenVisibilityLocked(wtoken, animLp, false, transit, false, voiceInteraction);
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToHide = false;
// Force the allDrawn flag, because we want to start
@@ -10266,10 +10291,6 @@ public class WindowManagerService extends IWindowManager.Stub
mPolicy.lockNow(options);
}
- public void showRecentApps() {
- mPolicy.showRecentApps();
- }
-
@Override
public boolean isSafeModeEnabled() {
return mSafeMode;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c88382c..4a80e3e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -714,6 +714,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
return mAppToken != null ? mAppToken.appToken : null;
}
+ @Override
+ public boolean isVoiceInteraction() {
+ return mAppToken != null ? mAppToken.voiceInteraction : false;
+ }
+
boolean setInsetsChanged() {
mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets);
mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1e79dcb..e257ebc 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1658,7 +1658,7 @@ class WindowStateAnimator {
break;
}
if (attr >= 0) {
- a = mService.mAppTransition.loadAnimation(mWin.mAttrs, attr);
+ a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
}
}
if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index de46b16..0f24ff6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -83,6 +83,7 @@ import com.android.server.power.ShutdownThread;
import com.android.server.search.SearchManagerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.task.TaskManagerService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.twilight.TwilightService;
@@ -132,6 +133,8 @@ public final class SystemServer {
"com.android.server.hdmi.HdmiCecService";
private static final String ETHERNET_SERVICE_CLASS =
"com.android.server.ethernet.EthernetService";
+ private static final String TASK_SERVICE_CLASS =
+ "com.android.server.task.TaskManagerService";
private final int mFactoryTestMode;
private Timer mProfilerSnapshotTimer;
@@ -183,7 +186,7 @@ public final class SystemServer {
// had to fallback to a different runtime because it is
// running as root and we need to be the system user to set
// the property. http://b/11463182
- SystemProperties.set("persist.sys.dalvik.vm.lib.1", VMRuntime.getRuntime().vmLibrary());
+ SystemProperties.set("persist.sys.dalvik.vm.lib.2", VMRuntime.getRuntime().vmLibrary());
// Enable the sampling profiler.
if (SamplingProfilerIntegration.isEnabled()) {
@@ -831,6 +834,8 @@ public final class SystemServer {
mSystemServiceManager.startService(UiModeManagerService.class);
+ mSystemServiceManager.startService(TaskManagerService.class);
+
if (!disableNonCoreServices) {
try {
if (pm.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 9b6daad..62ff121 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -113,7 +113,7 @@ class VoiceInteractionManagerServiceImpl {
if (mBound) {
try {
mIWindowManager.addWindowToken(mToken,
- WindowManager.LayoutParams.TYPE_INPUT_METHOD);
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION);
} catch (RemoteException e) {
Slog.w(TAG, "Failed adding window token", e);
}