Androidのアプリ開発関連のブログ

シェルをちょっとやった

SSHクライアントを作って、ほぼ流用でできそうだから、
Androidデバイス自身のshを使うコンソールの制作をちょっとやった。

Javaでの外部コマンドの実行は、ProcessBuilder で簡単だった。
String path="/system/bin/sh";
ProcessBuilder pb=new ProcessBuilder(path);
pb.redirectErrorStream(true);
Process sh=pb.start();
InputStream STDOUT=InputStreamsh.getInputStream();
OutputStream STDIN=sh.getOutputStream();
こんな感じ。
ProcessBuilderのコンストラクタ引数はStringを複数つけることもでき、複数渡す場合は2個目以降がオプションになるらしい。
手元デバイスのshは、/system/bin/sh → /system/bin/mksh とmkshへのリンクになってた。
ProcessBuilderの出力は標準出力とエラー出力が別だが、redirectErrorStream(true) するとエラーも標準出力になる。
start() でプロセスが開始され、Processが取得できる。
Process.getInputStream() と Process.getOutputStream() で入出力のストリームが取得できるが、
InputStream = 向こうからの出力
OutputStream = こっちからの入力
Javaの入出力のストリームは名前が紛らわしい・・・

で、lsコマンドだけ発行してみたが、sshの時はLFは下に移動だけにしたが、
lsがファイル名とLFの連続しか出力しないのでLFで行頭復帰させる必要があった。
VT100とかの情報見て「LFは下に移動だけ」だったのでSSHの時はそうしたが、Linuxコマンドなら本来LF改行だからsshサーバーが変換してるのか?
SSHの方もLFで行頭復帰するように変更した方が良いかな。
とも思ったが、Windows鯖に繋いだ時に考慮されてない可能性が考えられるからLFは下がるだけの方が良いか。

あと、コマンドプロンプトが表示されない。shの起動オプションとかで設定できるのかな?
画面サイズの変更通知はどうやってやるんだろうか?


完全流用ってわけには行かない感じなんだが、やろうと思えば2日は掛からないかな。
アプリは満遍なくリリースしていきたいから、SSHを配信開始したばっかだし、すぐに完成してももう少し温存しておこうかと。

SSHクライアント配信開始

制作していたSSHクライアント配信開始した。


いつもどおり一旦公開してからAdMobのID作ってから更新で、
Android-x86とGenymotionだけで動かして開発して、実機にはGoogle Play経由で入れて初確認したのだが、
Androidのバージョンが原因か?テスト環境では問題ないのに、キー入力すると常に即死する症状がw

というわけで確認したが、
AndroidManifestで起動時にキーボードを表示して、ActivityのオーバーライドしたdispatchKeyEventでキー入力を処理となっていたが、死ぬ。
logcatしてもわからなかったんで、試しにdispatchKeyEventをonKeyDownに変えてみたが、同じく死ぬ。
物理キーボード繋いでみても、そっちでも死ぬ。
で、tryしてみたら、結局死ぬw
そもそもdispatchKeyEventまで到達していないぽい。
EditTextなしでAndroidManifestでソフトキーボードを出してキーイベントを処理していたが、多分ダメなバージョンがあるんだと思うがヤメた方が良いぽい。

で、android:visibility="gone" で非表示であってもフォーカスさえあれば問題ない様だったので、
AndroidManifestでの起動時キーボード表示はヤメて、onCreateで表示されてないEditTextにフォーカスを当ててソフトキーボードを表示するようにした。
一応死ななくなったが、 キーボードが有効なままフォーカスが外れたら死ぬことになるね・・・一応ならないように考えたつもりだが・・・

さらに、入力で即死することはなくなったが、
全てのキーがdispatchKeyEventで取得できるつもりが、Enterとか一部のキー以外はdispatchKeyEventに来ない。
EditTextにフォーカスを当ててるからView.setOnKeyListener() も試してみたが、来ない。
フォーカスを当てている EditTextには入力されるので、Enterが押された場合はEditTextの内容を確認して必要なら中身送るようにした。
もうちょっと改良したほうが良いかも。

そこまでとAdMobの変更して2回目の配信したのだが、
作業中にスクリーン・タッチでのカーソルキー送信が反応が悪いことに気づいてたのだが対応忘れてたので、それを修正して再度更新した。
タッチの方は最初TextViewと重なってる箇所でダメなのかと思ったが、
View.setOnTouchListener()で取得できるMotionEventの座標(getX()とgetY())がテストの際に絶対座標と判断したのだが、(広告がなくてサイズが同一だったから)
実はMotionEventの座標はView上の座標だった。
それが原因で座標がズレてた。

あと、サーバー側から切断された場合にキー入力された場合などに何らかの方法でわかるようにしようかと思ったが、ちょっと難しい感じだったのでヤメた。
良い感じの方法思いついた感じなんで次回対応するかも。


