From c186116720dd4e48dde1c39e8fcf098e837ef1cc Mon Sep 17 00:00:00 2001 From: houzh <13923402541@gitee.com> Date: Sun, 4 Feb 2024 10:12:44 +0000 Subject: [PATCH] add inputeventconsistencyverifier --- src/gui/core/uievents.cc | 4 +- src/gui/core/uievents.h | 28 +- src/gui/core/uieventsource.cc | 2 +- src/gui/view/inputeventconsistencyverifier.cc | 676 ++++++++++++++++++ src/gui/view/inputeventconsistencyverifier.h | 196 +++++ src/gui/view/velocitytracker.cc | 4 +- src/gui/view/view.cc | 22 +- src/gui/view/view.h | 2 + src/gui/view/viewgroup.cc | 10 +- 9 files changed, 932 insertions(+), 12 deletions(-) create mode 100755 src/gui/view/inputeventconsistencyverifier.cc create mode 100755 src/gui/view/inputeventconsistencyverifier.h diff --git a/src/gui/core/uievents.cc b/src/gui/core/uievents.cc index 9eddde56..35289858 100755 --- a/src/gui/core/uievents.cc +++ b/src/gui/core/uievents.cc @@ -737,9 +737,9 @@ nsecs_t MotionEvent::getHistoricalEventTime(size_t historyPos) const{ } } -nsecs_t MotionEvent::getHistoricalEventTimeNano(size_t historyPos) const{ +nsecs_t MotionEvent::getHistoricalEventTimeNanos(size_t historyPos) const{ if(historyPos==HISTORY_CURRENT){ - return getEventTimeNano(); + return getEventTimeNanos(); }else{ const size_t historySize = getHistorySize(); if(historyPos<0||historyPos>=historySize)return 0; diff --git a/src/gui/core/uievents.h b/src/gui/core/uievents.h index 6d82afd2..03a99e97 100755 --- a/src/gui/core/uievents.h +++ b/src/gui/core/uievents.h @@ -88,14 +88,17 @@ public: InputEvent(); virtual ~InputEvent(); virtual int getType()const=0; + virtual InputEvent*copy()const=0; void initialize(int32_t deviceId, int32_t source); void initialize(const InputEvent& from); void setSource(int source){mSource=source;} int getSource()const{return mSource;} bool isFromSource(int s)const; - int getDeviceId(){return mDeviceId;} + int getDeviceId()const{return mDeviceId;} long getSequenceNumber()const{return mSeq;} - virtual nsecs_t getEventTimeNano() const { return mEventTime*NS_PER_MS; } + virtual bool isTainted()const=0; + virtual void setTainted(bool)=0; + virtual nsecs_t getEventTimeNanos() const { return mEventTime*NS_PER_MS; } virtual nsecs_t getEventTime()const{ return mEventTime;} virtual void recycle();/*only obtained event can call recycle*/ }; @@ -219,9 +222,15 @@ public: int deviceId, int scancode, int flags, int source/*,std::string characters*/); static KeyEvent* obtain(const KeyEvent& other); virtual int getType()const {return EV_KEY;} + KeyEvent*copy()const override{return obtain(*this);} int getKeyCode()const {return mKeyCode;} void setKeyCode(int k){mKeyCode=k;} int getFlags()const{return mFlags;} + inline bool isTainted()const{return (mFlags&FLAG_TAINTED)!=0;} + inline void setTainted(bool tainted){ + if(tainted)mFlags|=FLAG_TAINTED; + else mFlags&=~FLAG_TAINTED; + } inline int32_t getScanCode() const { return mScanCode; } inline int32_t getMetaState() const { return mMetaState; } int getAction()const{return mAction;}//key up-->0 down-->1 @@ -346,6 +355,12 @@ public: FLAG_IS_GENERATED_GESTURE = 0x8, FLAG_TAINTED = 0x80000000, FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000, + CLASSIFICATION_NONE = 0, + CLASSIFICATION_AMBIGUOUS_GESTURE = 1, + CLASSIFICATION_DEEP_PRESS = 2, + CLASSIFICATION_TWO_FINGER_SWIPE = 3, + CLASSIFICATION_MULTI_FINGER_SWIPE = 4, + CLASSIFICATION_PINCH = 5, }; private: static const int HISTORY_CURRENT = -0x80000000; @@ -368,7 +383,7 @@ protected: public: MotionEvent(); MotionEvent(const MotionEvent&m); - + MotionEvent*copy()const override{return obtain(*this);} void initialize(int deviceId,int source,int action,int actionButton, int flags, int edgeFlags,int metaState, int buttonState, float xOffset, float yOffset, float xPrecision, float yPrecision, @@ -401,6 +416,11 @@ public: inline nsecs_t getDownTime()const{return mDownTime;} inline int32_t getFlags() const { return mFlags; } inline void setFlags(int32_t flags) { mFlags = flags; } + inline bool isTainted()const{return (mFlags&FLAG_TAINTED)!=0;} + inline void setTainted(bool tainted){ + if(tainted)mFlags|=FLAG_TAINTED; + else mFlags&=~FLAG_TAINTED; + } inline int32_t getEdgeFlags() const { return mEdgeFlags; } inline void setEdgeFlags(int32_t edgeFlags) { mEdgeFlags = edgeFlags; } inline bool isHoverExitPending()const{return getFlags()&FLAG_HOVER_EXIT_PENDING!=0;} @@ -421,7 +441,7 @@ public: inline float getYPrecision() const { return mYPrecision; } inline size_t getHistorySize() const { return mSampleEventTimes.size() - 1; } nsecs_t getHistoricalEventTime(size_t historicalIndex) const; - nsecs_t getHistoricalEventTimeNano(size_t historicalIndex) const; + nsecs_t getHistoricalEventTimeNanos(size_t historicalIndex) const; void getPointerCoords(int pointerIndex, PointerCoords& outPointerCoords){ getHistoricalRawPointerCoords(pointerIndex,HISTORY_CURRENT,outPointerCoords); } diff --git a/src/gui/core/uieventsource.cc b/src/gui/core/uieventsource.cc index d9364d1e..4508f8cf 100755 --- a/src/gui/core/uieventsource.cc +++ b/src/gui/core/uieventsource.cc @@ -71,7 +71,7 @@ bool UIEventSource::postDelayed(Runnable& run,uint32_t delayedtime){ RUNNER runner; runner.time = SystemClock::uptimeMillis() + delayedtime; runner.run = run; - + for(auto itr = mRunnables.begin();itr != mRunnables.end();itr++){ if(runner.time < itr->time){ mRunnables.insert(itr,runner); diff --git a/src/gui/view/inputeventconsistencyverifier.cc b/src/gui/view/inputeventconsistencyverifier.cc new file mode 100755 index 00000000..9e730b5f --- /dev/null +++ b/src/gui/view/inputeventconsistencyverifier.cc @@ -0,0 +1,676 @@ +#include +#include +#include + +namespace cdroid{ +static const char* EVENT_TYPE_KEY = "KeyEvent"; +static const char* EVENT_TYPE_TRACKBALL = "TrackballEvent"; +static const char* EVENT_TYPE_TOUCH = "TouchEvent"; +static const char* EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent"; + +InputEventConsistencyVerifier::InputEventConsistencyVerifier(Object* caller, int flags) { + mCaller = caller; + mFlags = flags; + mMostRecentEventIndex = 0; + mCurrentEvent = nullptr; + mKeyStateList = nullptr; + //mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier"; +} + +/** + * Determines whether the instrumentation should be enabled. + * @return True if it should be enabled. + */ +bool InputEventConsistencyVerifier::isInstrumentationEnabled() { +#ifndef _DEBUG + return true;//IS_ENG_BUILD; +#else + return false; +#endif +} + +void InputEventConsistencyVerifier::reset() { + mLastEventSeq = -1; + mLastNestingLevel = 0; + mTrackballDown = false; + mTrackballUnhandled = false; + mTouchEventStreamPointers = 0; + mTouchEventStreamIsTainted = false; + mTouchEventStreamUnhandled = false; + mHoverEntered = false; + mButtonsPressed = 0; + + while (mKeyStateList != nullptr) { + KeyState* state = mKeyStateList; + mKeyStateList = state->next; + state->recycle(); + } +} + +/** + * Checks an arbitrary input event. + * @param event The event. + * @param 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. + */ +void InputEventConsistencyVerifier::onInputEvent(InputEvent& event, int nestingLevel) { + if (dynamic_cast(&event)) { + KeyEvent& keyEvent = (KeyEvent&)event; + onKeyEvent(keyEvent, nestingLevel); + } else { + MotionEvent& motionEvent = (MotionEvent&)event; + if (motionEvent.isTouchEvent()) { + onTouchEvent(motionEvent, nestingLevel); + } else if ((motionEvent.getSource() & InputDevice::SOURCE_CLASS_TRACKBALL) != 0) { + onTrackballEvent(motionEvent, nestingLevel); + } else { + onGenericMotionEvent(motionEvent, nestingLevel); + } + } +} + +/** + * Checks a key event. + * @param event The event. + * @param 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. + */ +void InputEventConsistencyVerifier::onKeyEvent(KeyEvent& event, int nestingLevel) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) { + return; + } + + try { + ensureMetaStateIsNormalized(event.getMetaState()); + + const int action = event.getAction(); + const int deviceId = event.getDeviceId(); + const int source = event.getSource(); + const int keyCode = event.getKeyCode(); + switch (action) { + case KeyEvent::ACTION_DOWN: { + KeyState* state = findKeyState(deviceId, source, keyCode, /*remove*/ false); + if (state != nullptr) { + // If the key is already down, ensure it is a repeat. + // We don't perform this check when processing raw device input + // because the input dispatcher itself is responsible for setting + // the key repeat count before it delivers input events. + if (state->unhandled) { + state->unhandled = false; + } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0 + && event.getRepeatCount() == 0) { + problem("ACTION_DOWN but key is already down and this event is not a key repeat."); + } + } else { + addKeyState(deviceId, source, keyCode); + } + break; + } + case KeyEvent::ACTION_UP: { + KeyState* state = findKeyState(deviceId, source, keyCode, /*remove*/ true); + if (state == nullptr) { + problem("ACTION_UP but key was not down."); + } else { + state->recycle(); + } + break; + } + case KeyEvent::ACTION_MULTIPLE: + break; + default: + problem("Invalid action " + KeyEvent::actionToString(action) + + " for key event."); + break; + } + }catch(std::exception&e){ + } + finishEvent(); +} + +/** + * Checks a trackball event. + * @param event The event. + * @param 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. + */ +void InputEventConsistencyVerifier::onTrackballEvent(MotionEvent& event, int nestingLevel) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) { + return; + } + + try { + ensureMetaStateIsNormalized(event.getMetaState()); + + const int action = event.getAction(); + const int source = event.getSource(); + if ((source & InputDevice::SOURCE_CLASS_TRACKBALL) != 0) { + switch (action) { + case MotionEvent::ACTION_DOWN: + if (mTrackballDown && !mTrackballUnhandled) { + problem("ACTION_DOWN but trackball is already down."); + } else { + mTrackballDown = true; + mTrackballUnhandled = false; + } + ensureHistorySizeIsZeroForThisAction(event); + ensurePointerCountIsOneForThisAction(event); + break; + case MotionEvent::ACTION_UP: + if (!mTrackballDown) { + problem("ACTION_UP but trackball is not down."); + } else { + mTrackballDown = false; + mTrackballUnhandled = false; + } + ensureHistorySizeIsZeroForThisAction(event); + ensurePointerCountIsOneForThisAction(event); + break; + case MotionEvent::ACTION_MOVE: + ensurePointerCountIsOneForThisAction(event); + break; + default: + problem("Invalid action " + MotionEvent::actionToString(action) + + " for trackball event."); + break; + } + + if (mTrackballDown && event.getPressure(0) <= 0.f) { + problem("Trackball is down but pressure is not greater than 0."); + } else if (!mTrackballDown && event.getPressure(0) != 0) { + problem("Trackball is up but pressure is not equal to 0."); + } + } else { + problem("Source was not SOURCE_CLASS_TRACKBALL."); + } + }catch(std::exception&e){ + } + finishEvent(); +} + +static int bitCount(int number) { + int count = 0; + while (number) { + count += number & 1; + number >>= 1; + } + return count; +} +/** + * Checks a touch event. + * @param event The event. + * @param 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. + */ +void InputEventConsistencyVerifier::onTouchEvent(MotionEvent& event, int nestingLevel) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) { + return; + } + + const int action = event.getAction(); + const bool newStream = action == MotionEvent::ACTION_DOWN + || action == MotionEvent::ACTION_CANCEL || action == MotionEvent::ACTION_OUTSIDE; + if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) { + mTouchEventStreamIsTainted = false; + mTouchEventStreamUnhandled = false; + mTouchEventStreamPointers = 0; + } + if (mTouchEventStreamIsTainted) { + event.setTainted(true); + } + + try { + ensureMetaStateIsNormalized(event.getMetaState()); + + const int deviceId = event.getDeviceId(); + const int source = event.getSource(); + + if (!newStream && mTouchEventStreamDeviceId != -1 + && (mTouchEventStreamDeviceId != deviceId + || mTouchEventStreamSource != source)) { + problem("Touch event stream contains events from multiple sources: previous device id %d" + ", previous source %x, new device id %d, new source %x", + mTouchEventStreamDeviceId,mTouchEventStreamSource,deviceId,source); + } + mTouchEventStreamDeviceId = deviceId; + mTouchEventStreamSource = source; + + const int pointerCount = event.getPointerCount(); + if ((source & InputDevice::SOURCE_CLASS_POINTER) != 0) { + switch (action) { + case 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 MotionEvent::ACTION_UP: + ensureHistorySizeIsZeroForThisAction(event); + ensurePointerCountIsOneForThisAction(event); + mTouchEventStreamPointers = 0; + mTouchEventStreamIsTainted = false; + break; + case MotionEvent::ACTION_MOVE: { + const int expectedPointerCount = bitCount(mTouchEventStreamPointers); + if (pointerCount != expectedPointerCount) { + problem("ACTION_MOVE contained %d pointers but there are currently %d pointers down.",pointerCount,expectedPointerCount); + mTouchEventStreamIsTainted = true; + } + break; + } + case MotionEvent::ACTION_CANCEL: + mTouchEventStreamPointers = 0; + mTouchEventStreamIsTainted = false; + break; + case MotionEvent::ACTION_OUTSIDE: + if (mTouchEventStreamPointers != 0) { + problem("ACTION_OUTSIDE but pointers are still down."); + } + ensureHistorySizeIsZeroForThisAction(event); + ensurePointerCountIsOneForThisAction(event); + mTouchEventStreamIsTainted = false; + break; + default: { + const int actionMasked = event.getActionMasked(); + const int actionIndex = event.getActionIndex(); + if (actionMasked == 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 %d but the pointer count is %d.", + actionIndex,pointerCount); + mTouchEventStreamIsTainted = true; + } else { + const int id = event.getPointerId(actionIndex); + const int idBit = 1 << id; + if ((mTouchEventStreamPointers & idBit) != 0) { + problem("ACTION_POINTER_DOWN specified pointer id %d which is already down.",id); + mTouchEventStreamIsTainted = true; + } else { + mTouchEventStreamPointers |= idBit; + } + } + ensureHistorySizeIsZeroForThisAction(event); + } else if (actionMasked == MotionEvent::ACTION_POINTER_UP) { + if (actionIndex < 0 || actionIndex >= pointerCount) { + problem("ACTION_POINTER_UP index is % but the pointer count is %d.",actionIndex,pointerCount); + mTouchEventStreamIsTainted = true; + } else { + const int id = event.getPointerId(actionIndex); + const int idBit = 1 << id; + if ((mTouchEventStreamPointers & idBit) == 0) { + problem("ACTION_POINTER_UP specified pointer id %d which is not currently down.",id); + mTouchEventStreamIsTainted = true; + } else { + mTouchEventStreamPointers &= ~idBit; + } + } + ensureHistorySizeIsZeroForThisAction(event); + } else { + problem("Invalid action %s for touch event.",MotionEvent::actionToString(action)); + } + break; + } + } + } else { + problem("Source was not SOURCE_CLASS_POINTER."); + } + }catch(std::exception&e){ + } + finishEvent(); +} + +/** + * Checks a generic motion event. + * @param event The event. + * @param 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. + */ +void InputEventConsistencyVerifier::onGenericMotionEvent(MotionEvent& event, int nestingLevel) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) { + return; + } + + try { + ensureMetaStateIsNormalized(event.getMetaState()); + + const int action = event.getAction(); + const int source = event.getSource(); + const int buttonState = event.getButtonState(); + const int actionButton = event.getActionButton(); + if ((source & InputDevice::SOURCE_CLASS_POINTER) != 0) { + switch (action) { + case MotionEvent::ACTION_HOVER_ENTER: + ensurePointerCountIsOneForThisAction(event); + mHoverEntered = true; + break; + case MotionEvent::ACTION_HOVER_MOVE: + ensurePointerCountIsOneForThisAction(event); + break; + case MotionEvent::ACTION_HOVER_EXIT: + ensurePointerCountIsOneForThisAction(event); + if (!mHoverEntered) { + problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER"); + } + mHoverEntered = false; + break; + case MotionEvent::ACTION_SCROLL: + ensureHistorySizeIsZeroForThisAction(event); + ensurePointerCountIsOneForThisAction(event); + break; + case MotionEvent::ACTION_BUTTON_PRESS: + ensureActionButtonIsNonZeroForThisAction(event); + if ((mButtonsPressed & actionButton) != 0) { + problem("Action button for ACTION_BUTTON_PRESS event is %d" + ", but it has already been pressed and " + "has yet to be released.",actionButton); + } + + mButtonsPressed |= actionButton; + // The system will automatically mirror the stylus buttons onto the button + // state as the old set of generic buttons for apps targeting pre-M. If + // it looks this has happened, go ahead and set the generic buttons as + // pressed to prevent spurious errors. + if (actionButton == MotionEvent::BUTTON_STYLUS_PRIMARY && + (buttonState & MotionEvent::BUTTON_SECONDARY) != 0) { + mButtonsPressed |= MotionEvent::BUTTON_SECONDARY; + } else if (actionButton == MotionEvent::BUTTON_STYLUS_SECONDARY && + (buttonState & MotionEvent::BUTTON_TERTIARY) != 0) { + mButtonsPressed |= MotionEvent::BUTTON_TERTIARY; + } + + if (mButtonsPressed != buttonState) { + problem("Reported button state differs from expect button state " + "based on press and release events. Is 0x%08x but expected 0x%08x.", + buttonState, mButtonsPressed); + } + break; + case MotionEvent::ACTION_BUTTON_RELEASE: + ensureActionButtonIsNonZeroForThisAction(event); + if ((mButtonsPressed & actionButton) != actionButton) { + problem("Action button for ACTION_BUTTON_RELEASE event is %d" + ", but it was either never pressed or has " + "already been released.",actionButton); + } + + mButtonsPressed &= ~actionButton; + // The system will automatically mirror the stylus buttons onto the button + // state as the old set of generic buttons for apps targeting pre-M. If + // it looks this has happened, go ahead and set the generic buttons as + // released to prevent spurious errors. + if (actionButton == MotionEvent::BUTTON_STYLUS_PRIMARY && + (buttonState & MotionEvent::BUTTON_SECONDARY) == 0) { + mButtonsPressed &= ~MotionEvent::BUTTON_SECONDARY; + } else if (actionButton == MotionEvent::BUTTON_STYLUS_SECONDARY && + (buttonState & MotionEvent::BUTTON_TERTIARY) == 0) { + mButtonsPressed &= ~MotionEvent::BUTTON_TERTIARY; + } + + if (mButtonsPressed != buttonState) { + problem("Reported button state differs from " + "expected button state based on press and release events. " + "Is 0x%08x but expected 0x%08x.", + buttonState, mButtonsPressed); + } + break; + default: + problem("Invalid action for generic pointer event."); + break; + } + } else if ((source & InputDevice::SOURCE_CLASS_JOYSTICK) != 0) { + switch (action) { + case MotionEvent::ACTION_MOVE: + ensurePointerCountIsOneForThisAction(event); + break; + default: + problem("Invalid action for generic joystick event."); + break; + } + } + }catch(std::exception&){ + } + finishEvent(); +} + +/** + * Notifies the verifier that a given event was unhandled and the rest of the + * trace for the event should be ignored. + * This method should only be called if the event was previously checked by + * the consistency verifier using {@link #onInputEvent} and other methods. + * @param event The event. + * @param 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. + */ +void InputEventConsistencyVerifier::onUnhandledEvent(InputEvent& event, int nestingLevel) { + if (nestingLevel != mLastNestingLevel) { + return; + } + + if (mRecentEventsUnhandled.size()) { + mRecentEventsUnhandled[mMostRecentEventIndex] = true; + } + + if (dynamic_cast(&event)) { + const KeyEvent& keyEvent = (KeyEvent&)event; + const int deviceId = keyEvent.getDeviceId(); + const int source = keyEvent.getSource(); + const int keyCode = keyEvent.getKeyCode(); + KeyState* state = findKeyState(deviceId, source, keyCode, /*remove*/ false); + if (state != nullptr) { + state->unhandled = true; + } + } else { + MotionEvent& motionEvent = (MotionEvent&)event; + if (motionEvent.isTouchEvent()) { + mTouchEventStreamUnhandled = true; + } else if ((motionEvent.getSource() & InputDevice::SOURCE_CLASS_TRACKBALL) != 0) { + if (mTrackballDown) { + mTrackballUnhandled = true; + } + } + } +} + +void InputEventConsistencyVerifier::ensureMetaStateIsNormalized(int metaState) { + const int normalizedMetaState = KeyEvent::normalizeMetaState(metaState); + if (normalizedMetaState != metaState) { + problem("Metastate not normalized. Was 0x%08x but expected 0x%08x.", + metaState, normalizedMetaState); + } +} + +void InputEventConsistencyVerifier::ensurePointerCountIsOneForThisAction(MotionEvent& event) { + const int pointerCount = event.getPointerCount(); + if (pointerCount != 1) { + problem("Pointer count is %d but it should always be 1 for %s", + pointerCount, MotionEvent::actionToString(event.getAction())); + } +} + +void InputEventConsistencyVerifier::ensureActionButtonIsNonZeroForThisAction(MotionEvent& event) { + const int actionButton = event.getActionButton(); + if (actionButton == 0) { + problem("No action button set. Action button should always be non-zero for %s", + MotionEvent::actionToString(event.getAction())); + + } +} + +void InputEventConsistencyVerifier::ensureHistorySizeIsZeroForThisAction(MotionEvent& event) { + const int historySize = event.getHistorySize(); + if (historySize != 0) { + problem("History size is %d but it should always be 0 for %s", + historySize,MotionEvent::actionToString(event.getAction())); + } +} + +bool InputEventConsistencyVerifier::startEvent(InputEvent& event, int nestingLevel,const std::string& eventType) { + // Ignore the event if we already checked it at a higher nesting level. + const int seq = event.getSequenceNumber(); + if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel + && eventType == mLastEventType) { + return false; + } + + if (nestingLevel > 0) { + mLastEventSeq = seq; + mLastEventType = eventType; + mLastNestingLevel = nestingLevel; + } else { + mLastEventSeq = -1; + mLastEventType.clear();// = nullptr; + mLastNestingLevel = 0; + } + + mCurrentEvent = &event; + mCurrentEventType = eventType; + return true; +} + +void InputEventConsistencyVerifier::finishEvent() { + if (mViolationMessage.str().length() != 0) { + if (!mCurrentEvent->isTainted()) { + // Write a log message only if the event was not already tainted. + //mViolationMessage.append("\n in ").append(mCaller); + mViolationMessage<<("\n "); + appendEvent(mViolationMessage, 0, *mCurrentEvent, false); + + if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents.size()) { + mViolationMessage<<("\n -- recent events --"); + for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) { + const int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i) + % RECENT_EVENTS_TO_LOG; + InputEvent* event = mRecentEvents[index]; + if (event == nullptr) { + break; + } + mViolationMessage<<("\n "); + appendEvent(mViolationMessage, i + 1, *event, mRecentEventsUnhandled[index]); + } + } + + LOGD("%s",mViolationMessage.str().c_str()); + + // Taint the event so that we do not generate additional violations from it + // further downstream. + mCurrentEvent->setTainted(true); + } + mViolationMessage.clear();//setLength(0); + } + + if (RECENT_EVENTS_TO_LOG != 0) { + if (mRecentEvents.empty()) { + mRecentEvents.resize(RECENT_EVENTS_TO_LOG); + mRecentEventsUnhandled.resize(RECENT_EVENTS_TO_LOG); + } + const int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG; + mMostRecentEventIndex = index; + if (mRecentEvents[index] != nullptr) { + mRecentEvents[index]->recycle(); + } + mRecentEvents[index] = mCurrentEvent->copy(); + mRecentEventsUnhandled[index] = false; + } + + mCurrentEvent = nullptr; + mCurrentEventType.clear(); +} + +void InputEventConsistencyVerifier::appendEvent(std::ostringstream& message, int index, + const InputEvent& event, bool unhandled) { + message<deviceId == deviceId && state->source == source + && state->keyCode == keyCode) { + if (remove) { + if (last != nullptr) { + last->next = state->next; + } else { + mKeyStateList = state->next; + } + state->next = nullptr; + } + return state; + } + last = state; + state = state->next; + } + return nullptr; +} + +void InputEventConsistencyVerifier::addKeyState(int deviceId, int source, int keyCode) { + KeyState* state = KeyState::obtain(deviceId, source, keyCode); + state->next = mKeyStateList; + mKeyStateList = state; +} + +InputEventConsistencyVerifier::KeyState* InputEventConsistencyVerifier::KeyState::mRecycledList = nullptr; +InputEventConsistencyVerifier::KeyState::KeyState() { +} + +InputEventConsistencyVerifier::KeyState* InputEventConsistencyVerifier::KeyState::obtain(int deviceId, int source, int keyCode) { + KeyState* state; + state = mRecycledList; + if (state != nullptr) { + mRecycledList = state->next; + } else { + state = new KeyState(); + } + state->deviceId = deviceId; + state->source = source; + state->keyCode = keyCode; + state->unhandled = false; + return state; +} + +void InputEventConsistencyVerifier::KeyState::recycle() { + next = mRecycledList; + mRecycledList = next; +} + +}/*endof namespace*/ diff --git a/src/gui/view/inputeventconsistencyverifier.h b/src/gui/view/inputeventconsistencyverifier.h new file mode 100755 index 00000000..49f4f846 --- /dev/null +++ b/src/gui/view/inputeventconsistencyverifier.h @@ -0,0 +1,196 @@ +#ifndef __INPUTEVENT_CONSISTENCYVERIFIER_H__ +#define __INPUTEVENT_CONSISTENCYVERIFIER_H__ +#include +#include +#include +namespace cdroid{ +class Object; +class InputEventConsistencyVerifier { +private: + class KeyState; + // The number of recent events to log when a problem is detected. + // Can be set to 0 to disable logging recent events but the runtime overhead of + // this feature is negligible on current hardware. + static constexpr int RECENT_EVENTS_TO_LOG = 5; + + // The object to which the verifier is attached. + Object* mCaller; + + // Consistency verifier flags. + int mFlags; + + // Tag for logging which a client can set to help distinguish the output + // from different verifiers since several can be active at the same time. + // If not provided defaults to the simple class name. + std::string mLogTag; + + // The most recently checked event and the nesting level at which it was checked. + // This is only set when the verifier is called from a nesting level greater than 0 + // so that the verifier can detect when it has been asked to verify the same event twice. + // It does not make sense to examine the contents of the last event since it may have + // been recycled. + int mLastEventSeq; + std::string mLastEventType; + int mLastNestingLevel; + + // Copy of the most recent events. + std::vector mRecentEvents; + std::vector mRecentEventsUnhandled; + int mMostRecentEventIndex; + + // Current event and its type. + InputEvent* mCurrentEvent; + std::string mCurrentEventType; + + // Linked list of key state objects. + KeyState* mKeyStateList; + + // Current state of the trackball. + bool mTrackballDown; + bool mTrackballUnhandled; + + // Bitfield of pointer ids that are currently down. + // Assumes that the largest possible pointer id is 31, which is potentially subject to change. + // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + int mTouchEventStreamPointers; + + // The device id and source of the current stream of touch events. + int mTouchEventStreamDeviceId = -1; + int mTouchEventStreamSource; + + // Set to true when we discover that the touch event stream is inconsistent. + // Reset on down or cancel. + bool mTouchEventStreamIsTainted; + + // Set to true if the touch event stream is partially unhandled. + bool mTouchEventStreamUnhandled; + + // Set to true if we received hover enter. + bool mHoverEntered; + + // The bitset of buttons which we've received ACTION_BUTTON_PRESS for. + int mButtonsPressed; + + // The current violation message. + std::ostringstream mViolationMessage; + + /** + * Indicates that the verifier is intended to act on raw device input event streams. + * Disables certain checks for invariants that are established by the input dispatcher + * itself as it delivers input events, such as key repeating behavior. + */ +private: + void ensureMetaStateIsNormalized(int metaState); + void ensurePointerCountIsOneForThisAction(MotionEvent& event); + void ensureActionButtonIsNonZeroForThisAction(MotionEvent& event); + void ensureHistorySizeIsZeroForThisAction(MotionEvent& event); + bool startEvent(InputEvent& event, int nestingLevel, const std::string& eventType); + void finishEvent(); + static void appendEvent(std::ostringstream& message, int index,const InputEvent& event, bool unhandled); + void problem(const std::string& message,...); + KeyState* findKeyState(int deviceId, int source, int keyCode, bool remove); + void addKeyState(int deviceId, int source, int keyCode); +public: + static const int FLAG_RAW_DEVICE_INPUT = 1 << 0; + + + /** + * Creates an input consistency verifier. + * @param caller The object to which the verifier is attached. + * @param flags Flags to the verifier, or 0 if none. + * @param logTag Tag for logging. If null defaults to the short class name. + */ + InputEventConsistencyVerifier(Object* caller, int flags); + + /** + * Determines whether the instrumentation should be enabled. + * @return True if it should be enabled. + */ + static bool isInstrumentationEnabled(); + void reset(); + + /** + * Checks an arbitrary input event. + * @param event The event. + * @param 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. + */ + void onInputEvent(InputEvent& event, int nestingLevel); + /** + * Checks a key event. + * @param event The event. + * @param 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. + */ + void onKeyEvent(KeyEvent& event, int nestingLevel); + + /** + * Checks a trackball event. + * @param event The event. + * @param 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. + */ + void onTrackballEvent(MotionEvent& event, int nestingLevel); + + /** + * Checks a touch event. + * @param event The event. + * @param 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. + */ + void onTouchEvent(MotionEvent& event, int nestingLevel); + /** + * Checks a generic motion event. + * @param event The event. + * @param 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. + */ + void onGenericMotionEvent(MotionEvent& event, int nestingLevel); + + /** + * Notifies the verifier that a given event was unhandled and the rest of the + * trace for the event should be ignored. + * This method should only be called if the event was previously checked by + * the consistency verifier using {@link #onInputEvent} and other methods. + * @param event The event. + * @param 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. + */ + void onUnhandledEvent(InputEvent& event, int nestingLevel); +}; + +class InputEventConsistencyVerifier::KeyState { +private: + static KeyState* mRecycledList; +private: + friend InputEventConsistencyVerifier; + KeyState(); +public: + KeyState* next; + int deviceId; + int source; + int keyCode; + bool unhandled; + static KeyState* obtain(int deviceId, int source, int keyCode); + void recycle(); +}; +}/*endof namespace*/ +#endif/*__INPUTEVENT_CONSISTENCYVERIFIER_H__*/ diff --git a/src/gui/view/velocitytracker.cc b/src/gui/view/velocitytracker.cc index 202ef8a4..b087334f 100755 --- a/src/gui/view/velocitytracker.cc +++ b/src/gui/view/velocitytracker.cc @@ -316,7 +316,7 @@ void VelocityTrackerImpl::addMovement(const MotionEvent& event) { size_t historySize = event.getHistorySize(); memset(positions,0,sizeof(positions)); for (size_t h = 0; h < historySize; h++) { - eventTime = event.getHistoricalEventTimeNano(h); + eventTime = event.getHistoricalEventTimeNanos(h); for (size_t i = 0; i < pointerCount; i++) { uint32_t index = pointerIndex[i]; positions[index].x = event.getHistoricalRawX(i, h); @@ -325,7 +325,7 @@ void VelocityTrackerImpl::addMovement(const MotionEvent& event) { addMovement(eventTime, idBits, positions); } - eventTime = event.getEventTimeNano(); + eventTime = event.getEventTimeNanos(); for (size_t i = 0; i < pointerCount; i++) { uint32_t index = pointerIndex[i]; positions[index].x = event.getRawX(i); diff --git a/src/gui/view/view.cc b/src/gui/view/view.cc index 649ccc0d..8c868b10 100755 --- a/src/gui/view/view.cc +++ b/src/gui/view/view.cc @@ -318,6 +318,9 @@ void View::initView(){ mPendingCheckForTap = nullptr; mUnsetPressedState = nullptr;; mPendingCheckForLongPress = nullptr; + mInputEventConsistencyVerifier = nullptr; + if(InputEventConsistencyVerifier::isInstrumentationEnabled()) + mInputEventConsistencyVerifier = new InputEventConsistencyVerifier(nullptr,0); mRenderNode = new RenderNode(); mScrollX = mScrollY = 0; @@ -396,6 +399,7 @@ View::~View(){ delete mTooltipInfo; delete mScrollIndicatorDrawable; delete mDefaultFocusHighlight; + delete mInputEventConsistencyVerifier; delete mBackground; delete mBackgroundTint; @@ -5713,12 +5717,16 @@ KeyEvent::DispatcherState* View::getKeyDispatcherState()const{ } bool View::dispatchKeyEvent(KeyEvent&event){ + if(mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onKeyEvent(event,0); if (mListenerInfo && mListenerInfo->mOnKeyListener && (mViewFlags & ENABLED_MASK) == ENABLED && mListenerInfo->mOnKeyListener(*this, event.getKeyCode(), event)) { return true; } const bool result = event.dispatch(this,(mAttachInfo? &mAttachInfo->mKeyDispatchState : nullptr),this); LOGV("%s.%s=%d",event.getLabel(event.getKeyCode()),KeyEvent::actionToString(event.getAction()).c_str(),result); + if(mInputEventConsistencyVerifier && (result==false)) + mInputEventConsistencyVerifier->onUnhandledEvent(event, 0); return result; } @@ -5899,6 +5907,8 @@ bool View::dispatchGenericMotionEventInternal(MotionEvent& event){ } break; } + if(mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onUnhandledEvent(event, 0); return false; } @@ -5936,7 +5946,9 @@ bool View::pointInHoveredChild(MotionEvent& event) { } bool View::dispatchGenericMotionEvent(MotionEvent&event){ - int source = event.getSource(); + const int source = event.getSource(); + if (mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onGenericMotionEvent(event, 0); if ((source & InputDevice::SOURCE_CLASS_POINTER) != 0) { int action = event.getAction(); if (action == MotionEvent::ACTION_HOVER_ENTER @@ -5954,12 +5966,17 @@ bool View::dispatchGenericMotionEvent(MotionEvent&event){ if (dispatchGenericMotionEventInternal(event)) { return true; } + if (mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onUnhandledEvent(event, 0); return false; } bool View::dispatchTouchEvent(MotionEvent&event){ bool result = false; const int actionMasked = event.getActionMasked(); + if (mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onTouchEvent(event, 0); + if (actionMasked == MotionEvent::ACTION_UP || actionMasked == MotionEvent::ACTION_CANCEL || (actionMasked == MotionEvent::ACTION_DOWN && !result)) { @@ -5979,7 +5996,8 @@ bool View::dispatchTouchEvent(MotionEvent&event){ if(!result&& onTouchEvent(event)){ result=true; } - + if (!result && mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onUnhandledEvent(event, 0); if (actionMasked == MotionEvent::ACTION_UP || actionMasked == MotionEvent::ACTION_CANCEL || (actionMasked == MotionEvent::ACTION_DOWN && !result)) { diff --git a/src/gui/view/view.h b/src/gui/view/view.h index 1aeb8806..9a941e4e 100755 --- a/src/gui/view/view.h +++ b/src/gui/view/view.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -357,6 +358,7 @@ private: bool mBoundsChangedmDefaultFocusHighlightSizeChanged; ViewOverlay* mOverlay; + InputEventConsistencyVerifier* mInputEventConsistencyVerifier; ViewTreeObserver* mFloatingTreeObserver; StateListAnimator* mStateListAnimator; ViewPropertyAnimator* mAnimator; diff --git a/src/gui/view/viewgroup.cc b/src/gui/view/viewgroup.cc index 8de46ee8..7a75cd85 100755 --- a/src/gui/view/viewgroup.cc +++ b/src/gui/view/viewgroup.cc @@ -2843,6 +2843,8 @@ void ViewGroup::onDescendantInvalidated(View* child,View* target){ } bool ViewGroup::dispatchKeyEvent(KeyEvent&event){ + if (mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onKeyEvent(event, 1); if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { if (View::dispatchKeyEvent(event)) { @@ -2854,6 +2856,8 @@ bool ViewGroup::dispatchKeyEvent(KeyEvent&event){ return true; } } + if (mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onUnhandledEvent(event, 1); return View::dispatchKeyEvent(event); } @@ -2876,6 +2880,9 @@ bool ViewGroup::dispatchTouchEvent(MotionEvent&ev){ const float scrolledXFloat = xf + mScrollX; const float scrolledYFloat = yf + mScrollY; + if (mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onTouchEvent(ev, 1); + if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } @@ -3010,7 +3017,8 @@ bool ViewGroup::dispatchTouchEvent(MotionEvent&ev){ int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } - + if (!handled && mInputEventConsistencyVerifier) + mInputEventConsistencyVerifier->onUnhandledEvent(ev, 1); return handled; }