2012/10/21

[n7]あの画面を出したい

ふと「あの画面を出したい」と思った。
この画面だ。

image

昨日のFukuoka NFCで見せてもらったのだが、2台のNFC Android端末を向かい合わせると、両方ともこの画面になる。
そして、片方がタッチすると送りつける。

私が今のところ作っているのは、PaSoRiからAndroid端末にSNEPでURLなんかを送りつけるアプリだ。
だから、受けとるAndroid端末にはこの画面が出てこない。

 

出したい・・・。


だいたい、当てはついていた。

  • どっちもこの画面になれるのなら、InitiatorやTargetは関係ない
  • タッチしたら、止めることはできない

ということだから、LLCPのSYMMを繰り返している期間だろう。

 

ふむ、当たりだ。
タッチするとCONNECTを送っているので、

  • CONNECTを送信する方が、SNEPクライアント
  • CONNECTを受信した方(CCを送信する方)が、SNEPサーバ

という立ち位置になるようだ。


ふっ、私もずいぶん理解できるようになったもんだ。
今年の初めの方まではさっぱりわからなかったけど、「突然わかる」というのはいつか訪れるもんだな。

 

あとはこの実装を、SNEPサーバになれるようにしてしまいたい。
そして複数のI PDUに対応すれば、Android側でBeamするアプリを作ってNFCだけでPCへファイルを送りつける、というようなものができんかなー、と思っている。
いや、Bluetoothで十分なんだけど、ちょっとやってみたいな、というところだ。

2012/10/14

[n7]FeliCa Lite用のライブラリを作ろう (2) : 片側認証編

「べ、別にFeliCa Liteのことが好きなわけじゃないんだから」というわけで(なんじゃこりゃ)、FeliCa Lite用ライブラリの続きを作ろう。

前回作ったのは、どちらかといえばNFC-Fの補足ライブラリのようなものであった。
今回は、個別化を移植しよう。

NfcAとNfcFのクラスライブラリは同じ水準かもしれない。
しかし、MifareUltralightがAndroidの標準にあって、Felicaがないのはよろしくない。
Felicaがないのなら、MifareUltraLightもあってはならない、と思うんだけどなあ。
いっそのこと、Type2TagとかType3Tagみたいなクラスにしてしまえばいいのに。


以前も、片側認証のための個別化カード鍵についていろいろ調べた。
特にはまったのが、3DESのエンディアン。
http://hiro99ma.blogspot.jp/2011/09/felica_04.html

FeliCa Liteのユーザーズマニュアル「5.1.1. MAC生成アルゴリズム」にも、添字の大きい方が上位、と書いてある。
なので、まあこれはいいとしよう。

 

それよりも困っているというか悩んでいるというか、個別化マスター鍵の値が1つ違っても、計算するMACの値が一致してしまうのだ。
FeliCa Liteは、IDブロックを読んだときのMACを使っている。
なにが悪いんだろう?

いろいろ調べていたが、DES暗号化は鍵が56bitだから、8byte(64bit)指定しても使われないビットが8bit分あるんじゃなかろうか、と思った。
「1つ違っても」で試したのは一番下のデータだったので、8byteのそれぞれ最下位ビットが使われないと考えるとつじつまが合いそうだ。

JavaでもC#でも同じように動いているということは、APIがだいたいそういうものということか。

 

http://www.arch.cs.kumamoto-u.ac.jp/~kuga/cad/crypt/des/
ここの説明に「パリティ」という説明があった。
使われないのではなく、パリティビットだったのか。。。
理屈はわかったが、「8byteの鍵」って書いてあると、ここまで気が回らないなあ。


そんなわけで、実装方法としては間違っていないようだ。
ただ、個別化マスター鍵の管理方法として、

  • 鍵バージョンと個別化マスター鍵はきっちり管理すること
  • 3DESなので、個別化マスター鍵のA[0~7]、B[8~15]、C[16~23]の各バイトの上位7bitが同じ値にならないようにすること(鍵Aで暗号化→それを鍵Bで復号化→それを鍵Cで暗号化、とするので、AとBが同じだと、Cで暗合しただけと同じになる)
  • 3DESなので、新しい鍵バージョンを追加するときは過去の個別化マスター鍵と比較し、各バイトの上位7bitが一致しないことを確認すること

というような取り決めにした方がよいだろう。

 

ようやく、片側認証らしきものを追加した。
https://github.com/hirokuma/NfcTest4

知らない分野はまだまだいろいろあるな。

2012/10/13

[n7]FeliCa Lite用のライブラリを作ろう (1)

「作ろう」って書くと、「レジ袋とペットボトルでペンギンを作ろう」という歌(オフロスキー)を思い出してしまう。

それはどうでもいいとして、前回FeliCa Liteの読み込みをやろうとしたが、MifareUltralightみたいなクラスがないのでめんどくさかった。

誰か作っていそうな気がするけど、勉強がてらFeliCa Lite用のライブラリを作ろう。


(途中省略)


ひとまず、1ブロックのReadとWriteを作った。
ReadはPAD0の読み込みだけ確認。Writeは動作確認すらしていない。

https://github.com/hirokuma/NfcTest4/blob/master/src/com/blogpost/hiro99ma/nfc/FelicaLite.java

 

