So, to put it simply, a handler I was using for multitouch would crash and throw an "IllegalArgumentException: pointerIndex out of range".
I tried just catching the error, but then realized that the (x, y) would default to (min.x, max.y), which was (0.0, 480.0) in my case.
After quite a bit of messing around, I found the error only cropped up while spamming touches, but could be specifically and very regularly reproduced simply by alternately tapping in two different places quickly enough(but still slow enough to happen during a regular multitouch game).
Here's the onTouch method:
public boolean onTouch(View v, MotionEvent event) {
synchronized (this) {
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
int pointerId = event.getPointerId(pointerIndex);
TouchEvent touchEvent;
// pointerId fix
if (pointerId >= event.getPointerCount() && pointerId > 0)
pointerId--;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_DOWN;
touchEvent.pointer = pointerId;
try {
touchEvent.x = touchX[pointerId] = (int) (event
.getX(pointerId) * scaleX);
touchEvent.y = touchY[pointerId] = (int) (event
.getY(pointerId) * scaleY);
} catch (IllegalArgumentException e) {
Log.d("multiTouchHandler", e.toString() + ":: PID = "
+ pointerId);
}
isTouched[pointerId] = true;
touchEventsBuffer.add(touchEvent);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_UP;
touchEvent.pointer = pointerId;
try {
touchEvent.x = touchX[pointerId] = (int) (event
.getX(pointerIndex) * scaleX);
touchEvent.y = touchY[pointerId] = (int) (event
.getY(pointerIndex) * scaleY);
} catch (IllegalArgumentException e) {
Log.d("multiTouchHandler", e.toString() + ":: PID = "
+ pointerId);
}
isTouched[pointerId] = false;
touchEventsBuffer.add(touchEvent);
return false;
case MotionEvent.ACTION_MOVE:
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_DRAGGED;
touchEvent.pointer = pointerId;
try {
touchEvent.x = touchX[pointerId] = (int) (event
.getX(pointerIndex) * scaleX);
touchEvent.y = touchY[pointerId] = (int) (event
.getY(pointerIndex) * scaleY);
} catch (IllegalArgumentException e) {
Log.d("multiTouchHandler", e.toString() + ":: PID = "
+ pointerId);
}
isTouched[pointerId] = false;
touchEventsBuffer.add(touchEvent);
}
break;
case MotionEvent.ACTION_CANCEL:
}
return true;
}
}
What it seemed was happening from the Log.d output was that occasionally the pointerId would get assigned to the next available pointer(ex. 1), but the actually pointer data would get stored in the previous pointer location(0).
By checking if the pointerId was out of range of the current pointerCount, and if so going back one, I was able to at the very least fix this issue for two fingers with two lines of code.
// pointerId fix
if (pointerId >= event.getPointerCount() && pointerId > 0)
pointerId--;
I'm not sure if this really fixes the issue, but it seems to work great for two-finger multitouch controls like those used for touch screen fps games. My own tests showed that it accurately was able to determine touch from both points without exceptions, and store the correct (x, y) values
Is there any better solution, or is the multitouch API use of pointer IDs just really that bad? If anyone wants to know, this was on a 4.0.3 device.
It seems strange to me that you access event.getX() and event.getY() with pointerId as the argument (at ACTION_POINTER_DOWN). I think it should be pointerIndex instead, as you do in all other cases further down.