Adding PIP logic for phones.

- Adding basic behavior to move PIP window and launch back into
  fullscreen, as well as drag it to dismiss.

Test: Deferring CTS tests as this interaction is only temporary and not
      final

Change-Id: I5272a045090c20c45b345813d10dc385c3f83221
diff --git a/packages/SystemUI/res/drawable/pip_dismiss.xml b/packages/SystemUI/res/drawable/pip_dismiss.xml
new file mode 100644
index 0000000..f656eeb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_dismiss.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="42.0dp"
+        android:height="42.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pip_dismiss_background.xml b/packages/SystemUI/res/drawable/pip_dismiss_background.xml
new file mode 100644
index 0000000..3a75296
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_dismiss_background.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2016 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <corners
+        android:radius="100dp" />
+    <solid
+        android:color="#66000000" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml
new file mode 100644
index 0000000..141e610
--- /dev/null
+++ b/packages/SystemUI/res/layout/pip_dismiss_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/pip_dismiss_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/pip_dismiss_background"
+    android:foreground="@drawable/pip_dismiss"
+    android:alpha="0"
+    android:forceHasOverlappingRendering="false" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 549d50e..12f7881 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -694,4 +694,7 @@
 
     <!-- The alpha to apply to the recents row when it doesn't have focus -->
     <item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
+
+    <!-- The size of the PIP dismiss target. -->
+    <dimen name="pip_dismiss_target_size">48dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index bfc8642..99e7876 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,7 +42,7 @@
 import com.android.systemui.statusbar.SystemBars;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tv.pip.PipUI;
