Fix user switching.

- Save and restore WindowManager stack states.
- Maintain ActivityManager activity states based on the stack
the activity is in.

Fixes bug 8646641.

Change-Id: I16c76c7708ab49121c3884a7e5bf219898b92d3f
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index dd113c3..c750cad 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3612,7 +3612,7 @@
                                     "for process:"+packageName);
                 }
             }
-            
+
             try {
                 // Clear application user data
                 pm.clearApplicationUserData(packageName, observer, userId);
@@ -3681,7 +3681,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        
+
         long callingId = Binder.clearCallingIdentity();
         try {
             synchronized(this) {
@@ -3702,7 +3702,7 @@
                         }
                     }
                 }
-                
+
                 int N = procs.size();
                 for (int i=0; i<N; i++) {
                     removeProcessLocked(procs.get(i), false, true, "kill all background");
@@ -3971,7 +3971,7 @@
                 procs.add(app);
             }
         }
-        
+
         int N = procs.size();
         for (int i=0; i<N; i++) {
             removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, reason);
@@ -4133,7 +4133,7 @@
                 }
             }
             if (mBooted) {
-                mStackSupervisor.resumeTopActivityLocked();
+                mStackSupervisor.resumeTopActivitiesLocked();
                 mStackSupervisor.scheduleIdleLocked();
             }
         }
@@ -4983,7 +4983,8 @@
             updateOomAdjLocked();
         }
     }
-    
+
+    @Override
     public void setProcessForeground(IBinder token, int pid, boolean isForeground) {
         enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
                 "setProcessForeground()");
@@ -5007,6 +5008,7 @@
                 }
                 if (isForeground && token != null) {
                     ForegroundToken newToken = new ForegroundToken() {
+                        @Override
                         public void binderDied() {
                             foregroundTokenDied(this);
                         }
@@ -5041,6 +5043,7 @@
             mActivityManagerService = activityManagerService;
         }
 
+        @Override
         public boolean checkPermission(String permission, int pid, int uid) {
             return mActivityManagerService.checkPermission(permission, pid,
                     uid) == PackageManager.PERMISSION_GRANTED;
@@ -5048,12 +5051,14 @@
     }
 
     class IntentFirewallInterface implements IntentFirewall.AMSInterface {
+        @Override
         public int checkComponentPermission(String permission, int pid, int uid,
                 int owningUid, boolean exported) {
             return ActivityManagerService.this.checkComponentPermission(permission, pid, uid,
                     owningUid, exported);
         }
 
+        @Override
         public Object getAMSLock() {
             return ActivityManagerService.this;
         }
@@ -8281,7 +8286,7 @@
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
-            mStackSupervisor.resumeTopActivityLocked();
+            mStackSupervisor.resumeTopActivitiesLocked();
             sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
         }
     }
@@ -8396,10 +8401,10 @@
                 // annoy the user repeatedly.  Unless it is persistent, since those
                 // processes run critical code.
                 removeProcessLocked(app, false, false, "crash");
-                mStackSupervisor.resumeTopActivityLocked();
+                mStackSupervisor.resumeTopActivitiesLocked();
                 return false;
             }
-            mStackSupervisor.resumeTopActivityLocked();
+            mStackSupervisor.resumeTopActivitiesLocked();
         } else {
             mStackSupervisor.finishTopRunningActivityLocked(app);
         }
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 3557e90..3758bb1 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -234,7 +234,7 @@
     int mThumbnailWidth = -1;
     int mThumbnailHeight = -1;
 
-    private int mCurrentUser;
+    int mCurrentUser;
 
     final int mStackId;
 
@@ -576,7 +576,8 @@
     }
 
     /*
-     * Move the activities around in the stack to bring a user to the foreground.
+     * Move the activities around in the stack to bring a user to the foreground. This only
+     * matters on the home stack. All other stacks are single user.
      * @return whether there are any activities for the specified user.
      */
     final boolean switchUserLocked(int userId, UserStartedState uss) {
@@ -602,9 +603,6 @@
             }
         }
 
