Merge "Merge "Double tap to expand PiP." into oc-mr1-dev am: 2a6f25b995" into oc-mr1-dev-plus-aosp

This commit is contained in:
Android Build Merger (Role)
2017-09-05 21:24:30 +00:00
committed by Android (Google) Code Review
5 changed files with 249 additions and 11 deletions

View File

@@ -55,6 +55,7 @@ import android.util.Log;
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 @@ public class PipMenuActivity extends Activity {
}
};
private PipTouchState mTouchState;
private PointF mDownPosition = new PointF();
private PointF mDownDelta = new PointF();
private ViewConfiguration mViewConfig;
@@ -175,6 +177,13 @@ public class PipMenuActivity extends Activity {
// 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 @@ public class PipMenuActivity extends Activity {
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);

View File

@@ -211,6 +211,10 @@ public class PipMenuActivityController {
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

View File

@@ -187,13 +187,15 @@ public class PipTouchHandler {
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 @@ public class PipTouchHandler {
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 @@ public class PipTouchHandler {
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();

View File

@@ -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 @@ public class PipTouchState {
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 @@ public class PipTouchState {
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 @@ public class PipTouchState {
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 @@ public class PipTouchState {
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 @@ public class PipTouchState {
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();

View File

@@ -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);
}
}