次以降の制作の構想もあるのだが、
localのshellを使うターミナルなら今回のコードをほぼ流用できると思うんで、Javaでの外部コマンド実行の方法をちょい調べたが、
簡単にできそうな気がしたんで、やはり次回はそれになりそう。
だが今作の流用になるし、タッチ操作でのCtrlキー送信機能とかつけて完成度上げてからのほうが良いかな。って感じ。

EditTextのカーソル色を変える

制作中のアプリで、ターミナルアプリなので画面全体が黒背景にしているが、テーマ自体は明るい背景のTheme.Holo.Lightを使ってる。
追加で入力エリアを実装しようとしているが、統一感出すために当該領域も黒背景にしたが、EditTextのカーソル(キャレット)が見えなかった。

で、EditTextのカーソル色を変える方法なのだが、
android:textCursorDrawable="@null"
textCursorDrawableはカーソル画像を変更するための属性だと思うが、APIでdrawableが用意されていないので扱いにくい。
だが、textCursorDrawableをnullにするとカーソル色はtextColorになるみたい。

Javaの内部文字コードはUTF-8で日本語もcharに入る

制作中のSSHクライアントでのマルチバイト文字の表示をやった。

受信データを1byteずつ処理してるから、
new String(byte[],String);
で第2引数に文字コードを渡して、tryでエラーになったらbyte不足。
ってな処理にしようとしたんだが、非対応byteでもエラーでずにStringにできちゃうんだねw


というわけで他の方法にしたが、以前作ったテキストエディターでは、
InputStreamReaderで文字コードを指定してBufferedReaderを介して読んで、
read()値をcharにキャストしてStringBufferに追加して行ってた。
当時は理解してたのかもしれないが、そのコード見て、
InputStreamReaderは文字コードの指定ができるから適切なサイズのint値がreadできるとは理解できるが、charに日本語入らないよね?

と思ったんだが、上のやり方で正しいんだね。
Javaの内部文字コードはUTF-8ではなくてUTF-16が使われている。UTF-8で日本語は3byteだが、UTF-16では2byteで日本語が入る。
Javaのcharは1byteではなく2byteでUTF-16のマルチバイト文字が入っちゃう。
InputStreamReaderで文字コードを指定すればUTF-16に変換したint値を送ってくれる。
って感じかね。


制作物でのマルチバイト文字はUTF-8なら表示できるようになったんで、
あとはユーザーがUTF-8以外の文字コードを設定できるようにするつもり。
入力の方もソフトキーならマルチバイトのUTF-16になってるのかな?
キーが押されるたびに処理してるが、UTF-16が取得できるなら、変換は容易そう。
Androidの場合物理キーでマルチバイト文字ってどうなってるんだろうか?物理キーのほうが面倒かもしれない。

キーボード表示状態の変更

制作中のSSHクライアントで、キーボード表示状態の変更による画面リサイズ時の処理をつけるために、
メニューからキーボード状態の変更する機能を付けた。

メニューはキボード表示状態で変化するチェックボックスにしようと思ったのだが、
キーボードの表示状態を判別する機能は存在しない模様・・・
InputMethodManager.isActive() で判別できるかと思ったが、入力受付中?は常時trueになっちゃうぽい。
しかたないので、チェックボックスはナシにして、メニュー項目を選択したら状態を切り替える仕様にした。

InputMethodManager.toggleSoftInput(0,0);
でtoggleSoftInputに0を渡すと状況に応じた切り替えになる。
パラメータは表示条件と隠れる条件のフラグだが、指定すれば強制表示にすることもできる。

showSoftInput() というメソッドもあるが、こちらは引数にViewを渡す必要がある。
今回の場合はViewではなくActivityで入力監視をしているので toggleSoftInput() の方にした。


AndroidManifest.xml の <activity> にandroid:windowSoftInputMode属性をつければ起動時にキーボードを表示させることができるが、
"stateAlwaysVisible|adjustResize"としていたが、stateAlwaysVisibleでもユーザー操作で消せちゃうので、"stateVisible|adjustResize"に変更した。
stateVisibleでも問題なく表示されてる。
adjustResizeは表示状態変更時にViewのサイズが調整される。

キーボード表示状態の変更によるViewサイズの変更検知は、
View.addOnLayoutChangeListener()
でできる。
ので、これで状態変更を検知してSSH鯖にサイズ変更コマンドを送るようにした。

サイズ変更コマンドを送った場合は、特別な応答はなく、VIMの場合は画面が消去されて0行目から再度送られてきた。
スクロール範囲が変更になるが、サイズ変更コマンドを送った時に応答無しでこっちで勝手にやる必要があるぽい。