2013/02/27

AndroidにUSB ADB経由でキーイベントとかタッチイベントを送り込む

かれこれ仕事でAndroidをやり始めて1年半がたちました。
そんな1年半目の発見として(?)
タッチイベントのリピーター(ユーザが入力したイベントをそっくりそのまま、コマンドを送り込んで再現させる)を作ってみました。

(とるのがヘタでごめんなさい)

目的は
 ・何度も何度も試験をしないといけないときに、PCとスマホをUSB接続して、試験ツール的なものをかけて夜帰って、朝に結果を見る、ということができるようにする。
 ・そのために、入力の基本である、キーイベントやタッチイベントをUSB経由でインジェクションする。

しかし
 ・キーイベントにくらべてタッチイベントのシナリオって、書くのがすごく難しい

なので
 ・タッチイベントの再現シナリオを簡単に作れるようにしました。

というストーリー。



キーイベントをUSB経由で送り込む

 すでに多くのサイトで書かれているので、省略。

 adb shell input text 文字列
 adb shell input keyevent キーコード


タッチイベントの意味を解析しよう

タッチイベントのシナリオを作るには、最低限のタッチイベントがどういうものかくらいは知っておく必要がある。


Nexus 7で、
adb shell getevent /dev/input/event0
とやって、タッチパネルから上がってくるイベントを観察してみた。

iwaki-yuusuke-no-MacBook-Air:~ yi01$ adb shell getevent /dev/input/event0
0003 0039 00000001
0003 0030 00000005
0003 003a 00000011
0003 0035 000000b4
0003 0036 000000fd
0000 0000 00000000
0003 0039 ffffffff
0000 0000 00000000

コマンドを打って、画面の左上の方を軽くタップした様子

うん、よくわからん
少し移動を加えてみた。

iwaki-yuusuke-no-MacBook-Air:~ yi01$ adb shell getevent /dev/input/event0
0003 0039 00000005
0003 0030 00000007
0003 003a 0000000d
0003 0035 000000ba
0003 0036 00000189
0000 0000 00000000
0003 003a 00000054
0003 0035 000000bf
0003 0036 0000018e
0000 0000 00000000
0003 003a 00000058
0003 0035 000000c5
0000 0000 00000000
0003 003a 0000004b
0003 0035 000000cb
0000 0000 00000000
0003 0039 ffffffff
0000 0000 00000000
うん。よくわからんけど、00000000・・・00ってのが区切り文字的なものに見える。あと、3つの値が常に並んでいるようだ。

少し解析スクリプトを書いてみた。
# -*- coding:utf-8 -*-

import os

pipe=os.popen("adb shell getevent /dev/input/event0")
for line in iter(pipe.readline,""):
    data = line.strip().split()
    if len(data)!=3: continue
    kind,key,value = [int(a,16) for a in data]

    if kind==0:
        if key==0 and value==0:
            print "="*20
        else:
            print "cmd",key,value
    else:
        print kind,key,value

すると。
iwaki-yuusuke-no-MacBook-Air:~ yi01$ python analyze_touchevent.py 
3 57 10
3 48 9
3 58 32
3 53 517
3 54 393
====================
3 57 4294967295
====================
おお、だいぶ見やすくなった。

さて、次に気になるのは、「3 57」から始まるやつ。↑の結果を出すまでに何度かタップ試していたのだが
3 57 8
3 48 6
3 58 18
3 53 630
3 54 1442
====================
3 57 4294967295
====================
3 57 9
3 48 4
3 58 21
3 53 479
3 54 542
====================
3 58 84
3 54 547
====================
3 57 4294967295
====================

なんかカウントアップしているように見える。あと、4294967295=0xffffffffである。
あとは、タッチ関連のイベントは、左1桁目はかならず「3」のようだ。
    if kind==0:
        if key==0 and value==0:
            print "="*20
        else:
            print "cmd",key,value
    elif kind==3:
        if key==57:
            if value==0xffffffff:
                print "touch","end","-"
            else:
                print "touch","start","seq_No."+str(value)
        else:
            print "touch",key,value

    else:
        print kind,key,value

ちょっとこんなかんじにスクリプトの条件文を書き換えたら
touch start seq_No.13
touch 58 26
touch 53 387
touch 54 1106
====================
touch 58 91
touch 53 382
touch 54 1103
====================
touch end -
====================
touch start seq_No.14
touch 58 30
touch 53 257
touch 54 473
====================
touch end -
====================
touch start seq_No.15
touch 58 28
touch 53 470
touch 54 725
====================
touch end -
====================

こんなかんじになった。だんだんそれっぽくなってきた。

次に目につくのは、53と54。これはどうもx座標とy座標のようだ。タップした座標に近いからだ。58ってのはよくわからないので、保留。

ここまでやったところで、「マルチタッチ」ということを思いつく。
iwaki-yuusuke-no-MacBook-Air:~ yi01$ python analyze_touchevent.py 
touch start seq_No.17
touch 48 7
touch 58 24
touch 53 404
touch 54 523
====================
touch 58 75
touch 47 1
touch start seq_No.18
touch 48 7
touch 58 29
touch 53 757
touch 54 409
====================
touch 47 0
touch end -
touch 47 1
touch 48 6
touch 58 35
====================
touch end -
====================

