Merge "DO NOT MERGE: Dynamic doze check should be required before adjusting sensor rate. am: 1f3da1c8b9  -s ours am: c7088d8bf8  -s ours am: ef6e002b30 am: 44bdde7bd1" into oc-mr1-dev
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 901b0b0..90f7b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -55,6 +55,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnTouchListener;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager.LayoutParams;
@@ -119,6 +120,7 @@
                 }
             };
 
+    private PipTouchState mTouchState;
     private PointF mDownPosition = new PointF();
     private PointF mDownDelta = new PointF();
     private ViewConfiguration mViewConfig;
@@ -175,6 +177,13 @@
         // Set the flags to allow us to watch for outside touches and also hide the menu and start
         // manipulating the PIP in the same touch gesture
         mViewConfig = ViewConfiguration.get(this);
+        mTouchState = new PipTouchState(mViewConfig, mHandler, () -> {
+            if (mMenuState == MENU_STATE_CLOSE) {
+                showPipMenu();
+            } else {
+                expandPip();
+            }
+        });
         getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);
 
         super.onCreate(savedInstanceState);
@@ -186,12 +195,28 @@
         mViewRoot.setBackground(mBackgroundDrawable);
         mMenuContainer = findViewById(R.id.menu_container);
         mMenuContainer.setAlpha(0);
-        mMenuContainer.setOnClickListener((v) -> {
-            if (mMenuState == MENU_STATE_CLOSE) {
-                showPipMenu();
-            } else {
-                expandPip();
+        mMenuContainer.setOnTouchListener((v, event) -> {
+            mTouchState.onTouchEvent(event);
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_UP:
+                    if (mTouchState.isDoubleTap() || mMenuState == MENU_STATE_FULL) {
+                        // Expand to fullscreen if this is a double tap or we are already expanded
+                        expandPip();
+                    } else if (!mTouchState.isWaitingForDoubleTap()) {
+                        // User has stalled long enough for this not to be a drag or a double tap,
+                        // just expand the menu if necessary
+                        if (mMenuState == MENU_STATE_CLOSE) {
+                            showPipMenu();
+                        }
+                    } else {
+                        // Next touch event _may_ be the second tap for the double-tap, schedule a
+                        // fallback runnable to trigger the menu if no touch event occurs before the
+                        // next tap
+                        mTouchState.scheduleDoubleTapTimeoutCallback();
+                    }
+                    break;
             }
+            return true;
         });
         mDismissButton = findViewById(R.id.dismiss);
         mDismissButton.setAlpha(0);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index e898a51..34666fb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -211,6 +211,10 @@
         EventBus.getDefault().register(this);
     }
 
+    public boolean isMenuActivityVisible() {
+        return mToActivityMessenger != null;
+    }
+
     public void onActivityPinned() {
         if (mMenuState == MENU_STATE_NONE) {
             // If the menu is not visible, then re-register the input consumer if it is not already
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 3181481..2b48e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -187,13 +187,15 @@
         mMenuController.addListener(mMenuListener);
         mDismissViewController = new PipDismissViewController(context);
         mSnapAlgorithm = new PipSnapAlgorithm(mContext);
-        mTouchState = new PipTouchState(mViewConfig);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 2.5f);
         mGestures = new PipTouchGesture[] {
                 mDefaultMovementGesture
         };
         mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mMenuController,
                 mSnapAlgorithm, mFlingAnimationUtils);
+        mTouchState = new PipTouchState(mViewConfig, mHandler,
+                () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
+                        mMovementBounds, true /* allowMenuTimeout */, willResizeMenu()));
 
         Resources res = context.getResources();
         mExpandedShortestEdgeSize = res.getDimensionPixelSize(
@@ -429,7 +431,7 @@
                 final float distance = bounds.bottom - target;
                 fraction = Math.min(distance / bounds.height(), 1f);
             }
-            if (Float.compare(fraction, 0f) != 0 || mMenuState != MENU_STATE_NONE) {
+            if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuActivityVisible()) {
                 // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
                 mMenuController.setDismissFraction(fraction);
             }
