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