在Android系統下模擬鼠標鍵盤等輸入設備,網絡上資料非常多。但不少是人云亦云,甚至測試都不愿測試一下就抄上來了。這次寫一點體會,當作拋磚引玉。
0. 背景知識:
眾所周知,Android是將Framework架在Linux之上的系統。Linux層和硬件打交道,Framework通過JNI等途徑得到底層信息。
消息的傳遞是:Linux -> Framework -> Application
因為此架構的特性,我們很容易知道可以在哪些環節,以何種途徑加入鼠標和鍵盤消息。
1. 添加鼠標鍵盤消息的方法:
我們知道消息傳遞的路徑,就很清楚可以在哪些環節把我們需要的鍵盤鼠標消息添加進去了。
1.1: Linux Driver 層面添加:
可以寫一個Linux Driver,注冊一個字符設備驅動程序,建立一個虛擬的字符設備,主設備號13。利用Ioctl()和應用程序溝通。
之前在Linux 2.4時代,Sam曾在S3C2440A上寫過這樣一個Driver,個人起名叫VInput。可以實現以上功能。
優點:很少。
缺點:
1.編程較為復雜。Linux Kernel從2.4到2.6,再到3.0。Kernel變動不小,僅字符設備驅動程序的注冊方法和Device的建立方法都有不小的變化,devfs也不支持了。
2. 需要有對應目標平臺的Kernel Source Code。
3.需要有root權限,才能夠insmod ko文件。
總結:這個方法并不好用。除了專業寫Driver的朋友外,估計沒有人會這么干。有一次曾想把Linux Kernel 2.4時代的VInput移植到Linux Kernel3.0來。但內核符號改變太大。沒能實現。
1.2: ? Linux 用戶層面添加:
在Linux Kernel 2.6的某個版本中,添加了UInput。即Input User level driver.?? 這個Driver允許應用程序通過和 /dev/uinput交互來創建一個新的Linux Input Device。 這個Device可以是Keyboard, Mouse,絕對位置設備等等。既然Linux 層面都模擬出具體設備了。則Framework更會認為這是個實實在在的輸入設備。則我們模擬出的消息會一路上傳,一直傳遞到App層面。
具體方法:
http://blog.sina.com.cn/s/blog_602f87700100llew.html
優點:
程序簡單易行,不需要Kernel Source Code。可以模擬幾乎一切常見的輸入設備。
缺點:
這個程序最好是使用NativeC程序寫成一個可執行程序。只在Linux層運行。?
但如果才用JNI把它做成一個庫,供上層Android程序調用。則有可能會遇到一個問題:權限不足。
我們在Android系統下常看到/dev/input設備的擁有者是system. ? 同組的其它用戶的權限常常是不可讀寫。而一般的APK的擁有者并不是system, 所以無法讀寫這個設備(/dev/uinput). 所以此方法在JNI方式下有可能會失敗。
除非/dev/uinput的權限是666. 則沒有問題。
(當然也有兩個辦法突破,但那是另一個話題了, 可以看看以下文檔系統簽名部分:
http://blog.sina.com.cn/s/blog_602f87700101jm9b.html)
總結:這個方法Sam一直在實際使用。效果很不錯。
1.3: Framework 層面修改:
這個辦法只是理論上可行,可以在Framework 讀取/dev/eventX 的JNI部分去下手。但實際上沒有人會為了這個功能去破壞Framework的穩定。所以只是理論上可行。以前一個同事曾研究過這一塊。但沒有最終動手做。
總結:除非有特殊需求,否則不要這么做。
1.4: 利用Instrumentation發送鍵盤鼠標消息:
Instrumentation可以監聽系統和應用程序之間的通訊。可以利用它給應用程序發送鼠標鍵盤消息。有點像Windows下的Hook。
具體方法:
如果僅想向本應用程序發送鍵盤鼠標消息。
Instrumentation inst=new Instrumentation();
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_A);
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 100, 200, 0));
SystemClock.sleep(1000);
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 200, 200, 0));
SystemClock.sleep(1000);
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 200, 200, 0));
Instrumentation inst=new Instrumentation();
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_A);
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 100, 200, 0));
SystemClock.sleep(1000);
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 200, 200, 0));
SystemClock.sleep(1000);
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 200, 200, 0));
發送鍵盤和鼠標消息給當前有焦點的窗口。
可以采用:
sendKeyDownUpSync()
sendKeySync()
sendCharacterSync()等方式發送鍵盤消息。
可以使用sendPointerSync()發送鼠標消息。
但如果想向其它App的窗口發送鍵盤鼠標消息。僅僅這樣做就會出錯,程序會Crash。
java.lang.SecurityException: Injecting to another application requires?INJECT_EVENT?permission.
好的,我們加上這個權限。
在AndroidManifest.xml 的Permissions選單中,添加Uses Permission.選中INJECT_EVENT.
此時?uses-permission android:name="android.permission.INJECT_EVENT"?被加入。
但編譯時會報錯,這個權限僅有System APP才能擁有。
呵呵,那只好再加系統權限了。
android:sharedUserId="android.uid.system">
加入。
生成未簽名的APK。 再使用apktools加上系統簽名文件。這樣,就可以向其它APP發送鼠標鍵盤消息了。
優點:簡單易行。
缺點:如果向其它程序發送鼠標鍵盤,則需要系統簽名文件。且一些程序估計從更底層拿消息,所以會產生在這類程序中無響應的情況。
總結:想向其它APP Window 發送消息。則一定需要系統簽名。
總的看來,在Android系統中模擬鼠標鍵盤。采用UInput方案且在Linux層做NativeC可執行程序最為穩妥。在Linux層面就直接創建了輸入設備。
如果采用Instrumentation方式,一方面一些APP可能不吃,另一方面,如果想向其它APP發送消息。則需要系統簽名文件。