예제 #1
0
  /**
   * 描述:TODO
   *
   * @see android.view.View#onKeyUp(int, android.view.KeyEvent)
   * @author: zhaoqp
   * @date:2013-11-28 上午11:14:35
   * @version v1.0
   */
  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    switch (keyCode) {
      case KeyEvent.KEYCODE_DPAD_CENTER:
      case KeyEvent.KEYCODE_ENTER:
        {
          if (mReceivedInvokeKeyDown) {
            if (mItemCount > 0) {

              dispatchPress(mSelectedChild);
              postDelayed(
                  new Runnable() {
                    public void run() {
                      dispatchUnpress();
                    }
                  },
                  ViewConfiguration.getPressedStateDuration());

              int selectedIndex = mSelectedPosition - mFirstPosition;
              performItemClick(
                  getChildAt(selectedIndex),
                  mSelectedPosition,
                  mAdapter.getItemId(mSelectedPosition));
            }
          }

          // Clear the flag
          mReceivedInvokeKeyDown = false;

          return true;
        }
    }

    return super.onKeyUp(keyCode, event);
  }
예제 #2
0
 public boolean dispatchTouchEvent(MotionEvent paramMotionEvent) {
   switch (paramMotionEvent.getAction()) {
     case 2:
     default:
     case 0:
     case 1:
     case 3:
     case 4:
   }
   while (true) {
     return super.dispatchTouchEvent(paramMotionEvent);
     if (this.mHighlighEnabled) {
       startDelayTimer();
       continue;
       if (this.mHighlighEnabled) removeDelayTimer();
       if (this.mPressed) {
         this.mPressed = false;
         invalidate();
       } else if (this.mHighlighEnabled) {
         if (this.mUnsetPressedState == null)
           this.mUnsetPressedState = new UnsetPressedState(null);
         this.mPressed = true;
         invalidate();
         this.mPressDelayHandler.postDelayed(
             this.mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
         continue;
         if (this.mHighlighEnabled) removeDelayTimer();
         if (this.mPressed) {
           this.mPressed = false;
           invalidate();
         }
       }
     }
   }
 }
예제 #3
0
  private void initView(Context context) {
    mPaint = new Paint();
    mPaint.setColor(Color.WHITE);
    Resources resources = context.getResources();

    // get viewConfiguration
    mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

    // get Bitmap
    mBottom = BitmapFactory.decodeResource(resources, R.drawable.bottom);
    mBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.btn_pressed);
    mBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.btn_unpressed);
    mFrame = BitmapFactory.decodeResource(resources, R.drawable.frame);
    mMask = BitmapFactory.decodeResource(resources, R.drawable.mask);
    mCurBtnPic = mBtnNormal;

    mBtnWidth = mBtnPressed.getWidth();
    mMaskWidth = mMask.getWidth();
    mMaskHeight = mMask.getHeight();

    mBtnOffPos = mBtnWidth / 2;
    mBtnOnPos = mMaskWidth - mBtnWidth / 2;

    mBtnPos = mChecked ? mBtnOnPos : mBtnOffPos;
    mRealPos = getRealPos(mBtnPos);

    final float density = getResources().getDisplayMetrics().density;
    mVelocity = (int) (VELOCITY * density + 0.5f);
    mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f);

    mSaveLayerRectF =
        new RectF(0, mExtendOffsetY, mMask.getWidth(), mMask.getHeight() + mExtendOffsetY);
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
  }
  @Override
  public boolean onTouchEvent(@NonNull MotionEvent event) {
    // Stop receiving touch events if we aren't within the bounds, including some slop.
    final int x = (int) event.getX();
    final int y = (int) event.getY();
    if (!pointInCloseBounds(x, y, mTouchSlop)) {
      setClosePressed(false);
      super.onTouchEvent(event);
      return false;
    }

    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        setClosePressed(true);
        break;
      case MotionEvent.ACTION_CANCEL:
        // Cancelled by a parent
        setClosePressed(false);
        break;
      case MotionEvent.ACTION_UP:
        if (isClosePressed()) {
          // Delay setting the unpressed state so that the button remains pressed
          // at least long enough to respond to the close event.
          if (mUnsetPressedState == null) {
            mUnsetPressedState = new UnsetPressedState();
          }
          postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
          performClose();
        }
        break;
    }
    return true;
  }
예제 #5
0
  private void init(Context context, AttributeSet attrs) {
    mPaint = new Paint();
    mPaint.setColor(Color.WHITE);
    Resources resources = context.getResources();

    // get attrConfiguration
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);
    int width = (int) array.getDimensionPixelSize(R.styleable.SwitchButton_bmWidth, 0);
    int height = (int) array.getDimensionPixelSize(R.styleable.SwitchButton_bmHeight, 0);
    array.recycle();

    // size width or height
    if (width <= 0 || height <= 0) {
      width = COMMON_WIDTH_IN_PIXEL;
      height = COMMON_HEIGHT_IN_PIXEL;
    } else {
      float scale = (float) COMMON_WIDTH_IN_PIXEL / COMMON_HEIGHT_IN_PIXEL;
      if ((float) width / height > scale) {
        width = (int) (height * scale);
      } else if ((float) width / height < scale) {
        height = (int) (width / scale);
      }
    }

    // get viewConfiguration
    mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

    // get Bitmap
    bmBgGreen = BitmapFactory.decodeResource(resources, R.drawable.switch_btn_bg_green);
    bmBgWhite = BitmapFactory.decodeResource(resources, R.drawable.switch_btn_bg_white);
    bmBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.switch_btn_normal);
    bmBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.switch_btn_pressed);

    // size Bitmap
    bmBgGreen = Bitmap.createScaledBitmap(bmBgGreen, width, height, true);
    bmBgWhite = Bitmap.createScaledBitmap(bmBgWhite, width, height, true);
    bmBtnNormal = Bitmap.createScaledBitmap(bmBtnNormal, height, height, true);
    bmBtnPressed = Bitmap.createScaledBitmap(bmBtnPressed, height, height, true);

    bmCurBtnPic = bmBtnNormal; // 初始按钮图片
    bmCurBgPic = mChecked ? bmBgGreen : bmBgWhite; // 初始背景图片
    bgWidth = bmBgGreen.getWidth(); // 背景宽度
    bgHeight = bmBgGreen.getHeight(); // 背景高度
    btnWidth = bmBtnNormal.getWidth(); // 按钮宽度
    offBtnPos = 0; // 关闭时在最左边
    onBtnPos = bgWidth - btnWidth; // 开始时在右边
    curBtnPos = mChecked ? onBtnPos : offBtnPos; // 按钮当前为初始位置

    // get density
    float density = resources.getDisplayMetrics().density;
    mVelocity = (int) (VELOCITY * density + 0.5f); // 动画距离
    mSaveLayerRectF = new RectF(0, 0, bgWidth, bgHeight);
  }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
   boolean superOnTouchEvent = super.onTouchEvent(event);
   if (!isEnabled()) return superOnTouchEvent;
   boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());
   if (isEventInBounds) {
     previousCoords.set(currentCoords.x, currentCoords.y);
     currentCoords.set((int) event.getX(), (int) event.getY());
   }
   int action = event.getActionMasked();
   switch (action) {
     case MotionEvent.ACTION_UP:
       if (pressed) {
         childView.setPressed(true);
         postDelayed(
             new Runnable() {
               @Override
               public void run() {
                 childView.setPressed(false);
               }
             },
             ViewConfiguration.getPressedStateDuration());
       }
       cancelPressedEvent();
       break;
     case MotionEvent.ACTION_CANCEL:
       //                currentCoords.set();
       childView.onTouchEvent(event);
       if (!pressed) startRipple();
       cancelPressedEvent();
       break;
     case MotionEvent.ACTION_DOWN:
       eventCancelled = false;
       pressEvent = new PressEvent(event);
       pressEvent.run();
       /** *这里注意当这个控件是在ScrollView内部的时候的问题 ** */
       break;
     case MotionEvent.ACTION_MOVE:
       if (isEventInBounds && !eventCancelled) invalidate();
       else if (!isEventInBounds) startRipple();
       if (!isEventInBounds) {
         cancelPressedEvent();
         if (hoverAnimator != null) hoverAnimator.cancel();
         childView.onTouchEvent(event);
         eventCancelled = true;
       }
       break;
   }
   return true;
 }
