SystemGesturesPointerEventListener.java revision 3595be4d19caaa7ddfbff0b979d135aaf5ac20b1
157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock/* 257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * Copyright (C) 2013 The Android Open Source Project 357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * 457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * Licensed under the Apache License, Version 2.0 (the "License"); 557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * you may not use this file except in compliance with the License. 657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * You may obtain a copy of the License at 757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * 857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * http://www.apache.org/licenses/LICENSE-2.0 957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * 1057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * Unless required by applicable law or agreed to in writing, software 1157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * distributed under the License is distributed on an "AS IS" BASIS, 1257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * See the License for the specific language governing permissions and 1457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * limitations under the License. 1557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock */ 1657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 17b10e33ff804a831c71be9303146cea892b9aeb5dJorim Jaggipackage com.android.server.policy; 1857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 1957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlockimport android.content.Context; 2057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlockimport android.util.Slog; 2157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlockimport android.view.MotionEvent; 22037aa8d434984840691378f3cc7d99d63dcc4076Craig Mautnerimport android.view.WindowManagerPolicy.PointerEventListener; 2357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 2457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock/* 2557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * Listens for system-wide input gestures, firing callbacks when detected. 2657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock * @hide 2757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock */ 28037aa8d434984840691378f3cc7d99d63dcc4076Craig Mautnerpublic class SystemGesturesPointerEventListener implements PointerEventListener { 2957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private static final String TAG = "SystemGestures"; 3057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private static final boolean DEBUG = false; 3157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private static final long SWIPE_TIMEOUT_MS = 500; 3257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private static final int MAX_TRACKED_POINTERS = 32; // max per input system 3357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private static final int UNTRACKED_POINTER = -1; 3457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 35ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private static final int SWIPE_NONE = 0; 36ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private static final int SWIPE_FROM_TOP = 1; 37ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private static final int SWIPE_FROM_BOTTOM = 2; 38ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private static final int SWIPE_FROM_RIGHT = 3; 39ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock 4057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private final int mSwipeStartThreshold; 419ba21fdc9ddf1d132215d29054b55af416561367John Spurlock private final int mSwipeDistanceThreshold; 4257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private final Callbacks mCallbacks; 4357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; 44ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; 4557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private final float[] mDownY = new float[MAX_TRACKED_POINTERS]; 4657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private final long[] mDownTime = new long[MAX_TRACKED_POINTERS]; 4757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 48ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock int screenHeight; 49ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock int screenWidth; 5057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private int mDownPointers; 51ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private boolean mSwipeFireable; 52ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private boolean mDebugFireable; 5357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 54037aa8d434984840691378f3cc7d99d63dcc4076Craig Mautner public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) { 5557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock mCallbacks = checkNull("callbacks", callbacks); 5657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock mSwipeStartThreshold = checkNull("context", context).getResources() 5757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 589ba21fdc9ddf1d132215d29054b55af416561367John Spurlock mSwipeDistanceThreshold = mSwipeStartThreshold; 599ba21fdc9ddf1d132215d29054b55af416561367John Spurlock if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold 609ba21fdc9ddf1d132215d29054b55af416561367John Spurlock + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); 6157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 6257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 6357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private static <T> T checkNull(String name, T arg) { 6457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (arg == null) { 6557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock throw new IllegalArgumentException(name + " must not be null"); 6657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 6757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock return arg; 6857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 6957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 7057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock @Override 71037aa8d434984840691378f3cc7d99d63dcc4076Craig Mautner public void onPointerEvent(MotionEvent event) { 7257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock switch (event.getActionMasked()) { 7357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock case MotionEvent.ACTION_DOWN: 74ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mSwipeFireable = true; 75ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mDebugFireable = true; 7657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock mDownPointers = 0; 7757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock captureDown(event, 0); 783595be4d19caaa7ddfbff0b979d135aaf5ac20b1Adrian Roos mCallbacks.onDown(); 7957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock break; 8057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock case MotionEvent.ACTION_POINTER_DOWN: 8157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock captureDown(event, event.getActionIndex()); 82ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (mDebugFireable) { 83ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mDebugFireable = event.getPointerCount() < 5; 84ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (!mDebugFireable) { 85ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (DEBUG) Slog.d(TAG, "Firing debug"); 86ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mCallbacks.onDebug(); 87ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } 88ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } 8957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock break; 9057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock case MotionEvent.ACTION_MOVE: 91ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (mSwipeFireable) { 92ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock final int swipe = detectSwipe(event); 93ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mSwipeFireable = swipe == SWIPE_NONE; 94ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (swipe == SWIPE_FROM_TOP) { 95ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop"); 96ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mCallbacks.onSwipeFromTop(); 97ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } else if (swipe == SWIPE_FROM_BOTTOM) { 98ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom"); 99ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mCallbacks.onSwipeFromBottom(); 100ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } else if (swipe == SWIPE_FROM_RIGHT) { 101ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight"); 102ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mCallbacks.onSwipeFromRight(); 103ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } 10457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 10557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock break; 10657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock case MotionEvent.ACTION_UP: 10757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock case MotionEvent.ACTION_CANCEL: 108ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mSwipeFireable = false; 109ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mDebugFireable = false; 1103595be4d19caaa7ddfbff0b979d135aaf5ac20b1Adrian Roos mCallbacks.onUpOrCancel(); 11157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock break; 11257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock default: 11357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (DEBUG) Slog.d(TAG, "Ignoring " + event); 11457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 11557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 11657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 11757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private void captureDown(MotionEvent event, int pointerIndex) { 11857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final int pointerId = event.getPointerId(pointerIndex); 11957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final int i = findIndex(pointerId); 12057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (DEBUG) Slog.d(TAG, "pointer " + pointerId + 12157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock " down pointerIndex=" + pointerIndex + " trackingIndex=" + i); 12257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (i != UNTRACKED_POINTER) { 123ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock mDownX[i] = event.getX(pointerIndex); 12457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock mDownY[i] = event.getY(pointerIndex); 12557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock mDownTime[i] = event.getEventTime(); 126ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (DEBUG) Slog.d(TAG, "pointer " + pointerId + 127ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock " down x=" + mDownX[i] + " y=" + mDownY[i]); 12857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 12957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 13057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 13157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock private int findIndex(int pointerId) { 13257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock for (int i = 0; i < mDownPointers; i++) { 13357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (mDownPointerId[i] == pointerId) { 13457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock return i; 13557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 13657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 13757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) { 13857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock return UNTRACKED_POINTER; 13957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 14057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock mDownPointerId[mDownPointers++] = pointerId; 14157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock return mDownPointers - 1; 14257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 14357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 144ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private int detectSwipe(MotionEvent move) { 14557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final int historySize = move.getHistorySize(); 14657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final int pointerCount = move.getPointerCount(); 14757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock for (int p = 0; p < pointerCount; p++) { 14857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final int pointerId = move.getPointerId(p); 14957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final int i = findIndex(pointerId); 15057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (i != UNTRACKED_POINTER) { 15157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock for (int h = 0; h < historySize; h++) { 15257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final long time = move.getHistoricalEventTime(h); 153ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock final float x = move.getHistoricalX(p, h); 15457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final float y = move.getHistoricalY(p, h); 155ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock final int swipe = detectSwipe(i, time, x, y); 156ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (swipe != SWIPE_NONE) { 157ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return swipe; 15857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 15957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 160ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p)); 161ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (swipe != SWIPE_NONE) { 162ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return swipe; 16357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 16457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 16557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 166ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return SWIPE_NONE; 16757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 16857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 169ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock private int detectSwipe(int i, long time, float x, float y) { 170ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock final float fromX = mDownX[i]; 17157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final float fromY = mDownY[i]; 17257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock final long elapsed = time - mDownTime[i]; 17357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i] 174ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed); 175ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (fromY <= mSwipeStartThreshold 1769ba21fdc9ddf1d132215d29054b55af416561367John Spurlock && y > fromY + mSwipeDistanceThreshold 177ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock && elapsed < SWIPE_TIMEOUT_MS) { 178ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return SWIPE_FROM_TOP; 179ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } 180ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (fromY >= screenHeight - mSwipeStartThreshold 1819ba21fdc9ddf1d132215d29054b55af416561367John Spurlock && y < fromY - mSwipeDistanceThreshold 182ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock && elapsed < SWIPE_TIMEOUT_MS) { 183ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return SWIPE_FROM_BOTTOM; 184ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } 185ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock if (fromX >= screenWidth - mSwipeStartThreshold 1869ba21fdc9ddf1d132215d29054b55af416561367John Spurlock && x < fromX - mSwipeDistanceThreshold 187ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock && elapsed < SWIPE_TIMEOUT_MS) { 188ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return SWIPE_FROM_RIGHT; 189ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock } 190ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock return SWIPE_NONE; 19157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 19257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock 19357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock interface Callbacks { 19457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock void onSwipeFromTop(); 195ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock void onSwipeFromBottom(); 196ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock void onSwipeFromRight(); 1973595be4d19caaa7ddfbff0b979d135aaf5ac20b1Adrian Roos void onDown(); 1983595be4d19caaa7ddfbff0b979d135aaf5ac20b1Adrian Roos void onUpOrCancel(); 199ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock void onDebug(); 20057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock } 20157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock} 202