Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.pip.phone;
     18 
     19 import android.graphics.PointF;
     20 import android.os.Handler;
     21 import android.os.SystemClock;
     22 import android.util.Log;
     23 import android.view.MotionEvent;
     24 import android.view.VelocityTracker;
     25 import android.view.ViewConfiguration;
     26 
     27 import com.android.internal.annotations.VisibleForTesting;
     28 
     29 import java.io.PrintWriter;
     30 
     31 /**
     32  * This keeps track of the touch state throughout the current touch gesture.
     33  */
     34 public class PipTouchState {
     35     private static final String TAG = "PipTouchHandler";
     36     private static final boolean DEBUG = false;
     37 
     38     @VisibleForTesting
     39     static final long DOUBLE_TAP_TIMEOUT = 200;
     40 
     41     private final Handler mHandler;
     42     private final ViewConfiguration mViewConfig;
     43     private final Runnable mDoubleTapTimeoutCallback;
     44 
     45     private VelocityTracker mVelocityTracker;
     46     private long mDownTouchTime = 0;
     47     private long mLastDownTouchTime = 0;
     48     private long mUpTouchTime = 0;
     49     private final PointF mDownTouch = new PointF();
     50     private final PointF mDownDelta = new PointF();
     51     private final PointF mLastTouch = new PointF();
     52     private final PointF mLastDelta = new PointF();
     53     private final PointF mVelocity = new PointF();
     54     private boolean mAllowTouches = true;
     55     private boolean mIsUserInteracting = false;
     56     // Set to true only if the multiple taps occur within the double tap timeout
     57     private boolean mIsDoubleTap = false;
     58     // Set to true only if a gesture
     59     private boolean mIsWaitingForDoubleTap = false;
     60     private boolean mIsDragging = false;
     61     // The previous gesture was a drag
     62     private boolean mPreviouslyDragging = false;
     63     private boolean mStartedDragging = false;
     64     private boolean mAllowDraggingOffscreen = false;
     65     private int mActivePointerId;
     66 
     67     public PipTouchState(ViewConfiguration viewConfig, Handler handler,
     68             Runnable doubleTapTimeoutCallback) {
     69         mViewConfig = viewConfig;
     70         mHandler = handler;
     71         mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
     72     }
     73 
     74     /**
     75      * Resets this state.
     76      */
     77     public void reset() {
     78         mAllowDraggingOffscreen = false;
     79         mIsDragging = false;
     80         mStartedDragging = false;
     81         mIsUserInteracting = false;
     82     }
     83 
     84     /**
     85      * Processes a given touch event and updates the state.
     86      */
     87     public void onTouchEvent(MotionEvent ev) {
     88         switch (ev.getAction()) {
     89             case MotionEvent.ACTION_DOWN: {
     90                 if (!mAllowTouches) {
     91                     return;
     92                 }
     93 
     94                 // Initialize the velocity tracker
     95                 initOrResetVelocityTracker();
     96 
     97                 mActivePointerId = ev.getPointerId(0);
     98                 if (DEBUG) {
     99                     Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
    100                 }
    101                 mLastTouch.set(ev.getX(), ev.getY());
    102                 mDownTouch.set(mLastTouch);
    103                 mAllowDraggingOffscreen = true;
    104                 mIsUserInteracting = true;
    105                 mDownTouchTime = ev.getEventTime();
    106                 mIsDoubleTap = !mPreviouslyDragging &&
    107                         (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
    108                 mIsWaitingForDoubleTap = false;
    109                 mLastDownTouchTime = mDownTouchTime;
    110                 if (mDoubleTapTimeoutCallback != null) {
    111                     mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
    112                 }
    113                 break;
    114             }
    115             case MotionEvent.ACTION_MOVE: {
    116                 // Skip event if we did not start processing this touch gesture
    117                 if (!mIsUserInteracting) {
    118                     break;
    119                 }
    120 
    121                 // Update the velocity tracker
    122                 mVelocityTracker.addMovement(ev);
    123                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
    124                 if (pointerIndex == -1) {
    125                     Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
    126                     break;
    127                 }
    128 
    129                 float x = ev.getX(pointerIndex);
    130                 float y = ev.getY(pointerIndex);
    131                 mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
    132                 mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
    133 
    134                 boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
    135                 if (!mIsDragging) {
    136                     if (hasMovedBeyondTap) {
    137                         mIsDragging = true;
    138                         mStartedDragging = true;
    139                     }
    140                 } else {
    141                     mStartedDragging = false;
    142                 }
    143                 mLastTouch.set(x, y);
    144                 break;
    145             }
    146             case MotionEvent.ACTION_POINTER_UP: {
    147                 // Skip event if we did not start processing this touch gesture
    148                 if (!mIsUserInteracting) {
    149                     break;
    150                 }
    151 
    152                 // Update the velocity tracker
    153                 mVelocityTracker.addMovement(ev);
    154 
    155                 int pointerIndex = ev.getActionIndex();
    156                 int pointerId = ev.getPointerId(pointerIndex);
    157                 if (pointerId == mActivePointerId) {
    158                     // Select a new active pointer id and reset the movement state
    159                     final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
    160                     mActivePointerId = ev.getPointerId(newPointerIndex);
    161                     if (DEBUG) {
    162                         Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
    163                                 mActivePointerId);
    164                     }
    165                     mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
    166                 }
    167                 break;
    168             }
    169             case MotionEvent.ACTION_UP: {
    170                 // Skip event if we did not start processing this touch gesture
    171                 if (!mIsUserInteracting) {
    172                     break;
    173                 }
    174 
    175                 // Update the velocity tracker
    176                 mVelocityTracker.addMovement(ev);
    177                 mVelocityTracker.computeCurrentVelocity(1000,
    178                         mViewConfig.getScaledMaximumFlingVelocity());
    179                 mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
    180 
    181                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
    182                 if (pointerIndex == -1) {
    183                     Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
    184                     break;
    185                 }
    186 
    187                 mUpTouchTime = ev.getEventTime();
    188                 mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
    189                 mPreviouslyDragging = mIsDragging;
    190                 mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
    191                         (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
    192 
    193                 // Fall through to clean up
    194             }
    195             case MotionEvent.ACTION_CANCEL: {
    196                 recycleVelocityTracker();
    197                 break;
    198             }
    199         }
    200     }
    201 
    202     /**
    203      * @return the velocity of the active touch pointer at the point it is lifted off the screen.
    204      */
    205     public PointF getVelocity() {
    206         return mVelocity;
    207     }
    208 
    209     /**
    210      * @return the last touch position of the active pointer.
    211      */
    212     public PointF getLastTouchPosition() {
    213         return mLastTouch;
    214     }
    215 
    216     /**
    217      * @return the movement delta between the last handled touch event and the previous touch
    218      *         position.
    219      */
    220     public PointF getLastTouchDelta() {
    221         return mLastDelta;
    222     }
    223 
    224     /**
    225      * @return the down touch position.
    226      */
    227     public PointF getDownTouchPosition() {
    228         return mDownTouch;
    229     }
    230 
    231     /**
    232      * @return the movement delta between the last handled touch event and the down touch
    233      *         position.
    234      */
    235     public PointF getDownTouchDelta() {
    236         return mDownDelta;
    237     }
    238 
    239     /**
    240      * @return whether the user has started dragging.
    241      */
    242     public boolean isDragging() {
    243         return mIsDragging;
    244     }
    245 
    246     /**
    247      * @return whether the user is currently interacting with the PiP.
    248      */
    249     public boolean isUserInteracting() {
    250         return mIsUserInteracting;
    251     }
    252 
    253     /**
    254      * @return whether the user has started dragging just in the last handled touch event.
    255      */
    256     public boolean startedDragging() {
    257         return mStartedDragging;
    258     }
    259 
    260     /**
    261      * Sets whether touching is currently allowed.
    262      */
    263     public void setAllowTouches(boolean allowTouches) {
    264         mAllowTouches = allowTouches;
    265 
    266         // If the user happens to touch down before this is sent from the system during a transition
    267         // then block any additional handling by resetting the state now
    268         if (mIsUserInteracting) {
    269             reset();
    270         }
    271     }
    272 
    273     /**
    274      * Disallows dragging offscreen for the duration of the current gesture.
    275      */
    276     public void setDisallowDraggingOffscreen() {
    277         mAllowDraggingOffscreen = false;
    278     }
    279 
    280     /**
    281      * @return whether dragging offscreen is allowed during this gesture.
    282      */
    283     public boolean allowDraggingOffscreen() {
    284         return mAllowDraggingOffscreen;
    285     }
    286 
    287     /**
    288      * @return whether this gesture is a double-tap.
    289      */
    290     public boolean isDoubleTap() {
    291         return mIsDoubleTap;
    292     }
    293 
    294     /**
    295      * @return whether this gesture will potentially lead to a following double-tap.
    296      */
    297     public boolean isWaitingForDoubleTap() {
    298         return mIsWaitingForDoubleTap;
    299     }
    300 
    301     /**
    302      * Schedules the callback to run if the next double tap does not occur.  Only runs if
    303      * isWaitingForDoubleTap() is true.
    304      */
    305     public void scheduleDoubleTapTimeoutCallback() {
    306         if (mIsWaitingForDoubleTap) {
    307             long delay = getDoubleTapTimeoutCallbackDelay();
    308             mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
    309             mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
    310         }
    311     }
    312 
    313     @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() {
    314         if (mIsWaitingForDoubleTap) {
    315             return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
    316         }
    317         return -1;
    318     }
    319 
    320     private void initOrResetVelocityTracker() {
    321         if (mVelocityTracker == null) {
    322             mVelocityTracker = VelocityTracker.obtain();
    323         } else {
    324             mVelocityTracker.clear();
    325         }
    326     }
    327 
    328     private void recycleVelocityTracker() {
    329         if (mVelocityTracker != null) {
    330             mVelocityTracker.recycle();
    331             mVelocityTracker = null;
    332         }
    333     }
    334 
    335     public void dump(PrintWriter pw, String prefix) {
    336         final String innerPrefix = prefix + "  ";
    337         pw.println(prefix + TAG);
    338         pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
    339         pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
    340         pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
    341         pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
    342         pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
    343         pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
    344         pw.println(innerPrefix + "mVelocity=" + mVelocity);
    345         pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
    346         pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
    347         pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
    348         pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen);
    349     }
    350 }
    351