Snap docked stack after screen rotation

- Move DividerSnapAlgorithm to com.android.internal, also move
some utility stuff into DividerUtils which is used from both
SystemUI and window manager
- When the screen rotation changes, rotate the stacks like before
but then also snap the docked stack to a valid snap position.

Change-Id: I9e1aa13f42f398a25c9016e6f20395ee212e405b
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index ecec258..a78b56a 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1328,4 +1328,15 @@
      * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
     public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+
+    /**
+     * Calculates the stable insets without running a layout.
+     *
+     * @param displayRotation the current display rotation
+     * @param outInsets the insets to return
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     */
+    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            Rect outInsets);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
rename to core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index e43d531..e79f1b8 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -14,18 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.stackdivider;
+package com.android.internal.policy;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 
-import com.android.systemui.statusbar.FlingAnimationUtils;
-
 import java.util.ArrayList;
 
 /**
  * Calculates the snap targets and the snap position given a position and a velocity. All positions
  * here are to be interpreted as the left/top edge of the divider rectangle.
+ *
+ * @hide
  */
 public class DividerSnapAlgorithm {
 
@@ -44,8 +45,7 @@
      */
     private static final int SNAP_ONLY_1_1 = 2;
 
-    private final Context mContext;
-    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final float mMinFlingVelocityPxPerSecond;
     private final int mDisplayWidth;
     private final int mDisplayHeight;
     private final int mDividerSize;
@@ -63,18 +63,17 @@
     private final SnapTarget mDismissStartTarget;
     private final SnapTarget mDismissEndTarget;
 
-    public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils,
+    public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond,
             int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
             Rect insets) {
-        mContext = ctx;
-        mFlingAnimationUtils = flingAnimationUtils;
+        mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond;
         mDividerSize = dividerSize;
         mDisplayWidth = displayWidth;
         mDisplayHeight = displayHeight;
         mInsets.set(insets);
-        mSnapMode = ctx.getResources().getInteger(
+        mSnapMode = res.getInteger(
                 com.android.internal.R.integer.config_dockedStackDividerSnapMode);
-        mFixedRatio = ctx.getResources().getFraction(
+        mFixedRatio = res.getFraction(
                 com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
         calculateTargets(isHorizontalDivision);
         mFirstSplitTarget = mTargets.get(1);
@@ -84,7 +83,7 @@
     }
 
     public SnapTarget calculateSnapTarget(int position, float velocity) {
-        if (Math.abs(velocity) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
             return snap(position);
         }
         if (position < mFirstSplitTarget.position && velocity < 0) {
@@ -100,6 +99,17 @@
         }
     }
 
+    public SnapTarget calculateNonDismissingSnapTarget(int position) {
+        SnapTarget target = snap(position);
+        if (target == mDismissStartTarget) {
+            return mFirstSplitTarget;
+        } else if (target == mDismissEndTarget) {
+            return mLastSplitTarget;
+        } else {
+            return target;
+        }
+    }
+
     public float calculateDismissingFraction(int position) {
         if (position < mFirstSplitTarget.position) {
             return 1f - (float) position / mFirstSplitTarget.position;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
new file mode 100644
index 0000000..25a060e
--- /dev/null
+++ b/core/java/com/android/internal/policy/DockedDividerUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 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.internal.policy;
+
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+/**
+ * Utility functions for docked stack divider used by both window manager and System UI.
+ *
+ * @hide
+ */
+public class DockedDividerUtils {
+
+    public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect,
+            int displayWidth, int displayHeight, int dividerSize) {
+        outRect.set(0, 0, displayWidth, displayHeight);
+        switch (dockSide) {
+            case WindowManager.DOCKED_LEFT:
+                outRect.right = position;
+                break;
+            case WindowManager.DOCKED_TOP:
+                outRect.bottom = position;
+                break;
+            case WindowManager.DOCKED_RIGHT:
+                outRect.left = position + dividerSize;
+                break;
+            case WindowManager.DOCKED_BOTTOM:
+                outRect.top = position + dividerSize;
+                break;
+        }
+        if (outRect.left > outRect.right) {
+            outRect.left = outRect.right;
+        }
+        if (outRect.top > outRect.bottom) {
+            outRect.top = outRect.bottom;
+        }
+        if (outRect.right < outRect.left) {
+            outRect.right = outRect.left;
+        }
+        if (outRect.bottom < outRect.top) {
+            outRect.bottom = outRect.top;
+        }
+    }
+
+    public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) {
+        switch (dockSide) {
+            case WindowManager.DOCKED_LEFT:
+                return bounds.right;
+            case WindowManager.DOCKED_TOP:
+                return bounds.bottom;
+            case WindowManager.DOCKED_RIGHT:
+                return bounds.left - dividerSize;
+            case WindowManager.DOCKED_BOTTOM:
+                return bounds.top - dividerSize;
+            default:
+                return 0;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 109cf47..824d10a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -28,7 +28,6 @@
 import android.graphics.Region.Op;
 import android.hardware.display.DisplayManager;
 import android.util.AttributeSet;
-import android.util.MathUtils;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
@@ -39,7 +38,6 @@
 import android.view.ViewConfiguration;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
@@ -48,8 +46,10 @@
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
 import com.android.systemui.R;
-import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
 import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
@@ -167,7 +167,8 @@
     public boolean startDragging(boolean animate) {
         mHandle.setTouching(true, animate);
         mDockSide = mWindowManagerProxy.getDockSide();
-        mSnapAlgorithm = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, mDisplayWidth,
+        mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
+                mFlingAnimationUtils.getMinVelocityPxPerSecond(), mDisplayWidth,
                 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
         if (mDockSide != WindowManager.DOCKED_INVALID) {
             mWindowManagerProxy.setResizing(true);
@@ -362,36 +363,6 @@
         return mStartPosition + touchY - mStartY;
     }
 
-    public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
-        outRect.set(0, 0, mDisplayWidth, mDisplayHeight);
-        switch (dockSide) {
-            case WindowManager.DOCKED_LEFT:
-                outRect.right = position;
-                break;
-            case WindowManager.DOCKED_TOP:
-                outRect.bottom = position;
-                break;
-            case WindowManager.DOCKED_RIGHT:
-                outRect.left = position + mDividerWindowWidth - 2 * mDividerInsets;
-                break;
-            case WindowManager.DOCKED_BOTTOM:
-                outRect.top = position + mDividerWindowWidth - 2 * mDividerInsets;
-                break;
-        }
-        if (outRect.left > outRect.right) {
-            outRect.left = outRect.right;
-        }
-        if (outRect.top > outRect.bottom) {
-            outRect.top = outRect.bottom;
-        }
-        if (outRect.right < outRect.left) {
-            outRect.right = outRect.left;
-        }
-        if (outRect.bottom < outRect.top) {
-            outRect.bottom = outRect.top;
-        }
-    }
-
     private int invertDockSide(int dockSide) {
         switch (dockSide) {
             case WindowManager.DOCKED_LEFT:
@@ -421,6 +392,11 @@
                 containingRect.right, containingRect.bottom);
     }
 
+    public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
+        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
+                mDisplayHeight, mDividerSize);
+    }
+
     public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
         calculateBoundsForPosition(position, mDockSide, mDockedRect);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index fb2bc17..e5b4f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -29,8 +29,7 @@
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget;
-import com.android.systemui.stackdivider.DividerView;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.systemui.tuner.TunerService;
 
 import static android.view.WindowManager.*;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f13d964..de1c1ea 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3788,7 +3788,7 @@
             // size.  We need to do this directly, instead of relying on
             // it to bubble up from the nav bar, because this needs to
             // change atomically with screen rotations.
-            mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight);
+            mNavigationBarOnBottom = isNavigationBarOnBottom(displayWidth, displayHeight);
             if (mNavigationBarOnBottom) {
                 // It's a system nav bar or a portrait screen; nav bar goes on bottom.
                 int top = displayHeight - overscanBottom
@@ -3859,6 +3859,10 @@
         return false;
     }
 
+    private boolean isNavigationBarOnBottom(int displayWidth, int displayHeight) {
+        return !mNavigationBarCanMove || displayWidth < displayHeight;
+    }
+
     /** {@inheritDoc} */
     @Override
     public int getSystemDecorLayerLw() {
@@ -5931,6 +5935,22 @@
         }
     }
 
+    @Override
+    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            Rect outInsets) {
+        outInsets.setEmpty();
+        if (mStatusBar != null) {
+            outInsets.top = mStatusBarHeight;
+        }
+        if (mNavigationBar != null) {
+            if (isNavigationBarOnBottom(displayWidth, displayHeight)) {
+                outInsets.bottom = getNavigationBarHeight(displayRotation, mUiMode);
+            } else {
+                outInsets.right = getNavigationBarWidth(displayRotation, mUiMode);
+            }
+        }
+    }
+
     void sendCloseSystemWindows() {
         PhoneWindow.sendCloseSystemWindows(mContext, null);
     }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index fc6ad70..369cc65 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager.StackId;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.util.EventLog;
@@ -26,6 +27,9 @@
 import android.view.DisplayInfo;
 import android.view.Surface;
 
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
+import com.android.internal.policy.DockedDividerUtils;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
@@ -245,19 +249,66 @@
                 setBounds(null);
             } else {
                 mTmpRect2.set(mBounds);
-                mDisplayContent.rotateBounds(
-                        mRotation, mDisplayContent.getDisplayInfo().rotation, mTmpRect2);
-                if (setBounds(mTmpRect2)) {
-                    // Post message to inform activity manager of the bounds change simulating
-                    // a one-way call. We do this to prevent a deadlock between window manager
-                    // lock and activity manager lock been held.
-                    mService.mH.sendMessage(mService.mH.obtainMessage(
-                            RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mBounds));
+                final int newRotation = mDisplayContent.getDisplayInfo().rotation;
+                if (mRotation == newRotation) {
+                    setBounds(mTmpRect2);
                 }
+
+                // If the rotation changes, we'll handle it in updateBoundsAfterRotation
             }
         }
     }
 
