0

DataItemでAndroidWearと本体のデータを共有する

Wearと本体でデータを共有するにはDataItemを使う。

DataItemはkey-valueストアで、1キーに100KBまで保存できる。文字列・数値だけでなく画像などのバイナリデータも保存できて、Bluetoothの帯域を適当に考慮して同期してくれるらしい。
具体的な処理は隠蔽されているがデータはGoogleのサーバーにも保存されているっぽい。
そのためGoogle API Clientとインターネットへの接続が必要。

key-valueは wear://<node_id>/<path> のようなURIの下に保存されるが、node_idは普通意識しないのでpathだけ指定して使う。


保存期限

いつまでデータが生きているのかは、ドキュメントに書かれていないのでわからない。
期限を設定するプロパティが無い事と、DataItemに書き込んでからスマホとWearを両方とも再起動してもデータは残っていたことから、まあずっと残ってるんじゃないかと思う。
SharedPreferenceみたいにも使えそう。

片方が生きてない時にDataItemに書き込んで、もう片方だけ起動したら読めるか?を試そうとしたけどWearはそもそもスマホがないとネットワーク接続できなかったから試せなかった。


使い方

“/testapp”というpathの中で、名前とウェブサイトをWearと本体間で共有する例


書き込み

GoogleApiClientを接続してからkey-valueを保存する
PutDataMapRequest mapReq = PutDataMapRequest.create("/testapp");
mapReq.getDataMap().putString("name", "shokai");
mapReq.getDataMap().putString("url", "http://shokai.org");
Wearable.DataApi.putDataItem(mGoogleApiClient, mapReq.asPutDataRequest());

読み出し(イベント)

Wearable.DataApiにDataApi.DataListenerを登録しておけばデータの変更が通知されてくる。

// Activity自身をlistenerとして登録
Wearable.DataApi.addListener(mGoogleApiClient, this);
// 解除
Wearable.DataApi.removeListener(mGoogleApiClient, this);

public class MainActivity extends Activity implements DataApi.DataListener {

// (略)

@Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
DataItem item = event.getDataItem();
if (item.getUri().getPath().equals("/testapp")) {
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
String name = dataMap.getString("name"); // "shokai"
String url = dataMap.getString("url"); // "http://shokai.org"
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
// 削除イベント
}
}
}

1回書き込んだだけでも、なぜか連続で2回イベントが来る事があった。1回目は古いデータで、2回目は更新されたデータがくる。


読み出し

イベントではなく、現在の値をこっちから読みに行く方法は
Android WearのData Layer APIを試してみた – bati11's diary
に書いてあった。GoogleAPIClientを接続してから
Wearable.DataApi.getDataItems(googleClient)
.setResultCallback(new ResultCallback() {
@Override
public void onResult(DataItemBuffer dataItems) {
String name = null;
String url = null;
for(DataItem dataItem : dataItems){
if(dataItem.getUri().getPath().equals("/testapp")) {
DataMap dataMap = DataMap.fromByteArray(dataItem.getData());
name = dataMap.getString("name");
url = dataMap.getString("url");
}
}
if(name != null && url != null) {
Log.i("userinfo", "found");
}
else{
Log.i("userinfo", "not found");
}
dataItems.release(); // releaseしないとリークするという警告がでる
}
});
この方法で他のアプリケーションのデータも読めてしまうかと思ったけど、読めなかった。よかった。


削除

DataMapまるごとしか削除できない。保存する時はpath指定なのに削除はURI指定しなければならない。

Uriのnode_idは省略できるので、/testappに保存してるDataMapはwear:/testappを指定したら削除できた。
削除するとonDataChangedイベントのTYPE_DELETEDが発火する。

Wearable.DataApi.deleteDataItems(mGoogleApiClient, Uri.parse("wear:/testapp"))
.setResultCallback(new ResultCallback() {
@Override
public void onResult(DataApi.DeleteDataItemsResult deleteDataItemsResult) {
Log.i("deleteDataItems", deleteDataItemsResult.getNumDeleted() + "個削除した");
}
});
}

