2011-01-30
GingerBreadでFeliCaのPush送信機能を使う
Push送信機能とは?
FeliCaのPush送信機能を使うとタッチした端末にコマンドを送り込める。
この機能は、IS03、Lynx3Dなどのおサイフケータイ対応Android端末で利用でき、 そのためのライブラリがフェリカネットワークスの配布ページからダウンロードできる。
Push送信機能は、三者間通信と呼ばれる方式を使って実現されている。
三者間通信とは、リーダーライター(またはリーダーライターモードで動作している端末)から、 カードエミュレーションモードで動作している端末にデータを送り込み、端末が送り込まれたコマンドを解釈して実行する方式である。
GingerBreadでカードエミュレーションを有効にする方法は今のところ確認されていないので、ここでは送信側として対応端末であるLynx3DにPush送信を試みた記録を記す。
結果はこんな感じ。
Push送信機能を使うには?
Arduino向けRC-S620/S制御ライブラリの提供、PaSoRi/RC-S320 - osdev-j (MMA)あたりを見るとPushはコマンドコード0xb0であることがわかる。
幸いにも先達が@hideなメソッドをリフレクションして、生のコマンドを送る方法を見いだしている。
その方法でPush(0xb0)コマンドを発行すれば目的は果たせる。(はずである)
しかし、上述のライブラリはオープンソースではないし、コマンドの発行などの低レイヤの処理は隠蔽されていて、Push送信で実際に送るデータの内容を知ることはできない。
ここで、同ライブラリの「従来のおサイフケータイ」にもPush可能な点に着目すると次のページが見つかる。
このページの「外部R/Wからの携帯電話アプリケーション制御」説明書」というPDFに、ブラウザを起動するためのデータフォーマットが記載されている。
- 個別部数(1byte) - 0x01
- 個別部
- チェックサム(2bytes)
あとは単純にこれをコマンドとして発行するだけである。
Push送信機能を実装する
個別部
個別部は
で構成される。
プログラム中の引数 browserStartupParam については上述の資料には記載がないが、端末によっては確認ダイアログなどに表示する文字列を指定する。
private static byte[][] buildPushStartBrowserSegment(int type, String url, String browserStartupParam) { byte[] urlByte = url.getBytes(); byte[] browserStartupParamByte = browserStartupParam == null ? new byte[0] : browserStartupParam.getBytes(); int capacity = urlByte.length + browserStartupParamByte.length + 5; ByteBuffer buffer = ByteBuffer.allocate(capacity); // 個別部ヘッダ // 起動制御情報 buffer.put((byte) type); // 個別部パラメータサイズ int paramSize = urlByte.length + browserStartupParamByte.length + 2; // urlLength(2bytes) putAsLittleEndian(paramSize, buffer); // 個別部パラメータ // URLサイズ putAsLittleEndian(urlByte.length, buffer); // URL buffer.put(urlByte); // (ブラウザスタートアップパラメータ) buffer.put(browserStartupParamByte); return new byte[][] { buffer.array() }; }
Envelope
ブラウザ起動については一つのみ、とのことであるがPush送信は複数のコマンドを含められるようになっている。
このためEnvelope(というのだろうか?)は複数の個別部を受け付けるようにしている。
チェックサムは、資料では非常に文芸的な書き方をしているが、つまり以下のとおりである。
private static byte[] packSegment(byte[]... segments) { int bytes = 3; // 個別部数(1byte) + チェックサム(2bytes) for (int i = 0; i < segments.length; ++i) bytes += segments[i].length; ByteBuffer buffer = ByteBuffer.allocate(bytes); // 個別部数 buffer.put((byte) segments.length); // 個別部 for (int i = 0; i < segments.length; ++i) buffer.put(segments[i]); // チェックサム int sum = segments.length; for (int i = 0; i < segments.length; ++i) { byte[] e = segments[i]; for (int j = 0; j < e.length; ++j) sum += e[j]; } int checksum = -sum & 0xffff; putAsBigEndian(checksum, buffer); return buffer.array(); }
Pushコマンド
Pushコマンドの形式は
- データのサイズ
- データ
である。
このため、Envelopeのサイズを先頭に付加している。
このデータのサイズは、Felicaコマンドのpreambleなどに指定するデータのサイズとは別の、Pushコマンドで必要となるサイズである。
private static byte[] packContent(byte[] segments) { byte[] buffer = new byte[segments.length + 1]; buffer[0] = (byte) segments.length; System.arraycopy(segments, 0, buffer, 1, segments.length); return buffer; }
Felicaコマンド
最後にこれらを順に呼び出してFelicaコマンドを構成する。
(すでに先達のブログなどで既出なので省略する。)
private byte[] buildPushData(String url, String browserStartupParam){ byte[] content = packContent(packSegment(buildPushStartBrowserSegment(0x02, url, browserStartupParam))); ... (略) return data; }
Push送信機能を実行する
こんな感じで @hide なRawTagConnection をリフレクションしてコマンドを発行すれば、受け手側のLinx3Dに冒頭のスクリーンショットのように確認ダイアログが表示される。
プログラム中の DelegateFactory はリフレクションのためのユーティリティクラス。
INfcAdapter、IRawTagConnection は@hideなクラス、メソッドを扱うためのプロキシインターフェイスで、プレフィクスの"I"を除いた同名のNFC APIのクラスのメソッドを呼び出している。
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(); INfcAdapter iNfcAdapter = DelegateFactory.create(INfcAdapter.class, nfcAdapter); Object rawTagConnection = iNfcAdapter.createRawTagConnection(tag); IRawTagConnection iRawTagConnection = DelegateFactory.create( IRawTagConnection.class, rawTagConnection); String url = editUrl.getEditableText().toString(); String browserStartupParam = "Hello Android!!"; byte[] pushData = buildPushData(url, browserStartupParam); try { iRawTagConnection.transceive(pushData); } catch (Exception e) { e.printStackTrace(); } finally { iRawTagConnection.close(); }
IntentをPush。。。したい
AndroidのPush送信機能では、Android同士の場合はIntentを飛ばすことができる。
しかし、これに関しては公開された情報がないため、まだ倒せていない。
Push送信機能をnfc-felicaで使ってみた。
Push送信機能のライブラリは MFCUtility_x.x.x.jar(xは適当なバージョン番号)として配布されている。(以下MFC)
import com.felicanetworks.mfc.PushStartBrowserSegment; ...(略) PushStartBrowserSegment segment = new PushStartBrowserSegment(url, browserStartupParam);
これを @Kazzzさんが公開されている nfc-felicaと組み合わせてみた。
まずはCommandPacketを継承して PushCommand を作る。
PushCommandはコンストラクションで MFCのPushStartBrowserSegmentを受け取り、nfc-felicaの内部表現に変換する。
package com.undrdevelopment.android.felica.push; import java.nio.ByteBuffer; import net.kazzz.felica.FeliCaException; import net.kazzz.felica.lib.FeliCaLib; import net.kazzz.felica.lib.FeliCaLib.CommandPacket; import net.kazzz.felica.lib.FeliCaLib.IDm; import com.felicanetworks.mfc.PushSegment; import com.felicanetworks.mfc.PushStartBrowserSegment; public class PushCommand extends CommandPacket { public static final byte PUSH = (byte) 0xb0; static { FeliCaLib.commandMap.put(PUSH, "Push"); } public PushCommand(IDm idm, PushSegment segment) throws FeliCaException { super(PUSH, idm, packContent(packSegment(buildData(segment)))); } private static byte[] packContent(byte[] segments) { byte[] buffer = new byte[segments.length + 1]; buffer[0] = (byte) segments.length; System.arraycopy(segments, 0, buffer, 1, segments.length); return buffer; } private static byte[] packSegment(byte[]... segments) { int bytes = 3; // 個別部数(1byte) + チェックサム(2bytes) for (int i = 0; i < segments.length; ++i) bytes += segments[i].length; ByteBuffer buffer = ByteBuffer.allocate(bytes); // 個別部数 buffer.put((byte) segments.length); // 個別部 for (int i = 0; i < segments.length; ++i) buffer.put(segments[i]); // チェックサム int sum = segments.length; for (int i = 0; i < segments.length; ++i) { byte[] e = segments[i]; for (int j = 0; j < e.length; ++j) sum += e[j]; } int checksum = -sum & 0xffff; putAsBigEndian(checksum, buffer); return buffer.array(); } private static byte[][] buildData(PushSegment segment) throws FeliCaException { if (segment instanceof PushStartBrowserSegment) return buildPushStartBrowserSegment((PushStartBrowserSegment) segment); throw new IllegalArgumentException("not supported " + segment); } private static byte[][] buildPushStartBrowserSegment( PushStartBrowserSegment segment) { final int type = segment.getType(); final String url = segment.getURL(); final String browserStartupParam = segment.getBrowserStartupParam(); byte[] urlByte = url.getBytes(); byte[] browserStartupParamByte = browserStartupParam == null ? new byte[0] : browserStartupParam.getBytes(); int capacity = urlByte.length + browserStartupParamByte.length + 5; ByteBuffer buffer = ByteBuffer.allocate(capacity); // 個別部ヘッダ // 起動制御情報 buffer.put((byte) type); // 個別部パラメータサイズ int paramSize = urlByte.length + browserStartupParamByte.length + 2; // urlLength(2bytes) putAsLittleEndian(paramSize, buffer); // 個別部パラメータ // URLサイズ putAsLittleEndian(urlByte.length, buffer); // URL buffer.put(urlByte); // (ブラウザスタートアップパラメータ) buffer.put(browserStartupParamByte); return new byte[][] { buffer.array() }; } private static void putAsLittleEndian(int i, ByteBuffer buffer) { buffer.put((byte) ((i >> 0) & 0xff)); buffer.put((byte) ((i >> 8) & 0xff)); } private static void putAsBigEndian(int i, ByteBuffer buffer){ buffer.put((byte) ((i >> 8) & 0xff)); buffer.put((byte) ((i >> 0) & 0xff)); } }
呼び出し部分。
String url = editUrl.getEditableText().toString(); String browserStartupParam = "Hello Android!!"; PushStartBrowserSegment segment = new PushStartBrowserSegment(url, browserStartupParam); PushCommand pushCommand; try { pushCommand = new PushCommand(new IDm(idm), segment); FeliCaLib.execute(tag, pushCommand); } catch (FeliCaException e) { e.printStackTrace(); }
(オチがない)
- 以上 -
- 97 http://members.jcom.home.ne.jp/sarasiru/
- 38 http://www.mew5.com/
- 27 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4SKPB_jaJP367JP368&q=googleドキュメント フォーム 確認画面
- 23 http://members.jcom.home.ne.jp/sarasiru/nikki.html
- 20 http://hootsuite.com/dashboard
- 19 http://twitter.com/
- 15 http://www.google.co.jp/search?q=すれちがったー&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&hl=ja&client=firefox-a
- 14 http://longurl.org
- 13 http://blogs.itmedia.co.jp/katabami/2010/07/androidbluetoot.html
- 13 http://code.google.com/p/android/issues/detail?id=719