touch 47 0
touch start seq_No.19
touch 58 26
touch 53 391
touch 54 631
====================
touch 58 79
touch 47 1
touch start seq_No.20
touch 48 8
touch 58 26
touch 53 939
touch 54 589
touch 47 2
touch start seq_No.21
touch 48 8
touch 58 27
touch 53 674
touch 54 514
====================
touch 47 0
touch 58 35
touch 47 1
touch 48 7
touch 58 32
touch 47 2
touch end -
====================
touch 47 0
touch end -
touch 47 1
touch end -
====================

2点、3点タップを試した結果が上記。ぱっと見だが、「touch 47」というのが「これからX番目の値を言いますよー」という宣言に見える。
    if kind==0:
        if key==0 and value==0:
            print "="*20
        else:
            print "cmd",key,value
    elif kind==3:
        if key==57:
            if value==0xffffffff:
                print "touch","end","-"
            else:
                print "touch","start","seq_No."+str(value)
        
        elif key==53:
            print "touch","x",value
        elif key==54:
            print "touch","y",value

        elif key==47:
            print "touch","index",value

        else:
            print "touch",key,value

    else:
        print kind,key,value

if文はこんなかんじに。だんだんと充実してきた。
しばらくグリグリタッチして遊んでみる。

まだ残っているのは48と58・・・。なんだろう。accuracyとかそういう系の指標っぽくもある。そんなに重要そうではない。
====================
touch 48 18
touch 58 248
touch x 398
touch y 1127
====================
touch 58 226
touch x 394
touch y 1122
====================
touch 58 202
touch x 389
touch y 1117
====================
touch 48 16
touch 58 193
touch x 385
touch y 1112
====================
touch 48 15
touch 58 176
touch x 380
touch y 1107
====================
touch 48 13
touch 58 164
touch x 375
touch y 1104
====================
touch 58 158
touch x 370
====================

まあ、リピーターなので、べつに意味を100%理解してなくてもよかろう。
とか言っているうちに、ソースコードから正解を見つけてしまった。(笑)
48はタッチの面積の半径、58はタッチの圧力のようだ。
うん。やっぱりそんなに重要じゃなさそうだ。

んん??圧力?タッチパネルって感圧なの??
iwaki-yuusuke-no-MacBook-Air:~ yi01$ adb shell getevent /dev/input/event0 | grep 3a
0003 003a 00000007
0003 003a 0000004d
0003 003a 0000008b
0003 003a 00000049
0003 003a 00000009
0003 003a 000000ac
0003 003a 000000b0
0003 003a 000000b9
0003 003a 000000ba
0003 003a 000000b8
0003 003a 000000b6
0003 003a 000000b5
0003 003a 000000b8
0003 003a 000000ba
0003 003a 000000c2
0003 003a 000000cb
0003 003a 000000cd
0003 003a 000000d3

実際見てみた(ぐいっと指を押し込んでみた)が、お世辞にも感度がいいとは言えない。むしろ、接地面積に比例した値を返しているだけのようだ。
ないよりはマシ、おまけ程度と考えていいだろう。
# -*- coding:utf-8 -*-

import os

pipe=os.popen("adb shell getevent /dev/input/event0")
for line in iter(pipe.readline,""):
    data = line.strip().split()
    if len(data)!=3: continue
    kind,key,value = [int(a,16) for a in data]

    if kind==0:
        if key==0 and value==0:
            print "="*20
        else:
            print "cmd",key,value
    elif kind==3:
        if key==57:
            if value==0xffffffff:
                print "touch","end","-"
            else:
                print "touch","start","seq_No."+str(value)
        
        elif key==53:
            print "touch","x",value
        elif key==54:
            print "touch","y",value

        elif key==47:
            print "touch","index",value
        elif key==48:
            print "touch","radius",value
        elif key==58:
            print "touch","pressure",value

        else:
            print "touch",key,value

    else:
        print kind,key,value

さて、突貫工事で、タッチイベントの解析スクリプトはできた。


タッチイベントをUSB経由で送り込む

ここまでできれば、あとは簡単。
adb shell sendevent  type key data
を使って送り込むだけである。
StackOverflowとかの情報を見ると、type, key, dataは16進数ではなく10進数で表記するらしい。

↑のシナリオを逆解析するのは、簡単なので省略。

問題は、adb shellを実行するところだ。
簡単な例があるのでそれだけ示しておく。
# -*- coding: utf-8 -*-

import os

class ADBTestShell:
    def __init__(self):
        print "adb shell"

    def execute(self,cmd):
        print "> "+cmd

    def __del__(self):
        self.execute("exit")
        print "EXIT"

class ADBShell:
    def __init__(self):
        self.pipe=os.popen("adb shell","w")

    def execute(self,cmd):
        self.pipe.write(cmd+"\n")

    def __del__(self):
        self.execute("exit")
        self.pipe.close()


adb=ADBShell()
adb.execute("ls /system/app")
del adb

これを組み合わせることで、冒頭に載せたようなキーイベントの送信のシナリオ作成・再現ができる。