+import com.android.systemui.pip.PipUI;
 import com.android.systemui.usb.StorageNotification;
 import com.android.systemui.volume.VolumeUI;
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
new file mode 100644
index 0000000..67dda51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 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.pip;
+
+import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+
+import com.android.systemui.SystemUI;
+
+/**
+ * Controls the picture-in-picture window.
+ */
+public class PipUI extends SystemUI {
+
+    private boolean mSupportsPip;
+    private boolean mIsLeanBackOnly;
+
+    @Override
+    public void start() {
+        PackageManager pm = mContext.getPackageManager();
+        mSupportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
+        mIsLeanBackOnly = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY);
+        if (!mSupportsPip) {
+            return;
+        }
+        if (mIsLeanBackOnly) {
+            com.android.systemui.tv.pip.PipManager.getInstance().initialize(mContext);
+        } else {
+            com.android.systemui.pip.phone.PipManager.getInstance().initialize(mContext);
+        }
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (!mSupportsPip) {
+            return;
+        }
+        if (mIsLeanBackOnly) {
+            com.android.systemui.tv.pip.PipManager.getInstance().onConfigurationChanged();
+        } else {
+            com.android.systemui.pip.phone.PipManager.getInstance().onConfigurationChanged();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
new file mode 100644
index 0000000..a7ac719
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.pip.phone;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.WindowManager;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+
+public class PipDismissViewController {
+
+    // This delay controls how long to wait before we show the target when the user first moves
+    // the PIP, to prevent the target from animating if the user just wants to fling the PIP
+    private static final int SHOW_TARGET_DELAY = 100;
+    private static final int SHOW_TARGET_DURATION = 200;
+
+    private Context mContext;
+    private WindowManager mWindowManager;
+
+    private View mDismissView;
+    private Rect mDismissTargetScreenBounds = new Rect();
+
+    public PipDismissViewController(Context context) {
+        mContext = context;
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    /**
+     * Creates the dismiss target for showing via {@link #showDismissTarget()}.
+     */
+    public void createDismissTarget() {
+        if (mDismissView == null) {
+            // Create a new view for the dismiss target
+            int dismissTargetSize = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.pip_dismiss_target_size);
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+            mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
+            mDismissView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    if (mDismissView != null) {
+                        mDismissView.getBoundsOnScreen(mDismissTargetScreenBounds);
+                    }
+                }
+            });
+
+            // Add the target to the window
+            WindowManager.LayoutParams lp =  new WindowManager.LayoutParams(
+                    dismissTargetSize,
+                    dismissTargetSize,
+                    WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    PixelFormat.TRANSLUCENT);
+            lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+            mWindowManager.addView(mDismissView, lp);
+        }
+        mDismissView.animate().cancel();
+    }
+
+    /**
+     * Shows the dismiss target.
+     */
+    public void showDismissTarget() {
+        mDismissView.animate()
+                .alpha(1f)
+                .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                .setStartDelay(SHOW_TARGET_DELAY)
+                .setDuration(SHOW_TARGET_DURATION)
+                .start();
+    }
+
+    /**
+     * Hides and destroys the dismiss target.
+     */
+    public void destroyDismissTarget() {
+        if (mDismissView != null) {
+            mDismissView.animate()
+                    .alpha(0f)
+                    .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+                    .setStartDelay(0)
+                    .setDuration(SHOW_TARGET_DURATION)
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mWindowManager.removeView(mDismissView);
+                            mDismissView = null;
+                        }
+                    })
+                    .start();
+        }
+    }
+
+    /**
+     * @return the dismiss target screen bounds.
+     */
+    public Rect getDismissBounds() {
+        return mDismissTargetScreenBounds;
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
new file mode 100644
index 0000000..53a4868
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.pip.phone;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipManager {
+    private static final String TAG = "PipManager";
+
+    private static PipManager sPipController;
+
+    private Context mContext;
+    private IActivityManager mActivityManager;
+    private IWindowManager mWindowManager;
+
+    private PipTouchHandler mTouchHandler;
+
+    private PipManager() {}
+
+    /**
+     * Initializes {@link PipManager}.
+     */
+    public void initialize(Context context) {
+        mContext = context;
+        mActivityManager = ActivityManagerNative.getDefault();
+        mWindowManager = WindowManagerGlobal.getWindowManagerService();
+
+        mTouchHandler = new PipTouchHandler(context, mActivityManager, mWindowManager);
+    }
+
+    /**
+     * Updates the PIP per configuration changed.
+     */
+    public void onConfigurationChanged() {}
+
+    /**
+     * Gets an instance of {@link PipManager}.
+     */
+    public static PipManager getInstance() {
+        if (sPipController == null) {
+            sPipController = new PipManager();
+        }
+        return sPipController;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
new file mode 100644
index 0000000..7aa9aa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2016 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.pip.phone;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+
+import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.recents.misc.Utilities.RECT_EVALUATOR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager.StackInfo;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler {
+    private static final String TAG = "PipTouchHandler";
+    private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false;
+
+    private static final int SNAP_STACK_DURATION = 225;
+    private static final int DISMISS_STACK_DURATION = 375;
+    private static final int EXPAND_STACK_DURATION = 225;
+
+    private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
+
+    private final Context mContext;
+    private final IActivityManager mActivityManager;
+    private final ViewConfiguration mViewConfig;
+    private final InputChannel mInputChannel = new InputChannel();
+
+    private final PipInputEventReceiver mInputEventReceiver;
+    private final PipDismissViewController mDismissViewController;
+
+    private final Rect mPinnedStackBounds = new Rect();
+    private final Rect mBoundedPinnedStackBounds = new Rect();
+    private ValueAnimator mPinnedStackBoundsAnimator = null;
+
+    private final PointF mDownTouch = new PointF();
+    private final PointF mLastTouch = new PointF();
+    private boolean mIsDragging;
+    private int mActivePointerId;
+
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final Scroller mScroller;
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * Input handler used for Pip windows.
+     */
+    private final class PipInputEventReceiver extends InputEventReceiver {
+        public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            boolean handled = true;
+            try {
+                // To be implemented for input handling over Pip windows
+                if (event instanceof MotionEvent) {
+                    MotionEvent ev = (MotionEvent) event;
+                    handleTouchEvent(ev);
+                }
+            } finally {
+                finishInputEvent(event, handled);
+            }
+        }
+    }
+
+    public PipTouchHandler(Context context, IActivityManager activityManager,
+            IWindowManager windowManager) {
+
+        // Initialize the Pip input consumer
+        try {
+            windowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
+            windowManager.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to create PIP input consumer", e);
+        }
+        mContext = context;
+        mActivityManager = activityManager;
+        mViewConfig = ViewConfiguration.get(context);
+        mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
+        mDismissViewController = new PipDismissViewController(context);
+        mScroller = new Scroller(context);
+        mScroller.setFriction(mViewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
+        mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+    }
+
+    private void handleTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                // Cancel any existing animations on the pinned stack
+                if (mPinnedStackBoundsAnimator != null) {
+                    mPinnedStackBoundsAnimator.cancel();
+                }
+
+                updateBoundedPinnedStackBounds();
+                initOrResetVelocityTracker();
+                mVelocityTracker.addMovement(ev);
+                mActivePointerId = ev.getPointerId(0);
+                mLastTouch.set(ev.getX(), ev.getY());
+                mDownTouch.set(mLastTouch);
+                mIsDragging = false;
+                // TODO: Consider setting a timer such at after X time, we show the dismiss target
+                //       if the user hasn't already dragged some distance
+                mDismissViewController.createDismissTarget();
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                // Update the velocity tracker
+                mVelocityTracker.addMovement(ev);
+
+                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (!mIsDragging) {
+                    // Check if the pointer has moved far enough
+                    float movement = PointF.length(mDownTouch.x - ev.getX(activePointerIndex),
+                            mDownTouch.y - ev.getY(activePointerIndex));
+                    if (movement > mViewConfig.getScaledTouchSlop()) {
+                        mIsDragging = true;
+                        mDismissViewController.showDismissTarget();
+                    }
+                }
+
+                if (mIsDragging) {
+                    // Move the pinned stack
+                    float dx = ev.getX(activePointerIndex) - mLastTouch.x;
+                    float dy = ev.getY(activePointerIndex) - mLastTouch.y;
+                    float left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
+                            mBoundedPinnedStackBounds.right, mPinnedStackBounds.left + dx));
+                    float top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
+                            mBoundedPinnedStackBounds.bottom, mPinnedStackBounds.top + dy));
+                    if (DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
+                        left = mPinnedStackBounds.left + dx;
+                        top = mPinnedStackBounds.top + dy;
+                    }
+                    movePinnedStack(left, top);
+                }
+                mLastTouch.set(ev.getX(), ev.getY());
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                // Update the velocity tracker
+                mVelocityTracker.addMovement(ev);
+
+                int pointerIndex = ev.getActionIndex();
+                int pointerId = ev.getPointerId(pointerIndex);
+                if (pointerId == mActivePointerId) {
+                    // Select a new active pointer id and reset the movement state
+                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+                    mActivePointerId = ev.getPointerId(newPointerIndex);
+                    mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                // Update the velocity tracker
+                mVelocityTracker.addMovement(ev);
+
+                if (mIsDragging) {
+                    mVelocityTracker.computeCurrentVelocity(1000,
+                            ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+                    float velocityX = mVelocityTracker.getXVelocity();
+                    float velocityY = mVelocityTracker.getYVelocity();
+                    float velocity = PointF.length(velocityX, velocityY);
+                    if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+                        flingToSnapTarget(velocity, velocityX, velocityY);
+                    } else {
+                        int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                        int x = (int) ev.getX(activePointerIndex);
+                        int y = (int) ev.getY(activePointerIndex);
+                        Rect dismissBounds = mDismissViewController.getDismissBounds();
+                        if (dismissBounds.contains(x, y)) {
+                            animateDismissPinnedStack(dismissBounds);
+                        } else {
+                            animateToSnapTarget();
+                        }
+                    }
+                } else {
+                    expandPinnedStackToFullscreen();
+                }
+                mDismissViewController.destroyDismissTarget();
+
+                // Fall through to clean up
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                mIsDragging = false;
+                recycleVelocityTracker();
+                break;
+            }
+        }
+    }
+
+    private void initOrResetVelocityTracker() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        } else {
+            mVelocityTracker.clear();
+        }
+    }
+
+    private void recycleVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    /**
+     * Creates an animation that continues the fling to a snap target.
+     */
+    private void flingToSnapTarget(float velocity, float velocityX, float velocityY) {
+        mScroller.fling(mPinnedStackBounds.left, mPinnedStackBounds.top,
+            (int) velocityX, (int) velocityY,
+            mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.right,
+            mBoundedPinnedStackBounds.top, mBoundedPinnedStackBounds.bottom);
+        Rect toBounds = findClosestBoundedPinnedStackSnapTarget(
+            mScroller.getFinalX(), mScroller.getFinalY());
+        mScroller.abortAnimation();
+        if (!mPinnedStackBounds.equals(toBounds)) {
+            mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+                toBounds, 0, FAST_OUT_SLOW_IN);
+            mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
+                distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
+                velocity);
+            mPinnedStackBoundsAnimator.start();
+        }
+    }
+
+    /**
+     * Animates the pinned stack to the closest snap target.
+     */
+    private void animateToSnapTarget() {
+        Rect toBounds = findClosestBoundedPinnedStackSnapTarget(
+            mPinnedStackBounds.left, mPinnedStackBounds.top);
+        if (!mPinnedStackBounds.equals(toBounds)) {
+            mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+                toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN);
+            mPinnedStackBoundsAnimator.start();
+        }
+    }
+
+    /**
+     * Animates the dismissal of the pinned stack into the given bounds.
+     */
+    private void animateDismissPinnedStack(Rect dismissBounds) {
+        Rect toBounds = new Rect(dismissBounds.centerX(),
+            dismissBounds.centerY(),
+            dismissBounds.centerX() + 1,
+            dismissBounds.centerY() + 1);
+        mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+            toBounds, DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN);
+        mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                BackgroundThread.getHandler().post(() -> {
+                    try {
+                        mActivityManager.removeStack(PINNED_STACK_ID);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to remove PIP", e);
+                    }
+                });
+            }
+        });
+        mPinnedStackBoundsAnimator.start();
+    }
+
+    /**
+     * Resizes the pinned stack back to fullscreen.
+     */
+    private void expandPinnedStackToFullscreen() {
+        BackgroundThread.getHandler().post(() -> {
+            try {
+                mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
+                        true /* allowResizeInDockedMode */, true /* preserveWindows */,
+                        true /* animate */, EXPAND_STACK_DURATION);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error showing PIP menu activity", e);
+            }
+        });
+    }
+
+    /**
+     * Updates the movement bounds of the pinned stack.
+     */
+    private void updateBoundedPinnedStackBounds() {
+        try {
+            StackInfo info = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            mPinnedStackBounds.set(info.bounds);
+            mBoundedPinnedStackBounds.set(mActivityManager.getPictureInPictureMovementBounds());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not fetch PIP movement bounds.", e);
+        }
+    }
+
+    /**
+     * Moves the pinned stack to the given {@param left} and {@param top} offsets.
+     */
+    private void movePinnedStack(float left, float top) {
+        if ((int) left != mPinnedStackBounds.left || (int) top != mPinnedStackBounds.top) {
+            mPinnedStackBounds.offsetTo((int) left, (int) top);
+            BackgroundThread.getHandler().post(() -> {
+                try {
+                    mActivityManager.resizePinnedStack(mPinnedStackBounds,
+                            null /* tempPinnedBounds */);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not move pinned stack to offset: (" + left + ", " + top + ")",
+                            e);
+                }
+            });
+        }
+    }
+
+    /**
+     * Resizes the pinned stack to the given {@param bounds}.
+     */
+    private void resizePinnedStack(Rect bounds) {
+        if (!mPinnedStackBounds.equals(bounds)) {
+            mPinnedStackBounds.set(bounds);
+            BackgroundThread.getHandler().post(() -> {
+                try {
+                    mActivityManager.resizePinnedStack(bounds, null);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not resize pinned stack to bounds: (" + bounds + ")");
+                }
+            });
+        }
+    }
+
+    /**
+     * Creates a resize-stack animation.
+     */
+    private ValueAnimator createResizePinnedStackAnimation(Rect toBounds, int duration,
+            Interpolator interpolator) {
+        ValueAnimator anim = ValueAnimator.ofObject(RECT_EVALUATOR,
+                mPinnedStackBounds, toBounds);
+        anim.setDuration(duration);
+        anim.setInterpolator(interpolator);
+        anim.addUpdateListener(
+                new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        resizePinnedStack((Rect) animation.getAnimatedValue());
+                    }
+                });
+        return anim;
+    }
+
+    /**
+     * @return the closest absolute bounded stack left/top to the given {@param x} and {@param y}.
+     */
+    private Rect findClosestBoundedPinnedStackSnapTarget(int x, int y) {
+        Point[] snapTargets;
+        int orientation = mContext.getResources().getConfiguration().orientation;
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            snapTargets = new Point[] {
+                new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.top),
+                new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.top),
+                new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom),
+                new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.bottom)
+            };
+        } else {
+            snapTargets = new Point[] {
+                new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.top),
+                new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.top),
+                new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom),
+                new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.bottom)
+            };
+        }
+
+        Point closestSnapTarget = null;
+        float minDistance = Float.MAX_VALUE;
+        for (Point p : snapTargets) {
+            float distance = distanceToPoint(p, x, y);
+            if (distance < minDistance) {
+                closestSnapTarget = p;
+                minDistance = distance;
+            }
+        }
+
+        Rect toBounds = new Rect(mPinnedStackBounds);
+        toBounds.offsetTo(closestSnapTarget.x, closestSnapTarget.y);
+        return toBounds;
+    }
+
+    /**
+     * @return the distance between point {@param p} and the given {@param x} and {@param y}.
+     */
+    private float distanceToPoint(Point p, int x, int y) {
+        return PointF.length(p.x - x, p.y - y);
+    }
+
+
+    /**
+     * @return the distance between points {@param p1} and {@param p2}.
+     */
+    private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
+        return PointF.length(r1.left - r2.left, r1.top - r2.top);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index cd98d19..65a1b44 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.media.session.MediaController;
@@ -33,16 +32,12 @@
 import android.media.session.PlaybackState;
 import android.os.Debug;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.view.IWindowManager;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
 import android.view.WindowManagerGlobal;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -53,7 +48,6 @@
 import java.util.List;
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
 
 /**
@@ -67,8 +61,6 @@
 
     private static PipManager sPipManager;
 
-    private static final int MAX_RUNNING_TASKS_COUNT = 10;
-
     /**
      * List of package and class name which are considered as Settings,
      * so PIP location should be adjusted to the left of the side panel.
@@ -163,9 +155,6 @@
     private boolean mOnboardingShown;
     private String[] mLastPackagesResourceGranted;
 
-    private InputChannel mInputChannel;
-    private PipInputEventReceiver mInputEventReceiver;
-
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
         public void run() {
@@ -203,25 +192,6 @@
                 }
             };
 
-    /**
-     * Input handler used for Pip windows.  Currently eats all the input events.
-     */
-    private final class PipInputEventReceiver extends InputEventReceiver {
-        public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
-            super(inputChannel, looper);
-        }
-
-        @Override
-        public void onInputEvent(InputEvent event) {
-            boolean handled = true;
-            try {
-                // To be implemented for input handling over Pip windows
-            } finally {
-                finishInputEvent(event, handled);
-            }
-        }
-    }
-
     private PipManager() { }
 
     /**
@@ -246,20 +216,6 @@
         mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
         mMediaSessionManager =
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
-        PackageManager pm = mContext.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
-            // Initialize the Pip input consumer
-            mInputChannel = new InputChannel();
-            try {
-                IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-                wm.destroyInputConsumer(INPUT_CONSUMER_PIP);
-                wm.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
-                mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to create PIP input consumer", e);
-            }
-        }
     }
 
     private void loadConfigurationsAndApply() {
@@ -290,7 +246,7 @@
     /**
      * Updates the PIP per configuration changed.
      */
