private int findNewActiveIndex(android.view.MotionEvent ev, int otherActiveId, int
                                       oldIndex)
        {
            int pointerCount = ev.getPointerCount();
            // It's ok if this isn't found and returns -1, it simply won't match.
            int otherActiveIndex = ev.findPointerIndex(otherActiveId);
            int newActiveIndex   = -1;

            {
                // Pick a new id and update tracking state. Only pick pointers not on the slop edges.
                for (int i = 0; i < pointerCount; i++)
                {
                    if (i != oldIndex && i != otherActiveIndex)
                    {
                        float edgeSlop   = mEdgeSlop;
                        float rightSlop  = mRightSlopEdge;
                        float bottomSlop = mBottomSlopEdge;
                        float x          = getRawX(ev, i);
                        float y          = getRawY(ev, i);
                        if (x >= edgeSlop && y >= edgeSlop && x <= rightSlop && y <= bottomSlop)
                        {
                            newActiveIndex = i;
                            break;
                        }
                    }
                }
            }
            return(newActiveIndex);
        }
示例#2
0
        private void ensurePointerCountIsOneForThisAction(android.view.MotionEvent @event
                                                          )
        {
            int pointerCount = @event.getPointerCount();

            if (pointerCount != 1)
            {
                problem("Pointer count is " + pointerCount + " but it should always be 1 for " +
                        android.view.MotionEvent.actionToString(@event.getAction()));
            }
        }
