Showing docked stack divider window.

When there is docked stack, we want to show a "handle" through which the
user will resize the docked stack. We add it from the system process,
because we want to avoid IPC calls.

Change-Id: If15fd2a0fcb7077446d1ba6c533acb3028a42039
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 04d382d..3974205 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2003,6 +2003,7 @@
             case TYPE_SYSTEM_DIALOG:
             case TYPE_VOLUME_OVERLAY:
             case TYPE_PRIVATE_PRESENTATION:
+            case TYPE_DOCK_DIVIDER:
                 break;
         }
 
@@ -2134,54 +2135,56 @@
         case TYPE_INPUT_METHOD_DIALOG:
             // on-screen keyboards and other such input method user interfaces go here.
             return 13;
+        case TYPE_DOCK_DIVIDER:
+            return 14;
         case TYPE_KEYGUARD_SCRIM:
             // the safety window that shows behind keyguard while keyguard is starting
-            return 14;
-        case TYPE_STATUS_BAR_SUB_PANEL:
             return 15;
-        case TYPE_STATUS_BAR:
+        case TYPE_STATUS_BAR_SUB_PANEL:
             return 16;
-        case TYPE_STATUS_BAR_PANEL:
+        case TYPE_STATUS_BAR:
             return 17;
-        case TYPE_KEYGUARD_DIALOG:
+        case TYPE_STATUS_BAR_PANEL:
             return 18;
+        case TYPE_KEYGUARD_DIALOG:
+            return 19;
         case TYPE_VOLUME_OVERLAY:
             // the on-screen volume indicator and controller shown when the user
             // changes the device volume
-            return 19;
+            return 20;
         case TYPE_SYSTEM_OVERLAY:
             // the on-screen volume indicator and controller shown when the user
             // changes the device volume
-            return 20;
+            return 21;
         case TYPE_NAVIGATION_BAR:
             // the navigation bar, if available, shows atop most things
-            return 21;
+            return 22;
         case TYPE_NAVIGATION_BAR_PANEL:
             // some panels (e.g. search) need to show on top of the navigation bar
-            return 22;
+            return 23;
         case TYPE_SYSTEM_ERROR:
             // system-level error dialogs
-            return 23;
+            return 24;
         case TYPE_MAGNIFICATION_OVERLAY:
             // used to highlight the magnified portion of a display
-            return 24;
+            return 25;
         case TYPE_DISPLAY_OVERLAY:
             // used to simulate secondary display devices
-            return 25;
+            return 26;
         case TYPE_DRAG:
             // the drag layer: input for drag-and-drop is associated with this window,
             // which sits above all other focusable windows
-            return 26;
+            return 27;
         case TYPE_ACCESSIBILITY_OVERLAY:
             // overlay put by accessibility services to intercept user interaction
-            return 27;
-        case TYPE_SECURE_SYSTEM_OVERLAY:
             return 28;
-        case TYPE_BOOT_PROGRESS:
+        case TYPE_SECURE_SYSTEM_OVERLAY:
             return 29;
+        case TYPE_BOOT_PROGRESS:
+            return 30;
         case TYPE_POINTER:
             // the (mouse) pointer layer
-            return 30;
+            return 31;
         }
         Log.e(TAG, "Unknown window type: " + type);
         return 2;
@@ -2271,6 +2274,7 @@
             case TYPE_WALLPAPER:
             case TYPE_DREAM:
             case TYPE_KEYGUARD_SCRIM:
+            case TYPE_DOCK_DIVIDER:
                 return false;
             default:
                 // Hide only windows below the keyguard host window.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cb0dba8..4392ab4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
 
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
@@ -111,6 +112,8 @@
     /** Remove this display when animation on it has completed. */
     boolean mDeferredRemoval;
 