NfcFクラスを継承して、置き換えて使ってもらえるようなものにしようかと思っていた。
が、NfcFクラスはfinal classで継承できず・・・。
では、その親のBasicTagTechnologyクラスを継承しようとしたが、これはpublicじゃないので見えず・・・。

じゃあ、packageをandroid.nfc.techにすればいいか?とも思ったが、それはだめなような気がするので、is-aは諦めてhas-aの形にした(なんて呼ぶかわからん)。


こういうの、C/C++だといろいろと「強引な」やりかたがあるんだけど、Javaはさすがにできませんな。
まあ、それはその方がいいと思う。


Read

public static byte[] readBlock(int blockNo) throws IOException {
    byte[] buf = new byte[16];
    buf[0] = 16;                    //length
    buf[1] = (byte)0x06;            //Read Without Encryption 
    System.arraycopy(mTag.getId(), 0, buf, 2, 8);
    buf[10] = (byte)0x01;            //service num
    buf[11] = (byte)0x0b;            //service code(lower)
    buf[12] = (byte)0x00;            //service code(upper)
    buf[13] = (byte)0x01;            //blocklist num
    buf[14] = (byte)0x80;            //2byte-blocklist(upper)
    buf[15] = (byte)blockNo;        //2byte-blocklist(lower)
    
    byte[] ret = mNfcF.transceive(buf);
    
    //length check
    if(ret.length != 29) {
        ret = null;
        return null;
    }
    //IDm check
    for(int i=2+0; i<2+8; i++) {
        if(ret[i] != buf[i]) {
            ret = null;
            return null;
        }
    }
    //status flag check
    if((ret[10] != 0x00) || (ret[11] != 0x00)) {
        ret = null;
        return null;
    }
    
    //read data copy
    System.arraycopy(ret, 13, buf, 0, 16);
    ret = null;
    return buf;
}

シンプルに「コマンドデータ作成」「送信」「受信データチェック」「受信データコピー」の順に処理。
まあ、それ以外無いけど。

あ、受信データチェックに応答コマンド値(0x07)を見てないな・・・。
ブロック数をチェックしてないのは、データ長でほぼそれがわかるし、それ以外の値だったとしてもまあいいか、ということで。

サービスコードが0x000b固定なのは、ReadOnlyにしておけばRWでもROでも読めるようだから。
製品仕様なのかな? ドキュメントを読みきれてないけど、動いてるというレベル。


Write

public static boolean writeBlock(int blockNo, byte[] data) throws IOException {
    byte[] buf = new byte[32];
    buf[0] = 32;                    //length
    buf[1] = (byte)0x08;            //Write Without Encryption
    System.arraycopy(mTag.getId(), 0, buf, 2, 8);
    buf[10] = (byte)0x01;            //service num
    buf[11] = (byte)0x09;            //service code(lower)
    buf[12] = (byte)0x00;            //service code(upper)
    buf[13] = (byte)0x01;            //blocklist num
    buf[14] = (byte)0x80;            //2byte-blocklist(upper)
    buf[15] = (byte)blockNo;        //2byte-blocklist(lower)
    System.arraycopy(data, 0, buf, 16, 16);
 
    byte[] ret = mNfcF.transceive(buf);
 
    //length check
    if(ret.length != 12) {
        ret = null;
        return false;
    }
    //IDm check
    for(int i=2+0; i<2+8; i++) {
        if(ret[i] != buf[i]) {
            ret = null;
            return false;
        }
    }
    //status flag check
    if((ret[10] != 0x00) || (ret[11] != 0x00)) {
        ret = null;
        return false;
    }
    ret = null;
    return true;
}

こちらも、基本はReadと同じ。
返すデータが書き込みの成功か失敗になる、というところか。


途中でreturnするのが嫌いな人も多いだろうが、私は関数の最初(パラメータチェック)と最後(結果のチェック)ならreturnで抜けてもいいかな、と思っているので、こうなってる。

 

NfcF#transceive()の戻り値をreturn前にnull代入しているが、そこまでする必要はあるのかな?
いらんと思うけど、知識がない。。
無駄なコードを作り込んでいるような気がしてならん。

Javaがよくわからん・・・

AndroidでNFC-Fのコマンド送受信をするときは、NfcF.transceive()を使う。
こんなメソッドだ。

public byte[] transceive(byte[] data) throws IOException {
    return transceive(data, true);
}

戻ってくるのは、応答した結果のbyte[]。

さて、このbyte[]、どうしたらいいんだろうか?
どっちかだと思う。

  • transceive()以下でメモリ確保してあげてるから、そのまま使っていいよ。
  • 暫定でメモリを渡してるんだから、さっさと自分でコピーしてね。

悩んでいたが、文字にしてみると、前者だろう、ということに気付いた。

 

まだ言語仕様がよくわかっていないが、Javaの引数はCの引数と同じイメージでいいようだ。

void f() {
  byte[] a = new byte[] { 1, 2, 3 };  //「new byte[]」はいらない?
  g(a);
  h(a);
}

void g(byte[] a) {
  a[0] = 4;
}

void h(byte[] b) {
  b = new byte[] { 5, 6, 7 };
}

f()が持つa[]は、g()によって書き換えはされるが、h()によって置き換えられることはない。
だから、相手先にメモリを確保してもらうなら、戻り値でもらうようにするのがよさそうだ。
引数で返してもらうなら、配列にしてしまうしかないのかな?

