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

シェルを作ろうとしていたが・・・

SSHクライアントの流用で簡単にできると思ったんで、shのターミナルを作ろうとしていたが、
問題があって不完全になっちゃいそうなんで、当面お蔵入りにしておこうかと。


プロンプトの表示は sh に -i オプションつければ表示される。
のだが、-i オプションを付けた場合 sh の起動時に tty の fd が取得できない的な表示が出る。動作はする。
この点が解決方法がわからない。

それと、SSHの場合は何か文字入力したらサーバーの方から表示が返ってくるが、コマンドの結果以外何も帰ってこない。
例えば、
$ ls[Enter]
な感じの時、lsコマンドの結果は返ってくるのだが、lsとEnterは出力されない。
そういうもんなのか?
こっちで勝手にやっちゃおうかとも思ったが、文字単位で送っちゃうと不整合が生じる可能性があるし、
Enterを押した時に送る感じも考えたが、コマンド入力じゃなくて[Y/N]みたいな選択のときもあるし難しい。


というわけで、シェルを作るのは保留にして次は何か別の作ろうかと。
難易度的に難しいと思うのだが、構想はあと2つある。

シェルをちょっとやった

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の場合物理キーでマルチバイト文字ってどうなってるんだろうか?物理キーのほうが面倒かもしれない。