Move mStacks to ActivityStackSupervisor

Lift all ActivityStack behaviors from ActivityManagerService.

Change-Id: I356f1c22fe31f0442ff5e363f62fe99b7bfd6153
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index ff86416..6c0be17 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -16,13 +16,24 @@
 
 package com.android.server.am;
 
+import static com.android.server.am.ActivityManagerService.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE;
+import static com.android.server.am.ActivityManagerService.TAG;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.os.Bundle;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.util.EventLog;
+import android.util.Slog;
 
+import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 public class ActivityStackSupervisor {
     public static final int HOME_STACK_ID = 0;
@@ -34,10 +45,22 @@
     /** Dismiss the keyguard after the next activity is displayed? */
     private boolean mDismissKeyguardOnNextActivity = false;
 
+    /** Identifier counter for all ActivityStacks */
+    private int mLastStackId = 0;
+
+    /** Task identifier that activities are currently being started in.  Incremented each time a
+     * new task is created. */
+    private int mCurTaskId = 0;
+
+    /** The stack containing the launcher app */
     private ActivityStack mHomeStack;
     private ActivityStack mMainStack;
+
+    /** All the non-launcher stacks */
     private ArrayList<ActivityStack> mStacks = new ArrayList<ActivityStack>();
 
+    private boolean mHomeOnTop = true;
+
     public ActivityStackSupervisor(ActivityManagerService service, Context context,
             Looper looper) {
         mService = service;
@@ -49,6 +72,7 @@
         mHomeStack = new ActivityStack(mService, mContext, mLooper, HOME_STACK_ID, this);
         setMainStack(mHomeStack);
         mService.mFocusedStack = mHomeStack;
+        mStacks.add(mHomeStack);
     }
 
     void dismissKeyguard() {
@@ -70,13 +94,376 @@
         mDismissKeyguardOnNextActivity = dismiss;
     }
 
+    TaskRecord anyTaskForIdLocked(int id) {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            ActivityStack stack = mStacks.get(stackNdx);
+            TaskRecord task = stack.taskForIdLocked(id);
+            if (task != null) {
+                return task;
+            }
+        }
+        return null;
+    }
+
+    int getNextTaskId() {
+        do {
+            mCurTaskId++;
+            if (mCurTaskId <= 0) {
+                mCurTaskId = 1;
+            }
+        } while (anyTaskForIdLocked(mCurTaskId) != null);
+        return mCurTaskId;
+    }
+
     void startHomeActivity(Intent intent, ActivityInfo aInfo) {
         mHomeStack.startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0,
                 null, false, null);