void f() {
  byte[][] a = new byte[1][];
  a[0] = new byte[] { 1, 2, 3 };
  h(a);
}

void h(byte[][] b) {
  b[0] = new byte[] { 5, 6, 7 };
}

 

なんか、C#の文法とかが入り交じってしまって、コンパイルもしてないから、私が思っているイメージが伝わればいいかな、ということで。


そしてまだよくわかってないものに、System.arraycopy()が挙げられる。

System.arraycopy()は、シャローコピー(shallow copy)だそうだ。
って、みんないってるけど、言語仕様なんだろうか?

package test;
 
public class Test {
    public static void main(String[] args) {
 
        byte[] a = { 1, 2, 3, 4, 5 };
        byte[] b = { 6, 7, 8, 9, 0 };
        byte[] c = new byte[2];
        System.arraycopy(a, 0, c, 0, 1);
        System.arraycopy(b, 0, c, 1, 1);
 
        //最初の値
        System.out.print("a:");
        for(byte k : a) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("b:");
        for(byte k : b) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("c:");
        for(byte k : c) {
            System.out.print(k + " ");
        }
        System.out.println();
        
        //書き換え
        c[0] = 10;
        c[1] = 11;
        
        //最後の値
        System.out.println("----------");
 
        System.out.print("a:");
        for(byte k : a) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("b:");
        for(byte k : b) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("c:");
        for(byte k : c) {
            System.out.print(k + " ");
        }
        System.out.println();
    }
}

結果

a:1 2 3 4 5
b:6 7 8 9 0
c:1 6
----------
a:1 2 3 4 5
b:6 7 8 9 0
c:10 11

 

あれ?
参照してるんだから、参照先が変わると参照元も変わるんじゃないの??

じゃあ、参照元だけ変更して、参照先はそのままにしてみた。

 

結果

a:1 2 3 4 5
b:6 7 8 9 0
c:1 6
----------
a:10 2 3 4 5
b:11 7 8 9 0
c:1 6

 

変わらん・・・。
ちゃんと値コピーされている。

 

いや、不思議に思ったのだ。
コピーって配列の一部だけコピーすることができるけど、それをわざわざ「ここからここまではこのオブジェクトを参照」「ここからここまでは、あのオブジェクト参照」なんて管理するのは、非常に面倒だし、かえって遅くなるではないか。

 

System.arraycopy()の「shallow」な対象は、配列オブジェクトではなく、配列オブジェクトの要素部分ということなんだな。
上の例では、要素部分はプリミティブなbyte型だから、そのまま値コピーされている。
要素部分がStringみたいな型だったら、それはアドレスをコピーするだけにしていて、その先は同じものを参照しますよ、ということか。

 

 image
要素部分がオブジェクト型の場合は、アドレスだけをコピー

 

image
要素部分がプリミティブ型の場合は、アドレスに相当するのが値なので、値コピー

 

試してよかった・・・。

[n7]USBデバッグモードにしておくと、MTPはだめらしい

私はWindows XPを使っているのだが、Nexus7をUSBでつなぐとデバイスマネージャで「! MTP」と出てくる。
MTPを反転させてるんじゃなくて、ドライバが見つからんよ、という意味だ。

ネットで調べてみると、MediaPlayerのバージョンがどうとか、あれをインストールしたとか、削除してつなぎ直したとか、いろいろ出てくる。
そして、いろいろやったが、変わらん。

もうどうでもいいや、と思って見ていると、USBデバッグ時はMTPできん、というサイトがあった。
Windows XPでNexus7の内部ストレージにアクセスする方法|はるかなぎの搜作記録

USBデバッグモードを解除してつなぎ直すと、あっさりドライバがインストールされて認識した。
なーんだ。
まあ、わかってしまえば大したことじゃなかったけど、それまではさっぱり見当も付かないことってあるよな、と自己弁護した。


ちなみに、Windows7のときも同じような目に会った。
hiro99ma blog: [n7]Windows7でNexus7がMTPとして認識してなかった

[n7]TagLostException (2)

TagLostExceptionをどうたどろうか考えていたら、先生が教えてくれた
さすがだ。

そんなわけで、transceive()しているデータを見直すことにした。
先に書いておくと、「結果としてその通りだった」ということになる。


NFC-Fの場合、先頭に送信データサイズが1byteあり、それに続けてコマンドが続く。
つまり、送信データサイズは、コマンド長+1になる。

Read w/o Enc(NFC Forumでいうところの、CHECK)は、こんな構成。
説明を簡単にするために、2byteブロックリストで1ブロックだけ読み出すときの例とする。

([0] 送信データサイズ)
[1] コマンド:0x06
[2~9]IDm(NFCID2)
[10]サービス数:0x01
[11~12]サービスコード
[13]ブロック数:0x01
[14~15]ブロックリスト

FeliCaチップに送信するときはもう少しデータを追加するのだが、transceive()にはこれくらいあればいいようだ。

データを見直してみると、どうもサービス数を書き込む配列の添字が間違っていて、IDmと重なっていたようだ。
修正して送信すると、TagLostExceptionは発生しない。
うむ、よかろう。

 

ということは、TagLostExceptionが発生するのは、InCommunicateThruに相当する命令を実行して、コマンド自体は成功したけれども、相手から応答がなくてタイムアウトした場合にも発生すると考えていいのかな。