예제 #7
0
  private void initView(Context context) {
    mPaint = new Paint();
    mPaint.setColor(Color.WHITE);
    Resources resources = context.getResources();

    // get viewConfiguration
    mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

    // get Bitmap
    mBottom = BitmapFactory.decodeResource(resources, R.drawable.kaiguan);
    mBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.yuan);
    mBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.yuan);
    mFrame = BitmapFactory.decodeResource(resources, R.drawable.guan);
    mMask = BitmapFactory.decodeResource(resources, R.drawable.kai);
    mCurBtnPic = mBtnNormal;

    mBtnWidth = mBtnPressed.getWidth();
    mMaskWidth = mMask.getWidth();
    mMaskHeight = mMask.getHeight();

    mBtnOffPos = mBtnWidth / 2;
    mBtnOnPos = mMaskWidth - mBtnWidth / 2;
    // 判断起始位置,如果设定了mChecked为true,起始位置为 mBtnOnPos
    mBtnPos = mChecked ? mBtnOnPos : mBtnOffPos;
    mRealPos = getRealPos(mBtnPos);
    // density 密度
    final float density = getResources().getDisplayMetrics().density; // 方法是获取资源密度(Density)
    mVelocity = (int) (VELOCITY * density + 0.5f);
    mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f);
    // 创建一个新的矩形与指定的坐标。
    mSaveLayerRectF =
        new RectF(0, mExtendOffsetY, mMask.getWidth(), mMask.getHeight() + mExtendOffsetY);
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); // PorterDuff.Mode.SRC_IN
    // :这个属性代表
    // 取两层绘制交集。显示上层。
  }
  @Test
  public void methodsShouldReturnAndroidConstants() {
    Activity context = new Activity();
    ViewConfiguration viewConfiguration = ViewConfiguration.get(context);

    assertEquals(10, ViewConfiguration.getScrollBarSize());
    assertEquals(250, ViewConfiguration.getScrollBarFadeDuration());
    assertEquals(300, ViewConfiguration.getScrollDefaultDelay());
    assertEquals(12, ViewConfiguration.getFadingEdgeLength());
    assertEquals(125, ViewConfiguration.getPressedStateDuration());
    assertEquals(500, ViewConfiguration.getLongPressTimeout());
    assertEquals(115, ViewConfiguration.getTapTimeout());
    assertEquals(500, ViewConfiguration.getJumpTapTimeout());
    assertEquals(300, ViewConfiguration.getDoubleTapTimeout());
    assertEquals(12, ViewConfiguration.getEdgeSlop());
    assertEquals(16, ViewConfiguration.getTouchSlop());
    assertEquals(16, ViewConfiguration.getWindowTouchSlop());
    assertEquals(50, ViewConfiguration.getMinimumFlingVelocity());
    assertEquals(4000, ViewConfiguration.getMaximumFlingVelocity());
    assertEquals(320 * 480 * 4, ViewConfiguration.getMaximumDrawingCacheSize());
    assertEquals(3000, ViewConfiguration.getZoomControlsTimeout());
    assertEquals(500, ViewConfiguration.getGlobalActionKeyTimeout());
    assertEquals(0.015f, ViewConfiguration.getScrollFriction());

    assertEquals(1f, context.getResources().getDisplayMetrics().density);

    assertEquals(10, viewConfiguration.getScaledScrollBarSize());
    assertEquals(12, viewConfiguration.getScaledFadingEdgeLength());
    assertEquals(12, viewConfiguration.getScaledEdgeSlop());
    assertEquals(16, viewConfiguration.getScaledTouchSlop());
    assertEquals(32, viewConfiguration.getScaledPagingTouchSlop());
    assertEquals(100, viewConfiguration.getScaledDoubleTapSlop());
    assertEquals(16, viewConfiguration.getScaledWindowTouchSlop());
    assertEquals(50, viewConfiguration.getScaledMinimumFlingVelocity());
    assertEquals(4000, viewConfiguration.getScaledMaximumFlingVelocity());
  }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    boolean wasHeaderChildBeingPressed = mHeaderChildBeingPressed;
    if (mHeaderChildBeingPressed) {
      final View tempHeader = getHeaderAt(mMotionHeaderPosition);
      final View headerHolder =
          mMotionHeaderPosition == MATCHED_STICKIED_HEADER
              ? tempHeader
              : getChildAt(mMotionHeaderPosition);
      if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        mHeaderChildBeingPressed = false;
      }
      if (tempHeader != null) {
        tempHeader.dispatchTouchEvent(transformEvent(ev, mMotionHeaderPosition));
        tempHeader.invalidate();
        tempHeader.postDelayed(
            new Runnable() {
              public void run() {
                invalidate(
                    0,
                    headerHolder.getTop(),
                    getWidth(),
                    headerHolder.getTop() + headerHolder.getHeight());
              }
            },
            ViewConfiguration.getPressedStateDuration());
        invalidate(
            0, headerHolder.getTop(), getWidth(), headerHolder.getTop() + headerHolder.getHeight());
      }
    }

    switch (action & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN:
        if (mPendingCheckForTap == null) {
          mPendingCheckForTap = new CheckForHeaderTap();
        }
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

        final int y = (int) ev.getY();
        mMotionY = y;
        mMotionHeaderPosition = findMotionHeader(y);
        if (mMotionHeaderPosition == NO_MATCHED_HEADER || mScrollState == SCROLL_STATE_FLING) {
          // Don't consume the event and pass it to super because we
          // can't handle it yet.
          break;
        } else {
          View tempHeader = getHeaderAt(mMotionHeaderPosition);
          if (tempHeader != null) {
            if (tempHeader.dispatchTouchEvent(transformEvent(ev, mMotionHeaderPosition))) {
              mHeaderChildBeingPressed = true;
              tempHeader.setPressed(true);
            }
            tempHeader.invalidate();
            if (mMotionHeaderPosition != MATCHED_STICKIED_HEADER) {
              tempHeader = getChildAt(mMotionHeaderPosition);
            }
            invalidate(
                0, tempHeader.getTop(), getWidth(), tempHeader.getTop() + tempHeader.getHeight());
          }
        }
        mTouchMode = TOUCH_MODE_DOWN;
        return true;
      case MotionEvent.ACTION_MOVE:
        if (mMotionHeaderPosition != NO_MATCHED_HEADER
            && Math.abs(ev.getY() - mMotionY) > mTouchSlop) {
          // Detected scroll initiation so cancel touch completion on
          // header.
          mTouchMode = TOUCH_MODE_REST;
          // if (!mHeaderChildBeingPressed) {
          final View header = getHeaderAt(mMotionHeaderPosition);
          if (header != null) {
            header.setPressed(false);
            header.invalidate();
          }
          final Handler handler = getHandler();
          if (handler != null) {
            handler.removeCallbacks(mPendingCheckForLongPress);
          }
          mMotionHeaderPosition = NO_MATCHED_HEADER;
          // }
        }
        break;
      case MotionEvent.ACTION_UP:
        if (mTouchMode == TOUCH_MODE_FINISHED_LONG_PRESS) {
          mTouchMode = TOUCH_MODE_REST;
          return true;
        }
        if (mTouchMode == TOUCH_MODE_REST || mMotionHeaderPosition == NO_MATCHED_HEADER) {
          break;
        }

        final View header = getHeaderAt(mMotionHeaderPosition);
        if (!wasHeaderChildBeingPressed) {
          if (header != null) {
            if (mTouchMode != TOUCH_MODE_DOWN) {
              header.setPressed(false);
            }

            if (mPerformHeaderClick == null) {
              mPerformHeaderClick = new PerformHeaderClick();
            }

            final PerformHeaderClick performHeaderClick = mPerformHeaderClick;
            performHeaderClick.mClickMotionPosition = mMotionHeaderPosition;
            performHeaderClick.rememberWindowAttachCount();

            if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
              final Handler handler = getHandler();
              if (handler != null) {
                handler.removeCallbacks(
                    mTouchMode == TOUCH_MODE_DOWN
                        ? mPendingCheckForTap
                        : mPendingCheckForLongPress);
              }

              if (!mDataChanged) {
                /*
                 * Got here so must be a tap. The long press
                 * would have triggered on the callback handler.
                 */
                mTouchMode = TOUCH_MODE_TAP;
                header.setPressed(true);
                setPressed(true);
                if (mTouchModeReset != null) {
                  removeCallbacks(mTouchModeReset);
                }
                mTouchModeReset =
                    new Runnable() {
                      @Override
                      public void run() {
                        mMotionHeaderPosition = NO_MATCHED_HEADER;
                        mTouchModeReset = null;
                        mTouchMode = TOUCH_MODE_REST;
                        header.setPressed(false);
                        setPressed(false);
                        header.invalidate();
                        invalidate(0, header.getTop(), getWidth(), header.getHeight());
                        if (!mDataChanged) {
                          performHeaderClick.run();
                        }
                      }
                    };
                postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration());
              } else {
                mTouchMode = TOUCH_MODE_REST;
              }
            } else if (!mDataChanged) {
              performHeaderClick.run();
            }
          }
        }
        mTouchMode = TOUCH_MODE_REST;
        return true;
    }
    return super.onTouchEvent(ev);
  }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN:
        if (mPendingCheckForTap == null) {
          mPendingCheckForTap = new CheckForHeaderTap();
        }
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

        final int y = (int) ev.getY();
        mMotionY = y;
        mMotionHeaderPosition = findMotionHeader(y);
        if (mMotionHeaderPosition == NO_MATCHED_HEADER || mScrollState == SCROLL_STATE_FLING) {
          // Don't consume the event and pass it to super because we
          // can't handle it yet.
          break;
        }
        mTouchMode = TOUCH_MODE_DOWN;
        return true;
      case MotionEvent.ACTION_MOVE:
        if (mMotionHeaderPosition != NO_MATCHED_HEADER
            && Math.abs(ev.getY() - mMotionY) > mTouchSlop) {
          // Detected scroll initiation so cancel touch completion on
          // header.
          mTouchMode = TOUCH_MODE_REST;
          final View header = getHeaderAt(mMotionHeaderPosition);
          if (header != null) {
            header.setPressed(false);
          }
          final Handler handler = getHandler();
          if (handler != null) {
            handler.removeCallbacks(mPendingCheckForLongPress);
          }
          mMotionHeaderPosition = NO_MATCHED_HEADER;
        }
        break;
      case MotionEvent.ACTION_UP:
        if (mTouchMode == TOUCH_MODE_FINISHED_LONG_PRESS) {
          return true;
        }
        if (mTouchMode == TOUCH_MODE_REST || mMotionHeaderPosition == NO_MATCHED_HEADER) {
          break;
        }

        final View header = getHeaderAt(mMotionHeaderPosition);
        if (header != null && !header.hasFocusable()) {
          if (mTouchMode != TOUCH_MODE_DOWN) {
            header.setPressed(false);
          }

          if (mPerformHeaderClick == null) {
            mPerformHeaderClick = new PerformHeaderClick();
          }

          final PerformHeaderClick performHeaderClick = mPerformHeaderClick;
          performHeaderClick.mClickMotionPosition = mMotionHeaderPosition;
          performHeaderClick.rememberWindowAttachCount();

          if (mTouchMode != TOUCH_MODE_DOWN || mTouchMode != TOUCH_MODE_TAP) {
            final Handler handler = getHandler();
            if (handler != null) {
              handler.removeCallbacks(
                  mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress);
            }

            if (!mDataChanged) {
              // Got here so must be a tap. The long press would
              // have trigger on the callback handler. Probably.
              mTouchMode = TOUCH_MODE_TAP;
              header.setPressed(true);
              setPressed(true);
              if (mTouchModeReset != null) {
                removeCallbacks(mTouchModeReset);
              }
              mTouchModeReset =
                  new Runnable() {
                    @Override
                    public void run() {
                      mTouchMode = TOUCH_MODE_REST;
                      header.setPressed(false);
                      setPressed(false);
                      if (!mDataChanged) {
                        performHeaderClick.run();
                      }
                    }
                  };
              postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration());
            } else {
              mTouchMode = TOUCH_MODE_REST;
            }
          } else if (!mDataChanged) {
            performHeaderClick.run();
          }
        }
        mTouchMode = TOUCH_MODE_REST;
        return true;
    }
    return super.onTouchEvent(ev);
  }
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    boolean superOnTouchEvent = super.onTouchEvent(event);

    if (!isEnabled() || !childView.isEnabled()) return superOnTouchEvent;

    boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());

    if (isEventInBounds) {
      previousCoords.set(currentCoords.x, currentCoords.y);
      currentCoords.set((int) event.getX(), (int) event.getY());
    }

    boolean gestureResult = gestureDetector.onTouchEvent(event);
    if (gestureResult || mHasPerformedLongPress) {
      return true;
    } else {
      int action = event.getActionMasked();
      switch (action) {
        case MotionEvent.ACTION_UP:
          pendingClickEvent = new PerformClickEvent();

          if (prePressed) {
            childView.setPressed(true);
            postDelayed(
                new Runnable() {
                  @Override
                  public void run() {
                    childView.setPressed(false);
                  }
                },
                ViewConfiguration.getPressedStateDuration());
          }

          if (isEventInBounds) {
            startRipple(pendingClickEvent);
          } else if (!rippleHover) {
            setRadius(0);
          }
          if (!rippleDelayClick && isEventInBounds) {
            pendingClickEvent.run();
          }
          cancelPressedEvent();
          break;
        case MotionEvent.ACTION_DOWN:
          setPositionInAdapter();
          eventCancelled = false;
          pendingPressEvent = new PressedEvent(event);
          if (isInScrollingContainer()) {
            cancelPressedEvent();
            prePressed = true;
            postDelayed(pendingPressEvent, ViewConfiguration.getTapTimeout());
          } else {
            pendingPressEvent.run();
          }
          break;
        case MotionEvent.ACTION_CANCEL:
          if (rippleInAdapter) {
            // dont use current coords in adapter since they tend to jump drastically on scroll
            currentCoords.set(previousCoords.x, previousCoords.y);
            previousCoords = new Point();
          }
          childView.onTouchEvent(event);
          if (rippleHover) {
            if (!prePressed) {
              startRipple(null);
            }
          } else {
            childView.setPressed(false);
          }
          cancelPressedEvent();
          break;
        case MotionEvent.ACTION_MOVE:
          if (rippleHover) {
            if (isEventInBounds && !eventCancelled) {
              invalidate();
            } else if (!isEventInBounds) {
              startRipple(null);
            }
          }

          if (!isEventInBounds) {
            cancelPressedEvent();
            if (hoverAnimator != null) {
              hoverAnimator.cancel();
            }
            childView.onTouchEvent(event);
            eventCancelled = true;
          }
          break;
      }
      return true;
    }
  }