-    void onConfigurationChanged() {
+    public void onConfigurationChanged() {
         loadConfigurationsAndApply();
         mPipRecentsOverlayManager.onConfigurationChanged(mContext);
     }
@@ -350,11 +306,7 @@
      */
     private void showPipOverlay() {
         if (DEBUG) Log.d(TAG, "showPipOverlay()");
-        // Temporary workaround to prevent the overlay on phones
-        PackageManager pm = mContext.getPackageManager();
-        if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
-            PipOverlayActivity.showPipOverlay(mContext);
-        }
+        PipOverlayActivity.showPipOverlay(mContext);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java
index ffe96af..3726fbc 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java
@@ -49,7 +49,6 @@
     }
 
     private final PipManager mPipManager = PipManager.getInstance();
-    private Listener mListener;
     private PipControlsView mPipControlsView;
     private View mScrim;
     private Animator mFocusGainAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
deleted file mode 100644
index 3306cb3..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 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.tv.pip;
-
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-
-import com.android.systemui.SystemUI;
-
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-/**
- * Controls the picture-in-picture window.
- */
-public class PipUI extends SystemUI {
-    private boolean mSupportPip;
-
-    @Override
-    public void start() {
-        PackageManager pm = mContext.getPackageManager();
-        mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
-        if (!mSupportPip) {
-            return;
-        }
-        PipManager pipManager = PipManager.getInstance();
-        pipManager.initialize(mContext);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        if (!mSupportPip) {
-            return;
-        }
-        PipManager.getInstance().onConfigurationChanged();
-    }
-}