では、うまくいかなかったPUSH(三者間通信)の方もやってみよう。

これも似たような間違いをしていて、IDmとデータ部が重なっていたり、データ長が違ったりしていた。
なんでそういう間違いが多いかというと、AndroidからPaSoRiを操作するライブラリを作ったとき、先頭のデータ長はライブラリで自動的に突っ込むようにしていたためだ。
それを、データ長付きのパラメータに変更したとき、いろいろ間違ったというところだ。
やれやれ。

 

修正して送信すると、TagLostExceptionは発生しない。
うむ、よかろう。
では携帯電話の方は・・・。

出てない。
エラーにならないのに・・・。

transceive()の戻り値を見ると、コマンドは通っているがエラーが返ってきているっぽい。
つまり、個別部のフォーマットが間違っているとかなのか?

ああ、IDmと個別部数の間に、パラメータ長がいるんだ。
修正している間に、間違って消してしまったらしい。
パラメータ長を追加すると、うまくいきましたとさ。
めでたしめでたし。

https://github.com/hirokuma/NfcTest3


今日わかった、transceive()によるTagLostException

  • Targetから応答がなかった場合に発生する
  • Targetから応答があれば、データの中身がエラーであっても成功したことになる。
    つまり「行って返るまで」の責任を負っている。

2012/10/12

[n7]TagLostException (1)

まずは、基礎的なアプリのサンプルをたくさんつくって、後はサンプルから使い回すようなことを考えている。

簡単そうな三者間通信をやってみよう。


エラーが出る。

android.nfc.TagLostException: Tag was lost.
    at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
    at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
    at android.nfc.tech.NfcF.transceive(NfcF.java:119)

"Tag was lost"とあるから、捕捉していたNFCカードを見失ったのか?
でも、カードは机の上に置いて、Nexus7はその上に置いているので、カードを見失ったとか、R/Wの範囲外になったというのは考えにくいように思う。

 

Pollingならうまくいくかと思ったが、そうでもなかった。
うーむ。
・・・あ、サイズを付け忘れていたのでやったら、Pollingは通った。
でも、Read w/o Encryptionはだめなんだよなぁ。


NFC-Aのコマンド(Read)は、特に問題なく通ったように見える。

うーん・・・・んんん?

 

まあ、まだ間違った認識を持っているのはありえるだろうね。

[a]インストール失敗の条件はいずれ調べたい(いずれ・・・)

いつもお世話になってるやましたさんに「permission revokeはpermission剥奪だけのはず」と教えてもらった。

そうか、permissionをrevokeする、というだけなんだ・・・。

 

もしかして、mmでビルドしてるからなのか?と思ったが関係ないし、mm用の自分用署名を使ってるからかと思ったが、それも関係が無かった。

なんだ、なんなんだ・・・?

 

今日、なにげなくデベロッパーコンソールを見ると、有効インストール数が1つ!
そうか、インストールできる人もいるんだ!

ってことは、環境なのか・・・。
Nexus7にはSDスロットがないから、とか、そんな理由なのかなぁ。

 

まあ、一人でもインストールできるならいいや、と私は細かいことを忘れることにした。

いつかね、いつか調べよう(たぶんそれも忘れる)。

[android]permission revokeとインストール失敗は関係ない?