-        // task is now the original topmost TaskRecord. Transition from the old top to the new top.
-        ActivityRecord top = task != null ? task.getTopActivity() : null;
-        resumeTopActivityLocked(top);
         return haveActivities;
     }
 
@@ -1718,7 +1716,7 @@
         }
 
         if (doResume) {
-            mStackSupervisor.resumeTopActivityLocked();
+            mStackSupervisor.resumeTopActivitiesLocked();
         }
     }
 
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 89b0ff2..528bf6f 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -61,6 +61,7 @@
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
@@ -160,6 +161,9 @@
      * is being brought in front of us. */
     boolean mUserLeaving = false;
 
+    /** Stacks belonging to users other than mCurrentUser. Indexed by userId. */
+    final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
+
     public ActivityStackSupervisor(ActivityManagerService service, Context context,
             Looper looper) {
         mService = service;
@@ -213,6 +217,9 @@
     }
 
     boolean isFrontStack(ActivityStack stack) {
+        if (stack.mCurrentUser != mCurrentUser) {
+            return false;
+        }
         return !(stack.isHomeStack() ^ getFocusedStack().isHomeStack());
     }
 
@@ -319,6 +326,9 @@
         final String processName = app.processName;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
+            if (!isFrontStack(stack)) {
+                continue;
+            }
             ActivityRecord hr = stack.topRunningActivityLocked(null);
             if (hr != null) {
                 if (hr.app == null && app.uid == hr.info.applicationInfo.uid
@@ -412,7 +422,7 @@
     }
 
     void reportActivityVisibleLocked(ActivityRecord r) {
-        for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) {
+        for (int i = mWaitingActivityVisible.size()-1; i >= 0; i--) {
             WaitResult w = mWaitingActivityVisible.get(i);
             w.timeout = false;
             if (r != null) {
@@ -449,6 +459,9 @@
         }
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
+            if (stack.mCurrentUser != mCurrentUser) {
+                continue;
+            }
             if (stack != mFocusedStack && isFrontStack(stack)) {
                 r = stack.topRunningActivityLocked(null);
                 if (r != null) {
@@ -1145,8 +1158,14 @@
 
     ActivityStack getCorrectStack(ActivityRecord r) {
         if (!r.isHomeActivity) {
-            if (mStacks.size() == 1) {
-                // Time to create the first app stack.
+            int stackNdx;
+            for (stackNdx = mStacks.size() - 1; stackNdx > 0; --stackNdx) {
+                if (mStacks.get(stackNdx).mCurrentUser == mCurrentUser) {
+                    break;
+                }
+            }
+            if (stackNdx == 0) {
+                // Time to create the first app stack for this user.
                 int stackId = mService.createStack(-1, HOME_STACK_ID,
                         StackBox.TASK_STACK_GOES_OVER, 1.0f);
                 mFocusedStack = getStack(stackId);
@@ -1776,7 +1795,7 @@
         return didSomething;
     }
 
-    void resumeTopActivityLocked() {
+    void resumeTopActivitiesLocked() {
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
             if (isFrontStack(stack)) {
@@ -1816,18 +1835,16 @@
     }
 
     int createStack() {
-        synchronized (this) {
-            while (true) {
-                if (++mLastStackId <= HOME_STACK_ID) {
-                    mLastStackId = HOME_STACK_ID + 1;
-                }
-                if (getStack(mLastStackId) == null) {
-                    break;
-                }
+        while (true) {
+            if (++mLastStackId <= HOME_STACK_ID) {
+                mLastStackId = HOME_STACK_ID + 1;
             }
-            mStacks.add(new ActivityStack(mService, mContext, mLooper, mLastStackId));
-            return mLastStackId;
+            if (getStack(mLastStackId) == null) {
+                break;
+            }
         }
+        mStacks.add(new ActivityStack(mService, mContext, mLooper, mLastStackId));
+        return mLastStackId;
     }
 
     void moveTaskToStack(int taskId, int stackId, boolean toTop) {
@@ -1938,16 +1955,22 @@
     }
 
     boolean switchUserLocked(int userId, UserStartedState uss) {
+        mUserStates.put(mCurrentUser, new UserState());
         mCurrentUser = userId;
-        mStartingUsers.add(uss);
-        boolean haveActivities = false;
-        final int numStacks = mStacks.size();
-        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-            final ActivityStack stack = mStacks.get(stackNdx);
-            if (isFrontStack(stack)) {
-                haveActivities |= stack.switchUserLocked(userId, uss);
-            }
+        UserState userState = mUserStates.get(userId);
+        if (userState != null) {
+            userState.restore();
+            mUserStates.delete(userId);
+        } else {
+            mFocusedStack = null;
+            mStackState = STACK_STATE_HOME_IN_FRONT;
         }
+
+        mStartingUsers.add(uss);
+        boolean haveActivities = mHomeStack.switchUserLocked(userId, uss);
+
+        resumeTopActivitiesLocked();
+
         return haveActivities;
     }
 
@@ -2203,4 +2226,21 @@
             }
         }
     }
+
+    private final class UserState {
+        final ActivityStack mSavedFocusedStack;
+        final int mSavedStackState;
+
+        public UserState() {
+            ActivityStackSupervisor supervisor = ActivityStackSupervisor.this;
+            mSavedFocusedStack = supervisor.mFocusedStack;
+            mSavedStackState = supervisor.mStackState;
+        }
+
+        void restore() {
+            ActivityStackSupervisor supervisor = ActivityStackSupervisor.this;
+            supervisor.mFocusedStack = mSavedFocusedStack;
+            supervisor.mStackState = mSavedStackState;
+        }
+    }
 }
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 8343986..45f018d 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -18,10 +18,12 @@
 
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 
 import android.graphics.Rect;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.InputChannel;
@@ -104,6 +106,8 @@
     /** Detect user tapping outside of current focused stack bounds .*/
     StackTapDetector mTapDetector;
 
+    SparseArray<UserStacks> mUserStacks = new SparseArray<UserStacks>();
+
     /**
      * @param display May not be null.
      */
@@ -145,6 +149,22 @@
      */
     ArrayList<Task> getTasks() {
         mTmpTasks.clear();
+        // First do the tasks belonging to other users.
+        final int numUserStacks = mUserStacks.size();
+        for (int i = 0; i < numUserStacks; ++i) {
+            UserStacks userStacks = mUserStacks.valueAt(i);
+            ArrayList<TaskStack> stacks = userStacks.mSavedStackHistory;
+            final int numStacks = stacks.size();
+            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+                TaskStack stack = stacks.get(stackNdx);
+                if (stack != mHomeStack) {
+                    if (WindowManagerService.DEBUG_LAYERS) Slog.i(TAG, "getTasks: mStackHistory=" +
+                            mStackHistory);
+                    mTmpTasks.addAll(stack.getTasks());
+                }
+            }
+        }
+        // Now do the current user's tasks.
         final int numStacks = mStackHistory.size();
         for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
             mTmpTasks.addAll(mStackHistory.get(stackNdx).getTasks());
@@ -292,6 +312,14 @@
                 return bounds;
             }
         }
+        // Not in the visible stacks, try the saved ones.
+        for (int userNdx = mUserStacks.size() - 1; userNdx >= 0; --userNdx) {
+            UserStacks userStacks = mUserStacks.valueAt(userNdx);
+            Rect bounds = userStacks.mSavedStackBox.getStackBounds(stackId);
+            if (bounds != null) {
+                return bounds;
+            }
+        }
         return null;
     }
 
@@ -300,6 +328,26 @@
         return topBox.stackIdFromPoint(x, y);
     }
 
+    void switchUserStacks(int oldUserId, int newUserId) {
+        final WindowList windows = getWindowList();
+        for (int i = 0; i < windows.size(); i++) {
+            final WindowState win = windows.get(i);
+            if (win.isHiddenFromUserLocked()) {
+                if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing " + newUserId + " hiding "
+                        + win + ", attrs=" + win.mAttrs.type + ", belonging to "
+                        + win.mOwnerUid);
+                win.hideLw(false);
+            }
+        }
+        // Clear the old user's non-home StackBox
+        mUserStacks.put(oldUserId, new UserStacks());
+        UserStacks userStacks = mUserStacks.get(newUserId);
+        if (userStacks != null) {
+            userStacks.restore();
+            mUserStacks.delete(newUserId);
+        }
+    }
+
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
         final String subPrefix = "  " + prefix;
@@ -365,6 +413,49 @@
                   token.dump(pw, "    ");
             }
         }
+        if (mUserStacks.size() > 0) {
+            pw.println();
+            pw.println("  Saved user stacks:");
+            for (int i = 0; i < mUserStacks.size(); ++i) {
+                UserStacks userStacks = mUserStacks.valueAt(i);
+                pw.print("  UserId="); pw.println(Integer.toHexString(mUserStacks.keyAt(i)));
+                pw.print("  StackHistory="); pw.println(userStacks.mSavedStackHistory);
+                pw.print("  StackBox="); userStacks.mSavedStackBox.dump("    ", pw);
+            }
+        }
         pw.println();
     }
