注意:Part 2 已经发布在这里。
android平台已经发布了有些年头了。终端用户从奇思幻想的应用中获得很多花里胡哨的玩意。但是对开发者来说,这个“开放为目的”的平台看起来比以前更复杂了。
这里有各种各样平台开发者经常调用的API依旧不能使用,其它一些或者在某个发行版本中被放弃或者出于高级安全的目的被锁住,当然他们可能觉得已经给出了正确的答案。
不管怎么说,android同样带着很多的失望,因为很多需要用于实现各种任务或者更高级应用的工具被有意地藏了起来。
一个例子就是以编程的方式注入一个按键或者相似的注入一个触摸时间(鼠标)。因为一个恶意的程序员可能会开发一个在你不知情的情况下打开应用市场并下载应用的程序,所以,所有的开发者都被禁止以编程的方式向其他的应用发送按键,除了他们自己的应用。
这真是可笑的狭隘的想法。
当然,在安全与功能之间永远会有一个平衡,但是很快这就变成了彻底的失败。换句话说,为什么限制如此有用的功能会造成如此小的伤害呢?通过正常的权限提示用户风险,其他的特性也应该这样。
我将抛开这些问题回归本文的主题:我意识到用于编程注入事件的方法,包括键盘事件(keys)和鼠标事件(touch event)。
方法1:使用内部API
这个方法有它的风险,可能它将永远是内部而不会发布的API。
这个想法是获取一个WindowManger的实例来访问injectKeyEvent/injectPointerEvent方法。
1. IBinder wmbinder = ServiceManager.getService( "window" );
2. IWindowManager m_WndManager = IWindowManager.Stub.asInterface( wmbinder );
ServiceManager和WindowManager被定义为Stub。这样我们可以绑定到这些服务上调用我们需要的方法。这些接口包含在本文后面的样例代码中。
可以使用下面的方法发送一个按键。
// key down
m_WndManager.injectKeyEvent( new KeyEvent( KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_A ),true );
// key up
m_WndManager.injectKeyEvent( new KeyEvent( KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_A ),true );
发送触摸/鼠标事件使用:
1. m_WndManager.injectPointerEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),MotionEvent.ACTION_DOWN,pozx, pozy, 0), true);
2. m_WndManager.injectPointerEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),MotionEvent.ACTION_UP,pozx, pozy, 0), true);
这个工作很有效,但是仅限于在你自己的应用里。
当你尝试向任何其他的窗口注入按键或触摸时间时,你会得到如下类似的一个强制关闭的异常:
E/AndroidRuntime(4908): java.lang.SecurityException: Injecting to another application
requires INJECT_EVENTS permission
没什么高兴的,因为INJECT_EVENT是一个系统权限。一个可行的解决方案点击这里和这里。
方法2:使用instrumentation对象
这是一个机遇公开的API的方案,但不幸的是,它依旧需要INJECT_EVENT权限。
1. Instrumentation m_Instrumentation = new Instrumentation();
2. m_Instrumentation.sendKeyDownUpSync( KeyEvent.KEYCODE_B );
触控事件如下:
//pozx goes from 0 to SCREEN WIDTH , pozy goes from 0 to SCREEN HEIGHT
1. m_Instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),MotionEvent.ACTION_DOWN,pozx, pozy, 0);
2. m_Instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),MotionEvent.ACTION_UP,pozx, pozy, 0);
在测试应用内部是正常的,但是放你尝试向外部应用注入按键时就会立即崩溃,并不是因为这个方法不工作,而是因为android开发者选择了这样。伙计们,多谢,你们很棒!Not。
通过查看sendPointerSync的代码,你很快就会发现它使用了和方法1中给出的一样的方法。所以这是一个东西,只是包装成了简单易用的API而已:
public void sendPointerSync(MotionEvent event) {
validateNotAppThread();
try{
(IWindowManager.Stub.asInterface(ServiceManager.getService("window"))).
injectPointerEvent(event, true);
} catch (RemoteException e) {
}
}
方法3:直接事件注入到/dev/input/eventX
Linux为每个设备如/dev/input/evnetX(X是一个整数)暴露了一个统一的输入事件接口。我们可以直接使用它来跳过上面的android平台权限问题。
要让这个工作,我们需要root权限,所以这个方法只能工作再一个rooted的设备上。
我觉得使用native C代码在Linux层处理是很简单的,但是一个纯粹的java实现同样可以。因此我已经添加了一个小的JNI组件来使用/dev/input/eventX处理这个接口。
我写的样例中没有自动的检测X号,所以要确保在运行该代码之前要先设置改数值。作为默认的,我将它设为了event3。你可以在NativeInput.java中修改这个值,参照下面的代码。
如我所说的,最后这个方法需要root。默认的,eventX文件拥有660的权限集(只有owner和group的读写权限)。要从我们的应用中注入按键,我们需要使其可写。多以要先如下处理:
adb shell
su
chmod 666 /dev/input/event3
你需要root权限来运行chmod命令。
样例代码
原文地址:http://www.pocketmagic.net/injecting-events-programatically-on-android/