気力がないので、logcatだけ貼っておく。
とにかく、EjectSD4が失敗するのはpermission revokeのせいだと思っていたが、そうでもない、ということのようだ。

 I/ActivityManager(  355): START {dat=file:///storage/sdcard0/Download/IKAKU_Widget.apk cmp=com.android.packageinstaller/.InstallAppProgress (has extras) u=0} from pid 7018
D/dalvikvm(  355): GC_FOR_ALLOC freed 1032K, 27% free 14840K/20103K, paused 45ms, total 49ms
W/ActivityManager(  355): No content provider found for permission revoke: file:///storage/sdcard0/Download/IKAKU_Widget.apk
W/ActivityManager(  355): No content provider found for permission revoke: file:///storage/sdcard0/Download/IKAKU_Widget.apk
I/ActivityManager(  355): Displayed com.android.packageinstaller/.InstallAppProgress: +78ms
W/ActivityManager(  355): Unable to start service Intent { act=com.extscreen.service }: not found
W/ActivityManager(  355): Unable to start service Intent { act=com.extscreen.service }: not found
I/PackageManager(  355): Running dexopt on: jp.typepad.hirokuma.IKAKU_Widget
D/dalvikvm( 7890): DexOpt: load 8ms, verify+opt 41ms, 321884 bytes
I/ActivityManager(  355): Force stopping package jp.typepad.hirokuma.IKAKU_Widget uid=10081
D/PackageManager(  355): New package installed in /data/app/jp.typepad.hirokuma.IKAKU_Widget-1.apk
I/InputReader(  355): Reconfiguring input devices.  changes=0x00000010




2012/10/10

[java]shallow copy

#(2012/10/13追記) System.arraycopy()についてよくわかってないことを書いてますが、後日調査しました。

NFCアプリを作るならば、NFCカードへのアクセスは避けられまい。
どうやってやるのか調べたところ(というほどでもないが)、MIFARE Ultralightの例があった。

なるほど、writePage()か。
いやいや、その前に「MifareUltralight」ってクラス名かい!
念のためにNfcAなどを見てみると、transceive()しかなかった。
うーん、それなら「Felica」クラスもあっていいと思うんだけどなぁ。


まあ、手段があるならいいや。
いつものようにバイト配列にコマンドを書いて送るだけだ。

・・・と思ったが、transceive()の引数はbyte[]だった。
長さの指定が無いということは、引数のlengthがそのままデータ長になるんだろう。
それだと、バッファとして用意したbyte[]にデータを突っ込むだけではだめだ。

 

他の人がどうやってるか知らないが、私はだいたい、NFC R/Wに対してWRITEバッファとREADバッファを1つずつ持つように作っている。
無線なので「同時読み書き」が発生しないからバッファは1つでも足りるのだけど、デバッグに便利だし、めんどくさいのでそうしている。
もったいないとは思うのだが、このくらいは許してくれる・・・と思っている。

そのバッファサイズは、FeliCaの最大にあわせて265byteにしている(たしか)。
プリアンブルとポストアンブルは固定だから4byteくらい削れるんじゃないの、といわれそうだが・・・・すまん、めんどくさかったのだ。


さて、本題だ。

私がR/Wに転送するときは、バッファにデータを詰めて、データとデータ長を渡すようにしている。
C/C++の配列にはデータ長がないので、ユーザが管理するのが普通だからだ。

しかし、transceive()はbyte[]のみで、lengthは渡せない。
これは困った。

Javaだとヒープに確保するんだから直前までサイズを固定しなければいいじゃないの、といわれそうだが、何となく慣れなくて・・・。
新しい革袋を用意すべきだとは思うのだが、私にはまだその準備ができていないのだ。

じゃあ、データを作った後になればサイズがわかるから、そのときにまた領域を確保してコピーすればいいやん、という考え方もあるが・・・もったいない。
ほら、我々の世代は「資源は有限、資源を大切に」と育てられた世代ではないか。
違うlengthを得るためだけにコピーするのは、心苦しい。
Genericとか、そんなしくみはいらず、とにかくバッファがほしいだけなんだけどなぁ。

 

と思って調べていると、JavaFAQというページがあった。
ここの「S006-Q09」に、こんな質問があった。

コピーした配列の要素を書き換えると元の配列でも書き換わってしまうのですが?

その回答が、こう。

配列をコピーした際に、配列の要素がコピーされていないからです。

 

おお、これこれ!
こういうのを期待していたんだよ。
shallow copyって、COWで書き込むまでのつなぎってイメージがあったけど、こういうときに使うものなのかな。


shallow copyなら、コピー先のnewはいらないんじゃないの?と思ってやってみた。

だめだった。
うーん、領域をコピーせんといかんのなら、なんか意味が無いなあ。

 

このnewは私に覚悟を問うているのかも。
「貴様に、これだけのメモリを確保する意気込みはあるのか」とか。
ないんだけど、ないんだけど・・・。

 

newすると当然うまくいくんだが、なんだかなぁ。
実際に使用するまでは、メモリ確保したふりをするだけだったらいいんだけど。

2012/10/08

disableForegroundDispatch()のcallがmustである理由を知りたい

一度気になると、なかなか忘れられないというところがある。
なぜ、onPause()が終わるまでにdisableForegroundDispatch()を呼び出さないといけないのだろう?


まずは、NfcAdapter.java。

public void disableForegroundDispatch(Activity activity) {
    ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
            mForegroundDispatchListener);
    disableForegroundDispatchInternal(activity, false);
}

まず、unregisterOnActivityPausedListener()を呼んでいる。
引数にしているmForegroundDispatchListenerは、こんな定義だ。

OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
    @Override
    public void onPaused(Activity activity) {
        disableForegroundDispatchInternal(activity, true);
    }
};

深追いしないが、名前からするとonPause()が呼ばれるようなタイミングになったら通知を受けるようなやつ(Listener)なんだろう。
中にはメソッドが1つあって、これがonPause()だ。中ではdisableForegroundDispatchInternal()を呼び出すだけ。

disableForegroundDispatchInternal()は、disableForegroundDispatch()の中でも呼ばれている。
違いは第2引数がtrueかfalseか。
仮引数名が"force"なので、強制的に無効にするかどうかのようだ。
何を持って「強制的」かというと、メインスレッドかどうかのようだ。
disableForegroundDispatch()の説明に「メインスレッドから呼べ」といっているのは、このことなのだろう。

 

強制的でも何でもonPause()が呼ばれたら無効にしてくれるんだったら、別にわざわざdisableを呼び出さなくてもいいんじゃないの、と思った。
前回「試したけど違いがわからん」といったのは、この強制的な手段が働いたからなのだろう。
それで十分やん!

 

とあさはかに考えそうだったが、disableForegroundDispatch()との違いはunregisterOnActivityPausedListener()の有無だ。
ここに秘密が隠されているのでは?


さっきのmForegroundDispatchListenerは、enableForegroundDispatch()で登録されて、disableForegroundDispatch()で登録解除されている。

何に対して登録/解除するかというと、ActivityThreadのmOnPauseListenersだ。
と、さも知ってるかのように書いたが、知らない。
名前からすると、onPause()を実行するときに呼び出すOnActivityPausedListenerの集合なんだろう。

この、よそ様の集合に登録しているから、最後はちゃんと解除しなさいよ、ということなのだろう。
onPause()が完了するまでに、という制限は、この集合がonPause()時に呼び出すためのものだからということか。

 