+    final DockedStackDividerController mDividerControllerLocked;
+
     /**
      * @param display May not be null.
      * @param service You know.
@@ -122,6 +125,7 @@
         display.getMetrics(mDisplayMetrics);
         isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
         mService = service;
+        mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
     }
 
     int getDisplayId() {
@@ -552,4 +556,14 @@
     public String toString() {
         return "Display " + mDisplayId + " info=" + mDisplayInfo + " stacks=" + mStacks;
     }
+
+    TaskStack getDockedStack() {
+        for (int i = mStacks.size() - 1; i >= 0; i--) {
+            TaskStack stack = mStacks.get(i);
+            if (stack.mStackId == DOCKED_STACK_ID) {
+                return stack;
+            }
+        }
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
new file mode 100644
index 0000000..ad207d4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2012 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.wm;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+/**
+ * Controls showing and hiding of a docked stack divider on the display.
+ */
+public class DockedStackDividerController {
+    private static final String TAG = "DockedStackDivider";
+    private final Context mContext;
+    private final int mDividerWidth;
+    private final DisplayContent mDisplayContent;
+    private View mView;
+    private Rect mTmpRect = new Rect();
+
+    DockedStackDividerController(Context context, DisplayContent displayContent) {
+        mContext = context;
+        mDisplayContent = displayContent;
+        mDividerWidth = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+    }
+
+    private void addDivider() {
+        View view = LayoutInflater.from(mContext).inflate(
+                com.android.internal.R.layout.docked_stack_divider, null);
+        WindowManagerGlobal manager = WindowManagerGlobal.getInstance();
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                mDividerWidth, MATCH_PARENT, TYPE_DOCK_DIVIDER,
+                FLAG_TOUCHABLE_WHEN_WAKING | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
+                        | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+                PixelFormat.OPAQUE);
+        params.setTitle(TAG);
+        manager.addView(view, params, mDisplayContent.getDisplay(), null);
+        mView = view;
+    }
+
+    private void removeDivider() {
+        WindowManagerGlobal manager = WindowManagerGlobal.getInstance();
+        manager.removeView(mView, true /* immediate */);
+        mView = null;
+    }
+
+    boolean hasDivider() {
+        return mView != null;
+    }
+
+    void update() {
+        TaskStack stack = mDisplayContent.getDockedStack();
+        if (stack != null && mView == null) {
+            addDivider();
+        } else if (stack == null && mView != null) {
+            removeDivider();
+        }
+    }
+
+    int getWidth() {
+        return mDividerWidth;
+    }
+
+
+    void positionDockedStackedDivider(Rect frame) {
+        TaskStack stack = mDisplayContent.getDockedStack();
+        if (stack == null) {
+            // Unfortunately we might end up with still having a divider, even though the underlying
+            // stack was already removed. This is because we are on AM thread and the removal of the
+            // divider was deferred to WM thread and hasn't happened yet.
+            return;
+        }
+        final @TaskStack.DockSide int side = stack.getDockSide();
+        stack.getBounds(mTmpRect);
+        switch (side) {
+            case TaskStack.DOCKED_LEFT:
+                frame.set(mTmpRect.right, frame.top, mTmpRect.right + frame.width(), frame.bottom);
+                break;
+            case TaskStack.DOCKED_TOP:
+                frame.set(frame.left, mTmpRect.bottom, mTmpRect.right,
+                        mTmpRect.bottom + frame.height());
+                break;
+            case TaskStack.DOCKED_RIGHT:
+                frame.set(mTmpRect.left - frame.width(), frame.top, mTmpRect.left, frame.bottom);
+                break;
+            case TaskStack.DOCKED_BOTTOM:
+                frame.set(frame.left, mTmpRect.top - frame.height(), frame.right, mTmpRect.top);
+                break;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4541dd6..6ebff42 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
@@ -432,6 +433,10 @@
         return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
     }
 
+    boolean inDockedWorkspace() {
+        return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
+    }
+
     @Override
     public boolean isFullscreen() {
         return mFullscreen;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 1362555..96fcf93 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -352,7 +352,8 @@
             // the docked stack occupies a dedicated region on screen.
             bounds = new Rect();
             displayContent.getLogicalDisplayRect(mTmpRect);
-            getInitialDockedStackBounds(mTmpRect, bounds, mStackId);
+            getInitialDockedStackBounds(mTmpRect, bounds, mStackId,
+                    mDisplayContent.mDividerControllerLocked.getWidth() / 2);
         }
 
         updateDisplayInfo(bounds);
@@ -371,27 +372,29 @@
      * @param displayRect The bounds of the display the docked stack is on.
      * @param outBounds Output bounds that should be used for the stack.
      * @param stackId Id of stack we are calculating the bounds for.
+     * @param adjustment
      */
-    private static void getInitialDockedStackBounds(
-            Rect displayRect, Rect outBounds, int stackId) {
+    private static void getInitialDockedStackBounds(Rect displayRect, Rect outBounds, int stackId,
+            int adjustment) {
         // Docked stack start off occupying half the screen space.
+        final boolean dockedStack = stackId == DOCKED_STACK_ID;
         final boolean splitHorizontally = displayRect.width() > displayRect.height();
         final boolean topOrLeftCreateMode =
                 WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-        final boolean placeTopOrLeft = (stackId == DOCKED_STACK_ID && topOrLeftCreateMode)
-                || (stackId != DOCKED_STACK_ID && !topOrLeftCreateMode);
+        final boolean placeTopOrLeft = (dockedStack && topOrLeftCreateMode)
+                || (!dockedStack && !topOrLeftCreateMode);
         outBounds.set(displayRect);
         if (placeTopOrLeft) {
             if (splitHorizontally) {
-                outBounds.right = displayRect.centerX();
+                outBounds.right = displayRect.centerX() - adjustment;
             } else {
-                outBounds.bottom = displayRect.centerY();
+                outBounds.bottom = displayRect.centerY() - adjustment;
             }
         } else {
             if (splitHorizontally) {
-                outBounds.left = displayRect.centerX();
+                outBounds.left = displayRect.centerX() + adjustment;
             } else {
-                outBounds.top = displayRect.centerY();
+                outBounds.top = displayRect.centerY() + adjustment;
             }
         }
     }
@@ -404,7 +407,8 @@
     private void resizeNonDockedStacks(boolean fullscreen) {
         mDisplayContent.getLogicalDisplayRect(mTmpRect);
         if (!fullscreen) {
-            getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID);
+            getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID,
+                    mDisplayContent.mDividerControllerLocked.getWidth());
         }
 
         final int count = mService.mStackIdToStack.size();
@@ -546,14 +550,14 @@
         final int orientation = mService.mCurConfiguration.orientation;
         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
             // Portrait mode, docked either at the top or the bottom.
-            if (mTmpRect.top - mBounds.top < mTmpRect.bottom - mBounds.bottom) {
+            if (mBounds.top - mTmpRect.top < mTmpRect.bottom - mBounds.bottom) {
                 return DOCKED_TOP;
             } else {
                 return DOCKED_BOTTOM;
             }
         } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
             // Landscape mode, docked either on the left or on the right.
-            if (mTmpRect.left - mBounds.left < mTmpRect.right - mBounds.right) {
+            if (mBounds.left - mTmpRect.left < mTmpRect.right - mBounds.right) {
                 return DOCKED_LEFT;
             } else {
                 return DOCKED_RIGHT;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 29598ab..d510c4a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -33,6 +34,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
@@ -921,6 +923,7 @@
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
 
+
         LocalServices.addService(WindowManagerInternal.class, new LocalService());
         initPolicy();
 
@@ -1854,6 +1857,11 @@
                             + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
+            } else if (type == TYPE_DOCK_DIVIDER) {
+                if (displayContent.mDividerControllerLocked.hasDivider()) {
+                    Slog.w(TAG, "Attempted to add docked stack divider twice. Aborting.");
+                    return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                }
             } else if (token.appWindowToken != null) {
                 Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type);
                 // It is not valid to use an app token with other system types; we will
@@ -4521,7 +4529,10 @@
                     }
                     stack.attachDisplayContent(displayContent);
                     displayContent.attachStack(stack, onTop);
-
+                    if (stack.mStackId == DOCKED_STACK_ID) {
+                        mH.obtainMessage(H.UPDATE_DOCKED_STACK_DIVIDER,
+                                displayContent).sendToTarget();
+                    }
                     moveStackWindowsLocked(displayContent);
                     final WindowList windows = displayContent.getWindowList();
                     for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
@@ -4544,6 +4555,11 @@
     void detachStackLocked(DisplayContent displayContent, TaskStack stack) {
         displayContent.detachStack(stack);
         stack.detachDisplay();
+        // We can't directly remove the divider, because only the WM thread can do these operations
+        // and we can be on AM thread.
+        if (stack.mStackId == DOCKED_STACK_ID) {
+            mH.obtainMessage(H.UPDATE_DOCKED_STACK_DIVIDER, displayContent).sendToTarget();
+        }
     }
 
     public void detachStack(int stackId) {
@@ -7220,6 +7236,8 @@
         public static final int TAP_DOWN_OUTSIDE_TASK = 40;
         public static final int FINISH_TASK_POSITIONING = 41;
 
+        public static final int UPDATE_DOCKED_STACK_DIVIDER = 42;
+
         @Override
         public void handleMessage(Message msg) {
             if (DEBUG_WINDOW_TRACE) {
@@ -7759,6 +7777,13 @@
                     }
                 }
                 break;
+                case UPDATE_DOCKED_STACK_DIVIDER: {
+                    DisplayContent content = (DisplayContent) msg.obj;
+                    synchronized (mWindowMap) {
+                        content.mDividerControllerLocked.update();
+                    }
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a6478a0..64440d3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -707,6 +708,8 @@
             mContentFrame.set(mFrame);
             mVisibleFrame.set(mContentFrame);
             mStableFrame.set(mContentFrame);
+        } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
+            mDisplayContent.mDividerControllerLocked.positionDockedStackedDivider(mFrame);
         } else {
             mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                     Math.max(mContentFrame.top, mFrame.top),