Android testing, how to simulate multitouch (zoom in/out) using instruments? - android

I can simply simulate single touches - tap, swipe, hold etc in my tests, but completely stuck with simulating multi touch on HTС Desire with Android 2.2.
Could you please advise, how can I reproduce events chain to test multi-touches?
I think I need to use some tricky kind of MotionEvent like MASK or something like this, but have no idea how do this.
I have found here a dump of events of reproduced zooming:
http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-3-understanding-touch-events/1775?tag=mantle_skin;content
1. event ACTION_DOWN[#0(pid 0)=135,179]
2. event ACTION_MOVE[#0(pid 0)=135,184]
3. event ACTION_MOVE[#0(pid 0)=144,205]
4. event ACTION_MOVE[#0(pid 0)=152,227]
5. event ACTION_POINTER_DOWN(pid 1)[#0(pid 0)=153,230;#1(pid 1)=380,538]
6. event ACTION_MOVE[#0(pid 0)=153,231;#1(pid 1)=380,538]
7. event ACTION_MOVE[#0(pid 0)=155,236;#1(pid 1)=364,512]
8. event ACTION_MOVE[#0(pid 0)=157,240;#1(pid 1)=350,498]
9. event ACTION_MOVE[#0(pid 0)=158,245;#1(pid 1)=343,494]
10. event ACTION_POINTER_UP(pid 0)[#0(pid 0)=158,247;#1(pid 1)=336,484]
11. event ACTION_MOVE[#0(pid 1)=334,481]
12. event ACTION_MOVE[#0(pid 1)=328,472]
13. event ACTION_UP[#0(pid 1)=327,471]
Here is my issue:
event ACTION_POINTER_DOWN(pid 1)[#0(pid 0)=153,230;#1(pid 1)=380,538]
event ACTION_MOVE[#0**(pid 0)=153,231**;#1**(pid 1)=380,538**]
How can I generate events with 4 coordinates (pid 0 x0 y0 and pid 1 x1 y1)?
Looks like I need to find the way how to use following event:
public static MotionEvent obtain (long downTime, long eventTime, int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags, int source, int flags)
Thanks to Dan for reply, I have tried this logic, but still encountering problems to add coordinates:
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, 135, 179, 0);
inst.sendPointerSync(event);
// eventTime+=100;
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, 135, 184, 0);
inst.sendPointerSync(event);
// eventTime+=100;
int pointerToMove = 1; // pointer IDs are zero-based
event = MotionEvent.obtain(downTime, eventTime, (pointerToMove << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_DOWN, 138, 189, 0);
inst.sendPointerSync(event);
event = MotionEvent.obtain(downTime, eventTime, (pointerToMove << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_MOVE, 158, 220, 0);
inst.sendPointerSync(event);
// eventTime+=100;
event = MotionEvent.obtain(downTime, eventTime, (2 * 256) + MotionEvent.ACTION_MOVE, 138, 180, 0);
inst.sendPointerSync(event);
// eventTime+=100;
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, 135, 184, 0);
These events sequence are caught in my test stub and dumped like:
(14368): event ACTION_DOWN[#0(pid 0)=135,179]
(14368): event ACTION_MOVE[#0(pid 0)=135,184]
(14368): event ACTION_POINTER_DOWN(pid 1)[#0(pid 0)=138,189]
(14368): event ACTION_MOVE[#0(pid 0)=158,220]
(14368): event ACTION_MOVE[#0(pid 0)=138,180]
(14368): event ACTION_MOVE[#0(pid 0)=135,184]
Here you can see, that
(2 * 256) + MotionEvent.ACTION_MOVE
doesn't change the pointer ID for event :(
And
pointerToMove << MotionEvent.ACTION_POINTER_INDEX_SHIFT
approach is not working for ACTION_POINTER_DOWN, may be I'm not allowed to use such way for POINTER_DOWN?
My issue is that I can't generate 2 pair of coords for Pointer 0 and Pointer 1:
(14368): event ACTION_POINTER_DOWN(pid 1)[#0(pid 0)=138,189]
Here you can see, that using your logic I have added pid1 to the event, but it is still has no coordinates, cause x and y was associated with pid 0..
Thank you in advance.
Yahor
Still have no ideas how to implement it, did somebody ever send a multitouch event?

I believe you just need to indicate the pointer index in the 'action' parameter passed to MotionEvent.obtain. Specifically, the upper 8-bits of the action is the pointer index and the lower 8-bits is the action (e.g. MotionEvent.ACTION_MOVE). So, if you want to move the second pointer this should work:
int pointerToMove = 1; // pointer IDs are zero-based
event = MotionEvent.obtain(downTime, eventTime, (pointerToMove << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_MOVE, x0, y0, 0);
inst.sendPointerSync(event);
-Dan

I supposed this can help you..

Related

Trigger ACTION_POINTER_DOWN event programmatically

want to trigger on two fingers down event for a few milliseconds.
I am able to trigger simple touch event by using this code:
objImageView.getLocationOnScreen(coords);
int xa = coords[0];
int ys = coords[1];
objImageView.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, xa, ys, 0.5f, 5, 0, 1, 1, 0, 0));
objImageView.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, xa, ys, 0.5f, 5, 0, 1, 1, 0, 0));
objImageView.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, xa, ys, 0.5f, 5, 0, 1, 1, 0, 0));
Now if I try to do the same for ACTION_POINTER_DOWN nothing is happening. Here is my code:
int[] coords = new int[2];
parentLayout.getLocationOnScreen(coords);
int xa = coords[0];
int ys = coords[1];
parentLayout.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_POINTER_DOWN, xa, ys, 0.5f, 5, 0, 1, 1, 0, 0));
parentLayout.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, xa, ys, 0.5f, 5, 0, 1, 1, 0, 0));
parentLayout.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_POINTER_UP, xa, ys, 0.5f, 5, 0, 1, 1, 0, 0));
I want to trigger the same event which called when user places the two fingers down and hold fingers there for a few milliseconds.
To simulate "two fingers" you will need to create a motion event that includes more than one pointer, as you do now.
To do that, use the MotionEvent obtain variation that supports multiple pointers as is documented here.
MotionEvent obtain (long downTime,
long eventTime,
int action,
int pointerCount,
PointerProperties[] pointerProperties,
PointerCoords[] pointerCoords,
int metaState,
int buttonState,
float xPrecision,
float yPrecision,
int deviceId,
int edgeFlags,
int source,
int flags)
First you should dispatch an event for the first finger (exactly like you did) and when you are about to simulate MotionEvent.ACTION_POINTER_DOWNyou should use the above obtain variation.
Fields are pretty self explanatory, make sure the params are:
pointerCount = 2
pointerProperties = [firstFingerPointerProperties, secondFingerPointerProperties]
pointerCoords = [firstFingerPointerCoords, secondFingerPointerCoords]
Documentation said that: "A gesture starts with a motion event with ACTION_DOWN that provides the location of the first pointer down. As each additional pointer that goes down or up, the framework will generate a motion event with ACTION_POINTER_DOWN or ACTION_POINTER_UP accordingly." so You need trigger simple touch event at first. And take a look at this question.
You should generate a sequence like this: ACTION_DOWN -> ACTION_POINTER_DOWN -> optionally small delay -> ACTION_POINTER_UP -> ACTION_UP.
You should also set pointer index (and other metadata) correctly for second pointer, because otherwise the gesture detection code may be unable to tell that ACTION_POINTER_DOWN for secondary press is describing a secondary finger, not primary.
Use this method to fill in all necessary information for ACTION_POINTER_DOWN describing second press. Make sure to pass correct pointerProperties!
This is an old thread, but since, none of the answers were complete, adding a bit more info here for future folks. Like the author I wanted simulate two touches, and even though my sequence wa:
ACTION_DOWN(0), PointerCount = 1
ACTION_POINTER_DOWN(5), PointerCount = 2
ACTION_POINTER_UP(6), PointerCount = 2
ACTION_UP(1), PointerCount = 1
It was simply not working, I only then understood, that you need to shift action by pointer count.
if ((action == ACTION_POINTER_DOWN || action == ACTION_POINTER_UP) && pointerCount > 1)
action |= 256 << (pointerCount - 2);
In other words, the action values become
ACTION_DOWN(0), PointerCount = 1
ACTION_POINTER_DOWN(261), PointerCount = 2
ACTION_POINTER_UP(262), PointerCount = 2
ACTION_UP(1), PointerCount = 1
Only then it started working for me

Programmatically animate Android BrokenView

I was going through some of the awesome Android libraries and I found the Android BrokenView. It's quite simple to use and uses the touch events to show the broken animation. Here's the code that we're required to add in order to enable a View to behave as a BrokenView:
brokenView = BrokenView.add2Window(context);
listener = new BrokenTouchListener.Builder(brokenView).build();
view.setOnTouchListener(listener);
The problem that I've is that I want to perform the broken animation programmatically i.e, without any actual touch events. I tried looking at the source code but couldn't figure out how to achieve that as most of the methods are protected.
Any ideas on how can I do that?
I've achieved this using a motion event. It works, but I'd prefer to call createAnimator() directly, as I don't want the user to be able to trigger the smash via a touch event.
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis()+1000;
int[] coords = new int[2];
targetView.getLocationOnScreen(coords);
float x = coords[0] + targetView.getWidth()/2;
float y = coords[1] + targetView.getHeight()/2;
MotionEvent motionEvent = MotionEvent.obtain(
downTime,
eventTime,
MotionEvent.ACTION_DOWN,
x,
y,
0
);
mBrokenTouchListener = new BrokenTouchListener.Builder(mBrokenView)
.setEnableArea(mRootView)
.build();
mBrokenTouchListener.onTouch(targetView, motionEvent);
What I'd like to do is this:
Point point = new Point((int)x, (int)y);
BrokenConfig config = new BrokenConfig();
brokenView.createAnimator(mFLTargetView, point, config);
brokenView.setEnable(true);
// or
brokenView.start();
but BrokenConfig is package private, and there's no start() method.
HTH

Drag and Drop Libgdx, without a Target

Im trying to implement the Drag And Drop method/class in libgdx, but what´s different for me is that I dont have a "Target" to drop my actor. I simply want to drag it around all over the screen. I haven´t really got any further than this.
dd = new DragAndDrop();
dd.addSource(new Source(stoneImage){
#Override
public Payload dragStart(InputEvent event, float x, float y, int pointer) {
Payload payload = new Payload();
payload.setObject("some payload");
payload.setDragActor(stoneImage);
return payload;
}
});
Here I set the Image as an actor:
stageMove.addActor(stoneImage);
At the moment it won´t move when I try to drag it.
I suggest you 2 easy ways to solve your problem.
1) Add a DragListener to your actor:
actor.addListener(new DragListener() {
public void drag(InputEvent event, float x, float y, int pointer) {
actor.moveBy(x - actor.getWidth() / 2, y - actor.getHeight() / 2);
}
});
2) Create a generic actor which size is filled to the stage, and use it as the target actor.

How to send out pointer event in Android

I'm trying to detect the virtual keyboard height in Android.
I found a similar topic: Get the height of virtual keyboard in Android
It seems the author found a way to detect the height:
I found a way to get it. After I request to open virtual keyboard, I
send pointer event that I generate. their y coordinate starts from
height of device and decreases.
I don't understand how to do that.
I'll be using the code provided at the link that you posted:
// Declare Variables
int softkeyboard_height = 0;
boolean calculated_keyboard_height;
Instrumentation instrumentation;
// Initialize instrumentation sometime before starting the thread
instrumentation = new Instrumentation();
mainScreenView is your base view, your activity's view. m(ACTION_DOWN) and m1(ACTION_UP) are touch events that are dispatched using Instrumentation#sendPointerSync(MotionEvent). The logic is that a MotionEvent dispatched to where the keyboard is being displayed will cause the following SecurityException:
java.lang.SecurityException: Injecting to another application requires
INJECT_EVENTS permission
So, we start at the bottom of the screen and make our way up (by decrementing y) on every iteration of the loop. For certain number of iterations, we will get a SecurityException (which we'll catch): this would imply that the MotionEvent is happening over the keyboard. The moment y gets small enough (when its just above the keyboard), we'll break out of the loop and calculate the keyboard's height using:
softkeyboard_height = mainScreenView.getHeight() - y;
Code:
Thread t = new Thread(){
public void run() {
int y = mainScreenView.getHeight()-2;
int x = 10;
int counter = 0;
int height = y;
while (true){
final MotionEvent m = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN,
x,
y,
1);
final MotionEvent m1 = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
x,
y,
1);
boolean pointer_on_softkeyboard = false;
try {
instrumentation.sendPointerSync(m);
instrumentation.sendPointerSync(m1);
} catch (SecurityException e) {
pointer_on_softkeyboard = true;
}
if (!pointer_on_softkeyboard){
if (y == height){
if (counter++ < 100){
Thread.yield();
continue;
}
} else if (y > 0){
softkeyboard_height = mainScreenView.getHeight() - y;
Log.i("", "Soft Keyboard's height is: " + softkeyboard_height);
}
break;
}
y--;
}
if (softkeyboard_height > 0 ){
// it is calculated and saved in softkeyboard_height
} else {
calculated_keyboard_height = false;
}
}
};
t.start();
Instrumentation#sendPointerSync(MotionEvent):
Dispatch a pointer event. Finished at some point after the recipient
has returned from its event processing, though it may not have
completely finished reacting from the event -- for example, if it
needs to update its display as a result, it may still be in the
process of doing that.
Use OnGlobalLayoutListener, it works perfectly for me.

Android touch events are always InputDevice.SOURCE_TOUCHSCREEN?

I have made an event injector for Android based on code from the AndroidScreencast project. I'm trying to simulate the Xperia Play touchpad on other devices. My code works great when I'm simulating normal touchscreen events, but does something unexpected when I try to actually simulate the touchpad. Here's my code:
MotionEvent.PointerCoords[] coords = { new MotionEvent.PointerCoords() };
coords[0].x = 200;
coords[0].y = 200;
int[] ptrs = { 0 };
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), action, 1, ptrs, coords, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHPAD, 0);
windowManager.injectPointerEvent(event, false);
Here's the strange part: the event is sent, but in my little testing app, it shows up as SOURCE_TOUCHSCREEN!
Am I doing something wrong, or is this some Android quirk where it converts non-supported sources?

Categories

Resources