車にAndroidボックス(Ottocast P3 Pro)を積んでいる。ここでGoogle Earthを動かして、走った道を眺めたい。運転席から画面をつついてもいいのだが、物理ボタンでノールックで操作できたほうが、画面まで手を伸ばすより操作しやすい。だから手元のコントローラーで操作したかった。
画面は手を伸ばす距離にあり、コントローラーはシフトレバー横で、ハンドルから手を下ろした位置にある
使ったのは8BitDo Micro。手のひらに収まる小さなゲームパッドで、接続先からはキーボードに見える「キーボードモード」を持っている。機器そのものの紹介と接続手順は導入編(Ottocast P3 Pro に 8BitDo Micro をつなぐ)に書いたので、ここでは省く。
結論から書くと、Key Mapper(ボタンを押したときの動作を好きに決められるアプリ)でも動くところまでは行ったが、たまに入力が効かなくなるのが直せず、コントローラー本体にキーを焼き込む方法に切り替えた。効かなくなる理由も、焼き込みで解決する理由も、どちらもAndroidの入力の仕組みの話として面白かったので、経緯を残しておく。
言葉を先に決めておく。この記事では、画面を触る操作をタッチ、キーボードのキーを押す操作をキーと呼び分ける。Androidの中ではこの2つが別々の道を通ってアプリに届く、というのが話の芯になる。
前提:指でのピンチが成立しない
自分の車(トヨタ シエンタ)では、Ottocastに届くタッチが1点だけだった。何本指で触っても、Ottocastには1本分しか届いていない(確かめ方は導入編に書いた)。
だから2本指で広げたりつまんだりする操作(ピンチ)が成立せず、地図の拡大縮小が指でできない。ここが出発点になる。
第一の壁:ピンチを合成しても、アプリが受け取らない
ハードウェアが2点を送ってこないなら、Android側で「2本指で触った」という信号を作ってしまえばいい。Key Mapperにはピンチのアクションがあるので、ボタンに割り当てた。
Key Mapper のアクション選択。Input の右端にある Pinch screen を割り当てた
合成そのものはできていた。開発者オプションの「ポインタの位置を表示」をONにしておくと、ボタンを押した瞬間に画面上のポインタが2点現れて、広がっていく。Androidの内側では、確かに2本指のピンチが起きている。
それでもGoogle Earthは反応しなかった。 指で1本のタッチをしたときと違って、画面は何も動かない。
理由は分からない。合成したタッチと本物のタッチをアプリが区別しているのか、Ottocast固有の事情なのか、切り分けられていない。Key MapperのFAQに「一度に1か所しかタップ/スワイプできない」という記述があるが、これは2つのアクションを同時に走らせられない(ジョイスティックを押しながら射撃はできない)という話で、ピンチが効かない理由にはならない。
Google マップだけは1本指ズームで拡大縮小できた
ただしGoogle マップは例外だった。マップには1本指でズームする操作(画面を2回タップして、2回目で指を離さずに上下に動かす)があるので、そちらを合成すれば拡大縮小できる。
同じ手をGoogle Earthでも試した。駄目だった。 そもそもEarthに同じ1本指の操作があるのかどうかも含めて、理由は分からない。
タッチを使う道はここで終わる。
発想の転換:タッチをやめる
タッチが使えないなら、そもそもタッチを経由しなければいい。Google Earthにはキーボードショートカットがある。パソコンでキーボードを押すと地図が動く、あれだ。
- PageUp / PageDown でズーム
- 矢印キーでパン(地図を上下左右に動かす)
nで北向きに戻す、uで真上ビュー、rでリセット、oで2D/3D切替
ただしGoogleの公式ドキュメントは「パソコンで」としか書いていない。Android版アプリがキーボードを受け付けるという記述も、受け付けないという報告も見つからなかった。
試したら動いた。Android版Google Earthはキー入力に反応する。公式には書かれていないが実装されている。これは調べた範囲でどこにも情報がなかったので、記録しておく価値がある。
実機で確認したのはズームとパンだけ。Androidは押されたキーを keycode という番号で扱うので、Key Mapperで指定するときはこの番号を使う。矢印キーに相当する番号は無く、ゲームパッドの十字キー(DPAD)と同じ番号として届く点に注意。
| Google Earthの操作 | Android keycode |
|---|---|
| ズームイン(PageUp) | 92 |
| ズームアウト(PageDown) | 93 |
| パン 上/下/左/右 | 19 / 20 / 21 / 22 |
Key Mapper の keycode 一覧。PAGE_UP が 92、PAGE_DOWN が 93
第二の壁:キーを送るにはIMEが要る
キーを送る方針は正しかったが、Key Mapper経由だと引っかかりが出た。押してから反応するまでのラグ、押しっぱなしにしても拡大がじりじりとしか進まないこと、そしてたまにボタンが効かなくなること。
このうち遅さは2つとも設定で解決した。残ったのは3つめで、そこを理解するにはKey Mapperがどうやってキーを送っているかを見る必要がある。
遅さは、どちらも設定で直った
ラグも拡大の遅さも、原因は Action options の既定値だった。 連打の間隔(Repeat every)と、押しっぱなしにしてから連打が始まるまでの待ち時間(Delay until repeat)が既定では大きい。そのままだと押してから動き出すまで待たされ、動き出してからも進み方が遅い。どちらも小さくすれば解決する。 IME 経由でも実用になる速さで動く。
Repeat の設定。Delay until repeat の既定は 400ms。ここと Repeat every を小さくすれば、IME 経由でも十分な速さになる
Androidは普通のアプリにキーを「押させて」くれない
Androidでは、自分以外のアプリのウィンドウにキーやタッチのイベントを流し込むのに INJECT_EVENTS という権限が要る。保護レベルが signature なので、Android本体と同じ署名を持つアプリにしか与えられない1。ストアから入れたアプリには降りてこない。勝手にキーを押せるアプリがあったら、裏でパスワードを打ち込むアプリも作れてしまうからだ。
そこでKey Mapperは、IME(文字入力アプリ。Gboardのようなキーボードアプリのこと)を経由する。IMEは、自分が接続しているアプリにキーを渡せる(InputConnection.sendKeyEvent())。キーボードアプリが文字を打てるのはこの経路で、Key Mapperはそこに相乗りしている。
ただし、Key Mapperの公式ドキュメントはIMEが要る理由までは説明していない。「Shizukuを入れないなら、Key MapperのキーボードかBasic Input Methodを使う必要がある」と書かれているだけである2。上の説明は権限の仕組みからの推測で、そこまでの裏は取れていない。
キーを送る手段が整っていないと、アクションが赤くなって「Tap actions to fix!」が出る
赤いアクションをタップすると、キーを送る手段を選ぶダイアログが出る。
キーを送る手段は「Key Mapper input method」(Key Mapper 内蔵の IME)か「Expert Mode」の二択しかない
残ったのは、IMEの取り合い
設定で片付かなかったのはこちらだった。たまに、Key Mapperのボタンが効かなくなる。
心当たりがあるのは AI Control(com.cartizan.ble_remote)で、スマホから Ottocast に文字を送るのに使っている。このアプリでスマホから文字を入力しようとすると、Key Mapper のボタンが効かなくなることがあった。 どちらも IME を必要とするので、選択中の IME を奪い合っているのではないか、と考えている。ただし、そこは確かめていない。 確かなのは、たまに効かなくなるという結果のほうだ。
AI Control の画面。方向キーやホームに混じって、右端にキーボードのボタンがある(赤丸)。ここから文字を送ろうとしたときに、Key Mapper のボタンが効かなくなった
頻度は低い。ただ、走行中に地図の操作が効かないのは困る。
これは Google Earth だから起きた、という話ではない。 IME の奪い合いが起きれば、どのアプリを操作していても Key Mapper のキーが送れなくなる可能性がありそうだ。
Expert Modeという抜け道と、その罠
Key Mapperには「Expert Mode」がある。Shizukuというアプリ(またはワイヤレスデバッグ)を通じて、開発者向けの接続経路(ADB)と同じ権限で動く裏方のサービス(System Bridge)を立ち上げる。この権限は INJECT_EVENTS を持つので、本物に近いキー入力ができる。
Key Mapper が用意している中では、これだけが壁を正面から越える手段だった。
Expert Mode には「Auto start and keep alive」がある。再起動時に自動で立ち上がる建前にはなっている
ただし、この権限は電源を切るたびに消える。Shizukuをroot無しで使う場合、再起動のたびに手で立ち上げ直す必要がある(Shizuku側の仕様)。さらに Key Mapper の Issue #2073 には、Shizukuが止まるとExpert Modeも止まり、「Auto start and keep alive」を有効にしていても自動では復帰せず、Key Mapperを手で開いて初めて立ち上がる、と報告されている。
エンジンを切るたびに電源が落ちる車載環境とは相性が悪い。試してはいないが、乗るたびに手で立ち上げ直すことになると判断して見送った。
解決:壁の下を通る
最終的にKey Mapperを完全に外した。
8BitDo MicroをKモード(キーボードモード)にして、8BitDo Ultimate SoftwareでボタンにPageUp / PageDownを直接焼き込む。「このボタンを押したらこのキーを送る」という設定を、コントローラー本体の中に書き込んでしまう方法だ。
8BitDo Ultimate Software。各ボタンに送るキーを指定して、本体へ書き込む
ラグはなくなり、押しっぱなしのズームは設定を詰めなくても滑らかになった。IMEを経由しないので、取り合いも起きない。
理由は単純で、KモードのMicroはBluetoothキーボードそのものだから。Ottocastから見ると、本物のキーボードが繋がって、本物のキーが押されている。Androidの内側では、他のキーボードと同じ経路(evdev → EventHub → InputReader → InputDispatcher)で運ばれる3。誰にも許可を求める必要がない。
壁を越えようとしているのではなく、壁より下の階層から入っている。OSの自動リピートも当然のように乗るし、IMEを選ぶ必要もない。
%% mermaid は subgraph を宣言と逆順に並べるため、
%% 記事の順(Key Mapper が先、K モードが後)に見せるには逆に書く
graph TD
subgraph R["K モードで本体に焼き込む"]
B1["8BitDo Micro"] -->|"Bluetooth キーボード"| B2["Android のカーネル<br>IME を使わない"]
B2 -->|"本物のキー入力"| B3["前面のアプリ"]
end
subgraph L["Key Mapper で合成する"]
A1["8BitDo Micro"] -->|"ゲームパッドのボタン"| A2["Key Mapper + IME<br>他のアプリと奪い合う"]
A2 -->|"IME 経由で渡す"| A3["前面のアプリ"]
endKey Mapper は壁の手前で IME に迂回し、K モードは壁の下を通る。IME を経由するかどうかが、取り合いが起きるかどうかを分ける
マッピングはコントローラー本体にあるので、Ottocast側で何が起きても消えない。
なお、Ultimate SoftwareにはAndroid版があるので、Ottocast(Android 13)本体にそのまま入る。車内で設定変更が完結するのは地味に大きい。
この方式のデメリット
いいことばかりではない。
- プロファイルは本体に1つだけで全アプリ共通。Key Mapperの「Google Earthが前面のときだけ」という条件分岐は失われる
- 長押しとダブル押しの区別、マクロ、条件分岐ができない。1ボタン1キーの素直な対応のみ
8BitDo MicroのKモードの工場出荷時マッピング(全ボタンが英字)は、導入編に表を載せた。
まとめ
振り返ると、引っかかったところは同じ一点に繋がっていた。
イベントを合成する道は、他のアプリと同じ土俵に乗る。
ピンチは合成できてもアプリが受け取らず、キーはIME経由で送るしかなかった。前者の理由は分からないままだが、後者はAndroidの権限の設計から来ている(Shizukuを使えば越えられるが、車では現実的でなかった)。そしてIMEは1つしか選べないので、同じくIMEを使うアプリとは取り合いになる。
速さは設定で詰められた。詰められなかったのは、その取り合いのほうだった。
抜け道は、イベントを作るのをやめて、本物のイベントを出す機器を用意することだった。ソフトウェアで解けない問題がハードウェアであっさり解ける、という珍しくもない話ではあるけれど、理由まで追いかけると納得感がある。
同じことをやろうとしている人の役に立てば。
INJECT_EVENTSは AOSP でprotectionLevel="signature"として定義されている。署名レベルの権限は、プラットフォームと同じ証明書で署名されたアプリにしか付与されない。参考: Keyboard devices | Android Open Source Project ↩︎Key Mapper のドキュメントには “If you don’t want to install Shizuku then you must be using the Key Mapper GUI Keyboard or the Key Mapper Basic Input Method for this to work.” とある。IMEが必要だという事実は書かれているが、その理由は書かれていない。 ↩︎
Keyboard devices | Android Open Source Project。EventHub が evdev から生のイベントを読み、InputReader がそれを解釈して InputDispatcher に渡す、という経路が説明されている。 ↩︎