なんとなくわかった気がする。


やはり、解除しないまま呼び先がなくなったときの挙動が気になる。

どちらかというと、解除されてないからGCが作動せず、メモリに残り続ける、ということになるのかな。
Javaは詳しくないが、そんな感じだったような気がする。
Androidだからメモリがなくなれば有無を言わさないのかもしれないが、それまでは居座るのかもね。

2012/10/07

[n7]カードをかざされても自動起動しないようにする

前回、適当ながらNFCアプリを作った。
このアプリには問題がある。カードをかざすと、勝手に起動してしまうことがあるのだ。
さっきも机の上にNexus7を置いたら、急にアプリが起動してしまった(机の上は片付けよう)。

 

アプリは、NFC技術ブログを参考にしたのだが、そもそもこれは「アプリを起動していなくてもNFCに反応する」が目的なのだ。
では、アプリを起動しているときだけに絞りたいときの記事を探すと、アトリエのどかさんの記事が出てきた。

AndroidManifest.xmlに<intent-filter>を書くと、システム側が検索してintentを投げるようなしくみになっているので、アプリが起動してからintent-filterを登録しましょう、ということだ。

そしてもう1つ。
NfcAdapter.enableForegroundDispatch()を呼んで、現在のアクティビティに検出intentを飛ばす優先度を上げる。
前回は「NDEFが書いてあるカードだとうまくいかん」と書いたが、こうしておけば先に拾うことができるのだろう。

enableForegroundDispatch()の対になるのは、disableForegroundDispatch()。
enableの方は、メインスレッドかつアクティビティが表になっているときに呼べ(onResume)、と。
disableの方は、onPause()が完了する前に呼べ、と。


ただ、Kazzzさんの記事によると、onPause()が意図しないタイミングで呼ばれることもあるそうな。
そりゃ、困る。
・・・と思う。なんとなくだが(よくわかってない、ともいう)。

「現在のActivityが新たなインテントを処理する際にも(onPauseが)呼ばれる場合がある」ということだが、私が作るようなアプリは、たぶんあまり複雑な動きをしないので、次にonResume()が呼び出され、そこでまたenableForegroundDispatch()が呼び出されさえすれば問題ないと思う。

さすがに、onPause()だけ呼び出されて、onResume()が呼ばれないままフォアグラウンドに回ってくるようなことはOSのバグじゃ無い限りは起きないんじゃないか、と思う。

あるいは、インテント受信のときにonPause()が呼ばれると、そのままonResume()が呼ばれないとか・・・?
それはそれでバグっぽい動きだよなぁ。
うーむ。

記事の内容に気を留めつつ、当面は普通の実装にしておこう。


どちらかといえば、onPause()でdisableForegroundDispatch()し忘れたらどうなるかが気になる。
やってみたのだが、よくわからない。
普通に動いているように見えるし、他のNFCアプリも普通に動いているようだ。

気になるなぁ。

[n7]NFCを設定アプリ以外から止めるのは難しそうだ

AndroidでNFCを有効にしていると、画面がロックされてなければ無線を出している。
べつにNexus7を持ち歩くわけでもないから電池寿命はそんなに気にしなくていいんだけど、止められるなら止めたい。

 

やはり同じようなことを考える人はいるようで、できなかった、という記事があった。
私も同じことをやって、同じようにだめだった。
/system/appだっけ、あそこに置いていないと解決できないそうだ。

 

自分でビルドした環境だったら好きに出来るのだが、なるべくそういうことをせずにやってみたい。
airplaneモードになれば止まるのだが、それもなんだか大味すぎる。

じゃあこういうのはどうだろうか、とNfcServiceが使っていると思われるlibnfc_jni.soを操作してみようとした。
そしたら・・・loadLibrary()の時点でだめだった。
JNI_OnLoad()でいろいろチェックしているから、そこだろうな。

 

なら自分で同じようなlibをつくるか、となると、どうせHWはライブラリが握ってるだろうから。。。とか考えると、そこまでやるのはちょっとやめよう。


しかし、同じNfcAdapterでも、isEnabled()とかは大丈夫で、enable()がだめなのは何でだろう?
どちらも、sServiceのメソッドを呼び出しているのに。

 

isEnabled()は、NfcAdapterService.getStatus()を呼び出している。
やってるのは、状態を返すだけ。

enable()は、NfcAdapterService.enable()を呼び出している。
やってるのは、状態を保存した後、EnableDisableTask().execute()の実行。

 

お、もしかしてsaveNfcOnSetting()で設定を保存するからセキュリティエラーになってるとかでは。
それなら、disable(false)ってすると保存しないので、止めることくらいなら・・・。

 

だめだった。
どうも、NfcService.enforceAdminPerm()を呼び出すメソッドが、そういうセキュリティ云々といわれる扱いになるようだ。


EnableDisableTask.disableInternal()なんかを呼び出せればいいんだろうけど、これはpublicクラスじゃない。
リフレクションとかいうやつを使うとprivateなメソッドなんかも呼べるようなことが書いてあったが、そういう難しそうなのはやりたくないな。

あの葡萄は酸っぱいんだ、ということで、もうやめることにしよう。

[n7]NFCカードを読み取る範囲

Nexus7で、NFCカードをいくつかかざしていたが、どうもかざす範囲に偏りがありそうな気がした。
調べてみよう。

