Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2013 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.server.policy;
     18 
     19 import android.content.Context;
     20 import android.os.Handler;
     21 import android.os.Looper;
     22 import android.os.SystemClock;
     23 import android.util.Slog;
     24 import android.view.GestureDetector;
     25 import android.view.InputDevice;
     26 import android.view.MotionEvent;
     27 import android.view.WindowManagerPolicyConstants.PointerEventListener;
     28 import android.widget.OverScroller;
     29 
     30 /*
     31  * Listens for system-wide input gestures, firing callbacks when detected.
     32  * @hide
     33  */
     34 public class SystemGesturesPointerEventListener implements PointerEventListener {
     35     private static final String TAG = "SystemGestures";
     36     private static final boolean DEBUG = false;
     37     private static final long SWIPE_TIMEOUT_MS = 500;
     38     private static final int MAX_TRACKED_POINTERS = 32;  // max per input system
     39     private static final int UNTRACKED_POINTER = -1;
     40     private static final int MAX_FLING_TIME_MILLIS = 5000;
     41 
     42     private static final int SWIPE_NONE = 0;
     43     private static final int SWIPE_FROM_TOP = 1;
     44     private static final int SWIPE_FROM_BOTTOM = 2;
     45     private static final int SWIPE_FROM_RIGHT = 3;
     46     private static final int SWIPE_FROM_LEFT = 4;
     47 
     48     private final Context mContext;
     49     private final int mSwipeStartThreshold;
     50     private final int mSwipeDistanceThreshold;
     51     private final Callbacks mCallbacks;
     52     private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
     53     private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
     54     private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
     55     private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
     56 
     57     private GestureDetector mGestureDetector;
     58     private OverScroller mOverscroller;
     59 
     60     int screenHeight;
     61     int screenWidth;
     62     private int mDownPointers;
     63     private boolean mSwipeFireable;
     64     private boolean mDebugFireable;
     65     private boolean mMouseHoveringAtEdge;
     66     private long mLastFlingTime;
     67 
     68     public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) {
     69         mContext = context;
     70         mCallbacks = checkNull("callbacks", callbacks);
     71         mSwipeStartThreshold = checkNull("context", context).getResources()
     72                 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
     73         mSwipeDistanceThreshold = mSwipeStartThreshold;
     74         if (DEBUG) Slog.d(TAG,  "mSwipeStartThreshold=" + mSwipeStartThreshold
     75                 + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
     76     }
     77 
     78     private static <T> T checkNull(String name, T arg) {
     79         if (arg == null) {
     80             throw new IllegalArgumentException(name + " must not be null");
     81         }
     82         return arg;
     83     }
     84 
     85     public void systemReady() {
     86         Handler h = new Handler(Looper.myLooper());
     87         mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), h);
     88         mOverscroller = new OverScroller(mContext);
     89     }
     90 
     91     @Override
     92     public void onPointerEvent(MotionEvent event) {
     93         if (mGestureDetector != null && event.isTouchEvent()) {
     94             mGestureDetector.onTouchEvent(event);
     95         }
     96         switch (event.getActionMasked()) {
     97             case MotionEvent.ACTION_DOWN:
     98                 mSwipeFireable = true;
     99                 mDebugFireable = true;
    100                 mDownPointers = 0;
    101                 captureDown(event, 0);
    102                 if (mMouseHoveringAtEdge) {
    103                     mMouseHoveringAtEdge = false;
    104                     mCallbacks.onMouseLeaveFromEdge();
    105                 }
    106                 mCallbacks.onDown();
    107                 break;
    108             case MotionEvent.ACTION_POINTER_DOWN:
    109                 captureDown(event, event.getActionIndex());
    110                 if (mDebugFireable) {
    111                     mDebugFireable = event.getPointerCount() < 5;
    112                     if (!mDebugFireable) {
    113                         if (DEBUG) Slog.d(TAG, "Firing debug");
    114                         mCallbacks.onDebug();
    115                     }
    116                 }
    117                 break;
    118             case MotionEvent.ACTION_MOVE:
    119                 if (mSwipeFireable) {
    120                     final int swipe = detectSwipe(event);
    121                     mSwipeFireable = swipe == SWIPE_NONE;
    122                     if (swipe == SWIPE_FROM_TOP) {
    123                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
    124                         mCallbacks.onSwipeFromTop();
    125                     } else if (swipe == SWIPE_FROM_BOTTOM) {
    126                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
    127                         mCallbacks.onSwipeFromBottom();
    128                     } else if (swipe == SWIPE_FROM_RIGHT) {
    129                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
    130                         mCallbacks.onSwipeFromRight();
    131                     } else if (swipe == SWIPE_FROM_LEFT) {
    132                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
    133                         mCallbacks.onSwipeFromLeft();
    134                     }
    135                 }
    136                 break;
    137             case MotionEvent.ACTION_HOVER_MOVE:
    138                 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
    139                     if (!mMouseHoveringAtEdge && event.getY() == 0) {
    140                         mCallbacks.onMouseHoverAtTop();
    141                         mMouseHoveringAtEdge = true;
    142                     } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
    143                         mCallbacks.onMouseHoverAtBottom();
    144                         mMouseHoveringAtEdge = true;
    145                     } else if (mMouseHoveringAtEdge
    146                             && (event.getY() > 0 && event.getY() < screenHeight - 1)) {
    147                         mCallbacks.onMouseLeaveFromEdge();
    148                         mMouseHoveringAtEdge = false;
    149                     }
    150                 }
    151                 break;
    152             case MotionEvent.ACTION_UP:
    153             case MotionEvent.ACTION_CANCEL:
    154                 mSwipeFireable = false;
    155                 mDebugFireable = false;
    156                 mCallbacks.onUpOrCancel();
    157                 break;
    158             default:
    159                 if (DEBUG) Slog.d(TAG, "Ignoring " + event);
    160         }
    161     }
    162 
    163     private void captureDown(MotionEvent event, int pointerIndex) {
    164         final int pointerId = event.getPointerId(pointerIndex);
    165         final int i = findIndex(pointerId);
    166         if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
    167                 " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
    168         if (i != UNTRACKED_POINTER) {
    169             mDownX[i] = event.getX(pointerIndex);
    170             mDownY[i] = event.getY(pointerIndex);
    171             mDownTime[i] = event.getEventTime();
    172             if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
    173                     " down x=" + mDownX[i] + " y=" + mDownY[i]);
    174         }
    175     }
    176 
    177     private int findIndex(int pointerId) {
    178         for (int i = 0; i < mDownPointers; i++) {
    179             if (mDownPointerId[i] == pointerId) {
    180                 return i;
    181             }
    182         }
    183         if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) {
    184             return UNTRACKED_POINTER;
    185         }
    186         mDownPointerId[mDownPointers++] = pointerId;
    187         return mDownPointers - 1;
    188     }
    189 
    190     private int detectSwipe(MotionEvent move) {
    191         final int historySize = move.getHistorySize();
    192         final int pointerCount = move.getPointerCount();
    193         for (int p = 0; p < pointerCount; p++) {
    194             final int pointerId = move.getPointerId(p);
    195             final int i = findIndex(pointerId);
    196             if (i != UNTRACKED_POINTER) {
    197                 for (int h = 0; h < historySize; h++) {
    198                     final long time = move.getHistoricalEventTime(h);
    199                     final float x = move.getHistoricalX(p, h);
    200                     final float y = move.getHistoricalY(p,  h);
    201                     final int swipe = detectSwipe(i, time, x, y);
    202                     if (swipe != SWIPE_NONE) {
    203                         return swipe;
    204                     }
    205                 }
    206                 final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
    207                 if (swipe != SWIPE_NONE) {
    208                     return swipe;
    209                 }
    210             }
    211         }
    212         return SWIPE_NONE;
    213     }
    214 
    215     private int detectSwipe(int i, long time, float x, float y) {
    216         final float fromX = mDownX[i];
    217         final float fromY = mDownY[i];
    218         final long elapsed = time - mDownTime[i];
    219         if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
    220                 + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
    221         if (fromY <= mSwipeStartThreshold
    222                 && y > fromY + mSwipeDistanceThreshold
    223                 && elapsed < SWIPE_TIMEOUT_MS) {
    224             return SWIPE_FROM_TOP;
    225         }
    226         if (fromY >= screenHeight - mSwipeStartThreshold
    227                 && y < fromY - mSwipeDistanceThreshold
    228                 && elapsed < SWIPE_TIMEOUT_MS) {
    229             return SWIPE_FROM_BOTTOM;
    230         }
    231         if (fromX >= screenWidth - mSwipeStartThreshold
    232                 && x < fromX - mSwipeDistanceThreshold
    233                 && elapsed < SWIPE_TIMEOUT_MS) {
    234             return SWIPE_FROM_RIGHT;
    235         }
    236         if (fromX <= mSwipeStartThreshold
    237                 && x > fromX + mSwipeDistanceThreshold
    238                 && elapsed < SWIPE_TIMEOUT_MS) {
    239             return SWIPE_FROM_LEFT;
    240         }
    241         return SWIPE_NONE;
    242     }
    243 
    244     private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
    245         @Override
    246         public boolean onSingleTapUp(MotionEvent e) {
    247             if (!mOverscroller.isFinished()) {
    248                 mOverscroller.forceFinished(true);
    249             }
    250             return true;
    251         }
    252         @Override
    253         public boolean onFling(MotionEvent down, MotionEvent up,
    254                 float velocityX, float velocityY) {
    255             mOverscroller.computeScrollOffset();
    256             long now = SystemClock.uptimeMillis();
    257 
    258             if (mLastFlingTime != 0 && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) {
    259                 mOverscroller.forceFinished(true);
    260             }
    261             mOverscroller.fling(0, 0, (int)velocityX, (int)velocityY,
    262                     Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
    263             int duration = mOverscroller.getDuration();
    264             if (duration > MAX_FLING_TIME_MILLIS) {
    265                 duration = MAX_FLING_TIME_MILLIS;
    266             }
    267             mLastFlingTime = now;
    268             mCallbacks.onFling(duration);
    269             return true;
    270         }
    271     }
    272 
    273     interface Callbacks {
    274         void onSwipeFromTop();
    275         void onSwipeFromBottom();
    276         void onSwipeFromRight();
    277         void onSwipeFromLeft();
    278         void onFling(int durationMs);
    279         void onDown();
    280         void onUpOrCancel();
    281         void onMouseHoverAtTop();
    282         void onMouseHoverAtBottom();
    283         void onMouseLeaveFromEdge();
    284         void onDebug();
    285     }
    286 }
    287