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
1757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlockpackage com.android.internal.policy.impl;
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);
7857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                break;
7957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            case MotionEvent.ACTION_POINTER_DOWN:
8057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                captureDown(event, event.getActionIndex());
81ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                if (mDebugFireable) {
82ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    mDebugFireable = event.getPointerCount() < 5;
83ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    if (!mDebugFireable) {
84ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        if (DEBUG) Slog.d(TAG, "Firing debug");
85ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        mCallbacks.onDebug();
86ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    }
87ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                }
8857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                break;
8957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            case MotionEvent.ACTION_MOVE:
90ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                if (mSwipeFireable) {
91ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    final int swipe = detectSwipe(event);
92ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    mSwipeFireable = swipe == SWIPE_NONE;
93ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    if (swipe == SWIPE_FROM_TOP) {
94ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
95ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        mCallbacks.onSwipeFromTop();
96ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    } else if (swipe == SWIPE_FROM_BOTTOM) {
97ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
98ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        mCallbacks.onSwipeFromBottom();
99ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    } else if (swipe == SWIPE_FROM_RIGHT) {
100ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
101ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        mCallbacks.onSwipeFromRight();
102ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    }
10357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                }
10457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                break;
10557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            case MotionEvent.ACTION_UP:
10657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            case MotionEvent.ACTION_CANCEL:
107ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                mSwipeFireable = false;
108ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                mDebugFireable = false;
10957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                break;
11057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            default:
11157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                if (DEBUG) Slog.d(TAG, "Ignoring " + event);
11257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        }
11357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    }
11457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock
11557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    private void captureDown(MotionEvent event, int pointerIndex) {
11657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        final int pointerId = event.getPointerId(pointerIndex);
11757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        final int i = findIndex(pointerId);
11857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
11957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
12057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        if (i != UNTRACKED_POINTER) {
121ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock            mDownX[i] = event.getX(pointerIndex);
12257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            mDownY[i] = event.getY(pointerIndex);
12357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            mDownTime[i] = event.getEventTime();
124ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock            if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
125ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    " down x=" + mDownX[i] + " y=" + mDownY[i]);
12657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        }
12757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    }
12857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock
12957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    private int findIndex(int pointerId) {
13057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        for (int i = 0; i < mDownPointers; i++) {
13157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            if (mDownPointerId[i] == pointerId) {
13257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                return i;
13357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            }
13457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        }
13557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) {
13657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            return UNTRACKED_POINTER;
13757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        }
13857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        mDownPointerId[mDownPointers++] = pointerId;
13957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        return mDownPointers - 1;
14057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    }
14157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock
142ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock    private int detectSwipe(MotionEvent move) {
14357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        final int historySize = move.getHistorySize();
14457306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        final int pointerCount = move.getPointerCount();
14557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        for (int p = 0; p < pointerCount; p++) {
14657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            final int pointerId = move.getPointerId(p);
14757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            final int i = findIndex(pointerId);
14857306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            if (i != UNTRACKED_POINTER) {
14957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                for (int h = 0; h < historySize; h++) {
15057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                    final long time = move.getHistoricalEventTime(h);
151ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    final float x = move.getHistoricalX(p, h);
15257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                    final float y = move.getHistoricalY(p,  h);
153ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    final int swipe = detectSwipe(i, time, x, y);
154ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    if (swipe != SWIPE_NONE) {
155ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                        return swipe;
15657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                    }
15757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                }
158ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
159ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                if (swipe != SWIPE_NONE) {
160ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                    return swipe;
16157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock                }
16257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock            }
16357306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        }
164ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        return SWIPE_NONE;
16557306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    }
16657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock
167ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock    private int detectSwipe(int i, long time, float x, float y) {
168ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        final float fromX = mDownX[i];
16957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        final float fromY = mDownY[i];
17057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        final long elapsed = time - mDownTime[i];
17157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
172ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
173ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        if (fromY <= mSwipeStartThreshold
1749ba21fdc9ddf1d132215d29054b55af416561367John Spurlock                && y > fromY + mSwipeDistanceThreshold
175ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                && elapsed < SWIPE_TIMEOUT_MS) {
176ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock            return SWIPE_FROM_TOP;
177ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        }
178ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        if (fromY >= screenHeight - mSwipeStartThreshold
1799ba21fdc9ddf1d132215d29054b55af416561367John Spurlock                && y < fromY - mSwipeDistanceThreshold
180ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                && elapsed < SWIPE_TIMEOUT_MS) {
181ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock            return SWIPE_FROM_BOTTOM;
182ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        }
183ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        if (fromX >= screenWidth - mSwipeStartThreshold
1849ba21fdc9ddf1d132215d29054b55af416561367John Spurlock                && x < fromX - mSwipeDistanceThreshold
185ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock                && elapsed < SWIPE_TIMEOUT_MS) {
186ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock            return SWIPE_FROM_RIGHT;
187ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        }
188ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        return SWIPE_NONE;
18957306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    }
19057306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock
19157306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    interface Callbacks {
19257306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock        void onSwipeFromTop();
193ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        void onSwipeFromBottom();
194ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        void onSwipeFromRight();
195ad3e6cb4db99ad33fcfc61f236d37cd83446866dJohn Spurlock        void onDebug();
19657306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock    }
19757306e6b79a87518b5739fc5717cd1cd47c75eaeJohn Spurlock}
198