やりかたは、NFCカードをずりずりと動かしていって、検知音がした位置を見るという方法。
厳密ではないが、簡単でいいだろう。

個体差があるかもしれないので「うちのNexus7では」ということにしておく。


NFC-A(NTAG203F)

中央下から、上に向かって移動

image

 

中央上から、下に向かって移動
image

 

上端を右に向かって移動・・・のつもりだったが検知しないので、少し下にずらした

image

 

左に向かって移動

image

 

"nexus"を右に向かって移動

image

 

"nexus"を左に向かって移動

image

 

左下限をさぐった

image

 

同じく、右下限

image

 


FM-108(MIFARE Classic 1K互換)

 

中央下から移動

 image

 

中央上から移動

image

 

上方から右へ移動

image

 

上方から左へ移動

image

 


RC-S886(FeliCa Lite)

 

中央下から移動

image

 

中央上から移動

image

 

上方から右へ移動

image

 

左へ移動

image

 

カードをひっくり返して、中央下から移動

image

 

カードをひっくり返して、上方から右へ移動

image


わかったこと

  • NFC-Aは、丸形、角型ともに広く検知する。
    Nexus7にぴたっと引っ付けなくても、指1本分くらい空けて検知できる。
  • NFC-F(FeliCa Lite)は、検知しやすい側としにくい側がある。
    検知しやすい側でも、NFC-Aより狭い。
    検知しにくい側は、ほぼ中央でないとだめ。
    感度があまりよくないのか、ゆっくりと中央にかざしても検知しないことがある。
    距離も、NFC-Aよりかなり近づけないと検知できない。

 

へー、というくらいしか言えることがない。
検知しやすいしにくいの側は、表裏ではなく、上下左右。

 

あと、やっていて気付いたことも。

  • NTAG203だけかどうかわからないが、妙に検知しないことがある。
    シールを投げ落としていると、ときどき検知しない。
    そのときはずっと置いていても検知せず、カードをずらしていくと検知した。
  • あ、FM-108でも起きる。
    どうも、かざす速度が速いと、かざした状態を維持させていてもだめなようだ。
    ポーリングして応答が返ったら検知とみなすのかと思っていたが、何かしらの変化を見るような、ポーリングを出す前段階がありそうな気がする。
  • NFC-V(前もらった、プリンタで出したやつ)も検知する。

 

ふーん。
これが、うちのNexus7だけなのか、Nexus7全体なのか、PN65だからなのか、NXP製品全体なのかというのは、よくわからん。
まあ、PaSoRiもカードをきっちり載せてしか確認してないから、「Nexus7は検知範囲が狭い」と責めるようなものではないだろう。

それよりも、FeliCa Liteを検知するのが鈍かった方が気になる。
なにかあるのかねぇ。

[n7]アンテナ感度

無線の調整は、けっこう難しいと思う。
NFCとは違う周波数帯の仕事をやることもあるのだが(NFCはやってないよ)、なんか難しい。
法律も絡んでくるので、勝手に強くしすぎることもできないが、そもそも何がどうすりゃこうなる、がよくわかってない。
Nexus7も、法律内で調整しているだろう(じゃないと通らん)。
私のイメージだけの話だが、こういう調整は法律の範囲外の部分もあるんじゃなかろうか。
範囲外、と書くと響きが悪いが、法律の範囲内はきっちり守っているが「自社製品はちゃんと動くようにする」というような類のものだ。
USBメモリなんかでもたまにあるが、相性、のような範囲にあるもの。
そういう微妙なラインが、あるんじゃなかろうか。

たとえば、IC免許証。
うちのNexus7だと、何かあることはわかっているが、エラー音らしきものがしている。
いや、そもそも誰が音を出しているのかわからないんだけどね。
FeliCa Liteの小さいタグもあるのだが、これも読み取りが弱い。
しばしばエラーになる。
ただ、NFC-Aだと小さめでもきっちり拾っているような気がする。
これはもう、言いがかりかもしれない。単なる印象だ。
ちなみに「エラー音」と思っているのは、「ぺろろ」みたいな音だ。
成功時は「ふぉ~~~ぺぽ」という感じだが、失敗は「ふぉ~~ぺろろ」のような。
うーん、音の表現は難しい!

あとね、同一カードの検出が多い。
これは検知してはじけばいいだけなのだけど、カードをかざして、少し位置を変えたくらいで検知しようとしているように見える。
もうちょっとドライバレベルでヒステリシスを持たせるなりして検知を甘くしないと、運用は大変なんじゃなかろうか。
NFC-Fは、カードを補足して、なおかつ通信には毎回IDmを含むコマンドがほとんどだ。
だからカード捕捉状態のまま別のR/Wフィールドに差し替えて通信しようとしても、IDm不一致になる。
NFC-Aにはそういうセッションのようなものがないから、もしかするとカード捕捉状態で別のRFに捕捉状態を保ったまま通信することも可能なのだろうか。

いろいろと不思議な感じはするが、今日はもうやめておこう。

2012/10/06

[n7]Androidアプリを作ろう (3)

Androidアプリを作ろう (1)
Androidアプリを作ろう (2)
Androidアプリを作ろう (3)

ししゃもを食べながらAndroidアプリを作っている(どうでもいい情報)。
ししゃもという名前は、柳と関係があったような。。。