+    /**
+     * Updates the bounds after rotating the screen. We can't handle it in
+     * {@link #updateDisplayInfo} because at that point the configuration might not be fully updated
+     * yet.
+     */
+    void updateBoundsAfterRotation() {
+        final int newRotation = getDisplayInfo().rotation;
+        mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
+        if (mStackId == DOCKED_STACK_ID) {
+            snapDockedStackAfterRotation(mTmpRect2);
+        }
+
+        // Post message to inform activity manager of the bounds change simulating
+        // a one-way call. We do this to prevent a deadlock between window manager
+        // lock and activity manager lock been held.
+        mService.mH.sendMessage(mService.mH.obtainMessage(
+                RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mTmpRect2));
+    }
+
+    /**
+     * Snaps the bounds after rotation to the closest snap target for the docked stack.
+     */
+    private void snapDockedStackAfterRotation(Rect outBounds) {
+
+        // Calculate the current position.
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+        final int dividerSize = mService.getDefaultDisplayContentLocked()
+                .getDockedDividerController().getContentWidth();
+        final int dockSide = getDockSide(outBounds);
+        final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds,
+                dockSide, dividerSize);
+        final int displayWidth = mDisplayContent.getDisplayInfo().logicalWidth;
+        final int displayHeight = mDisplayContent.getDisplayInfo().logicalHeight;
+
+        // Snap the position to a target.
+        final int rotation = displayInfo.rotation;
+        final int orientation = mService.mCurConfiguration.orientation;
+        mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
+        final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
+                mService.mContext.getResources(),
+                0 /* minFlingVelocityPxPerSecond */, displayWidth, displayHeight,
+                dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds);
+        final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
+
+        // Recalculate the bounds based on the position of the target.
+        DockedDividerUtils.calculateBoundsForPosition(target.position, dockSide,
+                outBounds, displayInfo.logicalWidth, displayInfo.logicalHeight,
+                dividerSize);
+    }
+
     boolean isAnimating() {
         for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens;
@@ -682,6 +733,10 @@
      * information which side of the screen was the dock anchored.
      */
     int getDockSide() {
+        return getDockSide(mBounds);
+    }
+
+    int getDockSide(Rect bounds) {
         if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) {
             return DOCKED_INVALID;
         }
@@ -692,14 +747,14 @@
         final int orientation = mService.mCurConfiguration.orientation;
         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
             // Portrait mode, docked either at the top or the bottom.
-            if (mBounds.top - mTmpRect.top < mTmpRect.bottom - mBounds.bottom) {
+            if (bounds.top - mTmpRect.top < mTmpRect.bottom - bounds.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 (mBounds.left - mTmpRect.left < mTmpRect.right - mBounds.right) {
+            if (bounds.left - mTmpRect.left < mTmpRect.right - bounds.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 aa5cc7f..b96cbfb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3523,6 +3523,7 @@
         }
     }
 
+    @Override
     public void setNewConfiguration(Configuration config) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "setNewConfiguration()")) {
@@ -3536,10 +3537,20 @@
                 mWaitingForConfig = false;
                 mLastFinishedFreezeSource = "new-config";
             }
+            if (orientationChanged) {
+                updateTaskStackBoundsAfterRotation();
+            }
             mWindowPlacerLocked.performSurfacePlacement();
         }
     }
 
+    private void updateTaskStackBoundsAfterRotation() {
+        for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) {
+            final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
+            stack.updateBoundsAfterRotation();
+        }
+    }
+
     @Override
     public void setAppOrientation(IApplicationToken token, int requestedOrientation) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,