Implement divider UX interactions

- Update visuals to spec
- Divider lifts when touching
- Implement basic version of snap points and animations
- Implement touch conflict behavior: If touched around 48x48dp
area around the handle, the divider handles all these touches.
If touched outside of the black background divider, touch goes
directly to underlying window. If touch on the black background
divider, touch is considered slippery and thus the window in
which the touch trace moves gets the touches.

Change-Id: I0307c191ae032672c4b73d439c23cf9833d3fce6
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index f8a80f0..949efc5 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -27,7 +27,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.systemui.stackdivider.DockedStackDivider;
+import com.android.systemui.stackdivider.Divider;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -53,7 +53,7 @@
             com.android.systemui.power.PowerUI.class,
             com.android.systemui.media.RingtonePlayer.class,
             com.android.systemui.keyboard.KeyboardUI.class,
-            DockedStackDivider.class
+            Divider.class
     };
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
new file mode 100644
index 0000000..dd894ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -0,0 +1,68 @@
+/*
+ * 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.systemui.stackdivider;
+
+import android.content.res.Configuration;
+import android.view.LayoutInflater;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+/**
+ * Controls the docked stack divider.
+ */
+public class Divider extends SystemUI {
+    private static final String TAG = "Divider";
+    private int mDividerWindowWidth;
+    private DividerWindowManager mWindowManager;
+
+    @Override
+    public void start() {
+        mWindowManager = new DividerWindowManager(mContext);
+        mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        update(mContext.getResources().getConfiguration());
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        update(newConfig);
+    }
+
+    private void addDivider(Configuration configuration) {
+        DividerView view = (DividerView)
+                LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
+        final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
+        final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
+        final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
+        mWindowManager.add(view, width, height);
+        view.setWindowManager(mWindowManager);
+    }
+
+    private void removeDivider() {
+        mWindowManager.remove();
+    }
+
+    private void update(Configuration configuration) {
+        removeDivider();
+        addDivider(configuration);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
new file mode 100644
index 0000000..5f983c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
@@ -0,0 +1,130 @@
+/*
+ * 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.systemui.stackdivider;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.DisplayInfo;
+
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+import java.util.ArrayList;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+/**
+ * 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.
+ */
+public class DividerSnapAlgorithm {
+
+    private final Context mContext;
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final int mDividerSize;
+    private final ArrayList<SnapTarget> mTargets;
+
+    /** The first target which is still splitting the screen */
+    private final SnapTarget mFirstSplitTarget;
+
+    /** The last target which is still splitting the screen */
+    private final SnapTarget mLastSplitTarget;
+
+    private final SnapTarget mDismissStartTarget;
+    private final SnapTarget mDismissEndTarget;
+
+    public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils,
+            int dividerSize, boolean isHorizontalDivision) {
+        mContext = ctx;
+        mFlingAnimationUtils = flingAnimationUtils;
+        mDividerSize = dividerSize;
+        mTargets = calculateTargets(isHorizontalDivision);
+        mFirstSplitTarget = mTargets.get(1);
+        mLastSplitTarget = mTargets.get(mTargets.size() - 2);
+        mDismissStartTarget = mTargets.get(0);
+        mDismissEndTarget = mTargets.get(mTargets.size() - 1);
+    }
+
+    public SnapTarget calculateSnapTarget(int position, float velocity) {
+        if (Math.abs(velocity) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return snap(position);
+        }
+        if (position < mFirstSplitTarget.position && velocity < 0) {
+            return mDismissStartTarget;
+        }
+        if (position > mLastSplitTarget.position && velocity > 0) {
+            return mDismissEndTarget;
+        }
+        if (velocity < 0) {
+            return mFirstSplitTarget;
+        } else {
+            return mLastSplitTarget;
+        }
+    }
+
+    private SnapTarget snap(int position) {
+        int minIndex = -1;
+        int minDistance = Integer.MAX_VALUE;
+        int size = mTargets.size();
+        for (int i = 0; i < size; i++) {
+            int distance = Math.abs(position - mTargets.get(i).position);
+            if (distance < minDistance) {
+                minIndex = i;
+                minDistance = distance;
+            }
+        }
+        return mTargets.get(minIndex);
+    }
+
+    private ArrayList<SnapTarget> calculateTargets(boolean isHorizontalDivision) {
+        ArrayList<SnapTarget> targets = new ArrayList<>();
+        DisplayMetrics info = mContext.getResources().getDisplayMetrics();
+        int dividerMax = isHorizontalDivision
+                ? info.heightPixels
+                : info.widthPixels;
+
+        // TODO: Better calculation
+        targets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
+        targets.add(new SnapTarget((int) (0.35f * dividerMax) - mDividerSize / 2,
+                SnapTarget.FLAG_NONE));
+        targets.add(new SnapTarget(dividerMax / 2 - mDividerSize / 2, SnapTarget.FLAG_NONE));
+        targets.add(new SnapTarget((int) (0.65f * dividerMax) - mDividerSize / 2,
+                SnapTarget.FLAG_NONE));
+        targets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
+        return targets;
+    }
+
+    /**
+     * Represents a snap target for the divider.
+     */
+    public static class SnapTarget {
+        public static final int FLAG_NONE = 0;
+
+        /** If the divider reaches this value, the left/top task should be dismissed. */
+        public static final int FLAG_DISMISS_START = 1;
+
+        /** If the divider reaches this value, the right/bottom task should be dismissed */
+        public static final int FLAG_DISMISS_END = 2;
+
+        public final int position;
+        public final int flag;
+
+        public SnapTarget(int position, int flag) {
+            this.position = position;
+            this.flag = flag;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
new file mode 100644
index 0000000..14d8fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -0,0 +1,311 @@
+/*
+ * 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.systemui.stackdivider;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+
+import com.android.systemui.R;
+import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+/**
+ * Docked stack divider.
+ */
+public class DividerView extends FrameLayout implements OnTouchListener,
+        OnComputeInternalInsetsListener {
+
+    private static final String TAG = "DividerView";
+
+    private ImageButton mHandle;
+    private View mBackground;
+    private int mStartX;
+    private int mStartY;
+    private int mStartPosition;
+    private int mDockSide;
+    private final int[] mTempInt2 = new int[2];
+
+    private int mDividerInsets;
+    private int mDisplayWidth;
+    private int mDisplayHeight;
+    private int mDividerWindowWidth;
+    private int mDividerSize;
+    private int mTouchElevation;
+
+    private final Rect mTmpRect = new Rect();
+    private final Rect mLastResizeRect = new Rect();
+    private final WindowManagerProxy mWindowManagerProxy = new WindowManagerProxy();
+    private Interpolator mFastOutSlowInInterpolator;
+    private final Interpolator mTouchResponseInterpolator =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+    private DividerWindowManager mWindowManager;
+    private VelocityTracker mVelocityTracker;
+    private FlingAnimationUtils mFlingAnimationUtils;
+
+    public DividerView(Context context) {
+        super(context);
+    }
+
+    public DividerView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mHandle = (ImageButton) findViewById(R.id.docked_divider_handle);
+        mBackground = findViewById(R.id.docked_divider_background);
+        mHandle.setOnTouchListener(this);
+        mDividerWindowWidth = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        mDividerInsets = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_insets);
+        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+        mTouchElevation = getResources().getDimensionPixelSize(
+                R.dimen.docked_stack_divider_lift_elevation);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+                android.R.interpolator.fast_out_slow_in);
+        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
+        updateDisplayInfo();
+        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+    }
+
+    public void setWindowManager(DividerWindowManager windowManager) {
+        mWindowManager = windowManager;
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        convertToScreenCoordinates(event);
+        final int action = event.getAction() & MotionEvent.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mVelocityTracker = VelocityTracker.obtain();
+                mVelocityTracker.addMovement(event);
+                mStartX = (int) event.getX();
+                mStartY = (int) event.getY();
+                getLocationOnScreen(mTempInt2);
+                mDockSide = mWindowManagerProxy.getDockSide();
+                if (isHorizontalDivision()) {
+                    mStartPosition = mTempInt2[1] + mDividerInsets;
+                } else {
+                    mStartPosition = mTempInt2[0] + mDividerInsets;
+                }
+                if (mDockSide != WindowManager.DOCKED_INVALID) {
+                    mWindowManagerProxy.setResizing(true);
+                    mWindowManager.setSlippery(false);
+                    liftBackground();
+                    return true;
+                } else {
+                    return false;
+                }
+            case MotionEvent.ACTION_MOVE:
+                mVelocityTracker.addMovement(event);
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                if (mDockSide != WindowManager.DOCKED_INVALID) {
+                    resizeStack(calculatePosition(x, y));
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mVelocityTracker.addMovement(event);
+
+                x = (int) event.getRawX();
+                y = (int) event.getRawY();
+
+                mVelocityTracker.computeCurrentVelocity(1000);
+                fling(x, y, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+                mWindowManager.setSlippery(true);
+                releaseBackground();
+                break;
+        }
+        return true;
+    }
+
+    private void convertToScreenCoordinates(MotionEvent event) {
+        event.setLocation(event.getRawX(), event.getRawY());
+    }
+
+    private void fling(int x, int y, float xVelocity, float yVelocity) {
+        int position = calculatePosition(x, y);
+        float velocity = isHorizontalDivision() ? yVelocity : xVelocity;
+        final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils,
+                mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity);
+
+        ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
+        anim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                resizeStack((Integer) animation.getAnimatedValue());
+            }
+        });
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                commitSnapFlags(snapTarget);
+                mWindowManagerProxy.setResizing(false);
+                mDockSide = WindowManager.DOCKED_INVALID;
+            }
+        });
+        mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
+        anim.start();
+    }
+
+    private void commitSnapFlags(SnapTarget target) {
+        if (target.flag == SnapTarget.FLAG_NONE) {
+            return;
+        }
+        boolean dismissOrMaximize;
+        if (target.flag == SnapTarget.FLAG_DISMISS_START) {
+            dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
+                    || mDockSide == WindowManager.DOCKED_TOP;
+        } else {
+            dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
+                    || mDockSide == WindowManager.DOCKED_BOTTOM;
+        }
+        if (dismissOrMaximize) {
+            mWindowManagerProxy.dismissDockedStack();
+        } else {
+            mWindowManagerProxy.maximizeDockedStack();
+        }
+    }
+
+    private void liftBackground() {
+        if (isHorizontalDivision()) {
+            mBackground.animate().scaleY(1.5f);
+        } else {
+            mBackground.animate().scaleX(1.5f);
+        }
+        mBackground.animate()
+                .setInterpolator(mTouchResponseInterpolator)
+                .setDuration(150)
+                .translationZ(mTouchElevation);
+
+        // Lift handle as well so it doesn't get behind the background, even though it doesn't
+        // cast shadow.
+        mHandle.animate()
+                .setInterpolator(mTouchResponseInterpolator)
+                .setDuration(150)
+                .translationZ(mTouchElevation);
+    }
+
+    private void releaseBackground() {
+        mBackground.animate()
+                .setInterpolator(mFastOutSlowInInterpolator)
+                .setDuration(200)
+                .translationZ(0)
+                .scaleX(1f)
+                .scaleY(1f);
+        mHandle.animate()
+                .setInterpolator(mFastOutSlowInInterpolator)
+                .setDuration(200)
+                .translationZ(0);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateDisplayInfo();
+    }
+
+    private void updateDisplayInfo() {
+        DisplayMetrics info = mContext.getResources().getDisplayMetrics();
+        mDisplayWidth = info.widthPixels;
+        mDisplayHeight = info.heightPixels;
+    }
+
+    private int calculatePosition(int touchX, int touchY) {
+        return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
+    }
+
+    private boolean isHorizontalDivision() {
+        return mDockSide == WindowManager.DOCKED_TOP
+                || mDockSide == WindowManager.DOCKED_BOTTOM;
+    }
+
+    private int calculateXPosition(int touchX) {
+        return mStartPosition + touchX - mStartX;
+    }
+
+    private int calculateYPosition(int touchY) {
+        return mStartPosition + touchY - mStartY;
+    }
+
+    private void resizeStack(int position) {
+        mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
+        switch (mDockSide) {
+            case WindowManager.DOCKED_LEFT:
+                mTmpRect.right = position;
+                break;
+            case WindowManager.DOCKED_TOP:
+                mTmpRect.bottom = position;
+                break;
+            case WindowManager.DOCKED_RIGHT:
+                mTmpRect.left = position + mDividerWindowWidth - 2 * mDividerInsets;
+                break;
+            case WindowManager.DOCKED_BOTTOM:
+                mTmpRect.top = position + mDividerWindowWidth - 2 * mDividerInsets;
+                break;
+        }
+        if (mTmpRect.equals(mLastResizeRect)) {
+            return;
+        }
+        mLastResizeRect.set(mTmpRect);
+        mWindowManagerProxy.resizeDockedStack(mTmpRect);
+    }
+
+    @Override
+    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
+        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
+                mHandle.getBottom());
+        inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
+                mBackground.getRight(), mBackground.getBottom(), Op.UNION);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
new file mode 100644
index 0000000..2251874
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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.systemui.stackdivider;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.View;
+import android.view.WindowManager;
+
+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_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+/**
+ * Manages the window parameters of the docked stack divider.
+ */
+public class DividerWindowManager {
+
+    private static final String WINDOW_TITLE = "DockedStackDivider";
+
+    private final WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLp;
+    private View mView;
+
+    public DividerWindowManager(Context ctx) {
+        mWindowManager = ctx.getSystemService(WindowManager.class);
+    }
+
+    public void add(View view, int width, int height) {
+        mLp = new WindowManager.LayoutParams(
+                width, height, TYPE_DOCK_DIVIDER,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
+                        | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+                PixelFormat.TRANSLUCENT);
+        mLp.setTitle(WINDOW_TITLE);
+        mWindowManager.addView(view, mLp);
+        mView = view;
+    }
+
+    public void remove() {
+        if (mView != null) {
+            mWindowManager.removeView(mView);
+        }
+        mView = null;
+    }
+
+    public void setSlippery(boolean slippery) {
+        boolean changed = false;
+        if (slippery && (mLp.flags & FLAG_SLIPPERY) == 0) {
+            mLp.flags |= FLAG_SLIPPERY;
+            changed = true;
+        } else if (!slippery && (mLp.flags & FLAG_SLIPPERY) != 0) {
+            mLp.flags &= ~FLAG_SLIPPERY;
+            changed = true;
+        }
+        if (changed) {
+            mWindowManager.updateViewLayout(mView, mLp);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java
deleted file mode 100644
index 646ee47..0000000
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * 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.systemui.stackdivider;
-
-import android.app.ActivityManagerNative;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-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_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-
-/**
- * Controls showing and hiding of a docked stack divider on the display.
- */
-public class DockedStackDivider extends SystemUI implements View.OnTouchListener {
-    private static final String TAG = "DockedStackDivider";
-    private int mDividerWidth;
-    private int mSideMargin;
-    private int mDisplayWidth;
-    private int mDisplayHeight;
-    private int mDisplayOrientation;
-    private View mView;
-    private final Rect mTmpRect = new Rect();
-    private final Rect mTmpRectBackground = new Rect();
-    private final Rect mLastResizeRect = new Rect();
-
-    @GuardedBy("mResizeRect")
-    private final Rect mResizeRect = new Rect();
-
-    private int mStartX;
-    private int mStartY;
-    private int mStartPosition;
-    private int mDockSide;
-    private WindowManager mWindowManager;
-    private final int[] mTempInt2 = new int[2];
-    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
-
-    private final Runnable mResizeRunnable = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mResizeRect) {
-                mTmpRectBackground.set(mResizeRect);
-            }
-            try {
-                ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID,
-                        mTmpRectBackground, true);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    };
-
-    @Override
-    public void start() {
-        mWindowManager = mContext.getSystemService(WindowManager.class);
-        updateDisplayInfo();
-        mDividerWidth = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_thickness);
-        mSideMargin = mContext.getResources().getDimensionPixelSize(
-                R.dimen.docked_stack_divider_dismiss_distance);
-        update(mContext.getResources().getConfiguration());
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        updateDisplayInfo();
-        update(newConfig);
-    }
-
-    private void addDivider(Configuration configuration) {
-        View view = LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
-        view.setOnTouchListener(this);
-        final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
-        final int width = landscape ? mDividerWidth : MATCH_PARENT;
-        final int height = landscape ? MATCH_PARENT : mDividerWidth;
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                width, height, TYPE_DOCK_DIVIDER,
-                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
-                        | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
-                PixelFormat.TRANSLUCENT);
-        params.setTitle(TAG);
-        mWindowManager.addView(view, params);
-        mView = view;
-    }
-
-    private void removeDivider() {
-        if (mView == null) return;
-        mView.setOnTouchListener(null);
-        mWindowManager.removeView(mView);
-        mView = null;
-    }
-
-
-    private void updateDisplayInfo() {
-        DisplayMetrics info = mContext.getResources().getDisplayMetrics();
-        mDisplayWidth = info.widthPixels;
-        mDisplayHeight = info.heightPixels;
-        mDisplayOrientation = mContext.getResources().getConfiguration().orientation;
-    }
-
-    private void update(Configuration configuration) {
-        removeDivider();
-        addDivider(configuration);
-    }
-
-    int getWidth() {
-        return mDividerWidth;
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        final int action = event.getAction() & MotionEvent.ACTION_MASK;
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                // We use raw values, because getX/Y() would give us results relative to the
-                // dock divider bounds.
-                mStartX = (int) event.getRawX();
-                mStartY = (int) event.getRawY();
-                mView.getLocationOnScreen(mTempInt2);
-                if (mDisplayOrientation == ORIENTATION_LANDSCAPE) {
-                    mStartPosition = mTempInt2[0];
-                } else {
-                    mStartPosition = mTempInt2[1];
-                }
-                mDockSide = getDockSide();
-                if (mDockSide != WindowManager.DOCKED_INVALID) {
-                    setResizing(true);
-                    return true;
-                } else {
-                    return false;
-                }
-            case MotionEvent.ACTION_MOVE:
-                int x = (int) event.getRawX();
-                int y = (int) event.getRawY();
-                if (mDockSide != WindowManager.DOCKED_INVALID) {
-                    resizeStack(x, y);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                x = (int) event.getRawX();
-                y = (int) event.getRawY();
-                // At most one of these will be executed, the other one will exit early.
-                maybeDismissTaskStack(x, y);
-                maybeMaximizeTaskStack(x, y);
-                mDockSide = WindowManager.DOCKED_INVALID;
-                setResizing(false);
-                break;
-        }
-        return true;
-    }
-
-    private void setResizing(boolean resizing) {
-        try {
-            WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling setDockedStackResizing: " + e);
-        }
-    }
-
-    private int getDockSide() {
-        try {
-            return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get dock side: " + e);
-        }
-        return WindowManager.DOCKED_INVALID;
-    }
-
-    private void maybeMaximizeTaskStack(int x, int y) {
-        final int distance = distanceFromFullScreen(mDockSide, x, y);
-        if (distance == -1) {
-            Log.wtf(TAG, "maybeMaximizeTaskStack: Unknown dock side=" + mDockSide);
-            return;
-        }
-        if (distance <= mSideMargin) {
-            try {
-                ActivityManagerNative.getDefault().resizeStack(
-                        DOCKED_STACK_ID, null, true);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    }
-
-    private void maybeDismissTaskStack(int x, int y) {
-        final int distance = distanceFromDockSide(mDockSide, x, y);
-        if (distance == -1) {
-            Log.wtf(TAG, "maybeDismissTaskStack: Unknown dock side=" + mDockSide);
-            return;
-        }
-        if (distance <= mSideMargin) {
-            try {
-                ActivityManagerNative.getDefault().removeStack(DOCKED_STACK_ID);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to remove stack: " + e);
-            }
-        }
-    }
-
-    private int distanceFromFullScreen(int dockSide, int x, int y) {
-        switch (dockSide) {
-            case WindowManager.DOCKED_LEFT:
-                return mDisplayWidth - x;
-            case WindowManager.DOCKED_TOP:
-                return mDisplayHeight - y;
-            case WindowManager.DOCKED_RIGHT:
-                return x;
-            case WindowManager.DOCKED_BOTTOM:
-                return y;
-        }
-        return -1;
-    }
-
-    private int distanceFromDockSide(int dockSide, int x, int y) {
-        switch (dockSide) {
-            case WindowManager.DOCKED_LEFT:
-                return x;
-            case WindowManager.DOCKED_TOP:
-                return y;
-            case WindowManager.DOCKED_RIGHT:
-                return mDisplayWidth - x;
-            case WindowManager.DOCKED_BOTTOM:
-                return mDisplayHeight - y;
-        }
-        return -1;
-    }
-
-    private void resizeStack(int x, int y) {
-        int deltaX = x - mStartX;
-        int deltaY = y - mStartY;
-        mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
-        switch (mDockSide) {
-            case WindowManager.DOCKED_LEFT:
-                mTmpRect.right = mStartPosition + deltaX;
-                break;
-            case WindowManager.DOCKED_TOP:
-                mTmpRect.bottom = mStartPosition + deltaY;
-                break;
-            case WindowManager.DOCKED_RIGHT:
-                mTmpRect.left = mStartPosition + deltaX + getWidth();
-                break;
-            case WindowManager.DOCKED_BOTTOM:
-                mTmpRect.top = mStartPosition + deltaY + getWidth();
-                break;
-        }
-        if (mTmpRect.equals(mLastResizeRect)) {
-            return;
-        }
-        mLastResizeRect.set(mTmpRect);
-        synchronized (mResizeRect) {
-            mResizeRect.set(mTmpRect);
-        }
-        mExecutor.execute(mResizeRunnable);
-
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
new file mode 100644
index 0000000..94809bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -0,0 +1,99 @@
+/*
+ * 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.systemui.stackdivider;
+
+import android.app.ActivityManagerNative;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.view.WindowManager.DOCKED_INVALID;
+
+/**
+ * Proxy to simplify calls into window manager/activity manager
+ */
+public class WindowManagerProxy {
+
+    private static final String TAG = "WindowManagerProxy";
+
+    @GuardedBy("mResizeRect")
+    private final Rect mResizeRect = new Rect();
+    private final Rect mTmpRect = new Rect();
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+    private final Runnable mResizeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mResizeRect) {
+                mTmpRect.set(mResizeRect);
+            }
+            try {
+                ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID,
+                        mTmpRect, true);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to resize stack: " + e);
+            }
+        }
+    };
+
+    public void resizeDockedStack(Rect rect) {
+        synchronized (mResizeRect) {
+            mResizeRect.set(rect);
+        }
+        mExecutor.execute(mResizeRunnable);
+    }
+
+    public void dismissDockedStack() {
+        try {
+            ActivityManagerNative.getDefault().removeStack(DOCKED_STACK_ID);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to remove stack: " + e);
+        }
+    }
+
+    public void maximizeDockedStack() {
+        try {
+            ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, null, true);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to resize stack: " + e);
+        }
+    }
+
+    public void setResizing(boolean resizing) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error calling setDockedStackResizing: " + e);
+        }
+    }
+
+    public int getDockSide() {
+        try {
+            return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to get dock side: " + e);
+        }
+        return DOCKED_INVALID;
+    }
+}