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