Monday, January 19, 2009

Generating keypresses programmatically

There was already a blog entry about programmatically generating key events but I was not satisfied with the outcome. The Instrumentation framework is nice but is somewhat heavyweight. If one wants to generate just keypresses, why to have all those entries in the manifest, instrumentation object, etc? So I used the wonderful new dedexer tool to go after the inner workings of the instrumentation framework.

The critical method in android.app.Instrumentation is this:


.method public sendKeySync(Landroid/view/KeyEvent;)V
.catch android/os/RemoteException from le5cf6 to le5d12 using le5d14
invoke-direct {v2},android/app/Instrumentation/validateNotAppThread
; validateNotAppThread()V
le5cf6:
const-string v0,"window"
invoke-static {v0},android/os/ServiceManager/getService
; getService(Ljava/lang/String;)Landroid/os/IBinder;
move-result-object v0
invoke-static {v0},android/view/IWindowManager$Stub/asInterface
; asInterface(Landroid/os/IBinder;)Landroid/view/IWindowManager;
move-result-object v0
const/4 v1,1
invoke-interface {v0,v3,v1},android/view/IWindowManager/injectKeyEvent
; injectKeyEvent(Landroid/view/KeyEvent;Z)Z
le5d12:
return-void
le5d14:
move-exception v0
goto le5d12
.end method


This is actually a public API method. The method obtains the binder of the WindowManagerService from the ServiceManager then it invokes the injectKeyEvent method on WindowManagerService's public interface. That is something we can do ourselves, can't we?

Click here to download the example program.

Unfortunately the situation is not that simple. Ever since the non-public classes like android.os.ServiceManager, android.view.IWindowManager, etc. were removed from android.jar, applications using non-public API cannot be compiled as easily as before. If you check the example program, you will see that I created stubs for these classes. These stubs are only there to allow compilation of the program, they don't get packaged into the apk file. On the platform itself, there will be real versions of them. In order to accomodate the stub compilation step, I modified somewhat the machine-generated build.xml file too.

It is also important to notice the validateNotAppThread private method invocation at the beginning of the method. Events cannot be generated neither from the application's own thread, nor from the UI thread. That's the reason we prepare another thread and install a looper into it. Then we post our actions into that thread and that thread will be able to generate keypresses.


If you launch the program and press the "Generate keypresses" button, you will see how the text field above the button receives the keypresses we generated programmatically. At this point you may get excited and would try to generate keypresses for other applications. This will not work. Android applications are allowed to generate keypresses only for themselves. In WindowManagerService class, you will find a permission check controlling this behaviour.


iget-object v1,v12,com/android/server/WindowManagerService.mContext Landroid/content/Context;
const-string v2,"android.permission.INJECT_EVENTS"
invoke-virtual {v1,v2,v14,v15},android/content/Context/checkPermission
; checkPermission(Ljava/lang/String;II)I
move-result v1
if-eqz v1,l4a7e0
const-string v1,"WindowManager"
new-instance v1,java/lang/StringBuilder
invoke-direct {v1},java/lang/StringBuilder/
; ()V
const-string v2,"Permission denied: injecting key event from pid "


The android.permission.INJECT_EVENT permission is not that of the application, the WindowManagerService must have this permission otherwise requests to inject events into other applications' windows will be rejected. By default, WindowManagerService does not have this permission, that is why it is impossible to generate events for somebody else's window.

After all this diving into Android's internals, I show that there is a simpler way to generate key events by abusing the android.app.Instrumentation class. Check out the generateKeysWInst method in the example program. This method simply instantiates the Instrumentation class and calls only the sendKeyDownUpSync public method. This works because we know that sendKeyDownUpSync is so simple that it will function even with this bizarrely created instrumentation object. I don't know which method is more futureproof: using internal, non-public API or abusing a public API. Decide yourself.

20 comments:

wipraj said...

Hi,

I need help in END CALL Key gen event..

Ans also that two OS pack you have mentaitond is not avail in the And.jar file...

Then how to use it...

sus4android said...

Hi,

