Home | History | Annotate | Download | only in impl
      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.internal.policy.impl;
     18 
     19 import android.content.Context;
     20 import android.util.Slog;
     21 import android.view.MotionEvent;
     22 import android.view.WindowManagerPolicy.PointerEventListener;
     23 
     24 /*
     25  * Listens for system-wide input gestures, firing callbacks when detected.
     26  * @hide
     27  */
     28 public class SystemGesturesPointerEventListener implements PointerEventListener {
     29     private static final String TAG = "SystemGestures";
     30     private static final boolean DEBUG = false;
     31     private static final long SWIPE_TIMEOUT_MS = 500;
     32     private static final int MAX_TRACKED_POINTERS = 32;  // max per input system
     33     private static final int UNTRACKED_POINTER = -1;
     34 
     35     private static final int SWIPE_NONE = 0;
     36     private static final int SWIPE_FROM_TOP = 1;
     37     private static final int SWIPE_FROM_BOTTOM = 2;
     38     private static final int SWIPE_FROM_RIGHT = 3;
     39 
     40     private final int mSwipeStartThreshold;
     41     private final int mSwipeDistanceThreshold;
     42     private final Callbacks mCallbacks;
     43     private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
     44     private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
     45     private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
     46     private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
     47 
     48     int screenHeight;
     49     int screenWidth;
     50     private int mDownPointers;
     51     private boolean mSwipeFireable;
     52     private boolean mDebugFireable;
     53 
     54     public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) {
     55         mCallbacks = checkNull("callbacks", callbacks);
     56         mSwipeStartThreshold = checkNull("context", context).getResources()
     57                 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
     58         mSwipeDistanceThreshold = mSwipeStartThreshold;
     59         if (DEBUG) Slog.d(TAG,  "mSwipeStartThreshold=" + mSwipeStartThreshold
     60                 + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
     61     }
     62 
     63     private static <T> T checkNull(String name, T arg) {
     64         if (arg == null) {
     65             throw new IllegalArgumentException(name + " must not be null");
     66         }
     67         return arg;
     68     }
     69 
     70     @Override
     71     public void onPointerEvent(MotionEvent event) {
     72         switch (event.getActionMasked()) {
     73             case MotionEvent.ACTION_DOWN:
     74                 mSwipeFireable = true;
     75                 mDebugFireable = true;
     76                 mDownPointers = 0;
     77                 captureDown(event, 0);
     78                 break;
     79             case MotionEvent.ACTION_POINTER_DOWN:
     80                 captureDown(event, event.getActionIndex());
     81                 if (mDebugFireable) {
     82                     mDebugFireable = event.getPointerCount() < 5;
     83                     if (!mDebugFireable) {
     84                         if (DEBUG) Slog.d(TAG, "Firing debug");
     85                         mCallbacks.onDebug();
     86                     }
     87                 }
     88                 break;
     89             case MotionEvent.ACTION_MOVE:
     90                 if (mSwipeFireable) {
     91                     final int swipe = detectSwipe(event);
     92                     mSwipeFireable = swipe == SWIPE_NONE;
     93                     if (swipe == SWIPE_FROM_TOP) {
     94                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
     95                         mCallbacks.onSwipeFromTop();
     96                     } else if (swipe == SWIPE_FROM_BOTTOM) {
     97                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
     98                         mCallbacks.onSwipeFromBottom();
     99                     } else if (swipe == SWIPE_FROM_RIGHT) {
    100                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
    101                         mCallbacks.onSwipeFromRight();
    102                     }
    103                 }
    104                 break;
    105             case MotionEvent.ACTION_UP:
    106             case MotionEvent.ACTION_CANCEL:
    107                 mSwipeFireable = false;
    108                 mDebugFireable = false;
    109                 break;
    110             default:
    111                 if (DEBUG) Slog.d(TAG, "Ignoring " + event);
    112         }
    113     }
    114 
    115     private void captureDown(MotionEvent event, int pointerIndex) {
    116         final int pointerId = event.getPointerId(pointerIndex);
    117         final int i = findIndex(pointerId);
    118         if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
    119                 " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
    120         if (i != UNTRACKED_POINTER) {
    121             mDownX[i] = event.getX(pointerIndex);
    122             mDownY[i] = event.getY(pointerIndex);
    123             mDownTime[i] = event.getEventTime();
    124             if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
    125                     " down x=" + mDownX[i] + " y=" + mDownY[i]);
    126         }
    127     }
    128 
    129     private int findIndex(int pointerId) {
    130         for (int i = 0; i < mDownPointers; i++) {
    131             if (mDownPointerId[i] == pointerId) {
    132                 return i;
    133             }
    134         }
    135         if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) {
    136             return UNTRACKED_POINTER;
    137         }
    138         mDownPointerId[mDownPointers++] = pointerId;
    139         return mDownPointers - 1;
    140     }
    141 
    142     private int detectSwipe(MotionEvent move) {
    143         final int historySize = move.getHistorySize();
    144         final int pointerCount = move.getPointerCount();
    145         for (int p = 0; p < pointerCount; p++) {
    146             final int pointerId = move.getPointerId(p);
    147             final int i = findIndex(pointerId);
    148             if (i != UNTRACKED_POINTER) {
    149                 for (int h = 0; h < historySize; h++) {
    150                     final long time = move.getHistoricalEventTime(h);
    151                     final float x = move.getHistoricalX(p, h);
    152                     final float y = move.getHistoricalY(p,  h);
    153                     final int swipe = detectSwipe(i, time, x, y);
    154                     if (swipe != SWIPE_NONE) {
    155                         return swipe;
    156                     }
    157                 }
    158                 final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
    159                 if (swipe != SWIPE_NONE) {
    160                     return swipe;
    161                 }
    162             }
    163         }
    164         return SWIPE_NONE;
    165     }
    166 
    167     private int detectSwipe(int i, long time, float x, float y) {
    168         final float fromX = mDownX[i];
    169         final float fromY = mDownY[i];
    170         final long elapsed = time - mDownTime[i];
    171         if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
    172                 + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
    173         if (fromY <= mSwipeStartThreshold
    174                 && y > fromY + mSwipeDistanceThreshold
    175                 && elapsed < SWIPE_TIMEOUT_MS) {
    176             return SWIPE_FROM_TOP;
    177         }
    178         if (fromY >= screenHeight - mSwipeStartThreshold
    179                 && y < fromY - mSwipeDistanceThreshold
    180                 && elapsed < SWIPE_TIMEOUT_MS) {
    181             return SWIPE_FROM_BOTTOM;
    182         }
    183         if (fromX >= screenWidth - mSwipeStartThreshold
    184                 && x < fromX - mSwipeDistanceThreshold
    185                 && elapsed < SWIPE_TIMEOUT_MS) {
    186             return SWIPE_FROM_RIGHT;
    187         }
    188         return SWIPE_NONE;
    189     }
    190 
    191     interface Callbacks {
    192         void onSwipeFromTop();
    193         void onSwipeFromBottom();
    194         void onSwipeFromRight();
    195         void onDebug();
    196     }
    197 }
    198