@@ -730,8 +732,20 @@
                         null /* animatorListener */);
                 setMinimizedStateInternal(false);
             } else if (mMenuState != MENU_STATE_FULL) {
-                mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                        mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
+                if (mTouchState.isDoubleTap()) {
+                    // Expand to fullscreen if this is a double tap
+                    mMotionHelper.expandPip();
+                } else if (!mTouchState.isWaitingForDoubleTap()) {
+                    // User has stalled long enough for this not to be a drag or a double tap, just
+                    // expand the menu
+                    mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
+                            mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
+                } else {
+                    // Next touch event _may_ be the second tap for the double-tap, schedule a
+                    // fallback runnable to trigger the menu if no touch event occurs before the
+                    // next tap
+                    mTouchState.scheduleDoubleTapTimeoutCallback();
+                }
             } else {
                 mMenuController.hideMenu();
                 mMotionHelper.expandPip();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 686b3bb..b9369d3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -17,11 +17,15 @@
 package com.android.systemui.pip.phone;
 
 import android.graphics.PointF;
+import android.os.Handler;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 
 /**
@@ -31,9 +35,17 @@
     private static final String TAG = "PipTouchHandler";
     private static final boolean DEBUG = false;
 
-    private ViewConfiguration mViewConfig;
+    @VisibleForTesting
+    static final long DOUBLE_TAP_TIMEOUT = 200;
+
+    private final Handler mHandler;
+    private final ViewConfiguration mViewConfig;
+    private final Runnable mDoubleTapTimeoutCallback;
 
     private VelocityTracker mVelocityTracker;
+    private long mDownTouchTime = 0;
+    private long mLastDownTouchTime = 0;
+    private long mUpTouchTime = 0;
     private final PointF mDownTouch = new PointF();
     private final PointF mDownDelta = new PointF();
     private final PointF mLastTouch = new PointF();
@@ -41,13 +53,22 @@
     private final PointF mVelocity = new PointF();
     private boolean mAllowTouches = true;
     private boolean mIsUserInteracting = false;
+    // Set to true only if the multiple taps occur within the double tap timeout
+    private boolean mIsDoubleTap = false;
+    // Set to true only if a gesture
+    private boolean mIsWaitingForDoubleTap = false;
     private boolean mIsDragging = false;
+    // The previous gesture was a drag
+    private boolean mPreviouslyDragging = false;
     private boolean mStartedDragging = false;
     private boolean mAllowDraggingOffscreen = false;
     private int mActivePointerId;
 
-    public PipTouchState(ViewConfiguration viewConfig) {
+    public PipTouchState(ViewConfiguration viewConfig, Handler handler,
+            Runnable doubleTapTimeoutCallback) {
         mViewConfig = viewConfig;
+        mHandler = handler;
+        mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
     }
 
     /**
@@ -81,6 +102,14 @@
                 mDownTouch.set(mLastTouch);
                 mAllowDraggingOffscreen = true;
                 mIsUserInteracting = true;
+                mDownTouchTime = ev.getEventTime();
+                mIsDoubleTap = !mPreviouslyDragging &&
+                        (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+                mIsWaitingForDoubleTap = false;
+                mLastDownTouchTime = mDownTouchTime;
+                if (mDoubleTapTimeoutCallback != null) {
+                    mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
+                }
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -155,7 +184,11 @@
                     break;
                 }
 
+                mUpTouchTime = ev.getEventTime();
                 mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                mPreviouslyDragging = mIsDragging;
+                mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
+                        (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
 
                 // Fall through to clean up
             }
@@ -251,6 +284,39 @@
         return mAllowDraggingOffscreen;
     }
 
+    /**
+     * @return whether this gesture is a double-tap.
+     */
+    public boolean isDoubleTap() {
+        return mIsDoubleTap;
+    }
+
+    /**
+     * @return whether this gesture will potentially lead to a following double-tap.
+     */
+    public boolean isWaitingForDoubleTap() {
+        return mIsWaitingForDoubleTap;
+    }
+
+    /**
+     * Schedules the callback to run if the next double tap does not occur.  Only runs if
+     * isWaitingForDoubleTap() is true.
+     */
+    public void scheduleDoubleTapTimeoutCallback() {
+        if (mIsWaitingForDoubleTap) {
+            long delay = getDoubleTapTimeoutCallbackDelay();
+            mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
+            mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
+        }
+    }
+
+    @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() {
+        if (mIsWaitingForDoubleTap) {
+            return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
+        }
+        return -1;
+    }
+
     private void initOrResetVelocityTracker() {
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java
new file mode 100644
index 0000000..b8c946d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 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.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.pip.phone.PipTouchState;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PipTouchStateTest extends SysuiTestCase {
+
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+    private PipTouchState mTouchState;
+    private CountDownLatch mDoubleTapCallbackTriggeredLatch;
+
+    @Before
+    public void setUp() throws Exception {
+        mHandlerThread = new HandlerThread("PipTouchStateTestThread");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1);
+        mTouchState = new PipTouchState(ViewConfiguration.get(getContext()),
+                mHandler, () -> {
+            mDoubleTapCallbackTriggeredLatch.countDown();
+        });
+        assertFalse(mTouchState.isDoubleTap());
+        assertFalse(mTouchState.isWaitingForDoubleTap());
+    }
+
+    @Test
+    public void testDoubleTapLongSingleTap_notDoubleTapAndNotWaiting() {
+        final long currentTime = SystemClock.uptimeMillis();
+
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_UP,
+                currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT + 10, 0, 0));
+        assertFalse(mTouchState.isDoubleTap());
+        assertFalse(mTouchState.isWaitingForDoubleTap());
+        assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == -1);
+    }
+
+    @Test
+    public void testDoubleTapTimeout_timeoutCallbackCalled() throws Exception {
+        final long currentTime = SystemClock.uptimeMillis();
+
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_UP,
+                currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT - 10, 0, 0));
+        assertFalse(mTouchState.isDoubleTap());
+        assertTrue(mTouchState.isWaitingForDoubleTap());
+
+        assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10);
+        mTouchState.scheduleDoubleTapTimeoutCallback();
+        mDoubleTapCallbackTriggeredLatch.await(1, TimeUnit.SECONDS);
+        assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0);
+    }
+
+    @Test
+    public void testDoubleTapDrag_doubleTapCanceled() {
+        final long currentTime = SystemClock.uptimeMillis();
+
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_MOVE, currentTime + 10, 500, 500));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_UP, currentTime + 20, 500, 500));
+        assertTrue(mTouchState.isDragging());
+        assertFalse(mTouchState.isDoubleTap());
+        assertFalse(mTouchState.isWaitingForDoubleTap());
+        assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == -1);
+    }
+
+    @Test
+    public void testDoubleTap_doubleTapRegistered() {
+        final long currentTime = SystemClock.uptimeMillis();
+
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_UP, currentTime + 10, 0, 0));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN,
+                currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT - 20, 0, 0));
+        mTouchState.onTouchEvent(createMotionEvent(ACTION_UP,
+                currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT - 10, 0, 0));
+        assertTrue(mTouchState.isDoubleTap());
+        assertFalse(mTouchState.isWaitingForDoubleTap());
+        assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == -1);
+    }
+
+    private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
+        return MotionEvent.obtain(0, eventTime, action, x, y, 0);
+    }
+}
\ No newline at end of file