I tried installing the apk file in the emulator and it works fine. But I wanted to know the way how the program is compiled. Because this is not possible in Eclipse and I guess some other tools are used for this.
Thanks in advance.

Regards,
Sush

flohier said...

Hi,

Thanks for this great post. I am interesting in injecting keypress event as a way to automatically accept an incoming call from a background service.

2 questions:
- If my service sends an intent to accept a call to the dialer (via ACTION_ANSWER), can it issue a keypress event programmatically from that same service for the dialer to accept the call
?
- If the above is not possible because of permission issue, is there a way to automatically accept incoming calls

Thanks,

ALL said...

If I build this application in the repo and add the permission android.permission.INJECT_EVENTS, it does send key event to other application.

In the application, I let the thread sleep several seconds then sends out the key. After press the generate keyevent button, I quit the application immediately. Then I saw these keys were typed in Quick Search box.

If I build it in SDK environment, It will get an error 'no permission' when the application is running.

Raj said...

hi All,

Can you please tell me how to build the applicaiton through repo. I need to end the call through program. Can you please check this.

Thanks
raj

Clemens said...

here is a howto buld an apk from the repo: http://asantoso.wordpress.com/2009/09/15/how-to-build-android-application-package-apk-from-the-command-line-using-the-sdk-tools-continuously-integrated-using-cruisecontrol/

Clemens said...

is there any possibility to inject key events to an other application? with root? can I change the permission of iwindowmanager?
can I create a testcase and use instrumentation on a ready made apk?
Anything?

Stesh said...

Hi!

Has anybody found solution how to simulate a key event to any active window?

Thanks

Clemens said...

There might be a possibility. There is a open source project called AndroidScreencast wich allows you to controll your phone via pc while connected to ADB. Maybe we could use its functionality. I opend a threat at alldroid.org http://alldroid.org/viewtopic.php?f=268&t=2010

rbd said...

Hi,

I've tried to compile the code on OS 2.1 but i keep getting error messages. Did someone managed to compile it?

Gabor Paller said...

rbd, did you build it with the Ant script provided, from the command line? The package includes some stub classes (under the stubs directory), they have to be on the classpath during compilation time but should not be packaged into the apk file. The modified build.xml does this. Look for the compile-stub task.

Anonymous said...

Would it be possible to inject events into other processes if you had root access?

Kayne said...

Dude, you are amazing. Not just this blog post. I've found a lot of useful information on your page that just isn't available elsewhere!

Anonymous said...

Thanks Dude. The instrumentation method works great.

Instrumentation inst = new Instrumentation();
inst.sendKeyDownUpSync( KeyEvent.KEYCODE_VOLUME_DOWN );

I used it to show the android volume popup (VolumePanel).

Thanks,
Leo.

Anonymous said...

Great article :-)
sadly the link is broken :-/

Can anyone send me the file (or a non broken link) ?

taiko3615 aaarobse gmail com

Gabor Paller said...

Which link is broken? I tried all of them and they all work for me.

Łukasz said...

Hi Gabor.
I have a problem with line:

import android.os.ServiceManager;

The library cannot be found. Please tell me what to do:)

What I intend to do is: create an application that wouldn't be disturbed by "Log press Home Button" which shows "Recent Apps" I thought about maybe redefining the "Long press Home Button" or sending hardware "Back" button click when RecentApps shows.

Gabor Paller said...

Lukasz, try to build with the build.xml script first in the package. There is a tricky classpath trick here (also mentioned in the post), stubs are provided for classes missing from android.jar. android.os.ServiceManager is such a class.

I don't think that you can redefine the "long press home button" but I may be wrong.

Michał said...

Hi,

Do you know why adding back key at the end is causing an exception ?

keyUpDown( wm,KeyEvent.KEYCODE_T );
keyUpDown( wm,KeyEvent.KEYCODE_BACK );

Thanks in advance for any tip.
kind Regards,
Michal

Anonymous said...

Hi. SOme comments here stte that it is possible to inject events to another application's window. I that true?
I used the example provided here to build my version that sends the event after a while to give me the time to switch to another applicatio.
By using both methods (with and without Inst) I get an error at runtime.