DataItemにはnullも書き込めるので、DataMapまるごと削除したくない時はnull埋めでもいいかもしれない。

1

Android版ArduinoFirmataを作った

AndroidのJavaの中で

int sensor = arduino.analogRead(1);
arduino.servoWrite(9, 155);
とか書けて便利です。

ここにチュートリアルとか、すぐ試せる無署名のapkとか置いといたのでどうぞ
ArduinoFirmata on Android

ArduinoFirmata on Android


先週ArduinoとAndroidを接続できたので、Firmataプロトコルの移植はRubyでもやった事だしさくっとAndroid版も作ってみた。



Android OS 3.2以上でUSBホスト機能がある端末とStandard FirmataをインストールしたArduinoを、USBホストケーブルで接続する。
Arduino IDE -> [File] -> [Examples] -> [Firmata] -> [StandardFirmata] でインストールできる。
(Firmataを入れたArduinoは、シリアル通信からの命令で動く操り人形モードになります)

Android用ライブラリはjarファイルにしてArduinoFirmata on Androidに置いてある。
Androidプロジェクトのlibsディレクトリに入れれば使える。


Arduino Duemillanove, UNO, Seeduino v2で動作確認した。
USBホストケーブルはこれこれが使えた。


ご意見ご感想はtwitterやgithubのissueやpull requestなどお待ちしております

0

PhoneGap/CordovaのWebViewの拡張方法(調査)

Android版PhoneGap(Cordova)で新しいwebブラウザのプロトタイプ的なものを作る方法を調べた。
既存のwebサイトを表示して、その上に自作のUIを表示したりしてページを操作できるようなのを作る。
例えばbubble cursorを常時使用し続けるAndroid用webブラウザ等が作れる。

っぽい方法をわりと真面目に調べた。まだ試していない。


手順

PhoneGapプラグインを作ると、Java側でonNativeReadyイベントを受け取れる。
そのタイミングでスクリプトタグをHTMLの末尾に挿入すれば、既存のwebサイト上にcordova.jsを読み込ませたり、自分のスクリプトを実行したりできる。
(試していないが多分できると思う)


既存サイトにスクリプトタグを挿入する


スクリプト挿入するbookmarkletを作る。

これを
(function(){
var load_script = function(url){
var script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
};
load_script('//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js');
})();


圧縮してbookmarklet形式にする
javascript:(function(){var e=function(e){var t=document.createElement("script");t.src=e,document.body.appendChild(t)};e("//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js")})();
nodeのuglify-jsを使った


試しにjQueryが読み込まれていないサイトで実行してみると、jQueryが使えるようになる


webView上でスクリプト実行


MainActivity.java のsuper.loadUrl();でindex.htmlを読み込んでいる。
その後で
super.loadurl("javascript:alert('hello!!!')");
を書くと、ページを読んだ時に実行できる。


前述のbookmarkletをloadUrlすればいい。


しかし、alertと違って前述のscriptタグ挿入bookmarkletは、HTMLがロード完了してDOMが構築し終わってからでないと動かなかった(当たり前だが)
また、MainActivity.javaに書いても最初の1ページ目でしかスクリプトが挿入できないので、ページ遷移する都度scriptタグ挿入してやる必要がある。


とりあえず後輩のうすきくんと一緒に検証してみた。

MainActivityの中で
super.loadUrl("http://www.yahoo.co.jp");
final MainActivity self = this;
new Thread(){
public void run(){
try{
Thread.sleep(10000);
self.loadUrl("javascript:(function(){var e=function(e){var t=document.createElement('script');t.src=e,document.body.appendChild(t)};e('http://shokai.org/alert.js')})();");
}
catch(Exception ex){
Log.e(ex);
}
}
}.start();
(このコード、思い出して書いたので多少間違ってるかも)


なんと動いた。
ページ遷移完了後にloadUrlでbookmarklet実行したいんだけど、イベントが取得できないので10秒待ってからスクリプトタグを挿入した。
まずはPhoneGapのwebview上に後からスクリプトタグ挿入できるかの検証なので、alert.jsの中身はalert(“script insert success!!!”);しか書かないようにした。