+
+    private final class UserStacks {
+        final ArrayList<TaskStack> mSavedStackHistory;
+        StackBox mSavedStackBox;
+        int mBoxNdx;
+
+        public UserStacks() {
+            mSavedStackHistory = new ArrayList<TaskStack>(mStackHistory);
+            for (int stackNdx = mStackHistory.size() - 1; stackNdx >=0; --stackNdx) {
+                if (mStackHistory.get(stackNdx) != mHomeStack) {
+                    mStackHistory.remove(stackNdx);
+                }
+            }
+            mSavedStackBox = null;
+            mBoxNdx = -1;
+            for (int boxNdx = mStackBoxes.size() - 1; boxNdx >= 0; --boxNdx) {
+                StackBox box = mStackBoxes.get(boxNdx);
+                if (box.mStack != mHomeStack) {
+                    mSavedStackBox = box;
+                    mBoxNdx = boxNdx;
+                    mStackBoxes.remove(boxNdx);
+                    break;
+                }
+            }
+        }
+
+        void restore() {
+            mStackHistory = mSavedStackHistory;
+            if (mBoxNdx >= 0) {
+                mStackBoxes.add(mBoxNdx, mSavedStackBox);
+            }
+        }
+    }
 }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 5a9d63a..cb4333f 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -5094,22 +5094,16 @@
 
     public void setCurrentUser(final int newUserId) {
         synchronized (mWindowMap) {
+            int oldUserId = mCurrentUserId;
             mCurrentUserId = newUserId;
             mPolicy.setCurrentUserLw(newUserId);
 
             // Hide windows that should not be seen by the new user.
             DisplayContentsIterator iterator = new DisplayContentsIterator();
             while (iterator.hasNext()) {
-                final WindowList windows = iterator.next().getWindowList();
-                for (int i = 0; i < windows.size(); i++) {
-                    final WindowState win = windows.get(i);
-                    if (win.isHiddenFromUserLocked()) {
-                        Slog.w(TAG, "current user violation " + newUserId + " hiding "
-                                + win + ", attrs=" + win.mAttrs.type + ", belonging to "
-                                + win.mOwnerUid);
-                        win.hideLw(false);
-                    }
-                }
+                DisplayContent displayContent = iterator.next();
+                displayContent.switchUserStacks(oldUserId, newUserId);
+                rebuildAppWindowListLocked(displayContent);
             }
             performLayoutAndPlaceSurfacesLocked();
         }