diff options
Diffstat (limited to 'services/core/java/com/android/server/am/ActivityStackSupervisor.java')
-rw-r--r-- | services/core/java/com/android/server/am/ActivityStackSupervisor.java | 2974 |
1 files changed, 2974 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java new file mode 100644 index 0000000..f1ac7fe --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -0,0 +1,2974 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.Manifest.permission.START_ANY_ACTIVITY; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.server.am.ActivityManagerService.localLOGV; +import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerService.DEBUG_FOCUS; +import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE; +import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS; +import static com.android.server.am.ActivityManagerService.DEBUG_STACK; +import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH; +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 android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityManager.StackInfo; +import android.app.ActivityOptions; +import android.app.AppGlobals; +import android.app.IActivityContainer; +import android.app.IActivityContainerCallback; +import android.app.IActivityManager; +import android.app.IApplicationThread; +import android.app.IThumbnailReceiver; +import android.app.PendingIntent; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.IActivityManager.WaitResult; +import android.app.ResultInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.graphics.Point; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Binder; +import android.os.Bundle; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.EventLog; +import android.util.Slog; +import android.util.SparseArray; + +import android.util.SparseIntArray; +import android.view.Display; +import android.view.DisplayInfo; +import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.os.TransferPipe; +import com.android.server.am.ActivityManagerService.PendingActivityLaunch; +import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.wm.WindowManagerService; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public final class ActivityStackSupervisor implements DisplayListener { + static final boolean DEBUG = ActivityManagerService.DEBUG || false; + static final boolean DEBUG_ADD_REMOVE = DEBUG || false; + static final boolean DEBUG_APP = DEBUG || false; + static final boolean DEBUG_SAVED_STATE = DEBUG || false; + static final boolean DEBUG_STATES = DEBUG || false; + static final boolean DEBUG_IDLE = DEBUG || false; + + public static final int HOME_STACK_ID = 0; + + /** How long we wait until giving up on the last activity telling us it is idle. */ + static final int IDLE_TIMEOUT = 10*1000; + + /** How long we can hold the sleep wake lock before giving up. */ + static final int SLEEP_TIMEOUT = 5*1000; + + // How long we can hold the launch wake lock before giving up. + static final int LAUNCH_TIMEOUT = 10*1000; + + static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG; + static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_STACK_MSG + 1; + static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2; + static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3; + static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4; + static final int HANDLE_DISPLAY_ADDED = FIRST_SUPERVISOR_STACK_MSG + 5; + static final int HANDLE_DISPLAY_CHANGED = FIRST_SUPERVISOR_STACK_MSG + 6; + static final int HANDLE_DISPLAY_REMOVED = FIRST_SUPERVISOR_STACK_MSG + 7; + + + // For debugging to make sure the caller when acquiring/releasing our + // wake lock is the system process. + static final boolean VALIDATE_WAKE_LOCK_CALLER = false; + + final ActivityManagerService mService; + + final ActivityStackSupervisorHandler mHandler; + + /** Short cut */ + WindowManagerService mWindowManager; + DisplayManager mDisplayManager; + + /** Dismiss the keyguard after the next activity is displayed? */ + boolean mDismissKeyguardOnNextActivity = false; + + /** Identifier counter for all ActivityStacks */ + private int mLastStackId = HOME_STACK_ID; + + /** Task identifier that activities are currently being started in. Incremented each time a + * new task is created. */ + private int mCurTaskId = 0; + + /** The current user */ + private int mCurrentUser; + + /** The stack containing the launcher app */ + private ActivityStack mHomeStack; + + /** The non-home stack currently receiving input or launching the next activity. If home is + * in front then mHomeStack overrides mFocusedStack. + * DO NOT ACCESS DIRECTLY - It may be null, use getFocusedStack() */ + private ActivityStack mFocusedStack; + + /** If this is the same as mFocusedStack then the activity on the top of the focused stack has + * been resumed. If stacks are changing position this will hold the old stack until the new + * stack becomes resumed after which it will be set to the new stack. */ + private ActivityStack mLastFocusedStack; + + /** List of activities that are waiting for a new activity to become visible before completing + * whatever operation they are supposed to do. */ + final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<ActivityRecord>(); + + /** List of processes waiting to find out about the next visible activity. */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = + new ArrayList<IActivityManager.WaitResult>(); + + /** List of processes waiting to find out about the next launched activity. */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched = + new ArrayList<IActivityManager.WaitResult>(); + + /** List of activities that are ready to be stopped, but waiting for the next activity to + * settle down before doing so. */ + final ArrayList<ActivityRecord> mStoppingActivities = new ArrayList<ActivityRecord>(); + + /** List of activities that are ready to be finished, but waiting for the previous activity to + * settle down before doing so. It contains ActivityRecord objects. */ + final ArrayList<ActivityRecord> mFinishingActivities = new ArrayList<ActivityRecord>(); + + /** List of activities that are in the process of going to sleep. */ + final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<ActivityRecord>(); + + /** List of ActivityRecord objects that have been finished and must still report back to a + * pending thumbnail receiver. */ + final ArrayList<ActivityRecord> mCancelledThumbnails = new ArrayList<ActivityRecord>(); + + /** Used on user changes */ + final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>(); + + /** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity + * is being brought in front of us. */ + boolean mUserLeaving = false; + + /** Set when we have taken too long waiting to go to sleep. */ + boolean mSleepTimeout = false; + + /** + * We don't want to allow the device to go to sleep while in the process + * of launching an activity. This is primarily to allow alarm intent + * receivers to launch an activity and get that to run before the device + * goes back to sleep. + */ + final PowerManager.WakeLock mLaunchingActivity; + + /** + * Set when the system is going to sleep, until we have + * successfully paused the current activity and released our wake lock. + * At that point the system is allowed to actually sleep. + */ + final PowerManager.WakeLock mGoingToSleep; + + /** Stack id of the front stack when user switched, indexed by userId. */ + SparseIntArray mUserStackInFront = new SparseIntArray(2); + + /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */ + SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>(); + + /** Mapping from displayId to display current state */ + SparseArray<ActivityDisplayInfo> mDisplayInfos = new SparseArray<ActivityDisplayInfo>(); + + public ActivityStackSupervisor(ActivityManagerService service) { + mService = service; + PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); + mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); + mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper()); + if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity = + pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); + mLaunchingActivity.setReferenceCounted(false); + } + + void setWindowManager(WindowManagerService wm) { + synchronized (mService) { + mWindowManager = wm; + + mDisplayManager = + (DisplayManager)mService.mContext.getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(this, null); + + Display[] displays = mDisplayManager.getDisplays(); + for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) { + final int displayId = displays[displayNdx].getDisplayId(); + ActivityDisplayInfo info = new ActivityDisplayInfo(displayId); + mDisplayInfos.put(displayId, info); + } + + createStackOnDisplay(null, HOME_STACK_ID, Display.DEFAULT_DISPLAY); + mHomeStack = mFocusedStack = mLastFocusedStack = getStack(HOME_STACK_ID); + } + } + + void dismissKeyguard() { + if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); + if (mDismissKeyguardOnNextActivity) { + mDismissKeyguardOnNextActivity = false; + mWindowManager.dismissKeyguard(); + } + } + + ActivityStack getFocusedStack() { + return mFocusedStack; + } + + ActivityStack getLastStack() { + return mLastFocusedStack; + } + + // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the + // top of all visible stacks. + boolean isFrontStack(ActivityStack stack) { + ArrayList<ActivityStack> stacks = stack.getStacksLocked(); + if (stacks != null && !stacks.isEmpty()) { + return stack == stacks.get(stacks.size() - 1); + } + return false; + } + + void moveHomeStack(boolean toFront) { + ArrayList<ActivityStack> stacks = mHomeStack.getStacksLocked(); + int topNdx = stacks.size() - 1; + if (topNdx <= 0) { + return; + } + ActivityStack topStack = stacks.get(topNdx); + final boolean homeInFront = topStack == mHomeStack; + if (homeInFront != toFront) { + mLastFocusedStack = topStack; + stacks.remove(mHomeStack); + stacks.add(toFront ? topNdx : 0, mHomeStack); + mFocusedStack = stacks.get(topNdx); + if (DEBUG_STACK) Slog.d(TAG, "moveHomeTask: topStack old=" + topStack + " new=" + + mFocusedStack); + } + } + + void moveHomeToTop() { + moveHomeStack(true); + mHomeStack.moveHomeTaskToTop(); + } + + boolean resumeHomeActivity(ActivityRecord prev) { + moveHomeToTop(); + if (prev != null) { + prev.task.mOnTopOfHome = false; + } + ActivityRecord r = mHomeStack.topRunningActivityLocked(null); + if (r != null && r.isHomeActivity()) { + mService.setFocusedActivityLocked(r); + return resumeTopActivitiesLocked(mHomeStack, prev, null); + } + return mService.startHomeActivityLocked(mCurrentUser); + } + + void setDismissKeyguard(boolean dismiss) { + if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(" dismiss=" + dismiss); + mDismissKeyguardOnNextActivity = dismiss; + } + + TaskRecord anyTaskForIdLocked(int id) { + int numDisplays = mDisplayInfos.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + ActivityStack stack = stacks.get(stackNdx); + TaskRecord task = stack.taskForIdLocked(id); + if (task != null) { + return task; + } + } + } + return null; + } + + ActivityRecord isInAnyStackLocked(IBinder token) { + int numDisplays = mDisplayInfos.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord r = stacks.get(stackNdx).isInStackLocked(token); + if (r != null) { + return r; + } + } + } + return null; + } + + int getNextTaskId() { + do { + mCurTaskId++; + if (mCurTaskId <= 0) { + mCurTaskId = 1; + } + } while (anyTaskForIdLocked(mCurTaskId) != null); + return mCurTaskId; + } + + void removeTask(TaskRecord task) { + mWindowManager.removeTask(task.taskId); + final ActivityStack stack = task.stack; + final ActivityRecord r = stack.mResumedActivity; + if (r != null && r.task == task) { + stack.mResumedActivity = null; + } + if (stack.removeTask(task) && !stack.isHomeStack()) { + if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack " + stack); + stack.mActivityContainer.detachLocked(); + final int stackId = stack.mStackId; + final int nextStackId = mWindowManager.removeStack(stackId); + // TODO: Perhaps we need to let the ActivityManager determine the next focus... + mFocusedStack = getStack(nextStackId); + } + } + + ActivityRecord resumedAppLocked() { + ActivityStack stack = getFocusedStack(); + if (stack == null) { + return null; + } + ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity == null || resumedActivity.app == null) { + resumedActivity = stack.mPausingActivity; + if (resumedActivity == null || resumedActivity.app == null) { + resumedActivity = stack.topRunningActivityLocked(null); + } + } + return resumedActivity; + } + + boolean attachApplicationLocked(ProcessRecord app) throws Exception { + final String processName = app.processName; + boolean didSomething = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (!isFrontStack(stack)) { + continue; + } + ActivityRecord hr = stack.topRunningActivityLocked(null); + if (hr != null) { + if (hr.app == null && app.uid == hr.info.applicationInfo.uid + && processName.equals(hr.processName)) { + try { + if (realStartActivityLocked(hr, app, true, true)) { + didSomething = true; + } + } catch (Exception e) { + Slog.w(TAG, "Exception in new application when starting activity " + + hr.intent.getComponent().flattenToShortString(), e); + throw e; + } + } + } + } + } + if (!didSomething) { + ensureActivitiesVisibleLocked(null, 0); + } + return didSomething; + } + + boolean allResumedActivitiesIdle() { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (!isFrontStack(stack)) { + continue; + } + final ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity == null || !resumedActivity.idle) { + return false; + } + } + } + return true; + } + + boolean allResumedActivitiesComplete() { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (isFrontStack(stack)) { + final ActivityRecord r = stack.mResumedActivity; + if (r != null && r.state != ActivityState.RESUMED) { + return false; + } + } + } + } + // TODO: Not sure if this should check if all Paused are complete too. + if (DEBUG_STACK) Slog.d(TAG, + "allResumedActivitiesComplete: mLastFocusedStack changing from=" + + mLastFocusedStack + " to=" + mFocusedStack); + mLastFocusedStack = mFocusedStack; + return true; + } + + boolean allResumedActivitiesVisible() { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + final ActivityRecord r = stack.mResumedActivity; + if (r != null && (!r.nowVisible || r.waitingVisible)) { + return false; + } + } + } + return true; + } + + /** + * Pause all activities in either all of the stacks or just the back stacks. + * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). + * @return true if any activity was paused as a result of this call. + */ + boolean pauseBackStacks(boolean userLeaving) { + boolean someActivityPaused = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (!isFrontStack(stack) && stack.mResumedActivity != null) { + if (DEBUG_STATES) Slog.d(TAG, "pauseBackStacks: stack=" + stack + + " mResumedActivity=" + stack.mResumedActivity); + stack.startPausingLocked(userLeaving, false); + someActivityPaused = true; + } + } + } + return someActivityPaused; + } + + boolean allPausedActivitiesComplete() { + boolean pausing = true; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + final ActivityRecord r = stack.mPausingActivity; + if (r != null && r.state != ActivityState.PAUSED + && r.state != ActivityState.STOPPED + && r.state != ActivityState.STOPPING) { + if (DEBUG_STATES) { + Slog.d(TAG, "allPausedActivitiesComplete: r=" + r + " state=" + r.state); + pausing = false; + } else { + return false; + } + } + } + } + return pausing; + } + + void reportActivityVisibleLocked(ActivityRecord r) { + for (int i = mWaitingActivityVisible.size()-1; i >= 0; i--) { + WaitResult w = mWaitingActivityVisible.get(i); + w.timeout = false; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.totalTime = SystemClock.uptimeMillis() - w.thisTime; + w.thisTime = w.totalTime; + } + mService.notifyAll(); + dismissKeyguard(); + } + + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, + long thisTime, long totalTime) { + for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) { + WaitResult w = mWaitingActivityLaunched.remove(i); + w.timeout = timeout; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.thisTime = thisTime; + w.totalTime = totalTime; + } + mService.notifyAll(); + } + + ActivityRecord topRunningActivityLocked() { + final ActivityStack focusedStack = getFocusedStack(); + ActivityRecord r = focusedStack.topRunningActivityLocked(null); + if (r != null) { + return r; + } + + // Return to the home stack. + final ArrayList<ActivityStack> stacks = mHomeStack.getStacksLocked(); + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (stack != focusedStack && isFrontStack(stack)) { + r = stack.topRunningActivityLocked(null); + if (r != null) { + return r; + } + } + } + return null; + } + + ActivityRecord getTasksLocked(int maxNum, IThumbnailReceiver receiver, + PendingThumbnailsRecord pending, List<RunningTaskInfo> list) { + ActivityRecord r = null; + + // Gather all of the running tasks for each stack into runningTaskLists. + ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = + new ArrayList<ArrayList<RunningTaskInfo>>(); + final int numDisplays = mDisplayInfos.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>(); + runningTaskLists.add(stackTaskList); + final ActivityRecord ar = stack.getTasksLocked(receiver, pending, stackTaskList); + if (r == null && isFrontStack(stack)) { + r = ar; + } + } + } + + // The lists are already sorted from most recent to oldest. Just pull the most recent off + // each list and add it to list. Stop when all lists are empty or maxNum reached. + while (maxNum > 0) { + long mostRecentActiveTime = Long.MIN_VALUE; + ArrayList<RunningTaskInfo> selectedStackList = null; + final int numTaskLists = runningTaskLists.size(); + for (int stackNdx = 0; stackNdx < numTaskLists; ++stackNdx) { + ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists.get(stackNdx); + if (!stackTaskList.isEmpty()) { + final long lastActiveTime = stackTaskList.get(0).lastActiveTime; + if (lastActiveTime > mostRecentActiveTime) { + mostRecentActiveTime = lastActiveTime; + selectedStackList = stackTaskList; + } + } + } + if (selectedStackList != null) { + list.add(selectedStackList.remove(0)); + --maxNum; + } else { + break; + } + } + + return r; + } + + ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, int userId) { + // Collect information about the target of the Intent. + ActivityInfo aInfo; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, resolvedType, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS, userId); + aInfo = rInfo != null ? rInfo.activityInfo : null; + } catch (RemoteException e) { + aInfo = null; + } + + if (aInfo != null) { + // Store the found target back into the intent, because now that + // we have it we never want to do this again. For example, if the + // user navigates back to this point in the history, we should + // always restart the exact same activity. + intent.setComponent(new ComponentName( + aInfo.applicationInfo.packageName, aInfo.name)); + + // Don't debug things in the system process + if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) { + if (!aInfo.processName.equals("system")) { + mService.setDebugApp(aInfo.processName, true, false); + } + } + + if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) { + if (!aInfo.processName.equals("system")) { + mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName); + } + } + + if (profileFile != null) { + if (!aInfo.processName.equals("system")) { + mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, + profileFile, profileFd, + (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0); + } + } + } + return aInfo; + } + + void startHomeActivity(Intent intent, ActivityInfo aInfo) { + moveHomeToTop(); + startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0, + null, false, null); + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + String callingPackage, Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, + Bundle options, int userId) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, + profileFile, profileFd, userId); + + synchronized (mService) { + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + + final ActivityStack stack = getFocusedStack(); + stack.mConfigWillChange = config != null + && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Starting activity when config will change = " + stack.mConfigWillChange); + + final long origId = Binder.clearCallingIdentity(); + + if (aInfo != null && + (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Check to see if we already + // have another, different heavy-weight process running. + if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { + if (mService.mHeavyWeightProcess != null && + (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || + !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { + int realCallingUid = callingUid; + if (caller != null) { + ProcessRecord callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + realCallingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + ActivityOptions.abort(options); + return ActivityManager.START_PERMISSION_DENIED; + } + } + + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, "android", + realCallingUid, userId, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT, null); + + Intent newIntent = new Intent(); + if (requestCode >= 0) { + // Caller is requesting a result. + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, + new IntentSender(target)); + if (mService.mHeavyWeightProcess.activities.size() > 0) { + ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, + hist.packageName); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, + hist.task.taskId); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, + aInfo.packageName); + newIntent.setFlags(intent.getFlags()); + newIntent.setClassName("android", + HeavyWeightSwitcherActivity.class.getName()); + intent = newIntent; + resolvedType = null; + caller = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); + componentSpecified = true; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, null, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS, userId); + aInfo = rInfo != null ? rInfo.activityInfo : null; + aInfo = mService.getActivityInfoForUser(aInfo, userId); + } catch (RemoteException e) { + aInfo = null; + } + } + } + } + + int res = startActivityLocked(caller, intent, resolvedType, + aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, + callingPackage, startFlags, options, componentSpecified, null); + + if (stack.mConfigWillChange) { + // If the caller also wants to switch to a new configuration, + // do so now. This allows a clean switch, as we are waiting + // for the current activity to pause (so we will not destroy + // it), and have not yet started the next activity. + mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, + "updateConfiguration()"); + stack.mConfigWillChange = false; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Updating to new configuration after starting activity."); + mService.updateConfigurationLocked(config, null, false, false); + } + + Binder.restoreCallingIdentity(origId); + + if (outResult != null) { + outResult.result = res; + if (res == ActivityManager.START_SUCCESS) { + mWaitingActivityLaunched.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } else if (res == ActivityManager.START_TASK_TO_FRONT) { + ActivityRecord r = stack.topRunningActivityLocked(null); + if (r.nowVisible) { + outResult.timeout = false; + outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.totalTime = 0; + outResult.thisTime = 0; + } else { + outResult.thisTime = SystemClock.uptimeMillis(); + mWaitingActivityVisible.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } + } + } + + return res; + } + } + + final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options, int userId) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + ActivityRecord[] outActivity = new ActivityRecord[1]; + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], + 0, null, null, userId); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); + + if (aInfo != null && + (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE) + != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + Bundle theseOptions; + if (options != null && i == intents.length-1) { + theseOptions = options; + } else { + theseOptions = null; + } + int res = startActivityLocked(caller, intent, resolvedTypes[i], + aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage, + 0, theseOptions, componentSpecified, outActivity); + if (res < 0) { + return res; + } + + resultTo = outActivity[0] != null ? outActivity[0].appToken : null; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return ActivityManager.START_SUCCESS; + } + + final boolean realStartActivityLocked(ActivityRecord r, + ProcessRecord app, boolean andResume, boolean checkConfig) + throws RemoteException { + + r.startFreezingScreenLocked(app, 0); + if (false) Slog.d(TAG, "realStartActivity: setting app visibility true"); + mWindowManager.setAppVisibility(r.appToken, true); + + // schedule launch ticks to collect information about slow apps. + r.startLaunchTickingLocked(); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. Note that + // as a result of this, it can call back into the activity + // manager with a new orientation. We don't care about that, + // because the activity is not currently running so we are + // just restarting it anyway. + if (checkConfig) { + Configuration config = mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + r.mayFreezeScreenLocked(app) ? r.appToken : null); + mService.updateConfigurationLocked(config, r, false, false); + } + + r.app = app; + app.waitingToKill = null; + r.launchCount++; + r.lastLaunchTime = SystemClock.uptimeMillis(); + + if (localLOGV) Slog.v(TAG, "Launching: " + r); + + int idx = app.activities.indexOf(r); + if (idx < 0) { + app.activities.add(r); + } + mService.updateLruProcessLocked(app, true, null); + mService.updateOomAdjLocked(); + + final ActivityStack stack = r.task.stack; + try { + if (app.thread == null) { + throw new RemoteException(); + } + List<ResultInfo> results = null; + List<Intent> newIntents = null; + if (andResume) { + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r + + " icicle=" + r.icicle + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + if (andResume) { + EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, + r.userId, System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + } + if (r.isHomeActivity() && r.isNotResolverActivity()) { + // Home process is the root process of the task. + mService.mHomeProcess = r.task.mActivities.get(0).app; + } + mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); + r.sleeping = false; + r.forceNewConfig = false; + mService.showAskCompatModeDialogLocked(r); + r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); + String profileFile = null; + ParcelFileDescriptor profileFd = null; + boolean profileAutoStop = false; + if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) { + if (mService.mProfileProc == null || mService.mProfileProc == app) { + mService.mProfileProc = app; + profileFile = mService.mProfileFile; + profileFd = mService.mProfileFd; + profileAutoStop = mService.mAutoStopProfiler; + } + } + app.hasShownUi = true; + app.pendingUiClean = true; + if (profileFd != null) { + try { + profileFd = profileFd.dup(); + } catch (IOException e) { + if (profileFd != null) { + try { + profileFd.close(); + } catch (IOException o) { + } + profileFd = null; + } + } + } + app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); + app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, + System.identityHashCode(r), r.info, + new Configuration(mService.mConfiguration), r.compat, + app.repProcState, r.icicle, results, newIntents, !andResume, + mService.isNextTransitionForward(), profileFile, profileFd, + profileAutoStop); + + if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Note that the package + // manager will ensure that only activity can run in the main + // process of the .apk, which is the only thing that will be + // considered heavy-weight. + if (app.processName.equals(app.info.packageName)) { + if (mService.mHeavyWeightProcess != null + && mService.mHeavyWeightProcess != app) { + Slog.w(TAG, "Starting new heavy weight process " + app + + " when already running " + + mService.mHeavyWeightProcess); + } + mService.mHeavyWeightProcess = app; + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); + msg.obj = r; + mService.mHandler.sendMessage(msg); + } + } + + } catch (RemoteException e) { + if (r.launchFailed) { + // This is the second time we failed -- finish activity + // and give up. + Slog.e(TAG, "Second failure launching " + + r.intent.getComponent().flattenToShortString() + + ", giving up", e); + mService.appDiedLocked(app, app.pid, app.thread); + stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, + "2nd-crash", false); + return false; + } + + // This is the first time we failed -- restart process and + // retry. + app.activities.remove(r); + throw e; + } + + r.launchFailed = false; + if (stack.updateLRUListLocked(r)) { + Slog.w(TAG, "Activity " + r + + " being launched, but already in LRU list"); + } + + if (andResume) { + // As part of the process of launching, ActivityThread also performs + // a resume. + stack.minimalResumeActivityLocked(r); + } else { + // This activity is not starting in the resumed state... which + // should look like we asked it to pause+stop (but remain visible), + // and it has done so and reported back the current icicle and + // other state. + if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + + " (starting in stopped state)"); + r.state = ActivityState.STOPPED; + r.stopped = true; + } + + // Launch the new version setup screen if needed. We do this -after- + // launching the initial activity (that is, home), so that it can have + // a chance to initialize itself while in the background, making the + // switch back to it faster and look better. + if (isFrontStack(stack)) { + mService.startSetupActivityLocked(); + } + + return true; + } + + void startSpecificActivityLocked(ActivityRecord r, + boolean andResume, boolean checkConfig) { + // Is this activity's application already running? + ProcessRecord app = mService.getProcessRecordLocked(r.processName, + r.info.applicationInfo.uid, true); + + r.task.stack.setLaunchTime(r); + + if (app != null && app.thread != null) { + try { + if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0 + || !"android".equals(r.info.packageName)) { + // Don't add this if it is a platform component that is marked + // to run in multiple processes, because this is actually + // part of the framework so doesn't make sense to track as a + // separate apk in the process. + app.addPackage(r.info.packageName, mService.mProcessStats); + } + realStartActivityLocked(r, app, andResume, checkConfig); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting activity " + + r.intent.getComponent().flattenToShortString(), e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, + "activity", r.intent.getComponent(), false, false, true); + } + + final int startActivityLocked(IApplicationThread caller, + Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, + String resultWho, int requestCode, + int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, + boolean componentSpecified, ActivityRecord[] outActivity) { + int err = ActivityManager.START_SUCCESS; + + ProcessRecord callerApp = null; + if (caller != null) { + callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + callingPid = callerApp.pid; + callingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = ActivityManager.START_PERMISSION_DENIED; + } + } + + if (err == ActivityManager.START_SUCCESS) { + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); + } + + ActivityRecord sourceRecord = null; + ActivityRecord resultRecord = null; + if (resultTo != null) { + sourceRecord = isInAnyStackLocked(resultTo); + if (DEBUG_RESULTS) Slog.v( + TAG, "Will send result to " + resultTo + " " + sourceRecord); + if (sourceRecord != null) { + if (requestCode >= 0 && !sourceRecord.finishing) { + resultRecord = sourceRecord; + } + } + } + ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; + + int launchFlags = intent.getFlags(); + + if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 + && sourceRecord != null) { + // Transfer the result target from the source activity to the new + // one being started, including any failures. + if (requestCode >= 0) { + ActivityOptions.abort(options); + return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; + } + resultRecord = sourceRecord.resultTo; + resultWho = sourceRecord.resultWho; + requestCode = sourceRecord.requestCode; + sourceRecord.resultTo = null; + if (resultRecord != null) { + resultRecord.removeResultsLocked( + sourceRecord, resultWho, requestCode); + } + } + + if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { + // We couldn't find a class that can handle the given Intent. + // That's the end of that! + err = ActivityManager.START_INTENT_NOT_RESOLVED; + } + + if (err == ActivityManager.START_SUCCESS && aInfo == null) { + // We couldn't find the specific class specified in the Intent. + // Also the end of the line. + err = ActivityManager.START_CLASS_NOT_FOUND; + } + + if (err != ActivityManager.START_SUCCESS) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + setDismissKeyguard(false); + ActivityOptions.abort(options); + return err; + } + + final int startAnyPerm = mService.checkPermission( + START_ANY_ACTIVITY, callingPid, callingUid); + final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid, + callingUid, aInfo.applicationInfo.uid, aInfo.exported); + if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + setDismissKeyguard(false); + String msg; + if (!aInfo.exported) { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " not exported from uid " + aInfo.applicationInfo.uid; + } else { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " requires " + aInfo.permission; + } + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, callingUid, + callingPid, resolvedType, aInfo.applicationInfo); + + if (mService.mController != null) { + try { + // The Intent we give to the watcher has the extra data + // stripped off, since it can contain private information. + Intent watchIntent = intent.cloneFilter(); + abort |= !mService.mController.activityStarting(watchIntent, + aInfo.applicationInfo.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + } + + if (abort) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + setDismissKeyguard(false); + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; + } + + ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, + intent, resolvedType, aInfo, mService.mConfiguration, + resultRecord, resultWho, requestCode, componentSpecified, this); + if (outActivity != null) { + outActivity[0] = r; + } + + final ActivityStack stack = getFocusedStack(); + if (stack.mResumedActivity == null + || stack.mResumedActivity.info.applicationInfo.uid != callingUid) { + if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { + PendingActivityLaunch pal = + new PendingActivityLaunch(r, sourceRecord, startFlags, stack); + mService.mPendingActivityLaunches.add(pal); + setDismissKeyguard(false); + ActivityOptions.abort(options); + return ActivityManager.START_SWITCHES_CANCELED; + } + } + + if (mService.mDidAppSwitch) { + // This is the second allowed switch since we stopped switches, + // so now just generally allow switches. Use case: user presses + // home (switches disabled, switch to home, mDidAppSwitch now true); + // user taps a home icon (coming from home so allowed, we hit here + // and now allow anyone to switch again). + mService.mAppSwitchesAllowedTime = 0; + } else { + mService.mDidAppSwitch = true; + } + + mService.doPendingActivityLaunchesLocked(false); + + err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options); + + if (allPausedActivitiesComplete()) { + // If someone asked to have the keyguard dismissed on the next + // activity start, but we are not actually doing an activity + // switch... just dismiss the keyguard now, because we + // probably want to see whatever is behind it. + dismissKeyguard(); + } + return err; + } + + ActivityStack adjustStackFocus(ActivityRecord r) { + final TaskRecord task = r.task; + if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) { + if (task != null) { + final ActivityStack taskStack = task.stack; + if (mFocusedStack != taskStack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Setting focused stack to r=" + r + " task=" + task); + mFocusedStack = taskStack; + } else { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Focused stack already=" + mFocusedStack); + } + return taskStack; + } + + if (mFocusedStack != mHomeStack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Have a focused stack=" + mFocusedStack); + return mFocusedStack; + } + + int numDisplays = mDisplayInfos.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + ActivityStack stack = stacks.get(stackNdx); + if (!stack.isHomeStack()) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Setting focused stack=" + stack); + mFocusedStack = stack; + return mFocusedStack; + } + } + } + + // Need to create an app stack for this user. + int stackId = createStackOnDisplay(null, getNextStackId(), Display.DEFAULT_DISPLAY); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r + + " stackId=" + stackId); + mFocusedStack = getStack(stackId); + return mFocusedStack; + } + return mHomeStack; + } + + void setFocusedStack(ActivityRecord r) { + if (r != null) { + final boolean isHomeActivity = + !r.isApplicationActivity() || (r.task != null && !r.task.isApplicationTask()); + moveHomeStack(isHomeActivity); + } + } + + final int startActivityUncheckedLocked(ActivityRecord r, + ActivityRecord sourceRecord, int startFlags, boolean doResume, + Bundle options) { + final Intent intent = r.intent; + final int callingUid = r.launchedFromUid; + + int launchFlags = intent.getFlags(); + + // We'll invoke onUserLeaving before onPause only if the launching + // activity did not explicitly state that this is an automated launch. + mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving); + + // If the caller has asked not to resume at this point, we make note + // of this in the record so that we can skip it when trying to find + // the top running activity. + if (!doResume) { + r.delayedResume = true; + } + + ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; + + // If the onlyIfNeeded flag is set, then we can do this if the activity + // being launched is the same as the one making the call... or, as + // a special case, if we do not know the caller then we count the + // current top activity as the caller. + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + ActivityRecord checkedCaller = sourceRecord; + if (checkedCaller == null) { + checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop); + } + if (!checkedCaller.realActivity.equals(r.realActivity)) { + // Caller is not the same as launcher, so always needed. + startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; + } + } + + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing " + + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + + ActivityInfo newTaskInfo = null; + Intent newTaskIntent = null; + final ActivityStack sourceStack; + if (sourceRecord != null) { + if (sourceRecord.finishing) { + // If the source is finishing, we can't further count it as our source. This + // is because the task it is associated with may now be empty and on its way out, + // so we don't want to blindly throw it in to that task. Instead we will take + // the NEW_TASK flow and try to find a task for it. But save the task information + // so it can be used when creating the new task. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from finishing " + sourceRecord + + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + newTaskInfo = sourceRecord.info; + newTaskIntent = sourceRecord.task.intent; + } + sourceRecord = null; + sourceStack = null; + } else { + sourceStack = sourceRecord.task.stack; + } + } else { + sourceStack = null; + } + + if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + // For whatever reason this activity is being launched into a new + // task... yet the caller has requested a result back. Well, that + // is pretty messed up, so instead immediately send back a cancel + // and let the new task continue launched as normal without a + // dependency on its originator. + Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); + r.resultTo.task.stack.sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + r.resultTo = null; + } + + boolean addingToTask = false; + boolean movedHome = false; + TaskRecord reuseTask = null; + ActivityStack targetStack; + if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && + (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // If bring to front is requested, and no result is requested, and + // we can find a task that was started with this same + // component, then instead of launching bring that one to the front. + if (r.resultTo == null) { + // See if there is a task to bring to the front. If this is + // a SINGLE_INSTANCE activity, there can be one and only one + // instance of it in the history, and it is always in its own + // unique task, so we do a special search. + ActivityRecord intentActivity = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE + ? findTaskLocked(r) + : findActivityLocked(intent, r.info); + if (intentActivity != null) { + if (r.task == null) { + r.task = intentActivity.task; + } + targetStack = intentActivity.task.stack; + targetStack.mLastPausedActivity = null; + if (DEBUG_TASKS) Slog.d(TAG, "Bring to front target: " + targetStack + + " from " + intentActivity); + moveHomeStack(targetStack.isHomeStack()); + if (intentActivity.task.intent == null) { + // This task was started because of movement of + // the activity based on affinity... now that we + // are actually launching it, we can assign the + // base intent. + intentActivity.task.setIntent(intent, r.info); + } + // If the target task is not in the front, then we need + // to bring it to the front... except... well, with + // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like + // to have the same behavior as if a new instance was + // being started, which means not bringing it to the front + // if the caller is not itself in the front. + final ActivityStack lastStack = getLastStack(); + ActivityRecord curTop = lastStack == null? + null : lastStack.topRunningNonDelayedActivityLocked(notTop); + if (curTop != null && (curTop.task != intentActivity.task || + curTop.task != lastStack.topTask())) { + r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + if (sourceRecord == null || (sourceStack.topActivity() != null && + sourceStack.topActivity().task == sourceRecord.task)) { + // We really do want to push this one into the + // user's face, right now. + movedHome = true; + targetStack.moveTaskToFrontLocked(intentActivity.task, r, options); + if ((launchFlags & + (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.mOnTopOfHome = true; + } + options = null; + } + } + // If the caller has requested that the target task be + // reset, then do so. + if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); + } + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + resumeTopActivitiesLocked(targetStack, null, options); + } else { + ActivityOptions.abort(options); + } + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // existing task with its new activity. Well that should + // not be too hard... + reuseTask = intentActivity.task; + reuseTask.performClearTaskLocked(); + reuseTask.setIntent(r.intent, r.info); + } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // In this situation we want to remove all activities + // from the task up to the one being started. In most + // cases this means we are resetting the task to its + // initial state. + ActivityRecord top = + intentActivity.task.performClearTaskLocked(r, launchFlags); + if (top != null) { + if (top.frontOfTask) { + // Activity aliases may mean we use different + // intents for the top activity, so make sure + // the task now has the identity of the new + // intent. + top.task.setIntent(r.intent, r.info); + } + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, + r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + } else { + // A special case: we need to + // start the activity because it is not currently + // running, and the caller has asked to clear the + // current task to have this activity at the top. + addingToTask = true; + // Now pretend like this activity is being started + // by the top of its task, so it is put in the + // right place. + sourceRecord = intentActivity; + } + } else if (r.realActivity.equals(intentActivity.task.realActivity)) { + // In this case the top activity on the task is the + // same as the one being launched, so we take that + // as a request to bring the task to the foreground. + // If the top activity in the task is the root + // activity, deliver this new intent to it if it + // desires. + if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) + && intentActivity.realActivity.equals(r.realActivity)) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, + intentActivity.task); + if (intentActivity.frontOfTask) { + intentActivity.task.setIntent(r.intent, r.info); + } + intentActivity.deliverNewIntentLocked(callingUid, r.intent); + } else if (!r.intent.filterEquals(intentActivity.task.intent)) { + // In this case we are launching the root activity + // of the task, but with a different intent. We + // should start a new instance on top. + addingToTask = true; + sourceRecord = intentActivity; + } + } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + // In this case an activity is being launched in to an + // existing task, without resetting that task. This + // is typically the situation of launching an activity + // from a notification or shortcut. We want to place + // the new activity on top of the current task. + addingToTask = true; + sourceRecord = intentActivity; + } else if (!intentActivity.task.rootWasReset) { + // In this case we are launching in to an existing task + // that has not yet been started from its front door. + // The current task has been brought to the front. + // Ideally, we'd probably like to place this new task + // at the bottom of its stack, but that's a little hard + // to do with the current organization of the code so + // for now we'll just drop it. + intentActivity.task.setIntent(r.intent, r.info); + } + if (!addingToTask && reuseTask == null) { + // We didn't do anything... but it was needed (a.k.a., client + // don't use that intent!) And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + targetStack.resumeTopActivityLocked(null, options); + } else { + ActivityOptions.abort(options); + } + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_TASK_TO_FRONT; + } + } + } + } + + //String uri = r.intent.toURI(); + //Intent intent2 = new Intent(uri); + //Slog.i(TAG, "Given intent: " + r.intent); + //Slog.i(TAG, "URI is: " + uri); + //Slog.i(TAG, "To intent: " + intent2); + + if (r.packageName != null) { + // If the activity being launched is the same as the one currently + // at the top, then we need to check if it should only be launched + // once. + ActivityStack topStack = getFocusedStack(); + ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); + if (top != null && r.resultTo == null) { + if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { + if (top.app != null && top.app.thread != null) { + if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, + top.task); + // For paranoia, make sure we have correctly + // resumed the top activity. + topStack.mLastPausedActivity = null; + if (doResume) { + resumeTopActivitiesLocked(); + } + ActivityOptions.abort(options); + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent); + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + } + } + + } else { + if (r.resultTo != null) { + r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, + r.requestCode, Activity.RESULT_CANCELED, null); + } + ActivityOptions.abort(options); + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_CLASS_NOT_FOUND; + } + + boolean newTask = false; + boolean keepCurTransition = false; + + // Should this be considered a new task? + if (r.resultTo == null && !addingToTask + && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + targetStack = adjustStackFocus(r); + moveHomeStack(targetStack.isHomeStack()); + if (reuseTask == null) { + r.setTask(targetStack.createTaskRecord(getNextTaskId(), + newTaskInfo != null ? newTaskInfo : r.info, + newTaskIntent != null ? newTaskIntent : intent, + true), null, true); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + + r.task); + } else { + r.setTask(reuseTask, reuseTask, true); + } + newTask = true; + if (!movedHome) { + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) + == (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.mOnTopOfHome = true; + } + } + } else if (sourceRecord != null) { + TaskRecord sourceTask = sourceRecord.task; + targetStack = sourceTask.stack; + moveHomeStack(targetStack.isHomeStack()); + if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + // In this case, we are adding the activity to an existing + // task, but the caller has asked to clear that task if the + // activity is already running. + ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); + keepCurTransition = true; + if (top != null) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + // For paranoia, make sure we have correctly + // resumed the top activity. + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + ActivityOptions.abort(options); + if (r.task == null) Slog.w(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } else if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { + // In this case, we are launching an activity in our own task + // that may already be running somewhere in the history, and + // we want to shuffle it to the front of the stack if so. + final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); + if (top != null) { + final TaskRecord task = top.task; + task.moveActivityToFrontLocked(top); + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); + top.updateOptionsLocked(options); + top.deliverNewIntentLocked(callingUid, r.intent); + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + // An existing activity is starting this new activity, so we want + // to keep the new one in the same task as the one that is starting + // it. + r.setTask(sourceTask, sourceRecord.thumbHolder, false); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in existing task " + r.task + " from source " + sourceRecord); + + } else { + // This not being started from an existing activity, and not part + // of a new task... just put it in the top task, though these days + // this case should never happen. + targetStack = adjustStackFocus(r); + moveHomeStack(targetStack.isHomeStack()); + ActivityRecord prev = targetStack.topActivity(); + r.setTask(prev != null ? prev.task + : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), + null, true); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new guessed " + r.task); + } + + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + intent, r.getUriPermissionsLocked()); + + if (newTask) { + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); + } + ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); + targetStack.mLastPausedActivity = null; + targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options); + mService.setFocusedActivityLocked(r); + return ActivityManager.START_SUCCESS; + } + + void acquireLaunchWakelock() { + if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.acquire(); + if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { + // To be safe, don't allow the wake lock to be held for too long. + mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT); + } + } + + // Checked. + final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, + Configuration config) { + if (localLOGV) Slog.v(TAG, "Activity idle: " + token); + + ArrayList<ActivityRecord> stops = null; + ArrayList<ActivityRecord> finishes = null; + ArrayList<UserStartedState> startingUsers = null; + int NS = 0; + int NF = 0; + IApplicationThread sendThumbnail = null; + boolean booting = false; + boolean enableScreen = false; + boolean activityRemoved = false; + + ActivityRecord r = ActivityRecord.forToken(token); + if (r != null) { + if (DEBUG_IDLE) Slog.d(TAG, "activityIdleInternalLocked: Callers=" + + Debug.getCallers(4)); + mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + r.finishLaunchTickingLocked(); + if (fromTimeout) { + reportActivityLaunchedLocked(fromTimeout, r, -1, -1); + } + + // This is a hack to semi-deal with a race condition + // in the client where it can be constructed with a + // newer configuration from when we asked it to launch. + // We'll update with whatever configuration it now says + // it used to launch. + if (config != null) { + r.configuration = config; + } + + // We are now idle. If someone is waiting for a thumbnail from + // us, we can now deliver. + r.idle = true; + + if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { + sendThumbnail = r.app.thread; + r.thumbnailNeeded = false; + } + + //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); + if (!mService.mBooted && isFrontStack(r.task.stack)) { + mService.mBooted = true; + enableScreen = true; + } + } + + if (allResumedActivitiesIdle()) { + if (r != null) { + mService.scheduleAppGcsLocked(); + } + + if (mLaunchingActivity.isHeld()) { + mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + if (VALIDATE_WAKE_LOCK_CALLER && + Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.release(); + } + ensureActivitiesVisibleLocked(null, 0); + } + + // Atomically retrieve all of the other things to do. + stops = processStoppingActivitiesLocked(true); + NS = stops != null ? stops.size() : 0; + if ((NF=mFinishingActivities.size()) > 0) { + finishes = new ArrayList<ActivityRecord>(mFinishingActivities); + mFinishingActivities.clear(); + } + + final ArrayList<ActivityRecord> thumbnails; + final int NT = mCancelledThumbnails.size(); + if (NT > 0) { + thumbnails = new ArrayList<ActivityRecord>(mCancelledThumbnails); + mCancelledThumbnails.clear(); + } else { + thumbnails = null; + } + + if (isFrontStack(mHomeStack)) { + booting = mService.mBooting; + mService.mBooting = false; + } + + if (mStartingUsers.size() > 0) { + startingUsers = new ArrayList<UserStartedState>(mStartingUsers); + mStartingUsers.clear(); + } + + // Perform the following actions from unsynchronized state. + final IApplicationThread thumbnailThread = sendThumbnail; + mHandler.post(new Runnable() { + @Override + public void run() { + if (thumbnailThread != null) { + try { + thumbnailThread.requestThumbnail(token); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown when requesting thumbnail", e); + mService.sendPendingThumbnail(null, token, null, null, true); + } + } + + // Report back to any thumbnail receivers. + for (int i = 0; i < NT; i++) { + ActivityRecord r = thumbnails.get(i); + mService.sendPendingThumbnail(r, null, null, null, true); + } + } + }); + + // Stop any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (int i = 0; i < NS; i++) { + r = stops.get(i); + final ActivityStack stack = r.task.stack; + if (r.finishing) { + stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false); + } else { + stack.stopActivityLocked(r); + } + } + + // Finish any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (int i = 0; i < NF; i++) { + r = finishes.get(i); + activityRemoved |= r.task.stack.destroyActivityLocked(r, true, false, "finish-idle"); + } + + if (booting) { + mService.finishBooting(); + } else if (startingUsers != null) { + for (int i = 0; i < startingUsers.size(); i++) { + mService.finishUserSwitch(startingUsers.get(i)); + } + } + + mService.trimApplications(); + //dump(); + //mWindowManager.dump(); + + if (enableScreen) { + mService.enableScreenAfterBoot(); + } + + if (activityRemoved) { + resumeTopActivitiesLocked(); + } + + return r; + } + + boolean handleAppDiedLocked(ProcessRecord app) { + boolean hasVisibleActivities = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + hasVisibleActivities |= stacks.get(stackNdx).handleAppDiedLocked(app); + } + } + return hasVisibleActivities; + } + + void closeSystemDialogsLocked() { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + stacks.get(stackNdx).closeSystemDialogsLocked(); + } + } + } + + void removeUserLocked(int userId) { + mUserStackInFront.delete(userId); + } + + /** + * @return true if some activity was finished (or would have finished if doit were true). + */ + boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) { + boolean didSomething = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + final int numStacks = stacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (stack.forceStopPackageLocked(name, doit, evenPersistent, userId)) { + didSomething = true; + } + } + } + return didSomething; + } + + void updatePreviousProcessLocked(ActivityRecord r) { + // Now that this process has stopped, we may want to consider + // it to be the previous app to try to keep around in case + // the user wants to return to it. + + // First, found out what is currently the foreground app, so that + // we don't blow away the previous app if this activity is being + // hosted by the process that is actually still the foreground. + ProcessRecord fgApp = null; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (isFrontStack(stack)) { + if (stack.mResumedActivity != null) { + fgApp = stack.mResumedActivity.app; + } else if (stack.mPausingActivity != null) { + fgApp = stack.mPausingActivity.app; + } + break; + } + } + } + + // Now set this one as the previous process, only if that really + // makes sense to. + if (r.app != null && fgApp != null && r.app != fgApp + && r.lastVisibleTime > mService.mPreviousProcessVisibleTime + && r.app != mService.mHomeProcess) { + mService.mPreviousProcess = r.app; + mService.mPreviousProcessVisibleTime = r.lastVisibleTime; + } + } + + boolean resumeTopActivitiesLocked() { + return resumeTopActivitiesLocked(null, null, null); + } + + boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target, + Bundle targetOptions) { + if (targetStack == null) { + targetStack = getFocusedStack(); + } + boolean result = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (isFrontStack(stack)) { + if (stack == targetStack) { + result = stack.resumeTopActivityLocked(target, targetOptions); + } else { + stack.resumeTopActivityLocked(null); + } + } + } + } + return result; + } + + void finishTopRunningActivityLocked(ProcessRecord app) { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + final int numStacks = stacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.finishTopRunningActivityLocked(app); + } + } + } + + void findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + if (stacks.get(stackNdx).findTaskToMoveToFrontLocked(taskId, flags, options)) { + if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack=" + + stacks.get(stackNdx)); + return; + } + } + } + } + + ActivityStack getStack(int stackId) { + ActivityContainer activityContainer = mActivityContainers.get(stackId); + if (activityContainer != null) { + return activityContainer.mStack; + } + return null; + } + + ArrayList<ActivityStack> getStacks() { + ArrayList<ActivityStack> allStacks = new ArrayList<ActivityStack>(); + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + allStacks.addAll(mDisplayInfos.valueAt(displayNdx).stacks); + } + return allStacks; + } + + IBinder getHomeActivityToken() { + final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = tasks.get(taskNdx); + if (task.isHomeTask()) { + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.isHomeActivity()) { + return r.appToken; + } + } + } + } + return null; + } + + ActivityContainer createActivityContainer(ActivityRecord parentActivity, int stackId, + IActivityContainerCallback callback) { + ActivityContainer activityContainer = new ActivityContainer(parentActivity, stackId, + callback); + mActivityContainers.put(stackId, activityContainer); + if (parentActivity != null) { + parentActivity.mChildContainers.add(activityContainer.mStack); + } + return activityContainer; + } + + ActivityContainer createActivityContainer(ActivityRecord parentActivity, + IActivityContainerCallback callback) { + return createActivityContainer(parentActivity, getNextStackId(), callback); + } + + private int createStackOnDisplay(ActivityRecord parentActivity, int stackId, int displayId) { + ActivityDisplayInfo displayInfo = mDisplayInfos.get(displayId); + if (displayInfo == null) { + return -1; + } + + ActivityContainer activityContainer = + createActivityContainer(parentActivity, stackId, null); + activityContainer.attachToDisplayLocked(displayInfo); + return stackId; + } + + int getNextStackId() { + while (true) { + if (++mLastStackId <= HOME_STACK_ID) { + mLastStackId = HOME_STACK_ID + 1; + } + if (getStack(mLastStackId) == null) { + break; + } + } + return mLastStackId; + } + + void moveTaskToStack(int taskId, int stackId, boolean toTop) { + final TaskRecord task = anyTaskForIdLocked(taskId); + if (task == null) { + return; + } + final ActivityStack stack = getStack(stackId); + if (stack == null) { + Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId); + return; + } + removeTask(task); + stack.addTask(task, toTop); + mWindowManager.addTask(taskId, stackId, toTop); + resumeTopActivitiesLocked(); + } + + ActivityRecord findTaskLocked(ActivityRecord r) { + if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + r); + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (!r.isApplicationActivity() && !stack.isHomeStack()) { + if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: " + stack); + continue; + } + final ActivityRecord ar = stack.findTaskLocked(r); + if (ar != null) { + return ar; + } + } + } + if (DEBUG_TASKS) Slog.d(TAG, "No task found"); + return null; + } + + ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord ar = stacks.get(stackNdx).findActivityLocked(intent, info); + if (ar != null) { + return ar; + } + } + } + return null; + } + + void goingToSleepLocked() { + scheduleSleepTimeout(); + if (!mGoingToSleep.isHeld()) { + mGoingToSleep.acquire(); + if (mLaunchingActivity.isHeld()) { + if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.release(); + mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + } + } + checkReadyForSleepLocked(); + } + + boolean shutdownLocked(int timeout) { + boolean timedout = false; + goingToSleepLocked(); + + final long endTime = System.currentTimeMillis() + timeout; + while (true) { + boolean cantShutdown = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + cantShutdown |= stacks.get(stackNdx).checkReadyForSleepLocked(); + } + } + if (cantShutdown) { + long timeRemaining = endTime - System.currentTimeMillis(); + if (timeRemaining > 0) { + try { + mService.wait(timeRemaining); + } catch (InterruptedException e) { + } + } else { + Slog.w(TAG, "Activity manager shutdown timed out"); + timedout = true; + break; + } + } else { + break; + } + } + + // Force checkReadyForSleep to complete. + mSleepTimeout = true; + checkReadyForSleepLocked(); + + return timedout; + } + + void comeOutOfSleepIfNeededLocked() { + removeSleepTimeouts(); + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.awakeFromSleepingLocked(); + if (isFrontStack(stack)) { + resumeTopActivitiesLocked(); + } + } + } + mGoingToSleepActivities.clear(); + } + + void activitySleptLocked(ActivityRecord r) { + mGoingToSleepActivities.remove(r); + checkReadyForSleepLocked(); + } + + void checkReadyForSleepLocked() { + if (!mService.isSleepingOrShuttingDown()) { + // Do not care. + return; + } + + if (!mSleepTimeout) { + boolean dontSleep = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + dontSleep |= stacks.get(stackNdx).checkReadyForSleepLocked(); + } + } + + if (mStoppingActivities.size() > 0) { + // Still need to tell some activities to stop; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop " + + mStoppingActivities.size() + " activities"); + scheduleIdleLocked(); + dontSleep = true; + } + + if (mGoingToSleepActivities.size() > 0) { + // Still need to tell some activities to sleep; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep " + + mGoingToSleepActivities.size() + " activities"); + dontSleep = true; + } + + if (dontSleep) { + return; + } + } + + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + stacks.get(stackNdx).goToSleep(); + } + } + + removeSleepTimeouts(); + + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + if (mService.mShuttingDown) { + mService.notifyAll(); + } + } + + boolean reportResumedActivityLocked(ActivityRecord r) { + final ActivityStack stack = r.task.stack; + if (isFrontStack(stack)) { + mService.updateUsageStats(r, true); + } + if (allResumedActivitiesComplete()) { + ensureActivitiesVisibleLocked(null, 0); + mWindowManager.executeAppTransition(); + return true; + } + return false; + } + + void handleAppCrashLocked(ProcessRecord app) { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + final int numStacks = stacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.handleAppCrashLocked(app); + } + } + } + + void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) { + // First the front stacks. In case any are not fullscreen and are in front of home. + boolean showHomeBehindStack = false; + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + final int topStackNdx = stacks.size() - 1; + for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + if (stackNdx == topStackNdx) { + // Top stack. + showHomeBehindStack = + stack.ensureActivitiesVisibleLocked(starting, configChanges); + } else { + // Back stack. + stack.ensureActivitiesVisibleLocked(starting, configChanges, + showHomeBehindStack); + } + } + } + } + + void scheduleDestroyAllActivities(ProcessRecord app, String reason) { + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + final int numStacks = stacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.scheduleDestroyActivities(app, false, reason); + } + } + } + + boolean switchUserLocked(int userId, UserStartedState uss) { + mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId()); + final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID); + mCurrentUser = userId; + + mStartingUsers.add(uss); + for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + stacks.get(stackNdx).switchUserLocked(userId); + } + } + + ActivityStack stack = getStack(restoreStackId); + if (stack == null) { + stack = mHomeStack; + } + final boolean homeInFront = stack.isHomeStack(); + moveHomeStack(homeInFront); + mWindowManager.moveTaskToTop(stack.topTask().taskId); + return homeInFront; + } + + final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) { + int N = mStoppingActivities.size(); + if (N <= 0) return null; + + ArrayList<ActivityRecord> stops = null; + + final boolean nowVisible = allResumedActivitiesVisible(); + for (int i=0; i<N; i++) { + ActivityRecord s = mStoppingActivities.get(i); + if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + + nowVisible + " waitingVisible=" + s.waitingVisible + + " finishing=" + s.finishing); + if (s.waitingVisible && nowVisible) { + mWaitingVisibleActivities.remove(s); + s.waitingVisible = false; + if (s.finishing) { + // If this activity is finishing, it is sitting on top of + // everyone else but we now know it is no longer needed... + // so get rid of it. Otherwise, we need to go through the + // normal flow and hide it once we determine that it is + // hidden by the activities in front of it. + if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); + mWindowManager.setAppVisibility(s.appToken, false); + } + } + if ((!s.waitingVisible || mService.isSleepingOrShuttingDown()) && remove) { + if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); + if (stops == null) { + stops = new ArrayList<ActivityRecord>(); + } + stops.add(s); + mStoppingActivities.remove(i); + N--; + i--; + } + } + + return stops; + } + + void validateTopActivitiesLocked() { + // FIXME +/* for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + final ActivityRecord r = stack.topRunningActivityLocked(null); + final ActivityState state = r == null ? ActivityState.DESTROYED : r.state; + if (isFrontStack(stack)) { + if (r == null) { + Slog.e(TAG, "validateTop...: null top activity, stack=" + stack); + } else { + final ActivityRecord pausing = stack.mPausingActivity; + if (pausing != null && pausing == r) { + Slog.e(TAG, "validateTop...: top stack has pausing activity r=" + r + + " state=" + state); + } + if (state != ActivityState.INITIALIZING && state != ActivityState.RESUMED) { + Slog.e(TAG, "validateTop...: activity in front not resumed r=" + r + + " state=" + state); + } + } + } else { + final ActivityRecord resumed = stack.mResumedActivity; + if (resumed != null && resumed == r) { + Slog.e(TAG, "validateTop...: back stack has resumed activity r=" + r + + " state=" + state); + } + if (r != null && (state == ActivityState.INITIALIZING + || state == ActivityState.RESUMED)) { + Slog.e(TAG, "validateTop...: activity in back resumed r=" + r + + " state=" + state); + } + } + } +*/ + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mDismissKeyguardOnNextActivity="); + pw.println(mDismissKeyguardOnNextActivity); + pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack); + pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack); + pw.print(prefix); pw.println("mSleepTimeout=" + mSleepTimeout); + pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId); + pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); + } + + ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { + return getFocusedStack().getDumpActivitiesLocked(name); + } + + static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage, + boolean needSep, String prefix) { + if (activity != null) { + if (dumpPackage == null || dumpPackage.equals(activity.packageName)) { + if (needSep) { + pw.println(); + } + pw.print(prefix); + pw.println(activity); + return true; + } + } + return false; + } + + boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll, + boolean dumpClient, String dumpPackage) { + boolean printed = false; + boolean needSep = false; + for (int displayNdx = 0; displayNdx < mDisplayInfos.size(); ++displayNdx) { + ActivityDisplayInfo info = mDisplayInfos.valueAt(displayNdx); + pw.print("Display #"); pw.println(info.mDisplayId); + ArrayList<ActivityStack> stacks = info.stacks; + final int numStacks = stacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + StringBuilder stackHeader = new StringBuilder(128); + stackHeader.append(" Stack #"); + stackHeader.append(stack.mStackId); + stackHeader.append(":"); + printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage, + needSep, stackHeader.toString()); + printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, " ", "Run", false, + !dumpAll, false, dumpPackage, true, + " Running activities (most recent first):", null); + + needSep = printed; + boolean pr = printThisActivity(pw, stack.mPausingActivity, dumpPackage, needSep, + " mPausingActivity: "); + if (pr) { + printed = true; + needSep = false; + } + pr = printThisActivity(pw, stack.mResumedActivity, dumpPackage, needSep, + " mResumedActivity: "); + if (pr) { + printed = true; + needSep = false; + } + if (dumpAll) { + pr = printThisActivity(pw, stack.mLastPausedActivity, dumpPackage, needSep, + " mLastPausedActivity: "); + if (pr) { + printed = true; + needSep = true; + } + printed |= printThisActivity(pw, stack.mLastNoHistoryActivity, dumpPackage, + needSep, " mLastNoHistoryActivity: "); + } + needSep = printed; + } + } + + printed |= dumpHistoryList(fd, pw, mFinishingActivities, " ", "Fin", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to finish:", null); + printed |= dumpHistoryList(fd, pw, mStoppingActivities, " ", "Stop", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to stop:", null); + printed |= dumpHistoryList(fd, pw, mWaitingVisibleActivities, " ", "Wait", false, !dumpAll, + false, dumpPackage, true, " Activities waiting for another to become visible:", + null); + printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, " ", "Sleep", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to sleep:", null); + printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, " ", "Sleep", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to sleep:", null); + + return printed; + } + + static boolean dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list, + String prefix, String label, boolean complete, boolean brief, boolean client, + String dumpPackage, boolean needNL, String header1, String header2) { + TaskRecord lastTask = null; + String innerPrefix = null; + String[] args = null; + boolean printed = false; + for (int i=list.size()-1; i>=0; i--) { + final ActivityRecord r = list.get(i); + if (dumpPackage != null && !dumpPackage.equals(r.packageName)) { + continue; + } + if (innerPrefix == null) { + innerPrefix = prefix + " "; + args = new String[0]; + } + printed = true; + final boolean full = !brief && (complete || !r.isInHistory()); + if (needNL) { + pw.println(""); + needNL = false; + } + if (header1 != null) { + pw.println(header1); + header1 = null; + } + if (header2 != null) { + pw.println(header2); + header2 = null; + } + if (lastTask != r.task) { + lastTask = r.task; + pw.print(prefix); + pw.print(full ? "* " : " "); + pw.println(lastTask); + if (full) { + lastTask.dump(pw, prefix + " "); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + if (lastTask.intent != null) { + pw.print(prefix); pw.print(" "); + pw.println(lastTask.intent.toInsecureStringWithClip()); + } + } + } + pw.print(prefix); pw.print(full ? " * " : " "); pw.print(label); + pw.print(" #"); pw.print(i); pw.print(": "); + pw.println(r); + if (full) { + r.dump(pw, innerPrefix); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + pw.print(innerPrefix); pw.println(r.intent.toInsecureString()); + if (r.app != null) { + pw.print(innerPrefix); pw.println(r.app); + } + } + if (client && r.app != null && r.app.thread != null) { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(), + r.appToken, innerPrefix, args); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(innerPrefix + "Failure while dumping the activity: " + e); + } catch (RemoteException e) { + pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); + } + needNL = true; + } + } + return printed; + } + + void scheduleIdleTimeoutLocked(ActivityRecord next) { + if (DEBUG_IDLE) Slog.d(TAG, "scheduleIdleTimeoutLocked: Callers=" + Debug.getCallers(4)); + Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next); + mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); + } + + final void scheduleIdleLocked() { + mHandler.sendEmptyMessage(IDLE_NOW_MSG); + } + + void removeTimeoutsForActivityLocked(ActivityRecord r) { + if (DEBUG_IDLE) Slog.d(TAG, "removeTimeoutsForActivity: Callers=" + Debug.getCallers(4)); + mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + } + + final void scheduleResumeTopActivities() { + mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); + } + + void removeSleepTimeouts() { + mSleepTimeout = false; + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + } + + final void scheduleSleepTimeout() { + removeSleepTimeouts(); + mHandler.sendEmptyMessageDelayed(SLEEP_TIMEOUT_MSG, SLEEP_TIMEOUT); + } + + @Override + public void onDisplayAdded(int displayId) { + mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_ADDED, displayId, 0)); + } + + @Override + public void onDisplayRemoved(int displayId) { + mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_REMOVED, displayId, 0)); + } + + @Override + public void onDisplayChanged(int displayId) { + mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0)); + } + + public void handleDisplayAddedLocked(int displayId) { + synchronized (mService) { + ActivityDisplayInfo info = new ActivityDisplayInfo(displayId); + mDisplayInfos.put(displayId, info); + } + mWindowManager.onDisplayAdded(displayId); + } + + public void handleDisplayRemovedLocked(int displayId) { + synchronized (mService) { + ActivityDisplayInfo info = mDisplayInfos.get(displayId); + if (info != null) { + ArrayList<ActivityStack> stacks = info.stacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + info.detachActivities(stacks.get(stackNdx)); + } + mDisplayInfos.remove(displayId); + } + } + mWindowManager.onDisplayRemoved(displayId); + } + + public void handleDisplayChangedLocked(int displayId) { + synchronized (mService) { + ActivityDisplayInfo info = mDisplayInfos.get(displayId); + if (info != null) { + // TODO: Update the bounds. + } + } + mWindowManager.onDisplayChanged(displayId); + } + + StackInfo getStackInfo(ActivityStack stack) { + StackInfo info = new StackInfo(); + mWindowManager.getStackBounds(stack.mStackId, info.bounds); + info.displayId = Display.DEFAULT_DISPLAY; + info.stackId = stack.mStackId; + + ArrayList<TaskRecord> tasks = stack.getAllTasks(); + final int numTasks = tasks.size(); + int[] taskIds = new int[numTasks]; + String[] taskNames = new String[numTasks]; + for (int i = 0; i < numTasks; ++i) { + final TaskRecord task = tasks.get(i); + taskIds[i] = task.taskId; + taskNames[i] = task.origActivity != null ? task.origActivity.flattenToString() + : task.realActivity != null ? task.realActivity.flattenToString() + : task.getTopActivity() != null ? task.getTopActivity().packageName + : "unknown"; + } + info.taskIds = taskIds; + info.taskNames = taskNames; + return info; + } + + StackInfo getStackInfoLocked(int stackId) { + ActivityStack stack = getStack(stackId); + if (stack != null) { + return getStackInfo(stack); + } + return null; + } + + ArrayList<StackInfo> getAllStackInfosLocked() { + ArrayList<StackInfo> list = new ArrayList<StackInfo>(); + for (int displayNdx = 0; displayNdx < mDisplayInfos.size(); ++displayNdx) { + ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks; + for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) { + list.add(getStackInfo(stacks.get(ndx))); + } + } + return list; + } + + private final class ActivityStackSupervisorHandler extends Handler { + + public ActivityStackSupervisorHandler(Looper looper) { + super(looper); + } + + void activityIdleInternal(ActivityRecord r) { + synchronized (mService) { + activityIdleInternalLocked(r != null ? r.appToken : null, true, null); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case IDLE_TIMEOUT_MSG: { + if (DEBUG_IDLE) Slog.d(TAG, "handleMessage: IDLE_TIMEOUT_MSG: r=" + msg.obj); + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); + return; + } + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + activityIdleInternal((ActivityRecord)msg.obj); + } break; + case IDLE_NOW_MSG: { + if (DEBUG_IDLE) Slog.d(TAG, "handleMessage: IDLE_NOW_MSG: r=" + msg.obj); + activityIdleInternal((ActivityRecord)msg.obj); + } break; + case RESUME_TOP_ACTIVITY_MSG: { + synchronized (mService) { + resumeTopActivitiesLocked(); + } + } break; + case SLEEP_TIMEOUT_MSG: { + synchronized (mService) { + if (mService.isSleepingOrShuttingDown()) { + Slog.w(TAG, "Sleep timeout! Sleeping now."); + mSleepTimeout = true; + checkReadyForSleepLocked(); + } + } + } break; + case LAUNCH_TIMEOUT_MSG: { + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT); + return; + } + synchronized (mService) { + if (mLaunchingActivity.isHeld()) { + Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); + if (VALIDATE_WAKE_LOCK_CALLER + && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.release(); + } + } + } break; + case HANDLE_DISPLAY_ADDED: { + handleDisplayAddedLocked(msg.arg1); + } break; + case HANDLE_DISPLAY_CHANGED: { + handleDisplayChangedLocked(msg.arg1); + } break; + case HANDLE_DISPLAY_REMOVED: { + handleDisplayRemovedLocked(msg.arg1); + } break; + } + } + } + + class ActivityContainer extends IActivityContainer.Stub { + final int mStackId; + final IActivityContainerCallback mCallback; + final ActivityStack mStack; + final ActivityRecord mParentActivity; + + /** Display this ActivityStack is currently on. Null if not attached to a Display. */ + ActivityDisplayInfo mActivityDisplayInfo; + + ActivityContainer(ActivityRecord parentActivity, int stackId, + IActivityContainerCallback callback) { + synchronized (mService) { + mStackId = stackId; + mStack = new ActivityStack(this); + mParentActivity = parentActivity; + mCallback = callback; + } + } + + void attachToDisplayLocked(ActivityDisplayInfo displayInfo) { + mActivityDisplayInfo = displayInfo; + displayInfo.attachActivities(mStack); + mWindowManager.createStack(mStackId, displayInfo.mDisplayId); + } + + @Override + public void attachToDisplay(int displayId) throws RemoteException { + synchronized (mService) { + ActivityDisplayInfo displayInfo = mDisplayInfos.get(displayId); + if (displayInfo == null) { + return; + } + attachToDisplayLocked(displayInfo); + } + } + + @Override + public int getStackId() throws RemoteException { + return mStack.mStackId; + } + + void detachLocked() { + if (mActivityDisplayInfo != null) { + mActivityDisplayInfo.detachActivities(mStack); + mActivityDisplayInfo = null; + } + } + + @Override + public void detachFromDisplay() throws RemoteException { + synchronized (mService) { + detachLocked(); + } + } + + @Override + public void startActivity(Intent intent) throws RemoteException { + + } + + @Override + public IBinder asBinder() { + return this; + } + + ActivityStackSupervisor getOuter() { + return ActivityStackSupervisor.this; + } + + boolean isAttached() { + return mActivityDisplayInfo != null; + } + + void getBounds(Point outBounds) { + if (mActivityDisplayInfo != null) { + mActivityDisplayInfo.getBounds(outBounds); + } else { + outBounds.set(0, 0); + } + } + } + + /** Exactly one of these classes per Display in the system. Capable of holding zero or more + * attached {@link ActivityStack}s */ + final class ActivityDisplayInfo { + /** Actual Display this object tracks. */ + final int mDisplayId; + final Display mDisplay; + final DisplayInfo mDisplayInfo = new DisplayInfo(); + + /** All of the stacks on this display. Order matters, topmost stack is in front of all other + * stacks, bottommost behind. Accessed directly by ActivityManager package classes */ + final ArrayList<ActivityStack> stacks = new ArrayList<ActivityStack>(); + + ActivityDisplayInfo(int displayId) { + mDisplayId = displayId; + mDisplay = mDisplayManager.getDisplay(displayId); + mDisplay.getDisplayInfo(mDisplayInfo); + } + + void attachActivities(ActivityStack stack) { + stacks.add(stack); + } + + void detachActivities(ActivityStack stack) { + stacks.remove(stack); + } + + void getBounds(Point bounds) { + mDisplay.getDisplayInfo(mDisplayInfo); + bounds.x = mDisplayInfo.appWidth; + bounds.y = mDisplayInfo.appHeight; + } + } +} |