あとは、10秒待つのではなくページ遷移毎にスクリプトタグ挿入できれば目標達成できる。


JavaScriptファイルはfile://の場所で指定してもロードしてくれなかったが、HTTPでアクセスできる場所に置いたらロードしてくれた。gistやgithubとかに置いてもいいと思う。


ページ遷移イベント


PhoneGapプラグインを作れば、外部サイトをロードした場合でもページ遷移完了イベントを受け取れる。


WebViewの画面遷移やロード完了イベントは、WebViewClientを設定すればイベントを受けられるのだが、PhoneGap(Cordova)本体が先にWebViewClientを設定してしまっている。
つまり先客がいるわけで、自作のWebViewClientをPhoneGapに食わせる事はできない。


とりあえずCordovaのソースコードを読んでみる
https://github.com/apache/incubator-cordova-android


framework/src/org/apache/cordova/CordovaWebViewClient.java がWebViewClientを継承したクラスで、コイツがPhoneGapのWebViewのイベントを全て受信している。
これを書きなおして自分でcordova.jarをビルドしてもいいのだが、本体を修正すると更新についていけなくなるので避けたい。


さらに辿ってみると、 framework/src/org/apache/cordova/CordovaWebViewClient.java からpostMessage関数で”onNativeReady”というイベントが送られている。
onNativeReadyは framework/assets/js/cordova.android.js の中から呼ばれている。
PhoneGapでよく使われるJS側のdevicereadyイベントの直後に呼び出される。

んで framework/src/org/apache/cordova/api/PluginManager.java から全プラグインにpostMessageの内容が中継されているので、

phonegap pluginを作って、onMessageで”onNativeReady”イベントを受け取れば、ページが読み込み終わった事がJava側でわかるはず。


そのタイミングでloadUrlにbookmarklet形式でスクリプトタグ挿入させればいいのではないか?

という所まで調べたのでうすきくん頑張ってください・・


PhoneGap pluginの作成


このへんが参考になると思われる。

Apache Cordova API Documentation Plugin Development Guide
橋本商会 » PhoneGap NFC Plugin作った

プラグインの解説はJavaのコードをJavaScriptから利用するためのインタフェースを作る部分が大半なのだが、
今回はPluginManagerが中継しているonNativeReadyイベントを受け取ってブックマークレット実行するだけなので、ほとんどの部分を読み飛ばして良いと思うしJSも書く必要ないと思う。

0

NanoHTTPDでJavaアプリにwebサーバーを埋め込む

Javaで1ファイルで実装されたNanoHTTPDを触ってみた。
Apacheみたいなのが起動するんじゃなくて、自分のプロセスにwebサーバーの機能を埋め込むタイプ。

NanoHTTPD
https://github.com/elonen/nanohttpd


Androidで動かしている例も検索するとそれなりに出てくる。すんなり動くらしい。
Android上でWeb serverを動かしてみた – komamitsu.log
Androidアプリ開発に挑戦: Android で NanoHTTPD を使ってみる


とりあえずMacで試した。Androidではまだ試してない。
手元のファイルをHTTPで配信もできるし、リクエストのパスやメソッドやプロパティを読むことも出来る。
Androidのプログラムに埋め込めば、パソコンのwebブラウザからAndroidにアクセスして遠隔操作できて便利だと思う。


使い方

ドキュメントが無いけど実装がシンプルなのでgithubの本体のコードを見た。
new NanoHTTPD(8080, File(“.”));するだけで8080番で現在のディレクトリをdocument rootにしたhttpdが起動する。

カスタムするにはサンプルと同じくNanoHTTPDクラスを継承して、関数を自分で上書きしてしまえばいい。
主にserve(String uri, String method, Properties header, Properties parms, Properties files)を上書きする事になると思う。

本体のコードを見るとserveからserveFile関数を呼び出しているので、自分でserve関数を定義し直したらそこからfileServeも呼び出しなおしてあげないとファイル配信できなくなる。
(同梱されていたサンプルのHelloServer.javaではfileServeが動いていない)


webブラウザからMacを遠隔操作する例