예제 #12
0
/**
 * Perform asynchronous dispatch of input events in a {@link WebView}.
 *
 * <p>This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit thread ({@link
 * WebViewCore}). The UI thread enqueues events for processing, waits for the web kit thread to
 * handle them, and then performs additional processing depending on the outcome.
 *
 * <p>How it works:
 *
 * <p>1. The web view thread receives an input event from the input system on the UI thread in its
 * {@link WebViewClassic#onTouchEvent} handler. It sends the input event to the dispatcher, then
 * immediately returns true to the input system to indicate that it will handle the event.
 *
 * <p>2. The web kit thread is notified that an event has been enqueued. Meanwhile additional events
 * may be enqueued from the UI thread. In some cases, the dispatcher may decide to coalesce motion
 * events into larger batches or to cancel events that have been sitting in the queue for too long.
 *
 * <p>3. The web kit thread wakes up and handles all input events that are waiting for it. After
 * processing each input event, it informs the dispatcher whether the web application has decided to
 * handle the event itself and to prevent default event handling.
 *
 * <p>4. If web kit indicates that it wants to prevent default event handling, then web kit consumes
 * the remainder of the gesture and web view receives a cancel event if needed. Otherwise, the web
 * view handles the gesture on the UI thread normally.
 *
 * <p>5. If the web kit thread takes too long to handle an input event, then it loses the right to
 * handle it. The dispatcher synthesizes a cancellation event for web kit and then tells the web
 * view on the UI thread to handle the event that timed out along with the rest of the gesture.
 *
 * <p>One thing to keep in mind about the dispatcher is that what goes into the dispatcher is not
 * necessarily what the web kit or UI thread will see. As mentioned above, the dispatcher may tweak
 * the input event stream to improve responsiveness. Both web view and web kit are guaranteed to
 * perceive a consistent stream of input events but they might not always see the same events
 * (especially if one decides to prevent the other from handling a particular gesture).
 *
 * <p>This implementation very deliberately does not refer to the {@link WebViewClassic} or {@link
 * WebViewCore} classes, preferring to communicate with them only via interfaces to avoid
 * unintentional coupling to their implementation details.
 *
 * <p>Currently, the input dispatcher only handles pointer events (includes touch, hover and scroll
 * events). In principle, it could be extended to handle trackball and key events if needed.
 *
 * @hide
 */
