Merge "Track unhandled input events in consistency verifiers."
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index f284f51..1ccc66f 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -590,8 +590,14 @@
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
+
case MotionEvent.ACTION_CANCEL:
cancel();
+ break;
+ }
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 6618f07..b5ca2c2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -54,6 +54,7 @@
// Copy of the most recent events.
private InputEvent[] mRecentEvents;
+ private boolean[] mRecentEventsUnhandled;
private int mMostRecentEventIndex;
// Current event and its type.
@@ -65,6 +66,7 @@
// Current state of the trackball.
private boolean mTrackballDown;
+ private boolean mTrackballUnhandled;
// Bitfield of pointer ids that are currently down.
// Assumes that the largest possible pointer id is 31, which is potentially subject to change.
@@ -79,6 +81,9 @@
// Reset on down or cancel.
private boolean mTouchEventStreamIsTainted;
+ // Set to true if the touch event stream is partially unhandled.
+ private boolean mTouchEventStreamUnhandled;
+
// Set to true if we received hover enter.
private boolean mHoverEntered;
@@ -117,9 +122,17 @@
mLastEvent = null;
mLastNestingLevel = 0;
mTrackballDown = false;
+ mTrackballUnhandled = false;
mTouchEventStreamPointers = 0;
mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
mHoverEntered = false;
+
+ while (mKeyStateList != null) {
+ final KeyState state = mKeyStateList;
+ mKeyStateList = state.next;
+ state.recycle();
+ }
}
/**
@@ -176,7 +189,9 @@
// 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 ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+ 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.");
@@ -229,10 +244,11 @@
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
switch (action) {
case MotionEvent.ACTION_DOWN:
- if (mTrackballDown) {
+ if (mTrackballDown && !mTrackballUnhandled) {
problem("ACTION_DOWN but trackball is already down.");
} else {
mTrackballDown = true;
+ mTrackballUnhandled = false;
}
ensureHistorySizeIsZeroForThisAction(event);
ensurePointerCountIsOneForThisAction(event);
@@ -242,6 +258,7 @@
problem("ACTION_UP but trackball is not down.");
} else {
mTrackballDown = false;
+ mTrackballUnhandled = false;
}
ensureHistorySizeIsZeroForThisAction(event);
ensurePointerCountIsOneForThisAction(event);
@@ -285,11 +302,13 @@
final int action = event.getAction();
final boolean newStream = action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_CANCEL;
- if (mTouchEventStreamIsTainted) {
+ if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
if (newStream) {
mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mTouchEventStreamPointers = 0;
} else {
- finishEvent(true);
+ finishEvent(mTouchEventStreamIsTainted);
return;
}
}
@@ -467,6 +486,48 @@
}
}
+ /**
+ * 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.
+ */
+ public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+ if (nestingLevel != mLastNestingLevel) {
+ return;
+ }
+
+ if (mRecentEventsUnhandled != null) {
+ mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+ }
+
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ final int deviceId = keyEvent.getDeviceId();
+ final int source = keyEvent.getSource();
+ final int keyCode = keyEvent.getKeyCode();
+ final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ state.unhandled = true;
+ }
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ mTouchEventStreamUnhandled = true;
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (mTrackballDown) {
+ mTrackballUnhandled = true;
+ }
+ }
+ }
+ }
+
private void ensureMetaStateIsNormalized(int metaState) {
final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
if (normalizedMetaState != metaState) {
@@ -518,7 +579,8 @@
private void finishEvent(boolean tainted) {
if (mViolationMessage != null && mViolationMessage.length() != 0) {
mViolationMessage.append("\n in ").append(mCaller);
- mViolationMessage.append("\n ").append(mCurrentEvent);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, 0, mCurrentEvent, false);
if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
mViolationMessage.append("\n -- recent events --");
@@ -529,7 +591,8 @@
if (event == null) {
break;
}
- mViolationMessage.append("\n ").append(i + 1).append(": ").append(event);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
}
}
@@ -547,6 +610,7 @@
if (RECENT_EVENTS_TO_LOG != 0) {
if (mRecentEvents == null) {
mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+ mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
}
final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
mMostRecentEventIndex = index;
@@ -554,12 +618,23 @@
mRecentEvents[index].recycle();
}
mRecentEvents[index] = mCurrentEvent.copy();
+ mRecentEventsUnhandled[index] = false;
}
mCurrentEvent = null;
mCurrentEventType = null;
}
+ private static void appendEvent(StringBuilder message, int index,
+ InputEvent event, boolean unhandled) {
+ message.append(index).append(": sent at ").append(event.getEventTimeNano());
+ message.append(", ");
+ if (unhandled) {
+ message.append("(unhandled) ");
+ }
+ message.append(event);
+ }
+
private void problem(String message) {
if (mViolationMessage == null) {
mViolationMessage = new StringBuilder();
@@ -608,6 +683,7 @@
public int deviceId;
public int source;
public int keyCode;
+ public boolean unhandled;
private KeyState() {
}
@@ -625,6 +701,7 @@
state.deviceId = deviceId;
state.source = source;
state.keyCode = keyCode;
+ state.unhandled = false;
return state;
}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 456857a..5e07e1a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -183,15 +183,15 @@
}
final int action = event.getActionMasked();
- boolean handled = true;
if (action == MotionEvent.ACTION_DOWN) {
reset(); // Start fresh
}
- if (mInvalidGesture) return false;
-
- if (!mGestureInProgress) {
+ boolean handled = true;
+ if (mInvalidGesture) {
+ handled = false;
+ } else if (!mGestureInProgress) {
switch (action) {
case MotionEvent.ACTION_DOWN: {
mActiveId0 = event.getPointerId(0);
@@ -467,6 +467,10 @@
break;
}
}
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
return handled;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index dc8e52f..fe8af19 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4614,8 +4614,15 @@
return true;
}
- return event.dispatch(this, mAttachInfo != null
- ? mAttachInfo.mKeyDispatchState : null, this);
+ if (event.dispatch(this, mAttachInfo != null
+ ? mAttachInfo.mKeyDispatchState : null, this)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4640,16 +4647,22 @@
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
+ if (onFilterTouchEventForSecurity(event)) {
+ //noinspection SimplifiableIfStatement
+ if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+ mOnTouchListener.onTouch(this, event)) {
+ return true;
+ }
+
+ if (onTouchEvent(event)) {
+ return true;
+ }
}
- //noinspection SimplifiableIfStatement
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
- return onTouchEvent(event);
+ return false;
}
/**
@@ -4682,7 +4695,14 @@
}
//Log.i("view", "view=" + this + ", " + event.toString());
- return onTrackballEvent(event);
+ if (onTrackballEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4723,7 +4743,15 @@
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
return true;
}
- return onGenericMotionEvent(event);
+
+ if (onGenericMotionEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0d4f3d0..08daa28 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1131,9 +1131,17 @@
}
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchKeyEvent(event);
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchKeyEvent(event);
+ if (mFocused.dispatchKeyEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@@ -1161,9 +1169,17 @@
}
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchTrackballEvent(event);
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchTrackballEvent(event);
+ if (mFocused.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@@ -1344,155 +1360,158 @@
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
- if (!onFilterTouchEventForSecurity(ev)) {
- return false;
- }
+ boolean handled = false;
+ if (onFilterTouchEventForSecurity(ev)) {
+ final int action = ev.getAction();
+ final int actionMasked = action & MotionEvent.ACTION_MASK;
- final int action = ev.getAction();
- final int actionMasked = action & MotionEvent.ACTION_MASK;
-
- // Handle an initial down.
- if (actionMasked == MotionEvent.ACTION_DOWN) {
- // Throw away all previous state when starting a new touch gesture.
- // The framework may have dropped the up or cancel event for the previous gesture
- // due to an app switch, ANR, or some other state change.
- cancelAndClearTouchTargets(ev);
- resetTouchState();
- }
-
- // Check for interception.
- final boolean intercepted;
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (!disallowIntercept) {
- intercepted = onInterceptTouchEvent(ev);
- ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
- } else {
- intercepted = false;
+ // Handle an initial down.
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Throw away all previous state when starting a new touch gesture.
+ // The framework may have dropped the up or cancel event for the previous gesture
+ // due to an app switch, ANR, or some other state change.
+ cancelAndClearTouchTargets(ev);
+ resetTouchState();
}
- } else {
- // There are no touch targets and this action is not an initial down
- // so this view group continues to intercept touches.
- intercepted = true;
- }
- // Check for cancelation.
- final boolean canceled = resetCancelNextUpFlag(this)
- || actionMasked == MotionEvent.ACTION_CANCEL;
-
- // Update list of touch targets for pointer down, if needed.
- final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
- TouchTarget newTouchTarget = null;
- boolean alreadyDispatchedToNewTouchTarget = false;
- if (!canceled && !intercepted) {
+ // Check for interception.
+ final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
- || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- final int actionIndex = ev.getActionIndex(); // always 0 for down
- final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
- : TouchTarget.ALL_POINTER_IDS;
+ || mFirstTouchTarget != null) {
+ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ if (!disallowIntercept) {
+ intercepted = onInterceptTouchEvent(ev);
+ ev.setAction(action); // restore action in case it was changed
+ } else {
+ intercepted = false;
+ }
+ } else {
+ // There are no touch targets and this action is not an initial down
+ // so this view group continues to intercept touches.
+ intercepted = true;
+ }
- // Clean up earlier touch targets for this pointer id in case they
- // have become out of sync.
- removePointersFromTouchTargets(idBitsToAssign);
+ // Check for cancelation.
+ final boolean canceled = resetCancelNextUpFlag(this)
+ || actionMasked == MotionEvent.ACTION_CANCEL;
- final int childrenCount = mChildrenCount;
- if (childrenCount != 0) {
- // Find a child that can receive the event. Scan children from front to back.
- final View[] children = mChildren;
- final float x = ev.getX(actionIndex);
- final float y = ev.getY(actionIndex);
+ // Update list of touch targets for pointer down, if needed.
+ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+ TouchTarget newTouchTarget = null;
+ boolean alreadyDispatchedToNewTouchTarget = false;
+ if (!canceled && !intercepted) {
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ final int actionIndex = ev.getActionIndex(); // always 0 for down
+ final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+ : TouchTarget.ALL_POINTER_IDS;
- for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
- if (!canViewReceivePointerEvents(child)
- || !isTransformedTouchPointInView(x, y, child, null)) {
+ // Clean up earlier touch targets for this pointer id in case they
+ // have become out of sync.
+ removePointersFromTouchTargets(idBitsToAssign);
+
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ // Find a child that can receive the event.
+ // Scan children from front to back.
+ final View[] children = mChildren;
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ // Child is already receiving touch within its bounds.
+ // Give it the new pointer in addition to the ones it is handling.
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ break;
+ }
+
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+ // Child wants to receive touch within its bounds.
+ mLastTouchDownTime = ev.getDownTime();
+ mLastTouchDownIndex = i;
+ mLastTouchDownX = ev.getX();
+ mLastTouchDownY = ev.getY();
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ break;
+ }
+ }
+ }
+
+ if (newTouchTarget == null && mFirstTouchTarget != null) {
+ // Did not find a child to receive the event.
+ // Assign the pointer to the least recently added target.
+ newTouchTarget = mFirstTouchTarget;
+ while (newTouchTarget.next != null) {
+ newTouchTarget = newTouchTarget.next;
+ }
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ }
+ }
+ }
+
+ // Dispatch to touch targets.
+ if (mFirstTouchTarget == null) {
+ // No touch targets so treat this as an ordinary view.
+ handled = dispatchTransformedTouchEvent(ev, canceled, null,
+ TouchTarget.ALL_POINTER_IDS);
+ } else {
+ // Dispatch to touch targets, excluding the new touch target if we already
+ // dispatched to it. Cancel touch targets if necessary.
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+ handled = true;
+ } else {
+ final boolean cancelChild = resetCancelNextUpFlag(target.child)
+ || intercepted;
+ if (dispatchTransformedTouchEvent(ev, cancelChild,
+ target.child, target.pointerIdBits)) {
+ handled = true;
+ }
+ if (cancelChild) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
continue;
}
-
- newTouchTarget = getTouchTarget(child);
- if (newTouchTarget != null) {
- // Child is already receiving touch within its bounds.
- // Give it the new pointer in addition to the ones it is handling.
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- break;
- }
-
- resetCancelNextUpFlag(child);
- if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
- // Child wants to receive touch within its bounds.
- mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = i;
- mLastTouchDownX = ev.getX();
- mLastTouchDownY = ev.getY();
- newTouchTarget = addTouchTarget(child, idBitsToAssign);
- alreadyDispatchedToNewTouchTarget = true;
- break;
- }
}
+ predecessor = target;
+ target = next;
}
+ }
- if (newTouchTarget == null && mFirstTouchTarget != null) {
- // Did not find a child to receive the event.
- // Assign the pointer to the least recently added target.
- newTouchTarget = mFirstTouchTarget;
- while (newTouchTarget.next != null) {
- newTouchTarget = newTouchTarget.next;
- }
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- }
+ // Update list of touch targets for pointer up or cancel, if needed.
+ if (canceled
+ || actionMasked == MotionEvent.ACTION_UP
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ resetTouchState();
+ } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ final int actionIndex = ev.getActionIndex();
+ final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+ removePointersFromTouchTargets(idBitsToRemove);
}
}
- // Dispatch to touch targets.
- boolean handled = false;
- if (mFirstTouchTarget == null) {
- // No touch targets so treat this as an ordinary view.
- handled = dispatchTransformedTouchEvent(ev, canceled, null,
- TouchTarget.ALL_POINTER_IDS);
- } else {
- // Dispatch to touch targets, excluding the new touch target if we already
- // dispatched to it. Cancel touch targets if necessary.
- TouchTarget predecessor = null;
- TouchTarget target = mFirstTouchTarget;
- while (target != null) {
- final TouchTarget next = target.next;
- if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
- handled = true;
- } else {
- final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
- if (dispatchTransformedTouchEvent(ev, cancelChild,
- target.child, target.pointerIdBits)) {
- handled = true;
- }
- if (cancelChild) {
- if (predecessor == null) {
- mFirstTouchTarget = next;
- } else {
- predecessor.next = next;
- }
- target.recycle();
- target = next;
- continue;
- }
- }
- predecessor = target;
- target = next;
- }
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
-
- // Update list of touch targets for pointer up or cancel, if needed.
- if (canceled
- || actionMasked == MotionEvent.ACTION_UP
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- resetTouchState();
- } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
- final int actionIndex = ev.getActionIndex();
- final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
- removePointersFromTouchTargets(idBitsToRemove);
- }
-
return handled;
}