サンプルのHelloServer.javaを改造してみた。特定のパス(/goと/stop)にリクエストが来たらMacのsayコマンドで「ゴー」「ストップ」と喋らせる。
それ以外のパスへのリクエストは、dataディレクトリ内のファイルを返すようにしてみた。
Androidのプログラムに埋め込む時は、SDカードやassetsのディレクトリでファイル配信すればいいと思う。
import java.io.*;
import java.util.*;

public class HelloServer extends NanoHTTPD
{
public HelloServer() throws IOException
{
super(8080, null);
}

public Response serve( String uri, String method, Properties header, Properties parms, Properties files )
{
System.out.println( method + " '" + uri + "' " );
if(uri.equals("/go")){
try{
Runtime.getRuntime().exec("/usr/bin/say go");
}catch(java.io.IOException e){
System.err.println(e);
}
System.out.println("go!!!!");
return new NanoHTTPD.Response( HTTP_OK, MIME_HTML, "go");
}
if(uri.equals("/stop")){
try{
Runtime.getRuntime().exec("/usr/bin/say stop");
}catch(java.io.IOException e){
System.err.println(e);
}
System.out.println("stop!!!!");
return new NanoHTTPD.Response( HTTP_OK, MIME_HTML, "stop");
}
return serveFile(uri, header, new File("./data"), true);
}


public static void main( String[] args )
{
try
{
new HelloServer();
}
catch( IOException ioe )
{
System.err.println( "Couldn't start server:" + ioe );
System.exit( -1 );
}
System.out.println( "Listening on port 8080. Hit Enter to stop." );
try { System.in.read(); } catch( Throwable t ) {};
}
}


dataというディレクトリを作ってその中にindex.htmlとして保存する。
ボタンが2つ並んでるだけだけど、押したらMacが「ゴー」「ストップ」とか喋る。
<html>
<head>
<script src='//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js' type='text/javascript'></script>
<script type="text/javascript">
$(function(){
console.log("start");

$("#go").click(function(){
console.log("go button");
$.get("/go");
});
$("#stop").click(function(){
console.log("stop button");
$.get("/stop");
});

});
</script>
</head>
<body>
<h1>remote controll</h1>
<div>
<input type="button" value="go" id="go"></input>
<input type="button" value="stop" id="stop"></input>
</div>
</body>
</html>

0

emacsでAndroidのAPIも入力補完する

それなりに補完してくれて便利。
ソースコードの静的解析もしてくれてるみたいで、キャストとかしてるとおかしくなるが、インスタンスの持ってるメソッドを引数付きで出してくれたりもする。


必要なものをインストール

homebrewのemacs23.4.1に、yasnippetauto-completeajc-java-completeyasnippet-java-modeを入れる。


Androidのクラスを入力補完できるようにする


ajc-java-completeのTagファイルにAndroidのjarも突っ込めばおk
git clone git://github.com/jixiuf/ajc-java-complete.git
cd ajc-java-complete
javac Tags.java

CLASSPATHが通っている所jarを読んでくれるので、Android4.1とGoogle APIにもpathを通す。
export CLASSPATH=/usr/local/var/lib/android-sdk/add-ons/addon-google_apis-google-16/libs/:/usr/local/var/lib/android-sdk/platforms/android-16/android.jar:/System/Library/Frameworks/JavaVM.framework/Classes/classes.jar:$CLASSPATH
java Tags

~/.java_base.tag ができる。 ~/.emacs.d/ajc.tag に移動させた。
(require 'ajc-java-complete-config)
(set 'ajc-tag-file "~/.emacs.d/ajc.tag")
(add-hook 'java-mode-hook 'ajc-java-complete-mode)
emacsに読み込ませて完成。


jarを探すのはMacならmdfind使うと速いと思う。
mdfind android.jar
mdfind maps.jar
とかすればすぐ出てくる。


いろんな所からもらってきたelispやらがごっちゃになってるけど、ここに.emacs以下置いてある
https://github.com/shokai/mac-dot-emacs

参考:Android開発にも役立つEmacsの補完プラグイン – ajc-java-complete – Clouder::Blogger