final class WebViewInputDispatcher {
  private static final String TAG = "WebViewInputDispatcher";
  private static final boolean DEBUG = false;
  // This enables batching of MotionEvents. It will combine multiple MotionEvents
  // together into a single MotionEvent if more events come in while we are
  // still waiting on the processing of a previous event.
  // If this is set to false, we will instead opt to drop ACTION_MOVE
  // events we cannot keep up with.
  // TODO: If batching proves to be working well, remove this
  private static final boolean ENABLE_EVENT_BATCHING = true;

  private final Object mLock = new Object();

  // Pool of queued input events.  (guarded by mLock)
  private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
  private DispatchEvent mDispatchEventPool;
  private int mDispatchEventPoolSize;

  // Posted state, tracks events posted to the dispatcher.  (guarded by mLock)
  private final TouchStream mPostTouchStream = new TouchStream();
  private boolean mPostSendTouchEventsToWebKit;
  private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
  private boolean mPostLongPressScheduled;
  private boolean mPostClickScheduled;
  private boolean mPostShowTapHighlightScheduled;
  private boolean mPostHideTapHighlightScheduled;
  private int mPostLastWebKitXOffset;
  private int mPostLastWebKitYOffset;
  private float mPostLastWebKitScale;

  // State for event tracking (click, longpress, double tap, etc..)
  private boolean mIsDoubleTapCandidate;
  private boolean mIsTapCandidate;
  private float mInitialDownX;
  private float mInitialDownY;
  private float mTouchSlopSquared;
  private float mDoubleTapSlopSquared;

  // Web kit state, tracks events observed by web kit.  (guarded by mLock)
  private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
  private final TouchStream mWebKitTouchStream = new TouchStream();
  private final WebKitCallbacks mWebKitCallbacks;
  private final WebKitHandler mWebKitHandler;
  private boolean mWebKitDispatchScheduled;
  private boolean mWebKitTimeoutScheduled;
  private long mWebKitTimeoutTime;
  /// M: Web kit state, tracks events prevent by web kit.
  private final PreventTouchStream mWebKitPreventTouchStream = new PreventTouchStream();

  // UI state, tracks events observed by the UI.  (guarded by mLock)
  private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
  private final TouchStream mUiTouchStream = new TouchStream();
  private final UiCallbacks mUiCallbacks;
  private final UiHandler mUiHandler;
  private boolean mUiDispatchScheduled;

  // Give up on web kit handling of input events when this timeout expires.
  private static final long WEBKIT_TIMEOUT_MILLIS = 200;
  private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
  private static final int LONG_PRESS_TIMEOUT =
      ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
  private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
  private static final int PRESSED_STATE_DURATION = ViewConfiguration.getPressedStateDuration();

  /**
   * Event type: Indicates a touch event type.
   *
   * <p>This event is delivered together with a {@link MotionEvent} with one of the following
   * actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE}, {@link
   * MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN}, {@link
   * MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
   */
  public static final int EVENT_TYPE_TOUCH = 0;

  /**
   * Event type: Indicates a hover event type.
   *
   * <p>This event is delivered together with a {@link MotionEvent} with one of the following
   * actions: {@link MotionEvent#ACTION_HOVER_ENTER}, {@link MotionEvent#ACTION_HOVER_MOVE}, {@link
   * MotionEvent#ACTION_HOVER_MOVE}.
   */
  public static final int EVENT_TYPE_HOVER = 1;

  /**
   * Event type: Indicates a scroll event type.
   *
   * <p>This event is delivered together with a {@link MotionEvent} with action {@link
   * MotionEvent#ACTION_SCROLL}.
   */
  public static final int EVENT_TYPE_SCROLL = 2;

  /**
   * Event type: Indicates a long-press event type.
   *
   * <p>This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events. It
   * includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE} that indicates the
   * current touch coordinates of the long-press.
   *
   * <p>This event is sent when the current touch gesture has been held longer than the long-press
   * interval.
   */
  public static final int EVENT_TYPE_LONG_PRESS = 3;

  /**
   * Event type: Indicates a click event type.
   *
   * <p>This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that comprise a
   * complete gesture ending with {@link MotionEvent#ACTION_UP}. It includes a {@link MotionEvent}
   * with action {@link MotionEvent#ACTION_UP} that indicates the location of the click.
   *
   * <p>This event is sent shortly after the end of a touch after the double-tap interval has
   * expired to indicate a click.
   */
  public static final int EVENT_TYPE_CLICK = 4;

  /**
   * Event type: Indicates a double-tap event type.
   *
   * <p>This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that comprise a
   * complete gesture ending with {@link MotionEvent#ACTION_UP}. It includes a {@link MotionEvent}
   * with action {@link MotionEvent#ACTION_UP} that indicates the location of the double-tap.
   *
   * <p>This event is sent immediately after a sequence of two touches separated in time by no more
   * than the double-tap interval and separated in space by no more than the double-tap slop.
   */
  public static final int EVENT_TYPE_DOUBLE_TAP = 5;

  /** Event type: Indicates that a hit test should be performed */
  public static final int EVENT_TYPE_HIT_TEST = 6;

  /** Flag: This event is private to this queue. Do not forward it. */
  public static final int FLAG_PRIVATE = 1 << 0;

  /**
   * Flag: This event is currently being processed by web kit. If a timeout occurs, make a copy of
   * it before forwarding the event to another queue.
   */
  public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;

  /** Flag: A timeout occurred while waiting for web kit to process this input event. */
  public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;

  /**
   * Flag: Indicates that the event was transformed for delivery to web kit. The event must be
   * transformed back before being delivered to the UI.
   */
  public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;

  public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
    this.mUiCallbacks = uiCallbacks;
    mUiHandler = new UiHandler(uiCallbacks.getUiLooper());

