diff options
Diffstat (limited to 'services')
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); } |