前回は、permissionの設定を調べていた。
今回は最終回。

 

どうやら、カードの読み出しには優先度があるらしい。
ブリリアントサービスさんのNFC技術ブログの話がわかりやすかった。

困ったことに、優先度が高いアプリがインストールされていると、低い方はかすりもしないようなのだ。
アプリが前面に出ているとかそういうのは関係ないみたいだけど、なんとかならんもんかね。


とりあえず、NDEFとかそういうのは考えたくないので、NFC-A、とか、NFC-B、というくらいの読み取りをしたい。
そうなると、ACTION_TECH_DISCOVEREDか、ACTION_TAG_DISCOVEREDのどちらかになる。
NDEFでなければ、次はTECHのようなので、TECHにしておく。
一番優先度が低いTAGは、どういう場合に使うのかね(つつましいアプリとかか)?

 

あとは、Android Developersを見ながらやっていく。

  • フィルタを作る(xml/xxxxxxx.xml)
    名前は何でもいいらしいが、TECHでひっかけるフィルタを作るようだ。
    res/xml/xxx.xmlという名前なら何でもいいらしい。
  • つくったフィルタをAndroidManifestに書く
    <meta-data>を自分で書かんといかん。
    使うaction名(TECH_DISCOVERED)と、フィルタ名(@xml/xxx)
    フィルタは、<tech-list>で囲む。
    1つの<tech-list>内に書いたものはAND条件らしい。NFC-AでNDEF、など。
    <tech-list>を複数書けば、それはOR条件になる。

NFC関係っぽいのは、こういうところなのかな。


そして、私にとってはそれからが長い。
Androidアプリの作り方があまりわからないからだ。
慣れた人にとってはどうでもいいことだろうが、やったことを書いておこう。

  • layoutを作る
    デフォルトでres/layout/activity_main.xmlができているので、ダブルクリック。
    なんか、昔よりハイカラになってる。。
    ボタンなんかを置いてみた。
  • OnClickを指定
    1.6くらいだっただろうか、ボタンのクリックメソッドが指定できるようになったのは。
    ただ、あまり使われている例を見たことがない。
    やはり、リソースとソースファイルに分割されてしまうのを嫌がるのだろうか。
    私?
    私は実装が減るのを歓迎するので、OnClickプロパティに書いたよ。
  • インテント
    カードを検知すると、インテントが飛んでくるようだ。
    忘れてたけど、インテントが飛んでくるとonCreate()が呼ばれて、最初から始まったような感じになってしまうのだな。
    インテントが来る前を覚えていたいので、プリファレンスを使うことになった。
  • Toastはスタックする
    ボタンを押したら、押した内容をToastで出すようにした。
    そうしたのだが、ボタンをたくさん押しても表示してくれない。
    どうやら、スタックして、タイムアウト時間が経過したら上書きするようだ。

ほら、どうでもいい感じだったでしょう。
でもね、よくわからん人にとっては、それぞれがよくわからなかったのだ。


そんなこんなで、Androidでの初NFCアプリを作った。

完成?版は、Bの方。
しかし、なぜかうちのNexus7はNFC-Bのカードを検出できなかった。
「なにかかざされた」というのはわかったようだが、エラー音らしきものがするのだ。
しかたなく、NFC-Bでやるべきところを、NFC-Fに置き換えたのがFの方。
同じpackageなので、どっちかしかインストールできないと思う。

 

なにをするアプリか、説明してなかった。
起動するとボタンが4つ出てくるが、その真ん中にTextViewが1つある。
ボタンを押したりNFCカードをかざしたりして、そこに「SUCCESS !」と表示させるアプリだ。
ヒントは、

  • B版はNFC-Bを認識してNFC-Fは認識しない、F版はNFC-Bの代わりにNFC-Fを認識。
    つまり、Bを認識する方を意図としている。
  • NFCカードは2種類必要。
  • すんませんが、NDEFは書かれていない方がよいです。
    書いてあると、うまくいかない気がします。。
  • Toastに出てくる数字は、手順。手順から外れると0に戻る。
  • 数字が進む手順がわかると、すぐわかりそうな気がする。

というところか。

 

SUCCESS、と表示されるだけで、それ以上のものは何もないです。
初めて作るのがこんなアプリとは・・・。

microUSB Aケーブルをつくってみよう

ネットで買った方が安いのに、わざわざヨドバシカメラまで出向いて買ったのに、買い間違えたmicroUSBケーブル。
また買うのももったいないので、つくってみよう。

まず、A500についていたmicroUSBケーブルをはがす。
image image
とあっさり書いたが、はがすのに1時間くらいかかった。
樹脂で固められていたのだ。
時給700円だったら、買いに行った方が安かったな。

microUSBプラグ(プラグが挿す方で、挿さる方は、ソケットとかレセプタクルとかいうらしい)は、正面から見た場合、右から順に1~5となっているそうだ。
1. VBus 赤
2. D- 白
3. D+ 緑
4. ID
5. GND 黒

4ピンと5ピンをショートさせればよいのだが、それはどこだろうか。
A500付属の場合、
・上側(左の写真):上側から見て左から、赤、被覆無しと黒
・下側(右の写真):下側から見て左から、緑、白

あれ・・・IDピンは?
まさか、スレーブ専用ってこと??
2つ立て続けに失敗すると、かなりダメージが大きいですな。。。