Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2012 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 android.webkit;
     18 
     19 import android.content.Context;
     20 import android.os.Handler;
     21 import android.os.Looper;
     22 import android.os.Message;
     23 import android.os.SystemClock;
     24 import android.util.Log;
     25 import android.view.MotionEvent;
     26 import android.view.ViewConfiguration;
     27 
     28 /**
     29  * Perform asynchronous dispatch of input events in a {@link WebView}.
     30  *
     31  * This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit
     32  * thread ({@link WebViewCore}).  The UI thread enqueues events for
     33  * processing, waits for the web kit thread to handle them, and then performs
     34  * additional processing depending on the outcome.
     35  *
     36  * How it works:
     37  *
     38  * 1. The web view thread receives an input event from the input system on the UI
     39  * thread in its {@link WebViewClassic#onTouchEvent} handler.  It sends the input event
     40  * to the dispatcher, then immediately returns true to the input system to indicate that
     41  * it will handle the event.
     42  *
     43  * 2. The web kit thread is notified that an event has been enqueued.  Meanwhile additional
     44  * events may be enqueued from the UI thread.  In some cases, the dispatcher may decide to
     45  * coalesce motion events into larger batches or to cancel events that have been
     46  * sitting in the queue for too long.
     47  *
     48  * 3. The web kit thread wakes up and handles all input events that are waiting for it.
     49  * After processing each input event, it informs the dispatcher whether the web application
     50  * has decided to handle the event itself and to prevent default event handling.
     51  *
     52  * 4. If web kit indicates that it wants to prevent default event handling, then web kit
     53  * consumes the remainder of the gesture and web view receives a cancel event if
     54  * needed.  Otherwise, the web view handles the gesture on the UI thread normally.
     55  *
     56  * 5. If the web kit thread takes too long to handle an input event, then it loses the
     57  * right to handle it.  The dispatcher synthesizes a cancellation event for web kit and
     58  * then tells the web view on the UI thread to handle the event that timed out along
     59  * with the rest of the gesture.
     60  *
     61  * One thing to keep in mind about the dispatcher is that what goes into the dispatcher
     62  * is not necessarily what the web kit or UI thread will see.  As mentioned above, the
     63  * dispatcher may tweak the input event stream to improve responsiveness.  Both web view and
     64  * web kit are guaranteed to perceive a consistent stream of input events but
     65  * they might not always see the same events (especially if one decides
     66  * to prevent the other from handling a particular gesture).
     67  *
     68  * This implementation very deliberately does not refer to the {@link WebViewClassic}
     69  * or {@link WebViewCore} classes, preferring to communicate with them only via
     70  * interfaces to avoid unintentional coupling to their implementation details.
     71  *
     72  * Currently, the input dispatcher only handles pointer events (includes touch,
     73  * hover and scroll events).  In principle, it could be extended to handle trackball
     74  * and key events if needed.
     75  *
     76  * @hide
     77  */
     78 final class WebViewInputDispatcher {
     79     private static final String TAG = "WebViewInputDispatcher";
     80     private static final boolean DEBUG = false;
     81     // This enables batching of MotionEvents. It will combine multiple MotionEvents
     82     // together into a single MotionEvent if more events come in while we are
     83     // still waiting on the processing of a previous event.
     84     // If this is set to false, we will instead opt to drop ACTION_MOVE
     85     // events we cannot keep up with.
     86     // TODO: If batching proves to be working well, remove this
     87     private static final boolean ENABLE_EVENT_BATCHING = true;
     88 
     89     private final Object mLock = new Object();
     90 
     91     // Pool of queued input events.  (guarded by mLock)
     92     private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
     93     private DispatchEvent mDispatchEventPool;
     94     private int mDispatchEventPoolSize;
     95 
     96     // Posted state, tracks events posted to the dispatcher.  (guarded by mLock)
     97     private final TouchStream mPostTouchStream = new TouchStream();
     98     private boolean mPostSendTouchEventsToWebKit;
     99     private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
    100     private boolean mPostLongPressScheduled;
    101     private boolean mPostClickScheduled;
    102     private boolean mPostShowTapHighlightScheduled;
    103     private boolean mPostHideTapHighlightScheduled;
    104     private int mPostLastWebKitXOffset;
    105     private int mPostLastWebKitYOffset;
    106     private float mPostLastWebKitScale;
    107 
    108     // State for event tracking (click, longpress, double tap, etc..)
    109     private boolean mIsDoubleTapCandidate;
    110     private boolean mIsTapCandidate;
    111     private float mInitialDownX;
    112     private float mInitialDownY;
    113     private float mTouchSlopSquared;
    114     private float mDoubleTapSlopSquared;
    115 
    116     // Web kit state, tracks events observed by web kit.  (guarded by mLock)
    117     private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
    118     private final TouchStream mWebKitTouchStream = new TouchStream();
    119     private final WebKitCallbacks mWebKitCallbacks;
    120     private final WebKitHandler mWebKitHandler;
    121     private boolean mWebKitDispatchScheduled;
    122     private boolean mWebKitTimeoutScheduled;
    123     private long mWebKitTimeoutTime;
    124 
    125     // UI state, tracks events observed by the UI.  (guarded by mLock)
    126     private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
    127     private final TouchStream mUiTouchStream = new TouchStream();
    128     private final UiCallbacks mUiCallbacks;
    129     private final UiHandler mUiHandler;
    130     private boolean mUiDispatchScheduled;
    131 
    132     // Give up on web kit handling of input events when this timeout expires.
    133     private static final long WEBKIT_TIMEOUT_MILLIS = 200;
    134     private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
    135     private static final int LONG_PRESS_TIMEOUT =
    136             ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
    137     private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
    138     private static final int PRESSED_STATE_DURATION = ViewConfiguration.getPressedStateDuration();
    139 
    140     /**
    141      * Event type: Indicates a touch event type.
    142      *
    143      * This event is delivered together with a {@link MotionEvent} with one of the
    144      * following actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE},
    145      * {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN},
    146      * {@link MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
    147      */
    148     public static final int EVENT_TYPE_TOUCH = 0;
    149 
    150     /**
    151      * Event type: Indicates a hover event type.
    152      *
    153      * This event is delivered together with a {@link MotionEvent} with one of the
    154      * following actions: {@link MotionEvent#ACTION_HOVER_ENTER},
    155      * {@link MotionEvent#ACTION_HOVER_MOVE}, {@link MotionEvent#ACTION_HOVER_MOVE}.
    156      */
    157     public static final int EVENT_TYPE_HOVER = 1;
    158 
    159     /**
    160      * Event type: Indicates a scroll event type.
    161      *
    162      * This event is delivered together with a {@link MotionEvent} with action
    163      * {@link MotionEvent#ACTION_SCROLL}.
    164      */
    165     public static final int EVENT_TYPE_SCROLL = 2;
    166 
    167     /**
    168      * Event type: Indicates a long-press event type.
    169      *
    170      * This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events.
    171      * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE}
    172      * that indicates the current touch coordinates of the long-press.
    173      *
    174      * This event is sent when the current touch gesture has been held longer than
    175      * the long-press interval.
    176      */
    177     public static final int EVENT_TYPE_LONG_PRESS = 3;
    178 
    179     /**
    180      * Event type: Indicates a click event type.
    181      *
    182      * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
    183      * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
    184      * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
    185      * that indicates the location of the click.
    186      *
    187      * This event is sent shortly after the end of a touch after the double-tap
    188      * interval has expired to indicate a click.
    189      */
    190     public static final int EVENT_TYPE_CLICK = 4;
    191 
    192     /**
    193      * Event type: Indicates a double-tap event type.
    194      *
    195      * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
    196      * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
    197      * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
    198      * that indicates the location of the double-tap.
    199      *
    200      * This event is sent immediately after a sequence of two touches separated
    201      * in time by no more than the double-tap interval and separated in space
    202      * by no more than the double-tap slop.
    203      */
    204     public static final int EVENT_TYPE_DOUBLE_TAP = 5;
    205 
    206     /**
    207      * Event type: Indicates that a hit test should be performed
    208      */
    209     public static final int EVENT_TYPE_HIT_TEST = 6;
    210 
    211     /**
    212      * Flag: This event is private to this queue.  Do not forward it.
    213      */
    214     public static final int FLAG_PRIVATE = 1 << 0;
    215 
    216     /**
    217      * Flag: This event is currently being processed by web kit.
    218      * If a timeout occurs, make a copy of it before forwarding the event to another queue.
    219      */
    220     public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;
    221 
    222     /**
    223      * Flag: A timeout occurred while waiting for web kit to process this input event.
    224      */
    225     public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;
    226 
    227     /**
    228      * Flag: Indicates that the event was transformed for delivery to web kit.
    229      * The event must be transformed back before being delivered to the UI.
    230      */
    231     public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;
    232 
    233     public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
    234         this.mUiCallbacks = uiCallbacks;
    235         mUiHandler = new UiHandler(uiCallbacks.getUiLooper());
    236 
    237         this.mWebKitCallbacks = webKitCallbacks;
    238         mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());
    239 
    240         ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
    241         mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
    242         mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
    243         mTouchSlopSquared = config.getScaledTouchSlop();
    244         mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
    245     }
    246 
    247     /**
    248      * Sets whether web kit wants to receive touch events.
    249      *
    250      * @param enable True to enable dispatching of touch events to web kit, otherwise
    251      * web kit will be skipped.
    252      */
    253     public void setWebKitWantsTouchEvents(boolean enable) {
    254         if (DEBUG) {
    255             Log.d(TAG, "webkitWantsTouchEvents: " + enable);
    256         }
    257         synchronized (mLock) {
    258             if (mPostSendTouchEventsToWebKit != enable) {
    259                 if (!enable) {
    260                     enqueueWebKitCancelTouchEventIfNeededLocked();
    261                 }
    262                 mPostSendTouchEventsToWebKit = enable;
    263             }
    264         }
    265     }
    266 
    267     /**
    268      * Posts a pointer event to the dispatch queue.
    269      *
    270      * @param event The event to post.
    271      * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
    272      * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
    273      * @param webKitScale The scale factor to apply to translated events before dispatching
    274      * them to web kit.
    275      * @return True if the dispatcher will handle the event, false if the event is unsupported.
    276      */
    277     public boolean postPointerEvent(MotionEvent event,
    278             int webKitXOffset, int webKitYOffset, float webKitScale) {
    279         if (event == null) {
    280             throw new IllegalArgumentException("event cannot be null");
    281         }
    282 
    283         if (DEBUG) {
    284             Log.d(TAG, "postPointerEvent: " + event);
    285         }
    286 
    287         final int action = event.getActionMasked();
    288         final int eventType;
    289         switch (action) {
    290             case MotionEvent.ACTION_DOWN:
    291             case MotionEvent.ACTION_MOVE:
    292             case MotionEvent.ACTION_UP:
    293             case MotionEvent.ACTION_POINTER_DOWN:
    294             case MotionEvent.ACTION_POINTER_UP:
    295             case MotionEvent.ACTION_CANCEL:
    296                 eventType = EVENT_TYPE_TOUCH;
    297                 break;
    298             case MotionEvent.ACTION_SCROLL:
    299                 eventType = EVENT_TYPE_SCROLL;
    300                 break;
    301             case MotionEvent.ACTION_HOVER_ENTER:
    302             case MotionEvent.ACTION_HOVER_MOVE:
    303             case MotionEvent.ACTION_HOVER_EXIT:
    304                 eventType = EVENT_TYPE_HOVER;
    305                 break;
    306             default:
    307                 return false; // currently unsupported event type
    308         }
    309 
    310         synchronized (mLock) {
    311             // Ensure that the event is consistent and should be delivered.
    312             MotionEvent eventToEnqueue = event;
    313             if (eventType == EVENT_TYPE_TOUCH) {
    314                 eventToEnqueue = mPostTouchStream.update(event);
    315                 if (eventToEnqueue == null) {
    316                     if (DEBUG) {
    317                         Log.d(TAG, "postPointerEvent: dropped event " + event);
    318                     }
    319                     unscheduleLongPressLocked();
    320                     unscheduleClickLocked();
    321                     hideTapCandidateLocked();
    322                     return false;
    323                 }
    324 
    325                 if (action == MotionEvent.ACTION_DOWN && mPostSendTouchEventsToWebKit) {
    326                     if (mUiCallbacks.shouldInterceptTouchEvent(eventToEnqueue)) {
    327                         mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
    328                     } else if (mPostDoNotSendTouchEventsToWebKitUntilNextGesture) {
    329                         // Recover from a previous web kit timeout.
    330                         mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
    331                     }
    332                 }
    333             }
    334 
    335             // Copy the event because we need to retain ownership.
    336             if (eventToEnqueue == event) {
    337                 eventToEnqueue = event.copy();
    338             }
    339 
    340             DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0,
    341                     webKitXOffset, webKitYOffset, webKitScale);
    342             updateStateTrackersLocked(d, event);
    343             enqueueEventLocked(d);
    344         }
    345         return true;
    346     }
    347 
    348     private void scheduleLongPressLocked() {
    349         unscheduleLongPressLocked();
    350         mPostLongPressScheduled = true;
    351         mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS,
    352                 LONG_PRESS_TIMEOUT);
    353     }
    354 
    355     private void unscheduleLongPressLocked() {
    356         if (mPostLongPressScheduled) {
    357             mPostLongPressScheduled = false;
    358             mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
    359         }
    360     }
    361 
    362     private void postLongPress() {
    363         synchronized (mLock) {
    364             if (!mPostLongPressScheduled) {
    365                 return;
    366             }
    367             mPostLongPressScheduled = false;
    368 
    369             MotionEvent event = mPostTouchStream.getLastEvent();
    370             if (event == null) {
    371                 return;
    372             }
    373 
    374             switch (event.getActionMasked()) {
    375                 case MotionEvent.ACTION_DOWN:
    376                 case MotionEvent.ACTION_MOVE:
    377                 case MotionEvent.ACTION_POINTER_DOWN:
    378                 case MotionEvent.ACTION_POINTER_UP:
    379                     break;
    380                 default:
    381                     return;
    382             }
    383 
    384             MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
    385             eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
    386             DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
    387                     mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
    388             enqueueEventLocked(d);
    389         }
    390     }
    391 
    392     private void hideTapCandidateLocked() {
    393         unscheduleHideTapHighlightLocked();
    394         unscheduleShowTapHighlightLocked();
    395         mUiCallbacks.showTapHighlight(false);
    396     }
    397 
    398     private void showTapCandidateLocked() {
    399         unscheduleHideTapHighlightLocked();
    400         unscheduleShowTapHighlightLocked();
    401         mUiCallbacks.showTapHighlight(true);
    402     }
    403 
    404     private void scheduleShowTapHighlightLocked() {
    405         unscheduleShowTapHighlightLocked();
    406         mPostShowTapHighlightScheduled = true;
    407         mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_SHOW_TAP_HIGHLIGHT,
    408                 TAP_TIMEOUT);
    409     }
    410 
    411     private void unscheduleShowTapHighlightLocked() {
    412         if (mPostShowTapHighlightScheduled) {
    413             mPostShowTapHighlightScheduled = false;
    414             mUiHandler.removeMessages(UiHandler.MSG_SHOW_TAP_HIGHLIGHT);
    415         }
    416     }
    417 
    418     private void scheduleHideTapHighlightLocked() {
    419         unscheduleHideTapHighlightLocked();
    420         mPostHideTapHighlightScheduled = true;
    421         mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_HIDE_TAP_HIGHLIGHT,
    422                 PRESSED_STATE_DURATION);
    423     }
    424 
    425     private void unscheduleHideTapHighlightLocked() {
    426         if (mPostHideTapHighlightScheduled) {
    427             mPostHideTapHighlightScheduled = false;
    428             mUiHandler.removeMessages(UiHandler.MSG_HIDE_TAP_HIGHLIGHT);
    429         }
    430     }
    431 
    432     private void postShowTapHighlight(boolean show) {
    433         synchronized (mLock) {
    434             if (show) {
    435                 if (!mPostShowTapHighlightScheduled) {
    436                     return;
    437                 }
    438                 mPostShowTapHighlightScheduled = false;
    439             } else {
    440                 if (!mPostHideTapHighlightScheduled) {
    441                     return;
    442                 }
    443                 mPostHideTapHighlightScheduled = false;
    444             }
    445             mUiCallbacks.showTapHighlight(show);
    446         }
    447     }
    448 
    449     private void scheduleClickLocked() {
    450         unscheduleClickLocked();
    451         mPostClickScheduled = true;
    452         mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
    453     }
    454 
    455     private void unscheduleClickLocked() {
    456         if (mPostClickScheduled) {
    457             mPostClickScheduled = false;
    458             mUiHandler.removeMessages(UiHandler.MSG_CLICK);
    459         }
    460     }
    461 
    462     private void postClick() {
    463         synchronized (mLock) {
    464             if (!mPostClickScheduled) {
    465                 return;
    466             }
    467             mPostClickScheduled = false;
    468 
    469             MotionEvent event = mPostTouchStream.getLastEvent();
    470             if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
    471                 return;
    472             }
    473 
    474             showTapCandidateLocked();
    475             MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
    476             DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
    477                     mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
    478             enqueueEventLocked(d);
    479         }
    480     }
    481 
    482     private void checkForDoubleTapOnDownLocked(MotionEvent event) {
    483         mIsDoubleTapCandidate = false;
    484         if (!mPostClickScheduled) {
    485             return;
    486         }
    487         int deltaX = (int) mInitialDownX - (int) event.getX();
    488         int deltaY = (int) mInitialDownY - (int) event.getY();
    489         if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
    490             unscheduleClickLocked();
    491             mIsDoubleTapCandidate = true;
    492         }
    493     }
    494 
    495     private boolean isClickCandidateLocked(MotionEvent event) {
    496         if (event == null
    497                 || event.getActionMasked() != MotionEvent.ACTION_UP
    498                 || !mIsTapCandidate) {
    499             return false;
    500         }
    501         long downDuration = event.getEventTime() - event.getDownTime();
    502         return downDuration < LONG_PRESS_TIMEOUT;
    503     }
    504 
    505     private void enqueueDoubleTapLocked(MotionEvent event) {
    506         MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
    507         DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
    508                 mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
    509         enqueueEventLocked(d);
    510     }
    511 
    512     private void enqueueHitTestLocked(MotionEvent event) {
    513         mUiCallbacks.clearPreviousHitTest();
    514         MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
    515         DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_HIT_TEST, 0,
    516                 mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
    517         enqueueEventLocked(d);
    518     }
    519 
    520     private void checkForSlopLocked(MotionEvent event) {
    521         if (!mIsTapCandidate) {
    522             return;
    523         }
    524         int deltaX = (int) mInitialDownX - (int) event.getX();
    525         int deltaY = (int) mInitialDownY - (int) event.getY();
    526         if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
    527             unscheduleLongPressLocked();
    528             mIsTapCandidate = false;
    529             hideTapCandidateLocked();
    530         }
    531     }
    532 
    533     private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
    534         mPostLastWebKitXOffset = d.mWebKitXOffset;
    535         mPostLastWebKitYOffset = d.mWebKitYOffset;
    536         mPostLastWebKitScale = d.mWebKitScale;
    537         int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
    538         if (d.mEventType != EVENT_TYPE_TOUCH) {
    539             return;
    540         }
    541 
    542         if (action == MotionEvent.ACTION_CANCEL
    543                 || event.getPointerCount() > 1) {
    544             unscheduleLongPressLocked();
    545             unscheduleClickLocked();
    546             hideTapCandidateLocked();
    547             mIsDoubleTapCandidate = false;
    548             mIsTapCandidate = false;
    549             hideTapCandidateLocked();
    550         } else if (action == MotionEvent.ACTION_DOWN) {
    551             checkForDoubleTapOnDownLocked(event);
    552             scheduleLongPressLocked();
    553             mIsTapCandidate = true;
    554             mInitialDownX = event.getX();
    555             mInitialDownY = event.getY();
    556             enqueueHitTestLocked(event);
    557             if (mIsDoubleTapCandidate) {
    558                 hideTapCandidateLocked();
    559             } else {
    560                 scheduleShowTapHighlightLocked();
    561             }
    562         } else if (action == MotionEvent.ACTION_UP) {
    563             unscheduleLongPressLocked();
    564             if (isClickCandidateLocked(event)) {
    565                 if (mIsDoubleTapCandidate) {
    566                     hideTapCandidateLocked();
    567                     enqueueDoubleTapLocked(event);
    568                 } else {
    569                     scheduleClickLocked();
    570                 }
    571             } else {
    572                 hideTapCandidateLocked();
    573             }
    574         } else if (action == MotionEvent.ACTION_MOVE) {
    575             checkForSlopLocked(event);
    576         }
    577     }
    578 
    579     /**
    580      * Dispatches pending web kit events.
    581      * Must only be called from the web kit thread.
    582      *
    583      * This method may be used to flush the queue of pending input events
    584      * immediately.  This method may help to reduce input dispatch latency
    585      * if called before certain expensive operations such as drawing.
    586      */
    587     public void dispatchWebKitEvents() {
    588         dispatchWebKitEvents(false);
    589     }
    590 
    591     private void dispatchWebKitEvents(boolean calledFromHandler) {
    592         for (;;) {
    593             // Get the next event, but leave it in the queue so we can move it to the UI
    594             // queue if a timeout occurs.
    595             DispatchEvent d;
    596             MotionEvent event;
    597             final int eventType;
    598             int flags;
    599             synchronized (mLock) {
    600                 if (!ENABLE_EVENT_BATCHING) {
    601                     drainStaleWebKitEventsLocked();
    602                 }
    603                 d = mWebKitDispatchEventQueue.mHead;
    604                 if (d == null) {
    605                     if (mWebKitDispatchScheduled) {
    606                         mWebKitDispatchScheduled = false;
    607                         if (!calledFromHandler) {
    608                             mWebKitHandler.removeMessages(
    609                                     WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
    610                         }
    611                     }
    612                     return;
    613                 }
    614 
    615                 event = d.mEvent;
    616                 if (event != null) {
    617                     event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
    618                     event.scale(d.mWebKitScale);
    619                     d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
    620                 }
    621 
    622                 eventType = d.mEventType;
    623                 if (eventType == EVENT_TYPE_TOUCH) {
    624                     event = mWebKitTouchStream.update(event);
    625                     if (DEBUG && event == null && d.mEvent != null) {
    626                         Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
    627                     }
    628                 }
    629 
    630                 d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
    631                 flags = d.mFlags;
    632             }
    633 
    634             // Handle the event.
    635             final boolean preventDefault;
    636             if (event == null) {
    637                 preventDefault = false;
    638             } else {
    639                 preventDefault = dispatchWebKitEvent(event, eventType, flags);
    640             }
    641 
    642             synchronized (mLock) {
    643                 flags = d.mFlags;
    644                 d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
    645                 boolean recycleEvent = event != d.mEvent;
    646 
    647                 if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
    648                     // A timeout occurred!
    649                     recycleDispatchEventLocked(d);
    650                 } else {
    651                     // Web kit finished in a timely manner.  Dequeue the event.
    652                     assert mWebKitDispatchEventQueue.mHead == d;
    653                     mWebKitDispatchEventQueue.dequeue();
    654 
    655                     updateWebKitTimeoutLocked();
    656 
    657                     if ((flags & FLAG_PRIVATE) != 0) {
    658                         // Event was intended for web kit only.  All done.
    659                         recycleDispatchEventLocked(d);
    660                     } else if (preventDefault) {
    661                         // Web kit has decided to consume the event!
    662                         if (d.mEventType == EVENT_TYPE_TOUCH) {
    663                             enqueueUiCancelTouchEventIfNeededLocked();
    664                             unscheduleLongPressLocked();
    665                         }
    666                     } else {
    667                         // Web kit is being friendly.  Pass the event to the UI.
    668                         enqueueUiEventUnbatchedLocked(d);
    669                     }
    670                 }
    671 
    672                 if (event != null && recycleEvent) {
    673                     event.recycle();
    674                 }
    675 
    676                 if (eventType == EVENT_TYPE_CLICK) {
    677                     scheduleHideTapHighlightLocked();
    678                 }
    679             }
    680         }
    681     }
    682 
    683     // Runs on web kit thread.
    684     private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
    685         if (DEBUG) {
    686             Log.d(TAG, "dispatchWebKitEvent: event=" + event
    687                     + ", eventType=" + eventType + ", flags=" + flags);
    688         }
    689         boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
    690                 this, event, eventType, flags);
    691         if (DEBUG) {
    692             Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
    693         }
    694         return preventDefault;
    695     }
    696 
    697     private boolean isMoveEventLocked(DispatchEvent d) {
    698         return d.mEvent != null
    699                 && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
    700     }
    701 
    702     private void drainStaleWebKitEventsLocked() {
    703         DispatchEvent d = mWebKitDispatchEventQueue.mHead;
    704         while (d != null && d.mNext != null
    705                 && isMoveEventLocked(d)
    706                 && isMoveEventLocked(d.mNext)) {
    707             DispatchEvent next = d.mNext;
    708             skipWebKitEventLocked(d);
    709             d = next;
    710         }
    711         mWebKitDispatchEventQueue.mHead = d;
    712     }
    713 
    714     // Called by WebKit when it doesn't care about the rest of the touch stream
    715     public void skipWebkitForRemainingTouchStream() {
    716         // Just treat this like a timeout
    717         handleWebKitTimeout();
    718     }
    719 
    720     // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
    721     private void handleWebKitTimeout() {
    722         synchronized (mLock) {
    723             if (!mWebKitTimeoutScheduled) {
    724                 return;
    725             }
    726             mWebKitTimeoutScheduled = false;
    727 
    728             if (DEBUG) {
    729                 Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
    730             }
    731 
    732             // Drain the web kit event queue.
    733             DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
    734 
    735             // If web kit was processing an event (must be at the head of the list because
    736             // it can only do one at a time), then clone it or ignore it.
    737             if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
    738                 d.mFlags |= FLAG_WEBKIT_TIMEOUT;
    739                 if ((d.mFlags & FLAG_PRIVATE) != 0) {
    740                     d = d.mNext; // the event is private to web kit, ignore it
    741                 } else {
    742                     d = copyDispatchEventLocked(d);
    743                     d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
    744                 }
    745             }
    746 
    747             // Enqueue all non-private events for handling by the UI thread.
    748             while (d != null) {
    749                 DispatchEvent next = d.mNext;
    750                 skipWebKitEventLocked(d);
    751                 d = next;
    752             }
    753 
    754             // Tell web kit to cancel all pending touches.
    755             // This also prevents us from sending web kit any more touches until the
    756             // next gesture begins.  (As required to ensure touch event stream consistency.)
    757             enqueueWebKitCancelTouchEventIfNeededLocked();
    758         }
    759     }
    760 
    761     private void skipWebKitEventLocked(DispatchEvent d) {
    762         d.mNext = null;
    763         if ((d.mFlags & FLAG_PRIVATE) != 0) {
    764             recycleDispatchEventLocked(d);
    765         } else {
    766             d.mFlags |= FLAG_WEBKIT_TIMEOUT;
    767             enqueueUiEventUnbatchedLocked(d);
    768         }
    769     }
    770 
    771     /**
    772      * Dispatches pending UI events.
    773      * Must only be called from the UI thread.
    774      *
    775      * This method may be used to flush the queue of pending input events
    776      * immediately.  This method may help to reduce input dispatch latency
    777      * if called before certain expensive operations such as drawing.
    778      */
    779     public void dispatchUiEvents() {
    780         dispatchUiEvents(false);
    781     }
    782 
    783     private void dispatchUiEvents(boolean calledFromHandler) {
    784         for (;;) {
    785             MotionEvent event;
    786             final int eventType;
    787             final int flags;
    788             synchronized (mLock) {
    789                 DispatchEvent d = mUiDispatchEventQueue.dequeue();
    790                 if (d == null) {
    791                     if (mUiDispatchScheduled) {
    792                         mUiDispatchScheduled = false;
    793                         if (!calledFromHandler) {
    794                             mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
    795                         }
    796                     }
    797                     return;
    798                 }
    799 
    800                 event = d.mEvent;
    801                 if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
    802                     event.scale(1.0f / d.mWebKitScale);
    803                     event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
    804                     d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
    805                 }
    806 
    807                 eventType = d.mEventType;
    808                 if (eventType == EVENT_TYPE_TOUCH) {
    809                     event = mUiTouchStream.update(event);
    810                     if (DEBUG && event == null && d.mEvent != null) {
    811                         Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
    812                     }
    813                 }
    814 
    815                 flags = d.mFlags;
    816 
    817                 if (event == d.mEvent) {
    818                     d.mEvent = null; // retain ownership of event, don't recycle it yet
    819                 }
    820                 recycleDispatchEventLocked(d);
    821 
    822                 if (eventType == EVENT_TYPE_CLICK) {
    823                     scheduleHideTapHighlightLocked();
    824                 }
    825             }
    826 
    827             // Handle the event.
    828             if (event != null) {
    829                 dispatchUiEvent(event, eventType, flags);
    830                 event.recycle();
    831             }
    832         }
    833     }
    834 
    835     // Runs on UI thread.
    836     private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
    837         if (DEBUG) {
    838             Log.d(TAG, "dispatchUiEvent: event=" + event
    839                     + ", eventType=" + eventType + ", flags=" + flags);
    840         }
    841         mUiCallbacks.dispatchUiEvent(event, eventType, flags);
    842     }
    843 
    844     private void enqueueEventLocked(DispatchEvent d) {
    845         if (!shouldSkipWebKit(d)) {
    846             enqueueWebKitEventLocked(d);
    847         } else {
    848             enqueueUiEventLocked(d);
    849         }
    850     }
    851 
    852     private boolean shouldSkipWebKit(DispatchEvent d) {
    853         switch (d.mEventType) {
    854             case EVENT_TYPE_CLICK:
    855             case EVENT_TYPE_HOVER:
    856             case EVENT_TYPE_SCROLL:
    857             case EVENT_TYPE_HIT_TEST:
    858                 return false;
    859             case EVENT_TYPE_TOUCH:
    860                 // TODO: This should be cleaned up. We now have WebViewInputDispatcher
    861                 // and WebViewClassic both checking for slop and doing their own
    862                 // thing - they should be consolidated. And by consolidated, I mean
    863                 // WebViewClassic's version should just be deleted.
    864                 // The reason this is done is because webpages seem to expect
    865                 // that they only get an ontouchmove if the slop has been exceeded.
    866                 if (mIsTapCandidate && d.mEvent != null
    867                         && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) {
    868                     return true;
    869                 }
    870                 return !mPostSendTouchEventsToWebKit
    871                         || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
    872         }
    873         return true;
    874     }
    875 
    876     private void enqueueWebKitCancelTouchEventIfNeededLocked() {
    877         // We want to cancel touch events that were delivered to web kit.
    878         // Enqueue a null event at the end of the queue if needed.
    879         if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
    880             DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
    881                     0, 0, 1.0f);
    882             enqueueWebKitEventUnbatchedLocked(d);
    883             mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
    884         }
    885     }
    886 
    887     private void enqueueWebKitEventLocked(DispatchEvent d) {
    888         if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
    889             if (DEBUG) {
    890                 Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
    891             }
    892             recycleDispatchEventLocked(d);
    893         } else {
    894             enqueueWebKitEventUnbatchedLocked(d);
    895         }
    896     }
    897 
    898     private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
    899         if (DEBUG) {
    900             Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
    901         }
    902         mWebKitDispatchEventQueue.enqueue(d);
    903         scheduleWebKitDispatchLocked();
    904         updateWebKitTimeoutLocked();
    905     }
    906 
    907     private void scheduleWebKitDispatchLocked() {
    908         if (!mWebKitDispatchScheduled) {
    909             mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
    910             mWebKitDispatchScheduled = true;
    911         }
    912     }
    913 
    914     private void updateWebKitTimeoutLocked() {
    915         DispatchEvent d = mWebKitDispatchEventQueue.mHead;
    916         if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
    917             return;
    918         }
    919         if (mWebKitTimeoutScheduled) {
    920             mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
    921             mWebKitTimeoutScheduled = false;
    922         }
    923         if (d != null) {
    924             mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
    925             mWebKitTimeoutScheduled = true;
    926             mWebKitTimeoutTime = d.mTimeoutTime;
    927         }
    928     }
    929 
    930     private void enqueueUiCancelTouchEventIfNeededLocked() {
    931         // We want to cancel touch events that were delivered to the UI.
    932         // Enqueue a null event at the end of the queue if needed.
    933         if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
    934             DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
    935                     0, 0, 1.0f);
    936             enqueueUiEventUnbatchedLocked(d);
    937         }
    938     }
    939 
    940     private void enqueueUiEventLocked(DispatchEvent d) {
    941         if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
    942             if (DEBUG) {
    943                 Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
    944             }
    945             recycleDispatchEventLocked(d);
    946         } else {
    947             enqueueUiEventUnbatchedLocked(d);
    948         }
    949     }
    950 
    951     private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
    952         if (DEBUG) {
    953             Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
    954         }
    955         mUiDispatchEventQueue.enqueue(d);
    956         scheduleUiDispatchLocked();
    957     }
    958 
    959     private void scheduleUiDispatchLocked() {
    960         if (!mUiDispatchScheduled) {
    961             mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
    962             mUiDispatchScheduled = true;
    963         }
    964     }
    965 
    966     private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
    967         if (!ENABLE_EVENT_BATCHING) {
    968             return false;
    969         }
    970         if (tail != null && tail.mEvent != null && in.mEvent != null
    971                 && in.mEventType == tail.mEventType
    972                 && in.mFlags == tail.mFlags
    973                 && in.mWebKitXOffset == tail.mWebKitXOffset
    974                 && in.mWebKitYOffset == tail.mWebKitYOffset
    975                 && in.mWebKitScale == tail.mWebKitScale) {
    976             return tail.mEvent.addBatch(in.mEvent);
    977         }
    978         return false;
    979     }
    980 
    981     private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
    982             int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
    983         DispatchEvent d = obtainUninitializedDispatchEventLocked();
    984         d.mEvent = event;
    985         d.mEventType = eventType;
    986         d.mFlags = flags;
    987         d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
    988         d.mWebKitXOffset = webKitXOffset;
    989         d.mWebKitYOffset = webKitYOffset;
    990         d.mWebKitScale = webKitScale;
    991         if (DEBUG) {
    992             Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
    993         }
    994         return d;
    995     }
    996 
    997     private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
    998         DispatchEvent copy = obtainUninitializedDispatchEventLocked();
    999         if (d.mEvent != null) {
   1000             copy.mEvent = d.mEvent.copy();
   1001         }
   1002         copy.mEventType = d.mEventType;
   1003         copy.mFlags = d.mFlags;
   1004         copy.mTimeoutTime = d.mTimeoutTime;
   1005         copy.mWebKitXOffset = d.mWebKitXOffset;
   1006         copy.mWebKitYOffset = d.mWebKitYOffset;
   1007         copy.mWebKitScale = d.mWebKitScale;
   1008         copy.mNext = d.mNext;
   1009         return copy;
   1010     }
   1011 
   1012     private DispatchEvent obtainUninitializedDispatchEventLocked() {
   1013         DispatchEvent d = mDispatchEventPool;
   1014         if (d != null) {
   1015             mDispatchEventPoolSize -= 1;
   1016             mDispatchEventPool = d.mNext;
   1017             d.mNext = null;
   1018         } else {
   1019             d = new DispatchEvent();
   1020         }
   1021         return d;
   1022     }
   1023 
   1024     private void recycleDispatchEventLocked(DispatchEvent d) {
   1025         if (d.mEvent != null) {
   1026             d.mEvent.recycle();
   1027             d.mEvent = null;
   1028         }
   1029 
   1030         if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
   1031             mDispatchEventPoolSize += 1;
   1032             d.mNext = mDispatchEventPool;
   1033             mDispatchEventPool = d;
   1034         }
   1035     }
   1036 
   1037     /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
   1038     public static interface UiCallbacks {
   1039         /**
   1040          * Gets the UI thread's looper.
   1041          * @return The looper.
   1042          */
   1043         public Looper getUiLooper();
   1044 
   1045         /**
   1046          * Gets the UI's context
   1047          * @return The context
   1048          */
   1049         public Context getContext();
   1050 
   1051         /**
   1052          * Dispatches an event to the UI.
   1053          * @param event The event.
   1054          * @param eventType The event type.
   1055          * @param flags The event's dispatch flags.
   1056          */
   1057         public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
   1058 
   1059         /**
   1060          * Asks the UI thread whether this touch event stream should be
   1061          * intercepted based on the touch down event.
   1062          * @param event The touch down event.
   1063          * @return true if the UI stream wants the touch stream without going
   1064          * through webkit or false otherwise.
   1065          */
   1066         public boolean shouldInterceptTouchEvent(MotionEvent event);
   1067 
   1068         /**
   1069          * Inform's the UI that it should show the tap highlight
   1070          * @param show True if it should show the highlight, false if it should hide it
   1071          */
   1072         public void showTapHighlight(boolean show);
   1073 
   1074         /**
   1075          * Called when we are sending a new EVENT_TYPE_HIT_TEST to WebKit, so
   1076          * previous hit tests should be cleared as they are obsolete.
   1077          */
   1078         public void clearPreviousHitTest();
   1079     }
   1080 
   1081     /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
   1082     public static interface WebKitCallbacks {
   1083         /**
   1084          * Gets the web kit thread's looper.
   1085          * @return The looper.
   1086          */
   1087         public Looper getWebKitLooper();
   1088 
   1089         /**
   1090          * Dispatches an event to web kit.
   1091          * @param dispatcher The WebViewInputDispatcher sending the event
   1092          * @param event The event.
   1093          * @param eventType The event type.
   1094          * @param flags The event's dispatch flags.
   1095          * @return True if web kit wants to prevent default event handling.
   1096          */
   1097         public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher,
   1098                 MotionEvent event, int eventType, int flags);
   1099     }
   1100 
   1101     // Runs on UI thread.
   1102     private final class UiHandler extends Handler {
   1103         public static final int MSG_DISPATCH_UI_EVENTS = 1;
   1104         public static final int MSG_WEBKIT_TIMEOUT = 2;
   1105         public static final int MSG_LONG_PRESS = 3;
   1106         public static final int MSG_CLICK = 4;
   1107         public static final int MSG_SHOW_TAP_HIGHLIGHT = 5;
   1108         public static final int MSG_HIDE_TAP_HIGHLIGHT = 6;
   1109 
   1110         public UiHandler(Looper looper) {
   1111             super(looper);
   1112         }
   1113 
   1114         @Override
   1115         public void handleMessage(Message msg) {
   1116             switch (msg.what) {
   1117                 case MSG_DISPATCH_UI_EVENTS:
   1118                     dispatchUiEvents(true);
   1119                     break;
   1120                 case MSG_WEBKIT_TIMEOUT:
   1121                     handleWebKitTimeout();
   1122                     break;
   1123                 case MSG_LONG_PRESS:
   1124                     postLongPress();
   1125                     break;
   1126                 case MSG_CLICK:
   1127                     postClick();
   1128                     break;
   1129                 case MSG_SHOW_TAP_HIGHLIGHT:
   1130                     postShowTapHighlight(true);
   1131                     break;
   1132                 case MSG_HIDE_TAP_HIGHLIGHT:
   1133                     postShowTapHighlight(false);
   1134                     break;
   1135                 default:
   1136                     throw new IllegalStateException("Unknown message type: " + msg.what);
   1137             }
   1138         }
   1139     }
   1140 
   1141     // Runs on web kit thread.
   1142     private final class WebKitHandler extends Handler {
   1143         public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
   1144 
   1145         public WebKitHandler(Looper looper) {
   1146             super(looper);
   1147         }
   1148 
   1149         @Override
   1150         public void handleMessage(Message msg) {
   1151             switch (msg.what) {
   1152                 case MSG_DISPATCH_WEBKIT_EVENTS:
   1153                     dispatchWebKitEvents(true);
   1154                     break;
   1155                 default:
   1156                     throw new IllegalStateException("Unknown message type: " + msg.what);
   1157             }
   1158         }
   1159     }
   1160 
   1161     private static final class DispatchEvent {
   1162         public DispatchEvent mNext;
   1163 
   1164         public MotionEvent mEvent;
   1165         public int mEventType;
   1166         public int mFlags;
   1167         public long mTimeoutTime;
   1168         public int mWebKitXOffset;
   1169         public int mWebKitYOffset;
   1170         public float mWebKitScale;
   1171     }
   1172 
   1173     private static final class DispatchEventQueue {
   1174         public DispatchEvent mHead;
   1175         public DispatchEvent mTail;
   1176 
   1177         public boolean isEmpty() {
   1178             return mHead != null;
   1179         }
   1180 
   1181         public void enqueue(DispatchEvent d) {
   1182             if (mHead == null) {
   1183                 mHead = d;
   1184                 mTail = d;
   1185             } else {
   1186                 mTail.mNext = d;
   1187                 mTail = d;
   1188             }
   1189         }
   1190 
   1191         public DispatchEvent dequeue() {
   1192             DispatchEvent d = mHead;
   1193             if (d != null) {
   1194                 DispatchEvent next = d.mNext;
   1195                 if (next == null) {
   1196                     mHead = null;
   1197                     mTail = null;
   1198                 } else {
   1199                     mHead = next;
   1200                     d.mNext = null;
   1201                 }
   1202             }
   1203             return d;
   1204         }
   1205 
   1206         public DispatchEvent dequeueList() {
   1207             DispatchEvent d = mHead;
   1208             if (d != null) {
   1209                 mHead = null;
   1210                 mTail = null;
   1211             }
   1212             return d;
   1213         }
   1214     }
   1215 
   1216     /**
   1217      * Keeps track of a stream of touch events so that we can discard touch
   1218      * events that would make the stream inconsistent.
   1219      */
   1220     private static final class TouchStream {
   1221         private MotionEvent mLastEvent;
   1222 
   1223         /**
   1224          * Gets the last touch event that was delivered.
   1225          * @return The last touch event, or null if none.
   1226          */
   1227         public MotionEvent getLastEvent() {
   1228             return mLastEvent;
   1229         }
   1230 
   1231         /**
   1232          * Updates the touch event stream.
   1233          * @param event The event that we intend to send, or null to cancel the
   1234          * touch event stream.
   1235          * @return The event that we should actually send, or null if no event should
   1236          * be sent because the proposed event would make the stream inconsistent.
   1237          */
   1238         public MotionEvent update(MotionEvent event) {
   1239             if (event == null) {
   1240                 if (isCancelNeeded()) {
   1241                     event = mLastEvent;
   1242                     if (event != null) {
   1243                         event.setAction(MotionEvent.ACTION_CANCEL);
   1244                         mLastEvent = null;
   1245                     }
   1246                 }
   1247                 return event;
   1248             }
   1249 
   1250             switch (event.getActionMasked()) {
   1251                 case MotionEvent.ACTION_MOVE:
   1252                 case MotionEvent.ACTION_UP:
   1253                 case MotionEvent.ACTION_POINTER_DOWN:
   1254                 case MotionEvent.ACTION_POINTER_UP:
   1255                     if (mLastEvent == null
   1256                             || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
   1257                         return null;
   1258                     }
   1259                     updateLastEvent(event);
   1260                     return event;
   1261 
   1262                 case MotionEvent.ACTION_DOWN:
   1263                     updateLastEvent(event);
   1264                     return event;
   1265 
   1266                 case MotionEvent.ACTION_CANCEL:
   1267                     if (mLastEvent == null) {
   1268                         return null;
   1269                     }
   1270                     updateLastEvent(null);
   1271                     return event;
   1272 
   1273                 default:
   1274                     return null;
   1275             }
   1276         }
   1277 
   1278         /**
   1279          * Returns true if there is a gesture in progress that may need to be canceled.
   1280          * @return True if cancel is needed.
   1281          */
   1282         public boolean isCancelNeeded() {
   1283             return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
   1284         }
   1285 
   1286         private void updateLastEvent(MotionEvent event) {
   1287             if (mLastEvent != null) {
   1288                 mLastEvent.recycle();
   1289             }
   1290             mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
   1291         }
   1292     }
   1293 }