    this.mWebKitCallbacks = webKitCallbacks;
    mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());

    ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
    mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
    mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
    mTouchSlopSquared = config.getScaledTouchSlop();
    mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
  }

  /**
   * Sets whether web kit wants to receive touch events.
   *
   * @param enable True to enable dispatching of touch events to web kit, otherwise web kit will be
   *     skipped.
   */
  public void setWebKitWantsTouchEvents(boolean enable) {
    if (DEBUG) {
      Log.d(TAG, "webkitWantsTouchEvents: " + enable);
    }
    synchronized (mLock) {
      if (mPostSendTouchEventsToWebKit != enable) {
        if (!enable) {
          enqueueWebKitCancelTouchEventIfNeededLocked();
        }
        mPostSendTouchEventsToWebKit = enable;
      }
    }
  }

  /**
   * Posts a pointer event to the dispatch queue.
   *
   * @param event The event to post.
   * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
   * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
   * @param webKitScale The scale factor to apply to translated events before dispatching them to
   *     web kit.
   * @return True if the dispatcher will handle the event, false if the event is unsupported.
   */
  public boolean postPointerEvent(
      MotionEvent event, int webKitXOffset, int webKitYOffset, float webKitScale) {
    if (event == null) {
      throw new IllegalArgumentException("event cannot be null");
    }

    if (DEBUG) {
      Log.d(TAG, "postPointerEvent: " + event);
    }

    final int action = event.getActionMasked();
    final int eventType;
    switch (action) {
      case MotionEvent.ACTION_DOWN:
      case MotionEvent.ACTION_MOVE:
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_POINTER_DOWN:
      case MotionEvent.ACTION_POINTER_UP:
      case MotionEvent.ACTION_CANCEL:
        eventType = EVENT_TYPE_TOUCH;
        break;
      case MotionEvent.ACTION_SCROLL:
        eventType = EVENT_TYPE_SCROLL;
        break;
      case MotionEvent.ACTION_HOVER_ENTER:
      case MotionEvent.ACTION_HOVER_MOVE:
      case MotionEvent.ACTION_HOVER_EXIT:
        eventType = EVENT_TYPE_HOVER;
        break;
      default:
        return false; // currently unsupported event type
    }

    synchronized (mLock) {
      // Ensure that the event is consistent and should be delivered.
      MotionEvent eventToEnqueue = event;
      if (eventType == EVENT_TYPE_TOUCH) {
        eventToEnqueue = mPostTouchStream.update(event);
        if (eventToEnqueue == null) {
          if (DEBUG) {
            Log.d(TAG, "postPointerEvent: dropped event " + event);
          }
          unscheduleLongPressLocked();
          unscheduleClickLocked();
          hideTapCandidateLocked();
          return false;
        }

        if (action == MotionEvent.ACTION_DOWN && mPostSendTouchEventsToWebKit) {
          if (mUiCallbacks.shouldInterceptTouchEvent(eventToEnqueue)) {
            mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
          } else if (mPostDoNotSendTouchEventsToWebKitUntilNextGesture) {
            // Recover from a previous web kit timeout.
            mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
          }
        }
      }

      // Copy the event because we need to retain ownership.
      if (eventToEnqueue == event) {
        eventToEnqueue = event.copy();
      }

      DispatchEvent d =
          obtainDispatchEventLocked(
              eventToEnqueue, eventType, 0, webKitXOffset, webKitYOffset, webKitScale);
      updateStateTrackersLocked(d, event);
      enqueueEventLocked(d);
    }
    return true;
  }

  private void scheduleLongPressLocked() {
    unscheduleLongPressLocked();
    mPostLongPressScheduled = true;
    mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS, LONG_PRESS_TIMEOUT);
  }

  private void unscheduleLongPressLocked() {
    if (mPostLongPressScheduled) {
      mPostLongPressScheduled = false;
      mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
    }
  }

  private void postLongPress() {
    synchronized (mLock) {
      if (!mPostLongPressScheduled) {
        return;
      }
      mPostLongPressScheduled = false;

      MotionEvent event = mPostTouchStream.getLastEvent();
      if (event == null) {
        return;
      }

      switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_POINTER_DOWN:
        case MotionEvent.ACTION_POINTER_UP:
          break;
        default:
          return;
      }

      MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
      eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
      DispatchEvent d =
          obtainDispatchEventLocked(
              eventToEnqueue,
              EVENT_TYPE_LONG_PRESS,
              0,
              mPostLastWebKitXOffset,
              mPostLastWebKitYOffset,
              mPostLastWebKitScale);
      enqueueEventLocked(d);
    }
  }

  private void hideTapCandidateLocked() {
    unscheduleHideTapHighlightLocked();
    unscheduleShowTapHighlightLocked();
    mUiCallbacks.showTapHighlight(false);
  }

  private void showTapCandidateLocked() {
    unscheduleHideTapHighlightLocked();
    unscheduleShowTapHighlightLocked();
    mUiCallbacks.showTapHighlight(true);
  }

  private void scheduleShowTapHighlightLocked() {
    unscheduleShowTapHighlightLocked();
    mPostShowTapHighlightScheduled = true;
    mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_SHOW_TAP_HIGHLIGHT, TAP_TIMEOUT);
  }

  private void unscheduleShowTapHighlightLocked() {
    if (mPostShowTapHighlightScheduled) {
      mPostShowTapHighlightScheduled = false;
      mUiHandler.removeMessages(UiHandler.MSG_SHOW_TAP_HIGHLIGHT);
    }
  }

  private void scheduleHideTapHighlightLocked() {
    unscheduleHideTapHighlightLocked();
    mPostHideTapHighlightScheduled = true;
    mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_HIDE_TAP_HIGHLIGHT, PRESSED_STATE_DURATION);
  }

  private void unscheduleHideTapHighlightLocked() {
    if (mPostHideTapHighlightScheduled) {
      mPostHideTapHighlightScheduled = false;
      mUiHandler.removeMessages(UiHandler.MSG_HIDE_TAP_HIGHLIGHT);
    }
  }

  private void postShowTapHighlight(boolean show) {
    synchronized (mLock) {
      if (show) {
        if (!mPostShowTapHighlightScheduled) {
          return;
        }
        mPostShowTapHighlightScheduled = false;
      } else {
        if (!mPostHideTapHighlightScheduled) {
          return;
        }
        mPostHideTapHighlightScheduled = false;
      }
      mUiCallbacks.showTapHighlight(show);
    }
  }

  private void scheduleClickLocked() {
    unscheduleClickLocked();
    mPostClickScheduled = true;
    mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
  }

  private void unscheduleClickLocked() {
    if (mPostClickScheduled) {
      mPostClickScheduled = false;
      mUiHandler.removeMessages(UiHandler.MSG_CLICK);
    }
  }

  private void postClick() {
    synchronized (mLock) {
      if (!mPostClickScheduled) {
        return;
      }
      mPostClickScheduled = false;

      MotionEvent event = mPostTouchStream.getLastEvent();
      if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
        return;
      }

      showTapCandidateLocked();
      MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
      DispatchEvent d =
          obtainDispatchEventLocked(
              eventToEnqueue,
              EVENT_TYPE_CLICK,
              0,
              mPostLastWebKitXOffset,
              mPostLastWebKitYOffset,
              mPostLastWebKitScale);
      enqueueEventLocked(d);
    }
  }

  private void checkForDoubleTapOnDownLocked(MotionEvent event) {
    mIsDoubleTapCandidate = false;
    if (!mPostClickScheduled) {
      return;
    }
    int deltaX = (int) mInitialDownX - (int) event.getX();
    int deltaY = (int) mInitialDownY - (int) event.getY();
    if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
      unscheduleClickLocked();
      mIsDoubleTapCandidate = true;
    }
  }

  private boolean isClickCandidateLocked(MotionEvent event) {
    if (event == null || event.getActionMasked() != MotionEvent.ACTION_UP || !mIsTapCandidate) {
      return false;
    }
    long downDuration = event.getEventTime() - event.getDownTime();
    return downDuration < LONG_PRESS_TIMEOUT;
  }

  private void enqueueDoubleTapLocked(MotionEvent event) {
    MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
    DispatchEvent d =
        obtainDispatchEventLocked(
            eventToEnqueue,
            EVENT_TYPE_DOUBLE_TAP,
            0,
            mPostLastWebKitXOffset,
            mPostLastWebKitYOffset,
            mPostLastWebKitScale);
    enqueueEventLocked(d);
  }

  private void enqueueHitTestLocked(MotionEvent event) {
    mUiCallbacks.clearPreviousHitTest();
    MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
    DispatchEvent d =
        obtainDispatchEventLocked(
            eventToEnqueue,
            EVENT_TYPE_HIT_TEST,
            0,
            mPostLastWebKitXOffset,
            mPostLastWebKitYOffset,
            mPostLastWebKitScale);
    enqueueEventLocked(d);
  }

  private void checkForSlopLocked(MotionEvent event) {
    if (!mIsTapCandidate) {
      return;
    }
    int deltaX = (int) mInitialDownX - (int) event.getX();
    int deltaY = (int) mInitialDownY - (int) event.getY();
    if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
      unscheduleLongPressLocked();
      mIsTapCandidate = false;
      hideTapCandidateLocked();
    }
  }

  private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
    mPostLastWebKitXOffset = d.mWebKitXOffset;
    mPostLastWebKitYOffset = d.mWebKitYOffset;
    mPostLastWebKitScale = d.mWebKitScale;
    int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
    if (d.mEventType != EVENT_TYPE_TOUCH) {
      return;
    }

    if (action == MotionEvent.ACTION_CANCEL || event.getPointerCount() > 1) {
      unscheduleLongPressLocked();
      unscheduleClickLocked();
      hideTapCandidateLocked();
      mIsDoubleTapCandidate = false;
      mIsTapCandidate = false;
      hideTapCandidateLocked();
    } else if (action == MotionEvent.ACTION_DOWN) {
      checkForDoubleTapOnDownLocked(event);
      scheduleLongPressLocked();
      mIsTapCandidate = true;
      mInitialDownX = event.getX();
      mInitialDownY = event.getY();
      enqueueHitTestLocked(event);
      if (mIsDoubleTapCandidate) {
        hideTapCandidateLocked();
      } else {
        scheduleShowTapHighlightLocked();
      }
    } else if (action == MotionEvent.ACTION_UP) {
      unscheduleLongPressLocked();
      if (isClickCandidateLocked(event)) {
        if (mIsDoubleTapCandidate) {
          hideTapCandidateLocked();
          enqueueDoubleTapLocked(event);
        } else {
          scheduleClickLocked();
        }
      } else {
        hideTapCandidateLocked();
      }
    } else if (action == MotionEvent.ACTION_MOVE) {
      checkForSlopLocked(event);
    }
  }

  /**
   * Dispatches pending web kit events. Must only be called from the web kit thread.
   *
   * <p>This method may be used to flush the queue of pending input events immediately. This method
   * may help to reduce input dispatch latency if called before certain expensive operations such as
   * drawing.
   */
  public void dispatchWebKitEvents() {
    dispatchWebKitEvents(false);
  }

  private void dispatchWebKitEvents(boolean calledFromHandler) {
    for (; ; ) {
      // Get the next event, but leave it in the queue so we can move it to the UI
      // queue if a timeout occurs.
      DispatchEvent d;
      MotionEvent event;
      final int eventType;
      int flags;
      synchronized (mLock) {
        if (!ENABLE_EVENT_BATCHING) {
          drainStaleWebKitEventsLocked();
        }
        d = mWebKitDispatchEventQueue.mHead;
        if (d == null) {
          if (mWebKitDispatchScheduled) {
            mWebKitDispatchScheduled = false;
            if (!calledFromHandler) {
              mWebKitHandler.removeMessages(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
            }
          }
          return;
        }

        event = d.mEvent;
        if (event != null) {
          event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
          event.scale(d.mWebKitScale);
          d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
        }

        eventType = d.mEventType;
        if (eventType == EVENT_TYPE_TOUCH) {
          event = mWebKitTouchStream.update(event);
          if (DEBUG && event == null && d.mEvent != null) {
            Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
          }
        }

        d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
        flags = d.mFlags;
      }

      // Handle the event.
      final boolean preventDefault;
      if (event == null) {
        preventDefault = false;
      } else {
        preventDefault = dispatchWebKitEvent(event, eventType, flags);
      }

      synchronized (mLock) {
        /// M: update last event prevent status. @ {
        boolean lastPreventDefault = false;
        if (d.mEventType == EVENT_TYPE_TOUCH) {
          lastPreventDefault = mWebKitPreventTouchStream.update(event, preventDefault);
        }
        /// @ }
        flags = d.mFlags;
        d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
        boolean recycleEvent = event != d.mEvent;

        if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
          // A timeout occurred!
          recycleDispatchEventLocked(d);
        } else {
          // Web kit finished in a timely manner.  Dequeue the event.
          assert mWebKitDispatchEventQueue.mHead == d;
          mWebKitDispatchEventQueue.dequeue();

          updateWebKitTimeoutLocked();

          if ((flags & FLAG_PRIVATE) != 0) {
            // Event was intended for web kit only.  All done.
            recycleDispatchEventLocked(d);
          } else if (preventDefault) {
            // Web kit has decided to consume the event!
            if (d.mEventType == EVENT_TYPE_TOUCH) {
              /// M: Consider two consistent events is preventDefault. @ {
              if (lastPreventDefault) {
                Log.d(TAG, "Webkit prevent current and last event, cancel ui event");
                enqueueUiCancelTouchEventIfNeededLocked();
              } else {
                Log.d(TAG, "Webkit prevent current but not last event, enqueue ui event");
                /// Only one event is preventDefault, Ui also handle this touch event.
                enqueueUiEventUnbatchedLocked(d);
              }
              unscheduleLongPressLocked();
              /// @ }
            }
          } else {
            // Web kit is being friendly.  Pass the event to the UI.
            enqueueUiEventUnbatchedLocked(d);
          }
        }

        if (event != null && recycleEvent) {
          event.recycle();
        }

        if (eventType == EVENT_TYPE_CLICK) {
          scheduleHideTapHighlightLocked();
        }
      }
    }
  }

  // Runs on web kit thread.
  private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
    if (DEBUG) {
      Log.d(
          TAG,
          "dispatchWebKitEvent: event=" + event + ", eventType=" + eventType + ", flags=" + flags);
    }
    boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(this, event, eventType, flags);
    if (DEBUG) {
      Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
    }
    return preventDefault;
  }

  private boolean isMoveEventLocked(DispatchEvent d) {
    return d.mEvent != null && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
  }

  private void drainStaleWebKitEventsLocked() {
    DispatchEvent d = mWebKitDispatchEventQueue.mHead;
    while (d != null && d.mNext != null && isMoveEventLocked(d) && isMoveEventLocked(d.mNext)) {
      DispatchEvent next = d.mNext;
      skipWebKitEventLocked(d);
      d = next;
    }
    mWebKitDispatchEventQueue.mHead = d;
  }

  // Called by WebKit when it doesn't care about the rest of the touch stream
  public void skipWebkitForRemainingTouchStream() {
    // Just treat this like a timeout
    handleWebKitTimeout();
  }

  // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
  private void handleWebKitTimeout() {
    synchronized (mLock) {
      if (!mWebKitTimeoutScheduled) {
        return;
      }
      mWebKitTimeoutScheduled = false;

      if (DEBUG) {
        Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
      }

      // Drain the web kit event queue.
      DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();

      // If web kit was processing an event (must be at the head of the list because
      // it can only do one at a time), then clone it or ignore it.
      if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
        d.mFlags |= FLAG_WEBKIT_TIMEOUT;
        if ((d.mFlags & FLAG_PRIVATE) != 0) {
          d = d.mNext; // the event is private to web kit, ignore it
        } else {
          d = copyDispatchEventLocked(d);
          d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
        }
      }

      // Enqueue all non-private events for handling by the UI thread.
      while (d != null) {
        DispatchEvent next = d.mNext;
        skipWebKitEventLocked(d);
        d = next;
      }

      // Tell web kit to cancel all pending touches.
      // This also prevents us from sending web kit any more touches until the
      // next gesture begins.  (As required to ensure touch event stream consistency.)
      enqueueWebKitCancelTouchEventIfNeededLocked();
    }
  }

  private void skipWebKitEventLocked(DispatchEvent d) {
    d.mNext = null;
    if ((d.mFlags & FLAG_PRIVATE) != 0) {
      recycleDispatchEventLocked(d);
    } else {
      d.mFlags |= FLAG_WEBKIT_TIMEOUT;
      enqueueUiEventUnbatchedLocked(d);
    }
  }

  /**
   * Dispatches pending UI events. Must only be called from the UI thread.
   *
   * <p>This method may be used to flush the queue of pending input events immediately. This method
   * may help to reduce input dispatch latency if called before certain expensive operations such as
   * drawing.
   */
  public void dispatchUiEvents() {
    dispatchUiEvents(false);
  }

  private void dispatchUiEvents(boolean calledFromHandler) {
    for (; ; ) {
      MotionEvent event;
      final int eventType;
      final int flags;
      synchronized (mLock) {
        DispatchEvent d = mUiDispatchEventQueue.dequeue();
        if (d == null) {
          if (mUiDispatchScheduled) {
            mUiDispatchScheduled = false;
            if (!calledFromHandler) {
              mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
            }
          }
          return;
        }

        event = d.mEvent;
        if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
          event.scale(1.0f / d.mWebKitScale);
          event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
          d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
        }

        eventType = d.mEventType;
        if (eventType == EVENT_TYPE_TOUCH) {
          event = mUiTouchStream.update(event);
          if (DEBUG && event == null && d.mEvent != null) {
            Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
          }
        }

        flags = d.mFlags;

        if (event == d.mEvent) {
          d.mEvent = null; // retain ownership of event, don't recycle it yet
        }
        recycleDispatchEventLocked(d);

        if (eventType == EVENT_TYPE_CLICK) {
          scheduleHideTapHighlightLocked();
        }
      }

      // Handle the event.
      if (event != null) {
        dispatchUiEvent(event, eventType, flags);
        event.recycle();
      }
    }
  }

  // Runs on UI thread.
  private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
    if (DEBUG) {
      Log.d(
          TAG, "dispatchUiEvent: event=" + event + ", eventType=" + eventType + ", flags=" + flags);
    }
    mUiCallbacks.dispatchUiEvent(event, eventType, flags);
  }

  private void enqueueEventLocked(DispatchEvent d) {
    if (!shouldSkipWebKit(d)) {
      enqueueWebKitEventLocked(d);
    } else {
      enqueueUiEventLocked(d);
    }
  }

  private boolean shouldSkipWebKit(DispatchEvent d) {
    switch (d.mEventType) {
      case EVENT_TYPE_CLICK:
      case EVENT_TYPE_HOVER:
      case EVENT_TYPE_SCROLL:
      case EVENT_TYPE_HIT_TEST:
        return false;
      case EVENT_TYPE_TOUCH:
        // TODO: This should be cleaned up. We now have WebViewInputDispatcher
        // and WebViewClassic both checking for slop and doing their own
        // thing - they should be consolidated. And by consolidated, I mean
        // WebViewClassic's version should just be deleted.
        // The reason this is done is because webpages seem to expect
        // that they only get an ontouchmove if the slop has been exceeded.
        if (mIsTapCandidate
            && d.mEvent != null
            && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) {
          return true;
        }
        return !mPostSendTouchEventsToWebKit || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
    }
    return true;
  }

  private void enqueueWebKitCancelTouchEventIfNeededLocked() {
    // We want to cancel touch events that were delivered to web kit.
    // Enqueue a null event at the end of the queue if needed.
    if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
      DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE, 0, 0, 1.0f);
      enqueueWebKitEventUnbatchedLocked(d);
      mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
    }
  }

  private void enqueueWebKitEventLocked(DispatchEvent d) {
    if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
      if (DEBUG) {
        Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
      }
      recycleDispatchEventLocked(d);
    } else {
      enqueueWebKitEventUnbatchedLocked(d);
    }
  }

  private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
    if (DEBUG) {
      Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
    }
    mWebKitDispatchEventQueue.enqueue(d);
    scheduleWebKitDispatchLocked();
    updateWebKitTimeoutLocked();
  }

  private void scheduleWebKitDispatchLocked() {
    if (!mWebKitDispatchScheduled) {
      mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
      mWebKitDispatchScheduled = true;
    }
  }

  private void updateWebKitTimeoutLocked() {
    DispatchEvent d = mWebKitDispatchEventQueue.mHead;
    if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
      return;
    }
    if (mWebKitTimeoutScheduled) {
      mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
      mWebKitTimeoutScheduled = false;
    }
    if (d != null) {
      mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
      mWebKitTimeoutScheduled = true;
      mWebKitTimeoutTime = d.mTimeoutTime;
    }
  }

  private void enqueueUiCancelTouchEventIfNeededLocked() {
    // We want to cancel touch events that were delivered to the UI.
    // Enqueue a null event at the end of the queue if needed.
    if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
      DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE, 0, 0, 1.0f);
      enqueueUiEventUnbatchedLocked(d);
    }
  }

  private void enqueueUiEventLocked(DispatchEvent d) {
    if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
      if (DEBUG) {
        Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
      }
      recycleDispatchEventLocked(d);
    } else {
      enqueueUiEventUnbatchedLocked(d);
    }
  }

  private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
    if (DEBUG) {
      Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
    }
    mUiDispatchEventQueue.enqueue(d);
    scheduleUiDispatchLocked();
  }

  private void scheduleUiDispatchLocked() {
    if (!mUiDispatchScheduled) {
      mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
      mUiDispatchScheduled = true;
    }
  }

  private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
    if (!ENABLE_EVENT_BATCHING) {
      return false;
    }
    if (tail != null
        && tail.mEvent != null
        && in.mEvent != null
        && in.mEventType == tail.mEventType
        && in.mFlags == tail.mFlags
        && in.mWebKitXOffset == tail.mWebKitXOffset
        && in.mWebKitYOffset == tail.mWebKitYOffset
        && in.mWebKitScale == tail.mWebKitScale) {
      return tail.mEvent.addBatch(in.mEvent);
    }
    return false;
  }

  private DispatchEvent obtainDispatchEventLocked(
      MotionEvent event,
      int eventType,
      int flags,
      int webKitXOffset,
      int webKitYOffset,
      float webKitScale) {
    DispatchEvent d = obtainUninitializedDispatchEventLocked();
    d.mEvent = event;
    d.mEventType = eventType;
    d.mFlags = flags;
    d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
    d.mWebKitXOffset = webKitXOffset;
    d.mWebKitYOffset = webKitYOffset;
    d.mWebKitScale = webKitScale;
    if (DEBUG) {
      Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
    }
    return d;
  }

  private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
    DispatchEvent copy = obtainUninitializedDispatchEventLocked();
    if (d.mEvent != null) {
      copy.mEvent = d.mEvent.copy();
    }
    copy.mEventType = d.mEventType;
    copy.mFlags = d.mFlags;
    copy.mTimeoutTime = d.mTimeoutTime;
    copy.mWebKitXOffset = d.mWebKitXOffset;
    copy.mWebKitYOffset = d.mWebKitYOffset;
    copy.mWebKitScale = d.mWebKitScale;
    copy.mNext = d.mNext;
    return copy;
  }

  private DispatchEvent obtainUninitializedDispatchEventLocked() {
    DispatchEvent d = mDispatchEventPool;
    if (d != null) {
      mDispatchEventPoolSize -= 1;
      mDispatchEventPool = d.mNext;
      d.mNext = null;
    } else {
      d = new DispatchEvent();
    }
    return d;
  }

  private void recycleDispatchEventLocked(DispatchEvent d) {
    if (d.mEvent != null) {
      d.mEvent.recycle();
      d.mEvent = null;
    }

    if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
      mDispatchEventPoolSize += 1;
      d.mNext = mDispatchEventPool;
      mDispatchEventPool = d;
    }
  }

  /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
  public static interface UiCallbacks {
    /**
     * Gets the UI thread's looper.
     *
     * @return The looper.
     */
    public Looper getUiLooper();

    /**
     * Gets the UI's context
     *
     * @return The context
     */
    public Context getContext();

    /**
     * Dispatches an event to the UI.
     *
     * @param event The event.
     * @param eventType The event type.
     * @param flags The event's dispatch flags.
     */
    public void dispatchUiEvent(MotionEvent event, int eventType, int flags);

    /**
     * Asks the UI thread whether this touch event stream should be intercepted based on the touch
     * down event.
     *
     * @param event The touch down event.
     * @return true if the UI stream wants the touch stream without going through webkit or false
     *     otherwise.
     */
    public boolean shouldInterceptTouchEvent(MotionEvent event);

    /**
     * Inform's the UI that it should show the tap highlight
     *
     * @param show True if it should show the highlight, false if it should hide it
     */
    public void showTapHighlight(boolean show);

    /**
     * Called when we are sending a new EVENT_TYPE_HIT_TEST to WebKit, so previous hit tests should
     * be cleared as they are obsolete.
     */
    public void clearPreviousHitTest();
  }

  /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
  public static interface WebKitCallbacks {
    /**
     * Gets the web kit thread's looper.
     *
     * @return The looper.
     */
    public Looper getWebKitLooper();

    /**
     * Dispatches an event to web kit.
     *
     * @param dispatcher The WebViewInputDispatcher sending the event
     * @param event The event.
     * @param eventType The event type.
     * @param flags The event's dispatch flags.
     * @return True if web kit wants to prevent default event handling.
     */
    public boolean dispatchWebKitEvent(
        WebViewInputDispatcher dispatcher, MotionEvent event, int eventType, int flags);
  }

  // Runs on UI thread.
  private final class UiHandler extends Handler {
    public static final int MSG_DISPATCH_UI_EVENTS = 1;
    public static final int MSG_WEBKIT_TIMEOUT = 2;
    public static final int MSG_LONG_PRESS = 3;
    public static final int MSG_CLICK = 4;
    public static final int MSG_SHOW_TAP_HIGHLIGHT = 5;
    public static final int MSG_HIDE_TAP_HIGHLIGHT = 6;

    public UiHandler(Looper looper) {
      super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
        case MSG_DISPATCH_UI_EVENTS:
          dispatchUiEvents(true);
          break;
        case MSG_WEBKIT_TIMEOUT:
          handleWebKitTimeout();
          break;
        case MSG_LONG_PRESS:
          postLongPress();
          break;
        case MSG_CLICK:
          postClick();
          break;
        case MSG_SHOW_TAP_HIGHLIGHT:
          postShowTapHighlight(true);
          break;
        case MSG_HIDE_TAP_HIGHLIGHT:
          postShowTapHighlight(false);
          break;
        default:
          throw new IllegalStateException("Unknown message type: " + msg.what);
      }
    }
  }

  // Runs on web kit thread.
  private final class WebKitHandler extends Handler {
    public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;

    public WebKitHandler(Looper looper) {
      super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
        case MSG_DISPATCH_WEBKIT_EVENTS:
          dispatchWebKitEvents(true);
          break;
        default:
          throw new IllegalStateException("Unknown message type: " + msg.what);
      }
    }
  }

  private static final class DispatchEvent {
    public DispatchEvent mNext;

    public MotionEvent mEvent;
    public int mEventType;
    public int mFlags;
    public long mTimeoutTime;
    public int mWebKitXOffset;
    public int mWebKitYOffset;
    public float mWebKitScale;
  }

  private static final class DispatchEventQueue {
    public DispatchEvent mHead;
    public DispatchEvent mTail;

    public boolean isEmpty() {
      return mHead != null;
    }

    public void enqueue(DispatchEvent d) {
      if (mHead == null) {
        mHead = d;
        mTail = d;
      } else {
        mTail.mNext = d;
        mTail = d;
      }
    }

    public DispatchEvent dequeue() {
      DispatchEvent d = mHead;
      if (d != null) {
        DispatchEvent next = d.mNext;
        if (next == null) {
          mHead = null;
          mTail = null;
        } else {
          mHead = next;
          d.mNext = null;
        }
      }
      return d;
    }

    public DispatchEvent dequeueList() {
      DispatchEvent d = mHead;
      if (d != null) {
        mHead = null;
        mTail = null;
      }
      return d;
    }
  }

  /**
   * Keeps track of a stream of touch events so that we can discard touch events that would make the
   * stream inconsistent.
   */
  private static final class TouchStream {
    private MotionEvent mLastEvent;

    /**
     * Gets the last touch event that was delivered.
     *
     * @return The last touch event, or null if none.
     */
    public MotionEvent getLastEvent() {
      return mLastEvent;
    }

    /**
     * Updates the touch event stream.
     *
     * @param event The event that we intend to send, or null to cancel the touch event stream.
     * @return The event that we should actually send, or null if no event should be sent because
     *     the proposed event would make the stream inconsistent.
     */
    public MotionEvent update(MotionEvent event) {
      if (event == null) {
        if (isCancelNeeded()) {
          event = mLastEvent;
          if (event != null) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            mLastEvent = null;
          }
        }
        return event;
      }

      switch (event.getActionMasked()) {
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_DOWN:
        case MotionEvent.ACTION_POINTER_UP:
          if (mLastEvent == null || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
            return null;
          }
          updateLastEvent(event);
          return event;

        case MotionEvent.ACTION_DOWN:
          updateLastEvent(event);
          return event;

        case MotionEvent.ACTION_CANCEL:
          if (mLastEvent == null) {
            return null;
          }
          updateLastEvent(null);
          return event;

        default:
          return null;
      }
    }

    /**
     * Returns true if there is a gesture in progress that may need to be canceled.
     *
     * @return True if cancel is needed.
     */
    public boolean isCancelNeeded() {
      return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
    }

    private void updateLastEvent(MotionEvent event) {
      if (mLastEvent != null) {
        mLastEvent.recycle();
      }
      mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
    }
  }

  /**
   * M: Keeps track of a stream of touch events so that we can prevent Ui to handle events that
   * previous event is also prevent.
   */
  private static final class PreventTouchStream {
    private boolean mLastPreventDefault;

    public boolean getLastPreventDefault() {
      return mLastPreventDefault;
    }
    /**
     * Update the prevent touch stream.
     *
     * @param event The event that webkit had handled, or null to drop it.
     * @param preventDefault Prevent Ui to handle this event or not.
     * @return true If last event if prevented or false if not..
     */
    public boolean update(MotionEvent event, boolean preventDefault) {
      // Don't prevent null event.
      if (event == null) {
        Log.e(TAG, "PreventTouchStream has null event.");
        return false;
      }

      if (DEBUG) {
        Log.d(TAG, "event action=" + event.getActionMasked() + " preventDefault=" + preventDefault);
      }

      boolean prevent = mLastPreventDefault;
      mLastPreventDefault = preventDefault;
      switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_POINTER_DOWN:
        case MotionEvent.ACTION_POINTER_UP:
          return prevent;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
          mLastPreventDefault = false;
          return prevent;
        default:
          return preventDefault;
      }
    }
  }
}
 @Override
 public boolean onTouchEvent(MotionEvent ev) {
     int action = ev.getAction();
     boolean handled = false;
     switch (action) {
         case MotionEvent.ACTION_DOWN: {
             View v = getCurrentView();
             if (v != null) {
                 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
                     if (mPendingCheckForTap == null) {
                         mPendingCheckForTap = new CheckForTap();
                     }
                     mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                 }
             }
             break;
         }
         case MotionEvent.ACTION_MOVE: break;
         case MotionEvent.ACTION_POINTER_UP: break;
         case MotionEvent.ACTION_UP: {
             if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
                 final View v = getCurrentView();
                 final ViewAndMetaData viewData = getMetaDataForChild(v);
                 if (v != null) {
                     if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
                         final Handler handler = getHandler();
                         if (handler != null) {
                             handler.removeCallbacks(mPendingCheckForTap);
                         }
                         showTapFeedback(v);
                         postDelayed(new Runnable() {
                             public void run() {
                                 hideTapFeedback(v);
                                 post(new Runnable() {
                                     public void run() {
                                         if (viewData != null) {
                                             performItemClick(v, viewData.adapterPosition,
                                                     viewData.itemId);
                                         } else {
                                             performItemClick(v, 0, 0);
                                         }
                                     }
                                 });
                             }
                         }, ViewConfiguration.getPressedStateDuration());
                         handled = true;
                     }
                 }
             }
             mTouchMode = TOUCH_MODE_NONE;
             break;
         }
         case MotionEvent.ACTION_CANCEL: {
             View v = getCurrentView();
             if (v != null) {
                 hideTapFeedback(v);
             }
             mTouchMode = TOUCH_MODE_NONE;
         }
     }
     return handled;
 }