+        mHomeOnTop = true;
+    }
+
+    void handleAppDiedLocked(ProcessRecord app, boolean restarting) {
+        // Just in case.
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            if (stack.mPausingActivity != null && stack.mPausingActivity.app == app) {
+                if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG,
+                        "App died while pausing: " + stack.mPausingActivity);
+                stack.mPausingActivity = null;
+            }
+            if (stack.mLastPausedActivity != null && stack.mLastPausedActivity.app == app) {
+                stack.mLastPausedActivity = null;
+            }
+
+            // Remove this application's activities from active lists.
+            boolean hasVisibleActivities = stack.removeHistoryRecordsForAppLocked(app);
+
+            if (!restarting) {
+                if (!stack.resumeTopActivityLocked(null)) {
+                    // If there was nothing to resume, and we are not already
+                    // restarting this process, but there is a visible activity that
+                    // is hosted by the process...  then make sure all visible
+                    // activities are running, taking care of restarting this
+                    // process.
+                    if (hasVisibleActivities) {
+                        stack.ensureActivitiesVisibleLocked(null, 0);
+                    }
+                }
+            }
+        }
+    }
+
+    void closeSystemDialogsLocked() {
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.closeSystemDialogsLocked();
+        }
+    }
+
+    /**
+     * @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;
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            if (stack.forceStopPackageLocked(name, doit, evenPersistent, userId)) {
+                didSomething = true;
+            }
+        }
+        return didSomething;
+    }
+
+    void resumeTopActivityLocked() {
+        final int start, end;
+        if (mHomeOnTop) {
+            start = 0;
+            end = 1;
+        } else {
+            start = 1;
+            end = mStacks.size();
+        }
+        for (int stackNdx = start; stackNdx < end; ++stackNdx) {
+            mStacks.get(stackNdx).resumeTopActivityLocked(null);
+        }
+    }
+
+    void finishTopRunningActivityLocked(ProcessRecord app) {
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.finishTopRunningActivityLocked(app);
+        }
+    }
+
+    void scheduleIdleLocked() {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).scheduleIdleLocked();
+        }
+    }
+
+    void findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            if (mStacks.get(stackNdx).findTaskToMoveToFrontLocked(taskId, flags, options)) {
+                return;
+            }
+        }
+    }
+
+    private ActivityStack getStack(int stackId) {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            if (stack.getStackId() == stackId) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    int createStack(int relativeStackId, int position, float weight) {
+        synchronized (this) {
+            while (true) {
+                if (++mLastStackId <= HOME_STACK_ID) {
+                    mLastStackId = HOME_STACK_ID + 1;
+                }
+                if (getStack(mLastStackId) == null) {
+                    break;
+                }
+            }
+            mStacks.add(new ActivityStack(mService, mContext, mLooper, mLastStackId, this));
+            return mLastStackId;
+        }
+    }
+
+    void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+        final ActivityStack stack = getStack(stackId);
+        if (stack == null) {
+            Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
+            return;
+        }
+        stack.moveTask(taskId, toTop);
+    }
+
+    void goingToSleepLocked() {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).stopIfSleepingLocked();
+        }
+    }
+
+    boolean shutdownLocked(int timeout) {
+        boolean timedout = false;
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            if (stack.mResumedActivity != null) {
+                stack.stopIfSleepingLocked();
+                final long endTime = System.currentTimeMillis() + timeout;
+                while (stack.mResumedActivity != null || stack.mPausingActivity != null) {
+                    long delay = endTime - System.currentTimeMillis();
+                    if (delay <= 0) {
+                        Slog.w(TAG, "Activity manager shutdown timed out");
+                        timedout = true;
+                        break;
+                    }
+                    try {
+                        mService.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+        return timedout;
+    }
+
+    void comeOutOfSleepIfNeededLocked() {
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.awakeFromSleepingLocked();
+            stack.resumeTopActivityLocked(null);
+        }
+    }
+
+    void handleAppCrashLocked(ProcessRecord app) {
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.handleAppCrashLocked(app);
+        }
+    }
+
+    boolean updateConfigurationLocked(int changes, ActivityRecord starting) {
+        boolean kept = true;
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            if (changes != 0 && starting == null) {
+                // If the configuration changed, and the caller is not already
+                // in the process of starting an activity, then find the top
+                // activity to check if its configuration needs to change.
+                starting = stack.topRunningActivityLocked(null);
+            }
+
+            if (starting != null) {
+                if (!stack.ensureActivityConfigurationLocked(starting, changes)) {
+                    kept = false;
+                }
+                // And we need to make sure at this point that all other activities
+                // are made visible with the correct configuration.
+                stack.ensureActivitiesVisibleLocked(starting, changes);
+            }
+        }
+        return kept;
+    }
+
+    void scheduleDestroyAllActivities(ProcessRecord app, String reason) {
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.scheduleDestroyActivities(app, false, reason);
+        }
+    }
+
+    boolean switchUserLocked(int userId, UserStartedState uss) {
+        boolean haveActivities = false;
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            haveActivities |= stack.switchUserLocked(userId, uss);
+        }
+        return haveActivities;
     }
 
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mDismissKeyguardOnNextActivity:");
                 pw.println(mDismissKeyguardOnNextActivity);
     }
+
+    boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
+            boolean dumpClient, String dumpPackage) {
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            pw.print("  Stack #"); pw.print(mStacks.indexOf(stack)); pw.println(":");
+            stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage);
+            pw.println(" ");
+            pw.println("  Running activities (most recent first):");
+            dumpHistoryList(fd, pw, stack.mLRUActivities, "  ", "Run", false, !dumpAll, false,
+                    dumpPackage);
+            if (stack.mWaitingVisibleActivities.size() > 0) {
+                pw.println(" ");
+                pw.println("  Activities waiting for another to become visible:");
+                dumpHistoryList(fd, pw, stack.mWaitingVisibleActivities, "  ", "Wait", false,
+                        !dumpAll, false, dumpPackage);
+            }
+            if (stack.mStoppingActivities.size() > 0) {
+                pw.println(" ");
+                pw.println("  Activities waiting to stop:");
+                dumpHistoryList(fd, pw, stack.mStoppingActivities, "  ", "Stop", false,
+                        !dumpAll, false, dumpPackage);
+            }
+            if (stack.mGoingToSleepActivities.size() > 0) {
+                pw.println(" ");
+                pw.println("  Activities waiting to sleep:");
+                dumpHistoryList(fd, pw, stack.mGoingToSleepActivities, "  ", "Sleep", false,
+                        !dumpAll, false, dumpPackage);
+            }
+            if (stack.mFinishingActivities.size() > 0) {
+                pw.println(" ");
+                pw.println("  Activities waiting to finish:");
+                dumpHistoryList(fd, pw, stack.mFinishingActivities, "  ", "Fin", false,
+                        !dumpAll, false, dumpPackage);
+            }
+        }
+
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            pw.print("  Stack #"); pw.println(mStacks.indexOf(stack));
+            if (stack.mPausingActivity != null) {
+                pw.println("  mPausingActivity: " + stack.mPausingActivity);
+            }
+            pw.println("  mResumedActivity: " + stack.mResumedActivity);
+            if (dumpAll) {
+                pw.println("  mLastPausedActivity: " + stack.mLastPausedActivity);
+                pw.println("  mSleepTimeout: " + stack.mSleepTimeout);
+            }
+        }
+
+        if (dumpAll) {
+            pw.println(" ");
+            pw.println("  mCurTaskId: " + mCurTaskId);
+        }
+        return true;
+    }
+
+    static final void dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list,
+            String prefix, String label, boolean complete, boolean brief, boolean client,
+            String dumpPackage) {
+        TaskRecord lastTask = null;
+        boolean needNL = false;
+        final String innerPrefix = prefix + "      ";
+        final String[] args = new String[0];
+        for (int i=list.size()-1; i>=0; i--) {
+            final ActivityRecord r = list.get(i);
+            if (dumpPackage != null && !dumpPackage.equals(r.packageName)) {
+                continue;
+            }
+            final boolean full = !brief && (complete || !r.isInHistory());
+            if (needNL) {
+                pw.println(" ");
+                needNL = false;
+            }
+            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;
+            }
+        }
+    }
 }