示例#3
0
        /// <summary>
        /// Analyzes the given motion event and if applicable triggers the
        /// appropriate callbacks on the
        /// <see cref="OnGestureListener">OnGestureListener</see>
        /// supplied.
        /// </summary>
        /// <param name="ev">The current motion event.</param>
        /// <returns>
        /// true if the
        /// <see cref="OnGestureListener">OnGestureListener</see>
        /// consumed the event,
        /// else false.
        /// </returns>
        public virtual bool onTouchEvent(android.view.MotionEvent ev)
        {
            if (mInputEventConsistencyVerifier != null)
            {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
            }
            int   action = ev.getAction();
            float y      = ev.getY();
            float x      = ev.getX();

            if (mVelocityTracker == null)
            {
                mVelocityTracker = android.view.VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(ev);
            bool handled = false;

            switch (action & android.view.MotionEvent.ACTION_MASK)
            {
            case android.view.MotionEvent.ACTION_POINTER_DOWN:
            {
                if (mIgnoreMultitouch)
                {
                    // Multitouch event - abort.
                    cancel();
                }
                break;
            }

            case android.view.MotionEvent.ACTION_POINTER_UP:
            {
                // Ending a multitouch gesture and going back to 1 finger
                if (mIgnoreMultitouch && ev.getPointerCount() == 2)
                {
                    int index = (((action & android.view.MotionEvent.ACTION_POINTER_INDEX_MASK) >> android.view.MotionEvent
                                  .ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
                    mLastMotionX = ev.getX(index);
                    mLastMotionY = ev.getY(index);
                    mVelocityTracker.recycle();
                    mVelocityTracker = android.view.VelocityTracker.obtain();
                }
                break;
            }

            case android.view.MotionEvent.ACTION_DOWN:
            {
                if (mDoubleTapListener != null)
                {
                    bool hadTapMessage = mHandler.hasMessages(TAP);
                    if (hadTapMessage)
                    {
                        mHandler.removeMessages(TAP);
                    }
                    if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
                        isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev))
                    {
                        // This is a second tap
                        mIsDoubleTapping = true;
                        // Give a callback with the first tap of the double-tap
                        handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                        // Give a callback with down event of the double-tap
                        handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                    }
                    else
                    {
                        // This is a first tap
                        mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
                    }
                }
                mLastMotionX = x;
                mLastMotionY = y;
                if (mCurrentDownEvent != null)
                {
                    mCurrentDownEvent.recycle();
                }
                mCurrentDownEvent        = android.view.MotionEvent.obtain(ev);
                mAlwaysInTapRegion       = true;
                mAlwaysInBiggerTapRegion = true;
                mStillDown   = true;
                mInLongPress = false;
                if (mIsLongpressEnabled)
                {
                    mHandler.removeMessages(LONG_PRESS);
                    mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT
                                                    + LONGPRESS_TIMEOUT);
                }
                mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT
                                                );
                handled |= mListener.onDown(ev);
                break;
            }

            case android.view.MotionEvent.ACTION_MOVE:
            {
                if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1))
                {
                    break;
                }
                float scrollX = mLastMotionX - x;
                float scrollY = mLastMotionY - y;
                if (mIsDoubleTapping)
                {
                    // Give the move events of the double-tap
                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                }
                else
                {
                    if (mAlwaysInTapRegion)
                    {
                        int deltaX   = (int)(x - mCurrentDownEvent.getX());
                        int deltaY   = (int)(y - mCurrentDownEvent.getY());
                        int distance = (deltaX * deltaX) + (deltaY * deltaY);
                        if (distance > mTouchSlopSquare)
                        {
                            handled            = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                            mLastMotionX       = x;
                            mLastMotionY       = y;
                            mAlwaysInTapRegion = false;
                            mHandler.removeMessages(TAP);
                            mHandler.removeMessages(SHOW_PRESS);
                            mHandler.removeMessages(LONG_PRESS);
                        }
                        if (distance > mBiggerTouchSlopSquare)
                        {
                            mAlwaysInBiggerTapRegion = false;
                        }
                    }
                    else
                    {
                        if ((System.Math.Abs(scrollX) >= 1) || (System.Math.Abs(scrollY) >= 1))
                        {
                            handled      = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                            mLastMotionX = x;
                            mLastMotionY = y;
                        }
                    }
                }
                break;
            }

            case android.view.MotionEvent.ACTION_UP:
            {
                mStillDown = false;
                android.view.MotionEvent currentUpEvent = android.view.MotionEvent.obtain(ev);
                if (mIsDoubleTapping)
                {
                    // Finally, give the up event of the double-tap
                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                }
                else
                {
                    if (mInLongPress)
                    {
                        mHandler.removeMessages(TAP);
                        mInLongPress = false;
                    }
                    else
                    {
                        if (mAlwaysInTapRegion)
                        {
                            handled = mListener.onSingleTapUp(ev);
                        }
                        else
                        {
                            // A fling must travel the minimum tap distance
                            android.view.VelocityTracker velocityTracker = mVelocityTracker;
                            velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
                            float velocityY = velocityTracker.getYVelocity();
                            float velocityX = velocityTracker.getXVelocity();
                            if ((System.Math.Abs(velocityY) > mMinimumFlingVelocity) || (System.Math.Abs(velocityX
                                                                                                         ) > mMinimumFlingVelocity))
                            {
                                handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
                            }
                        }
                    }
                }
                if (mPreviousUpEvent != null)
                {
                    mPreviousUpEvent.recycle();
                }
                // Hold the event we obtained above - listeners may have changed the original.
                mPreviousUpEvent = currentUpEvent;
                mVelocityTracker.recycle();
                mVelocityTracker = null;
                mIsDoubleTapping = false;
                mHandler.removeMessages(SHOW_PRESS);
                mHandler.removeMessages(LONG_PRESS);
                break;
            }

            case android.view.MotionEvent.ACTION_CANCEL:
            {
                cancel();
                break;
            }
            }
            if (!handled && mInputEventConsistencyVerifier != null)
            {
                mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
            }
            return(handled);
        }
示例#4
0
        public virtual bool onTouch(android.view.View v, android.view.MotionEvent @event)
        {
            int action = @event.getAction();

            if (@event.getPointerCount() > 1)
            {
                // ZoomButtonsController doesn't handle mutitouch. Give up control.
                return(false);
            }
            if (mReleaseTouchListenerOnUp)
            {
                // The controls were dismissed but we need to throw away all events until the up
                if (action == android.view.MotionEvent.ACTION_UP || action == android.view.MotionEvent
                    .ACTION_CANCEL)
                {
                    mOwnerView.setOnTouchListener(null);
                    setTouchTargetView(null);
                    mReleaseTouchListenerOnUp = false;
                }
                // Eat this event
                return(true);
            }
            dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
            android.view.View targetView = mTouchTargetView;
            switch (action)
            {
            case android.view.MotionEvent.ACTION_DOWN:
            {
                targetView = findViewForTouch((int)@event.getRawX(), (int)@event.getRawY());
                setTouchTargetView(targetView);
                break;
            }

            case android.view.MotionEvent.ACTION_UP:
            case android.view.MotionEvent.ACTION_CANCEL:
            {
                setTouchTargetView(null);
                break;
            }
            }
            if (targetView != null)
            {
                // The upperleft corner of the target view in raw coordinates
                int targetViewRawX = mContainerRawLocation[0] + mTouchTargetWindowLocation[0];
                int targetViewRawY = mContainerRawLocation[1] + mTouchTargetWindowLocation[1];
                android.view.MotionEvent containerEvent = android.view.MotionEvent.obtain(@event);
                // Convert the motion event into the target view's coordinates (from
                // owner view's coordinates)
                containerEvent.offsetLocation(mOwnerViewRawLocation[0] - targetViewRawX, mOwnerViewRawLocation
                                              [1] - targetViewRawY);
                // These are floats because we need to potentially offset away this exact amount
                float containerX = containerEvent.getX();
                float containerY = containerEvent.getY();
                if (containerX < 0 && containerX > -ZOOM_CONTROLS_TOUCH_PADDING)
                {
                    containerEvent.offsetLocation(-containerX, 0);
                }
                if (containerY < 0 && containerY > -ZOOM_CONTROLS_TOUCH_PADDING)
                {
                    containerEvent.offsetLocation(0, -containerY);
                }
                bool retValue = targetView.dispatchTouchEvent(containerEvent);
                containerEvent.recycle();
                return(retValue);
            }
            else
            {
                return(false);
            }
        }
        public virtual bool onTouchEvent(android.view.MotionEvent @event)
        {
            if (mInputEventConsistencyVerifier != null)
            {
                mInputEventConsistencyVerifier.onTouchEvent(@event, 0);
            }
            int action = @event.getActionMasked();

            if (action == android.view.MotionEvent.ACTION_DOWN)
            {
                reset();
            }
            // Start fresh
            bool handled = true;

            if (mInvalidGesture)
            {
                handled = false;
            }
            else
            {
                if (!mGestureInProgress)
                {
                    switch (action)
                    {
                    case android.view.MotionEvent.ACTION_DOWN:
                    {
                        mActiveId0         = @event.getPointerId(0);
                        mActive0MostRecent = true;
                        break;
                    }

                    case android.view.MotionEvent.ACTION_UP:
                    {
                        reset();
                        break;
                    }

                    case android.view.MotionEvent.ACTION_POINTER_DOWN:
                    {
                        // We have a new multi-finger gesture
                        // as orientation can change, query the metrics in touch down
                        android.util.DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
                        mRightSlopEdge  = metrics.widthPixels - mEdgeSlop;
                        mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
                        if (mPrevEvent != null)
                        {
                            mPrevEvent.recycle();
                        }
                        mPrevEvent = android.view.MotionEvent.obtain(@event);
                        mTimeDelta = 0;
                        int index1 = @event.getActionIndex();
                        int index0 = @event.findPointerIndex(mActiveId0);
                        mActiveId1 = @event.getPointerId(index1);
                        if (index0 < 0 || index0 == index1)
                        {
                            // Probably someone sending us a broken event stream.
                            index0     = findNewActiveIndex(@event, index0 == index1 ? -1 : mActiveId1, index0);
                            mActiveId0 = @event.getPointerId(index0);
                        }
                        mActive0MostRecent = false;
                        setContext(@event);
                        // Check if we have a sloppy gesture. If so, delay
                        // the beginning of the gesture until we're sure that's
                        // what the user wanted. Sloppy gestures can happen if the
                        // edge of the user's hand is touching the screen, for example.
                        float edgeSlop = mEdgeSlop;
                        float rightSlop = mRightSlopEdge;
                        float bottomSlop = mBottomSlopEdge;
                        float x0 = getRawX(@event, index0);
                        float y0 = getRawY(@event, index0);
                        float x1 = getRawX(@event, index1);
                        float y1 = getRawY(@event, index1);
                        bool  p0sloppy = x0 <edgeSlop || y0 <edgeSlop || x0> rightSlop || y0> bottomSlop;
                        bool  p1sloppy = x1 <edgeSlop || y1 <edgeSlop || x1> rightSlop || y1> bottomSlop;
                        if (p0sloppy && p1sloppy)
                        {
                            mFocusX        = -1;
                            mFocusY        = -1;
                            mSloppyGesture = true;
                        }
                        else
                        {
                            if (p0sloppy)
                            {
                                mFocusX        = @event.getX(index1);
                                mFocusY        = @event.getY(index1);
                                mSloppyGesture = true;
                            }
                            else
                            {
                                if (p1sloppy)
                                {
                                    mFocusX        = @event.getX(index0);
                                    mFocusY        = @event.getY(index0);
                                    mSloppyGesture = true;
                                }
                                else
                                {
                                    mSloppyGesture     = false;
                                    mGestureInProgress = mListener.onScaleBegin(this);
                                }
                            }
                        }
                        break;
                    }

                    case android.view.MotionEvent.ACTION_MOVE:
                    {
                        if (mSloppyGesture)
                        {
                            // Initiate sloppy gestures if we've moved outside of the slop area.
                            float edgeSlop = mEdgeSlop;
                            float rightSlop = mRightSlopEdge;
                            float bottomSlop = mBottomSlopEdge;
                            int   index0 = @event.findPointerIndex(mActiveId0);
                            int   index1 = @event.findPointerIndex(mActiveId1);
                            float x0 = getRawX(@event, index0);
                            float y0 = getRawY(@event, index0);
                            float x1 = getRawX(@event, index1);
                            float y1 = getRawY(@event, index1);
                            bool  p0sloppy = x0 <edgeSlop || y0 <edgeSlop || x0> rightSlop || y0> bottomSlop;
                            bool  p1sloppy = x1 <edgeSlop || y1 <edgeSlop || x1> rightSlop || y1> bottomSlop;
                            if (p0sloppy)
                            {
                                // Do we have a different pointer that isn't sloppy?
                                int index = findNewActiveIndex(@event, mActiveId1, index0);
                                if (index >= 0)
                                {
                                    index0     = index;
                                    mActiveId0 = @event.getPointerId(index);
                                    x0         = getRawX(@event, index);
                                    y0         = getRawY(@event, index);
                                    p0sloppy   = false;
                                }
                            }
                            if (p1sloppy)
                            {
                                // Do we have a different pointer that isn't sloppy?
                                int index = findNewActiveIndex(@event, mActiveId0, index1);
                                if (index >= 0)
                                {
                                    index1     = index;
                                    mActiveId1 = @event.getPointerId(index);
                                    x1         = getRawX(@event, index);
                                    y1         = getRawY(@event, index);
                                    p1sloppy   = false;
                                }
                            }
                            if (p0sloppy && p1sloppy)
                            {
                                mFocusX = -1;
                                mFocusY = -1;
                            }
                            else
                            {
                                if (p0sloppy)
                                {
                                    mFocusX = @event.getX(index1);
                                    mFocusY = @event.getY(index1);
                                }
                                else
                                {
                                    if (p1sloppy)
                                    {
                                        mFocusX = @event.getX(index0);
                                        mFocusY = @event.getY(index0);
                                    }
                                    else
                                    {
                                        mSloppyGesture     = false;
                                        mGestureInProgress = mListener.onScaleBegin(this);
                                    }
                                }
                            }
                        }
                        break;
                    }

                    case android.view.MotionEvent.ACTION_POINTER_UP:
                    {
                        if (mSloppyGesture)
                        {
                            int pointerCount = @event.getPointerCount();
                            int actionIndex  = @event.getActionIndex();
                            int actionId     = @event.getPointerId(actionIndex);
                            if (pointerCount > 2)
                            {
                                if (actionId == mActiveId0)
                                {
                                    int newIndex = findNewActiveIndex(@event, mActiveId1, actionIndex);
                                    if (newIndex >= 0)
                                    {
                                        mActiveId0 = @event.getPointerId(newIndex);
                                    }
                                }
                                else
                                {
                                    if (actionId == mActiveId1)
                                    {
                                        int newIndex = findNewActiveIndex(@event, mActiveId0, actionIndex);
                                        if (newIndex >= 0)
                                        {
                                            mActiveId1 = @event.getPointerId(newIndex);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // Set focus point to the remaining finger
                                int index = @event.findPointerIndex(actionId == mActiveId0 ? mActiveId1 : mActiveId0
                                                                    );
                                if (index < 0)
                                {
                                    mInvalidGesture = true;
                                    android.util.Log.e(TAG, "Invalid MotionEvent stream detected.", new System.Exception
                                                           ());
                                    if (mGestureInProgress)
                                    {
                                        mListener.onScaleEnd(this);
                                    }
                                    return(false);
                                }
                                mActiveId0         = @event.getPointerId(index);
                                mActive0MostRecent = true;
                                mActiveId1         = -1;
                                mFocusX            = @event.getX(index);
                                mFocusY            = @event.getY(index);
                            }
                        }
                        break;
                    }
                    }
                }
                else
                {
                    switch (action)
                    {
                    case android.view.MotionEvent.ACTION_POINTER_DOWN:
                    {
                        // Transform gesture in progress - attempt to handle it
                        // End the old gesture and begin a new one with the most recent two fingers.
                        mListener.onScaleEnd(this);
                        int oldActive0 = mActiveId0;
                        int oldActive1 = mActiveId1;
                        reset();
                        mPrevEvent         = android.view.MotionEvent.obtain(@event);
                        mActiveId0         = mActive0MostRecent ? oldActive0 : oldActive1;
                        mActiveId1         = @event.getPointerId(@event.getActionIndex());
                        mActive0MostRecent = false;
                        int index0 = @event.findPointerIndex(mActiveId0);
                        if (index0 < 0 || mActiveId0 == mActiveId1)
                        {
                            // Probably someone sending us a broken event stream.
                            android.util.Log.e(TAG, "Got " + android.view.MotionEvent.actionToString(action)
                                               + " with bad state while a gesture was in progress. " + "Did you forget to pass an event to "
                                               + "ScaleGestureDetector#onTouchEvent?");
                            index0 = findNewActiveIndex(@event, mActiveId0 == mActiveId1 ? -1 : mActiveId1, index0
                                                        );
                            mActiveId0 = @event.getPointerId(index0);
                        }
                        setContext(@event);
                        mGestureInProgress = mListener.onScaleBegin(this);
                        break;
                    }

                    case android.view.MotionEvent.ACTION_POINTER_UP:
                    {
                        int  pointerCount = @event.getPointerCount();
                        int  actionIndex  = @event.getActionIndex();
                        int  actionId     = @event.getPointerId(actionIndex);
                        bool gestureEnded = false;
                        if (pointerCount > 2)
                        {
                            if (actionId == mActiveId0)
                            {
                                int newIndex = findNewActiveIndex(@event, mActiveId1, actionIndex);
                                if (newIndex >= 0)
                                {
                                    mListener.onScaleEnd(this);
                                    mActiveId0         = @event.getPointerId(newIndex);
                                    mActive0MostRecent = true;
                                    mPrevEvent         = android.view.MotionEvent.obtain(@event);
                                    setContext(@event);
                                    mGestureInProgress = mListener.onScaleBegin(this);
                                }
                                else
                                {
                                    gestureEnded = true;
                                }
                            }
                            else
                            {
                                if (actionId == mActiveId1)
                                {
                                    int newIndex = findNewActiveIndex(@event, mActiveId0, actionIndex);
                                    if (newIndex >= 0)
                                    {
                                        mListener.onScaleEnd(this);
                                        mActiveId1         = @event.getPointerId(newIndex);
                                        mActive0MostRecent = false;
                                        mPrevEvent         = android.view.MotionEvent.obtain(@event);
                                        setContext(@event);
                                        mGestureInProgress = mListener.onScaleBegin(this);
                                    }
                                    else
                                    {
                                        gestureEnded = true;
                                    }
                                }
                            }
                            mPrevEvent.recycle();
                            mPrevEvent = android.view.MotionEvent.obtain(@event);
                            setContext(@event);
                        }
                        else
                        {
                            gestureEnded = true;
                        }
                        if (gestureEnded)
                        {
                            // Gesture ended
                            setContext(@event);
                            // Set focus point to the remaining finger
                            int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
                            int index    = @event.findPointerIndex(activeId);
                            mFocusX = @event.getX(index);
                            mFocusY = @event.getY(index);
                            mListener.onScaleEnd(this);
                            reset();
                            mActiveId0         = activeId;
                            mActive0MostRecent = true;
                        }
                        break;
                    }

                    case android.view.MotionEvent.ACTION_CANCEL:
                    {
                        mListener.onScaleEnd(this);
                        reset();
                        break;
                    }

                    case android.view.MotionEvent.ACTION_UP:
                    {
                        reset();
                        break;
                    }

                    case android.view.MotionEvent.ACTION_MOVE:
                    {
                        setContext(@event);
                        // Only accept the event if our relative pressure is within
                        // a certain limit - this can help filter shaky data as a
                        // finger is lifted.
                        if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD)
                        {
                            bool updatePrevious = mListener.onScale(this);
                            if (updatePrevious)
                            {
                                mPrevEvent.recycle();
                                mPrevEvent = android.view.MotionEvent.obtain(@event);
                            }
                        }
                        break;
                    }
                    }
                }
            }
            if (!handled && mInputEventConsistencyVerifier != null)
            {
                mInputEventConsistencyVerifier.onUnhandledEvent(@event, 0);
            }
            return(handled);
        }
示例#6
0
        /// <summary>Checks a touch event.</summary>
        /// <remarks>Checks a touch event.</remarks>
        /// <param name="event">The event.</param>
        /// <param name="nestingLevel">
        /// The nesting level: 0 if called from the base class,
        /// or 1 from a subclass.  If the event was already checked by this consistency verifier
        /// at a higher nesting level, it will not be checked again.  Used to handle the situation
        /// where a subclass dispatching method delegates to its superclass's dispatching method
        /// and both dispatching methods call into the consistency verifier.
        /// </param>
        public void onTouchEvent(android.view.MotionEvent @event, int nestingLevel)
        {
            if (!startEvent(@event, nestingLevel, EVENT_TYPE_TOUCH))
            {
                return;
            }
            int  action    = @event.getAction();
            bool newStream = action == android.view.MotionEvent.ACTION_DOWN || action == android.view.MotionEvent
                             .ACTION_CANCEL;

            if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled))
            {
                mTouchEventStreamIsTainted = false;
                mTouchEventStreamUnhandled = false;
                mTouchEventStreamPointers  = 0;
            }
            if (mTouchEventStreamIsTainted)
            {
                @event.setTainted(true);
            }
            try
            {
                ensureMetaStateIsNormalized(@event.getMetaState());
                int deviceId = @event.getDeviceId();
                int source   = @event.getSource();
                if (!newStream && mTouchEventStreamDeviceId != -1 && (mTouchEventStreamDeviceId !=
                                                                      deviceId || mTouchEventStreamSource != source))
                {
                    problem("Touch event stream contains events from multiple sources: " + "previous device id "
                            + mTouchEventStreamDeviceId + ", previous source " + Sharpen.Util.IntToHexString
                                (mTouchEventStreamSource) + ", new device id " + deviceId + ", new source " + Sharpen.Util.IntToHexString
                                (source));
                }
                mTouchEventStreamDeviceId = deviceId;
                mTouchEventStreamSource   = source;
                int pointerCount = @event.getPointerCount();
                if ((source & android.view.InputDevice.SOURCE_CLASS_POINTER) != 0)
                {
                    switch (action)
                    {
                    case android.view.MotionEvent.ACTION_DOWN:
                    {
                        if (mTouchEventStreamPointers != 0)
                        {
                            problem("ACTION_DOWN but pointers are already down.  " + "Probably missing ACTION_UP from previous gesture."
                                    );
                        }
                        ensureHistorySizeIsZeroForThisAction(@event);
                        ensurePointerCountIsOneForThisAction(@event);
                        mTouchEventStreamPointers = 1 << @event.getPointerId(0);
                        break;
                    }

                    case android.view.MotionEvent.ACTION_UP:
                    {
                        ensureHistorySizeIsZeroForThisAction(@event);
                        ensurePointerCountIsOneForThisAction(@event);
                        mTouchEventStreamPointers  = 0;
                        mTouchEventStreamIsTainted = false;
                        break;
                    }

                    case android.view.MotionEvent.ACTION_MOVE:
                    {
                        int expectedPointerCount = Sharpen.Util.IntGetBitCount(mTouchEventStreamPointers);
                        if (pointerCount != expectedPointerCount)
                        {
                            problem("ACTION_MOVE contained " + pointerCount + " pointers but there are currently "
                                    + expectedPointerCount + " pointers down.");
                            mTouchEventStreamIsTainted = true;
                        }
                        break;
                    }

                    case android.view.MotionEvent.ACTION_CANCEL:
                    {
                        mTouchEventStreamPointers  = 0;
                        mTouchEventStreamIsTainted = false;
                        break;
                    }

                    case android.view.MotionEvent.ACTION_OUTSIDE:
                    {
                        if (mTouchEventStreamPointers != 0)
                        {
                            problem("ACTION_OUTSIDE but pointers are still down.");
                        }
                        ensureHistorySizeIsZeroForThisAction(@event);
                        ensurePointerCountIsOneForThisAction(@event);
                        mTouchEventStreamIsTainted = false;
                        break;
                    }

                    default:
                    {
                        int actionMasked = @event.getActionMasked();
                        int actionIndex  = @event.getActionIndex();
                        if (actionMasked == android.view.MotionEvent.ACTION_POINTER_DOWN)
                        {
                            if (mTouchEventStreamPointers == 0)
                            {
                                problem("ACTION_POINTER_DOWN but no other pointers were down.");
                                mTouchEventStreamIsTainted = true;
                            }
                            if (actionIndex < 0 || actionIndex >= pointerCount)
                            {
                                problem("ACTION_POINTER_DOWN index is " + actionIndex + " but the pointer count is "
                                        + pointerCount + ".");
                                mTouchEventStreamIsTainted = true;
                            }
                            else
                            {
                                int id    = @event.getPointerId(actionIndex);
                                int idBit = 1 << id;
                                if ((mTouchEventStreamPointers & idBit) != 0)
                                {
                                    problem("ACTION_POINTER_DOWN specified pointer id " + id + " which is already down."
                                            );
                                    mTouchEventStreamIsTainted = true;
                                }
                                else
                                {
                                    mTouchEventStreamPointers |= idBit;
                                }
                            }
                            ensureHistorySizeIsZeroForThisAction(@event);
                        }
                        else
                        {
                            if (actionMasked == android.view.MotionEvent.ACTION_POINTER_UP)
                            {
                                if (actionIndex < 0 || actionIndex >= pointerCount)
                                {
                                    problem("ACTION_POINTER_UP index is " + actionIndex + " but the pointer count is "
                                            + pointerCount + ".");
                                    mTouchEventStreamIsTainted = true;
                                }
                                else
                                {
                                    int id    = @event.getPointerId(actionIndex);
                                    int idBit = 1 << id;
                                    if ((mTouchEventStreamPointers & idBit) == 0)
                                    {
                                        problem("ACTION_POINTER_UP specified pointer id " + id + " which is not currently down."
                                                );
                                        mTouchEventStreamIsTainted = true;
                                    }
                                    else
                                    {
                                        mTouchEventStreamPointers &= ~idBit;
                                    }
                                }
                                ensureHistorySizeIsZeroForThisAction(@event);
                            }
                            else
                            {
                                problem("Invalid action " + android.view.MotionEvent.actionToString(action) + " for touch event."
                                        );
                            }
                        }
                        break;
                    }
                    }
                }
                else
                {
                    problem("Source was not SOURCE_CLASS_POINTER.");
                }
            }
            finally
            {
                finishEvent();
            }
        }