2011/08/31

[type3]Type 3 TagのAttribute Information Format

わからんわからんといっていた、FeiCa Liteに書かれたS_PAD0のデータ。
これはType3 Tagのドキュメントに書かれているところまではわかった。

わかったのだから、読んでみよう。


ドキュメント名は、「NFCForum-TS-Type-3-Tag_1.0.pdf」。
NFC Forumからダウンロードしたものだ。

 

Ver Nbr Nbw Nmaxb1 Nmaxb2 - - - - WriteF RW Flag Ln1 Ln2 Ln3 ChkSum1 ChkSum2

ならびは、ビッグエンディアンのようだ。

[0x00]Ver ... 1byte
Tagのバージョン。
4bitずつで表し、上位4bitが正の数、下位4bitが小数点以下、というところか。
今回は「NFCForum-TS-Type-3-Tag_1.0.pdf」なので、0x10。

※2011/09/04追記
Type3 Tagドキュメントのバージョン(T3VNo)と、デバイスに実装した(書き込んだ?)バージョン(NFCDevVNo)には、紐付けに4つのケースがある。
説明が面倒になるので、メジャーバージョンをX or x、マイナーバージョンをY or yとする。

NFCDevVNo : XY
T3VNo : xy

  1. X=x、Y>=y : デバイスはType3 Tagドキュメントに記載してある全てを満たす
  2. X=x、Y<y : Type3 Tagの全部にはアクセスできない。
  3. X<x : デバイスはType3 Tagデータを扱えない。
  4. X>x : デバイスはたぶん前のバージョンをサポートする。サポートしてないならType3 Tagを扱えない。

 

[0x01]Nbr ... 1byte
1回のコマンドで読み込むことができるブロック数。
FeliCa LiteのRead w/o Encryptionは4ブロック以下なので、0x04。

 

[0x02]Nbw ... 1byte
1回のコマンドで書き込むことができるブロック数。
FeliCa LiteのWrite w/o Encryptionは1ブロックなので、0x01。

 

[0x03, 0x04]Nmaxb ... 2byte
NDEFとして使用できるブロック数。
最小値は4。
FeliCa Liteでは、0x000dが取得できた。これはS_PAD1~S_PAD13に相当する。

 

[0x09]WriteF ... 1byte
書き込み中フラグみたいだ。
Type3 Tagに書き込み開始前に0x0Fを書き込み、書き込み終了時に0x00を書き込む。
なので、普通は0x00が読める。


[0x0A]RW Flag ... 1byte
読み込み専用かどうか。
読み込み専用であれば0x00を、そうでないなら0x01を書き込む。
ここが0x00だったら、技術的に書き込み可能であっても、読み込みのみと考えること。
デバイスはここの値を書き換えないこと。


[0x0B, 0x0C, 0x0D]Ln ... 3byte
実際に書き込まれているNDEFのサイズ。単位はバイト。
データ書き換えがあると、ここも更新すること。
S_PAD0の先頭から、NDEF Recordの最後のデータまでになっていた。
1ブロック16byteだから後ろに空きがあるかもしれないが、そこは含めない。


[0x0E, 0x0F]CheckSum ... 2byte
このブロックの[0x00]~[0x0D]までを1byteずつ加算。
[0x00]~[0x0D]のデータが書き換わったら、更新すること。

[felica]ブロックリストエレメントの「サービスコード順番」

「恥ずかしながら」という言葉があるが、今回はまさにそれだ。

FeliCaの"Read Without Encryption"や"Write Without Encryption"を使うとき、パラメータにブロックリストエレメントというものがある。
アクセスしたいブロックに関する情報を記載したリストだ。

この中に「サービスコード順番」というものがある。
これの意味が、長いことわからなかった。
FeliCa Liteのユーザーズマニュアルにもブロックリストエレメントの例が書かれているのだが、サービスコード順番についての説明がない。
困ったことに、FeliCa Standardのユーザーズマニュアルには「サービスコードリスト順番」となっていて、同じ意味なのかどうかすらわからない。
用語は統一してほしいが、SONYだからあえて別の意味ということを表しているような気もする。


FeliCa Standardのユーザーズマニュアルには、説明があるのだ。
「サービスコードリスト内の何番目のサービス」という順番である、と。
サービスコードリストというのは、Read w/o EncrptionとWrite w/o Encryptionのパラメータにある、サービスコードを列挙したリストだ。
サービスコードリスト順番は、0から始まるということも書かれている。

これでもう怖くない!


とはいえ、なぜFeliCa Liteでは「サービスコード順番」という、意味が読み取れない言葉になったのか。
それはおそらく、FeliCa Liteではサービスコードを1種類しか指定できないため、「リスト」ではないと考えたからではないだろうか。

A : 「FeliCa Standardでは"サービスコードリスト"ってなってるけど、Liteではどうしようか?」
B : 「もう、同じ名称でいいんじゃないの」
A : 「いや、だってさ、1つしか指定できんのだから、リストじゃないやん」
B : 「そんなこといっても、Standardがそう書いてるんだから、あわせたらいいやん」
A : 「だってだって、これを英語化するときのこと考えてよ。リストじゃないのに・・・」
B : 「あー、もうどうでもいいよ!」

そんなやりとりがあったのだろうか。

英語版を見たけど・・・「Service Code List Order」だった。
まあ、そんなもんだ。

いろいろ考えたあげく「単なる誤字では?」というところに落ち着いた。
最初から「サービスコードリスト順番」って書いてあれば、意味がわかったように思うんだけどねぇ。

 

なお、これが急にわかったのはNFCドキュメントのおかげだ。
「Service Order : 0」って書いてあったので、「あ、順番が0番目なんだ」ってのがわかった。
FeliCa Liteでは0しかないのだったら、ブロックリストエレメントの説明にも0って書いておいてほしかったな。

2011/08/30

[felica]NDEFのデータはS_PAD1から始まる

NDEF対応されたFeliCa Liteカードがあったので、そのデータを読んでいた。
S_PAD0(ブロック0)から順に、NFC ForumのNDEFフォーマットと見合わせて読んでいったのだが、どうにもあわない。

仕方ないので、後ろから順に読んでいった。
URLっぽいのがあったので、そこからたどっていったのだ。
すると、S_PAD1でデータが終わってしまった。
ん、S_PAD0には何が書かれているんだ??

NDEFで検索すると、こちらにたどりついた。
http://y-anz-m.blogspot.com/2011/01/android-nfc-no-felica-ndef.html

最後の方で、ブロック0を読み込んだときの判定が書かれていた。
verとかで始まっているので、これはNDEF messageのフォーマットではない!

それがわかったのでTag3の仕様書を読むと、NDEF検出手順が書かれていた。
なるほど、そういう手順があるんだ・・・。

FeliCaの仕様書を見ていくと、FeliCa Standardのような共通領域を持つものとNDEFの両立したカードもあるようだ。
ああ、前回いろいろ考えて、FeliCa Liteしか対応しないんじゃないの、という考えに至ったのに・・・。
下手な考え休むに似たり、ということか・・・。

まあ、たどるところがわかったので、読んでいきましょうかね。

2011/08/29

[felica]FeliCa StandardでのNDEFはあるのか?

FeliCa Liteカードを使って、Read without EncryptionやWrite without Encryptionを試している。
動かすだけならパケット仕様書通りに流せばいいのだけど、アクセスしやすさなんかも一緒に考えている。
Android用なので、どうclass分けしようか、ということだ。

 

NFC-Fとしては、システムコードが0x12FCとなっている。
これは、今のところFeliCa Liteカードでしか使えなくなっている。
0次発行されたFeliCa LiteカードのMCブロックに「NDEFにするよ」とやると、Pollingで0x12FCに応答するようになるのだ。

 

これをFeliCa Standardではできないけど、将来的にできるとしたらどうやるのだろうか。
一番簡単なのは、システム分割だ。
0x12FCというシステムを作ってしまえばよい。。。のだが、これはモバイルFeliCaチップなら動的にできる。
既にあるカードは、もちろんそういうことはできない。

ということで、FeliCa Standardカードを発行する時点で、NDEF対応できていなくてはならない。
しかし、FeliCa StandardカードがNDEF対応すると、何か嬉しいだろうか?
FeliCaとして使うのなら、今までのままでよい。
NDEFではお金などのセキュアなやりとりは、厳しいだろう。

何かやるとしたら、P2P・・・はカードでは無理だな。
やっぱり、URLが書かれているのを読み取るとかしかなさそうに思う。
でも、モバイルFeliCaチップみたいにいろいろなものが共存しているところに1つだけ公開した領域があると「俺も俺も」ってあぶれてしまいそうだ。

 

うーん、当面はFeliCa LiteしかNDEF対応しないってclass構成でいいかなぁ。。。
いや、FeliCa Standardと共通にしてもいいんだけど、使い勝手が結構違うので、いっそのこと分けてしまった方が使いやすいと思うのだ。
そのとき、NDEFをどういう位置づけにしようか迷っているというところ。

2011/08/28

[felica]FeliCa Liteの1次発行と2次発行

今日はFeliCa Liteの仕様書を眺めている。
1次発行と2次発行について手順も含めて書かれているのだが、要約するとこういうことだと思う。

  • 1次発行:MC[2]に0x00を書き込むこと
  • 2次発行:MC[1]のb7に0を書き込むこと

 

1次発行をすると、システムレジスタの書き換えがMC[0], [1]を除いてできなくなる。
IDmもDFCも確定するので、基本的なカード特性が決まることになる。
NDEF対応するかどうかも、ここで決める。

 

2次発行は、ブロック0x00~0x0eのR/W or ROを決めてしまう作業。
MC[0], [1]の書き換えもできなくなるので、ユーザブロック以外の書き換えができなくなる。


手持ちのFeliCa Liteカードを読んでみると、NDEF化ツールを通したものは1次発行まで終わっている。
スイッチサイエンス社から買ったままのカードは、どっちも終わっていない。

 

片側認証をやってみようとは思うが、1次発行しないように気をつけねばならんな。
もちろん、2次発行もだけど。
0次発行のみのカードは手持ちが1枚しかないので、失敗できん。
まあ、そういうときはだいたい失敗するんだけどね。

[felica]鍵バージョンを考慮した片側認証

急に、片側認証をやりたくなった。
シーケンスだけひっぱってみた。

2 鍵バージョンを考慮

 

Live Writerのファイルアップロードテストも兼ねているのは、いうまでもない。

 

1 基本

[java]鍵が違うのに同じ結果になるなあ

こんなソースを書いた。
いや、書いたというのはおこがましい。
こちらの記事のソースファイルをまるまるコピーして一部書き換えただけだ。
http://www.trustss.co.jp/Java/JEncrypt122.html


追加したのは、秘密鍵SecretKeyのところ。
鍵が違えば、復号しても失敗するよね、というのを見たかったのだ。

   1: import java.util.Arrays;
   2:  
   3: import javax.crypto.Cipher;
   4: import javax.crypto.SecretKey;
   5: import javax.crypto.SecretKeyFactory;
   6: import javax.crypto.spec.DESedeKeySpec;
   7:  
   8: public class CipherTest {
   9:  
  10:     /**
  11:      * @param args
  12:      */
  13:     public static void main(String[] args) {
  14:         try {
  15:             /*
  16:              * 鍵
  17:              */
  18:             SecretKeyFactory kf = SecretKeyFactory.getInstance("DESede");
  19:  
  20:             // 秘密鍵1を準備
  21:             byte[] key1 = "12345678abcdefgh12345678".getBytes();
  22:             DESedeKeySpec dk1 = new DESedeKeySpec(key1);
  23:             Arrays.fill(key1, (byte)0x00);
  24:             SecretKey sk1 = kf.generateSecret(dk1);
  25:             dk1 = null;
  26:  
  27:             // 秘密鍵2を準備
  28:             byte[] key2 = "12345678abcdefgh12345679".getBytes();
  29:             DESedeKeySpec dk2 = new DESedeKeySpec(key2);
  30:             Arrays.fill(key2, (byte)0x00);
  31:             SecretKey sk2 = kf.generateSecret(dk2);
  32:             dk2 = null;
  33:  
  34:             kf = null;
  35:  
  36:             /*
  37:              * 暗号化
  38:              */
  39:             // 暗号化準備
  40:             Cipher c = Cipher.getInstance("DESede/ECB/NoPadding");
  41:  
  42:             c.init(Cipher.ENCRYPT_MODE, sk1);
  43:             byte[] cleartext = "abcdefghijklmnop".getBytes();
  44:             // 暗号化
  45:             byte[] encrypted = c.doFinal(cleartext);
  46:  
  47:             /*
  48:              * 復号1
  49:              */
  50:             // 復号準備
  51:             c.init(Cipher.DECRYPT_MODE, sk1);
  52:             // 復号
  53:             byte[] output = c.doFinal(encrypted);
  54:  
  55:             // 表示
  56:             System.out.println("[sk1]The string was ");
  57:             System.out.println(new String(output));
  58:  
  59:             /*
  60:              * 復号2
  61:              */
  62:             // 復号準備
  63:             c.init(Cipher.DECRYPT_MODE, sk2);
  64:             // 復号
  65:             byte[] output2 = c.doFinal(encrypted);
  66:  
  67:             // 表示
  68:             System.out.println("[sk2]The string was ");
  69:             System.out.println(new String(output2));
  70:  
  71:         } catch (Exception e) {
  72:             e.printStackTrace();
  73:         }
  74:     }
  75: }

結果

   1: [sk1]The string was 
   2: abcdefghijklmnop
   3: [sk2]The string was 
   4: abcdefghijklmnop

 

あれ?
違う鍵で復号させたのに、正しい結果が出てしまった。

12345678abcdefgh12345678

12345678abbdefgh12345678→一致
12345678abddefgh12345678→不一致
12345678accdefgh12345678→一致
12345678adcdefgh12345678→不一致

文字位置とずらし方に関係がありそうだ。

03254769`cbedgfi03254769→一致

うーん、何か間違ってるのかなぁ・・・。

2011/08/27

Inflating例外はコンストラクト失敗でも出る

かなり昔作ったアプリがあるので、ちょっと書き直していた。

AbsoluteLayoutを使っていたので、別のに置き換えようかとね。
Viewを指定した位置に配置する方法がどこかのサイトにあったので、置き換えは成功した。
(サイトがどこだったか、忘れてしまった。。。)

それが先週の話。
今週もちょっといじろうと思って動かしたのだが、起動しただけでエラーになって立ち上がらない。
onCreate()のsetContentView(R.layout.main)を実行した中で例外が出るのだ。
Inflatingなんとか、という例外のようだ。
Binary XMLがうんたらかんたら、とも出てきた。

このアプリは、LinearLayoutをextendsして、自前のLayoutを作っている。
ImageViewもextendsして、自前のViewにしている。
特徴はそれくらいだ。



はて、先週までは動いていたのに。。。
しかしよく考えると、最後にちょっと変更だけしていたようなのだ。

検索すると、レイアウトのxmlに書いた名前を書き間違えてるとか、そのclassがpublicになってないとか、そういうのが出てきた。
でも、今回はそういうことはない。

さっぱりわからないので、extendsしたLayoutクラスのコンストラクタが呼ばれるのかをブレークポイントで見てみた。
呼ばれている。
呼ばれているが、実行しようとすると例外が出る。

あ、メンバ変数(フィールドっていうのかな)に代入する際、nullオブジェクトのメンバ関数(メソッド、なのかな)を呼び出してる。。。


つまり、Layoutを作ろうとしてコンストラクタを呼び出したんだけど、nullオブジェクトのメソッド呼び出しによって例外が発生し、そのせいでレイアウトが作れなかったのでInflatingなんとか例外が出た、と。

ってことは、例外時のスタックトレースを全部見ていたら原因がわかったのかな。。。
なんだかよくわからない例外の時は、トレースの先頭だけしか見ていなかったけど、もうちょっと先まで見るようにせねば。

[C++]staticはやはりいる

C++では、const変数はデフォルトでローカルスコープだ。
本では「デフォルトで外部リンケージを持たない」とある。

簡単な例では、こんなのか。

[inc.h]
#ifndef INC_H_
#define INC_H_
const int A = 5;
#endif

[a.cpp]
#include "inc.h"

int f()
{
  return A;
}

[b.cpp]
#include "inc.h"

int g()
{
  return A * 2;
}
コンパイルしてないけど、Cだとリンクエラーになるはず。
C++だと通るはず。
エラーになるのは、変数Aの定義が複数あるからだ。
C++では外部リンケージを持たないので、各ファイル内で閉じている。
イメージとしては、「static const int A=5;」と書かれているような感じだ。


ふと思ったのだが、もしかしたら最近のC++ではクラスに定義するstatic const変数の「static」もなくていけるようになったりしてないだろうか。

const変数はデフォルトでexternされないのだから、staticがなくてもわかるやん、と。

やってみたら、エラーになった。
まあ、そんなことはないってことだ。

2011/08/23

[nfc]グループ分けをしてみる

NFC関連でグループ分けがいくつかできないだろうか、と思いついてやってみた。
そういいつつも、Type-AとType-Bの仕様書は読んだことがないのだ。



プロトコル

grp1.png

NFC-B/Type-Bが少ないのは、私が商品化されたものを知らないからだ。
Type-Bだけは、他とちょっと毛色が異なると思ってる。
運転免許証や住基カードはType-Bだというけれども、セキュアな部分はどうなっているのだろうか?

Type-A/BとNFC-A/Bを分けているのは、私がType-A/Bの仕様書を読んでいないから。
たぶんType-A/Bにはセキュアなことも書いてあるんじゃないかな、という予想だ。

FeliCaの「Type-A/B」に相当する名前がわからなかったので、JISから引っ張ってきた。
Type-Fとは呼ばないしなぁ。

FeliCaとFeliCa Liteを分けるべきか迷ったが、分けるとMifare 1KとかULとかも分けないといかんだろう。
なので、ここは分けない。
MPCOSは、ホームページに「type-A and type B」とあったので、間に置いた。
JCOP30はよく知らないのだが、SEL_RESの値で分類できるようなので、記載した。


通信速度

grp2.png

規格上はまだ高速な値もあるのだが、実現しているのは上記2つじゃないだろうか。
ISO18092なんかは、Type-Aとかではなく通信速度で分けられていた。

この速度は、エア上の速度であって、カードを制御するI/F間の通信速度ではない。


規格

grp3.png

NFC-A/B/Fは、NFC Forumだけど省略した。

こうやって見ると、Type-Aはうまいと思う。
FeliCaは14443に差し込めなかったのが響いているような気もするが、そうでもないかもしれない。
クローズドな戦略は、やはり難しいのかなぁ。

2011/08/21

[felica]CommunicateThruEXを考える

RC-S620/SのCommunicateThruEXは、PN533にはないコマンドだ。
PN533で相当するのは「InDataExchange」「InCommunicateThru」だろう。



用途は、Targetへのデータ送信。
CommunicateThruEXのデータ部が、Targetへ渡されるときにはプリアンブル、Sync Codeに引き続いてそのまま渡されることになっている。
まあ、WirelessコマンドとしてはCRCが必要なので、そこはチップがうまくやってくれているのだろう。



紛らわしいので書いておくが、JISやNFC Forumで定義されているのは無線時に使用する「Wirelessコマンド」である。
R/Wで使うコマンドは有線時に使用する「Wiredコマンド」だ。
「Wirelessコマンド」とか「Wiredコマンド」とかは私が勝手に呼んでいるだけで、正式には知らん。



例えば「ポーリング」という動作がある。
R/WへはWiredコマンドとして「InListPassiveTarget」コマンドを送信する。
コマンドを受けとったR/Wは、パケットの中身を解析する。
InListPassiveTargetはパラメータによってNFC-AとかNFC-Fなんかを使い分ける。
NFC-A用のパラメータだったら、Wirelessコマンド「SENS_REQ」なんかに置き換えるし、NFC-F用のパラメータだったら「POL_REQ」に置き換える。
Target側はWirelessコマンドを受けとったら、それに返信としてSENS_RESなりPOL_RESなりを返す。
こういうのをR/WとTarget側で繰り返し、InListPassiveTargetとして満足したらレスポンスデータを作成して呼び元に返す、ということになっている。



このあたりは、PN533のドキュメントがわかりやすいと思う。
わかりやすいというか、しくみがわかってから読むとわかりやすいというか。


話が少し脱線したが、戻そう。


InListPassiveTargetなどのWiredコマンドは、複数のWirelessコマンドを発行したり、R/W内部のパラメータを変更したりするのに使われる。
それに対してCommunicateThruEXなどは、引数を単にTargetに送信し、渡されたレスポンスを受信するだけだ。
よって、CommunicateThruEXを使うためには、Wirelessコマンドを知っておかなくてはならない。

ここら辺まで来て、ようやくJISだのNFC ForumのDigitalProtocolだのを読んだ知識が使えることになる。

例えば前々回作った、nimocaの残高を読むアプリ。
あれはFeliCaでいうところの「Read Without Encryption」、NFC Forumでいうところの「Type Tag 3」の「CHECK」を使っている。
読み出す手順なんかもドキュメントに書いてある。

FeliCaカード ユーザーズマニュアル(824KB) : p.59

http://www.nfc-forum.org/specs/spec_license : 「NFC Forum Type 3 Tag Operation Specification 1.1」p.10


RC-S620/Sで使えるコマンド一覧は、RC-S620/Sを購入したときに「公開しない」という条件を元に入手できている。
なので、SONYで公開されていない情報は書くことができない。。。

うーん、やっぱり使用できる有線プロトコルの仕様は全部出してほしいなあ。
まあここは内部でがんばってもらうしかない。

仕様全部はあきらめるとしても、せめてRC-S620/S購入の時にもらった資料が一般入手できるようになれば、書けることも増えるし、教えてもらったりもできるのだが。
これからの動きに期待しよう。

[app]「Push Push」を公開しました

3部作?の最後です。

https://market.android.com/details?id=com.blogpost.hiro99ma.felicapush

URLをPaSoRiからpushします。
解析とか特にやってないので、変なURLでも気にしません。
(trimして、スペースを削除するくらいはやってますが)


技術的には、特にない。
CommunicateThruEXを使っているだけだ(詳細は、RC-S620/Sのコマンドリファレンスマニュアル参照)。

2011/08/20

[a500]MEDIA_SCANNER_FINISHEDでよさそうに見える

SDカードを挿抜してもMOUNTEDなどがわからないので、MEDIA_SCANNER_FINISHEDを受けとるようにしてみた。
うん、よさそうだ。


Eject SD



まあ、よしとしよう。

------------------------------------

このアプリは、こんなものをimportしている。

import android.os.storage.IMountService;
import android.os.ServiceManager;


なので、eclipseからではビルドできない。
私は、Android2.3.4_r1環境でビルドさせている。

これを普通にやるなら、リフレクション、というやつを使うのかしら?
そこら辺は詳しくないので、割愛だ。

マウント状態の取得には、これを使っている。

IMountService::getVolumeState(パス);

staticメソッドではないので、IMountServiceのインスタンスをもらってこなくてはならない。


IBinder service = ServiceManager.getService("mount");
mMountService = IMountService.Stub.asInterface(service);

マウント状態は「mounted」とか「unmounted」などが戻ってくるのだが、文字列がどこから来るのかが分かっていない。
やってみたらこういう文字列が戻って来たので、それで実装している。
ちょっと怖いところだ。

マウントさせるときは、こう。

mMountService.mountVolume(パス);


A500でしか確認していないので、他で動作するかはよくわからん。



[app]「nimoca check」を公開しました

最近、威嚇シリーズを作ってないよなあ、と思いつつも、ツール造りが好きなのでやってしまった。

nimoca check

Suicaも読めるはず。。。
サイバネ系であれば、同じじゃないのかな?


どうやっているのか?

参考にしたのは、こちら。

http://sourceforge.jp/projects/felicalib/wiki/suica

FeliCaを、システムコード0x0003でポーリングして捕捉する。
次に、サービスコード0x090fのブロック番号0を暗号化無しで読む。
読んだ16byteのうち、10byte目と11byte目をリトルエンディアンで表示する。
そんだけ。

最初に思ったのは、「6万円くらいまでしか入らんやん」ということ。
nimocaもSuicaも、制限は2万円だった。
そういえば、signedなのかunsignedなのかはわからなかったな。

次に思ったのは「残高が書かれているこの領域は、おまけにすぎないよなー」ということ。
同時に、どういう経路でデータを書き込んでいるのかが気になった。



私はnimocaを西鉄バスで使っている。
バスに乗ると、まずR/Wにかざす。
そして降りるときにかざす。

乗るときは、現在の残高が表示される。
降りるときは、運賃が引かれた額が表示される。
いくら運賃がかかったのかは、バスしかわからないような気がする。
しかし、乗った駅と降りた駅の情報がわかれば、それで運賃がわかるはずだ。

降りるときにはかざすと勝手に運賃が引かれているので、少なくとも運転手は何もしていない。
となると、あとはネット経由で運賃を調べるか、R/Wに直結したコンピュータが運賃を調べるか、だ。

私は、コンピュータが乗車駅と降車駅の情報から運賃を調べているのだろうと思う。
ネットだと、バスみたいなものは無線を使わなくてはならない。
そうなると、バス路線のすべてに無線システムがなくては使えなくなってしまう。
それではだめだろう。


何を私が気にしているかというと、どこまでネット上で管理されているのか、ということだ。
バスに乗って、すぐに電車に乗ったとしても、残高はちゃんと減っていくと思う。
ということは、残高に関する情報はカードが持っていると考えていいはずだ。


しかし、このカードを持った人がどこから乗ってどこで降りたか、という情報はサーバも持っていると思う。
R/Wで同時に収集して、一日の終わりかどこかでまとめてサーバに吸い上げるとか。
そういったサービス側の知識がないので、なにがどう役立つのかがあんまりわかってないけど、たぶん役立つんだと思う。

昔のSFとかで、IDカードを闇で買う、なんてシーンがしばしばあった。
しかしFeliCaなんかも、けっこうこれに近いことがあるのかもしれん。
警察も実は、地道な足取り調査の前にはnimocaなんかに確認してポイントを絞っているのかもね。

2011/08/18

[a500]MEDIA_MOUNTEDの代わりにMEDIA_SCANNER_FINISHEDでよいかも

気になるので、実験をしてみた。
BroadcastReceiveでMEDIA関連のインテントをとりあえず受けとるようにして、SDを挿抜したときに何が出るのか見てみた。

こんなインテントフィルタ。

            <intent-filter>
                <action android:name="android.intent.action.MEDIA_EJECT"/>
                <action android:name="android.intent.action.MEDIA_SCANNER_STARTED"/>
                <action android:name="android.intent.action.MEDIA_REMOVED"/>
                <action android:name="android.intent.action.MEDIA_BUTTON"/>
                <action android:name="android.intent.action.MEDIA_UNMOUNTABLE"/>
                <action android:name="android.intent.action.MEDIA_BAD_REMOVAL"/>
                <action android:name="android.intent.action.MEDIA_MOUNTED"/>
                <action android:name="android.intent.action.MEDIA_SCANNER_FINISHED"/>
                <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
                <action android:name="android.intent.action.MEDIA_CHECKING"/>
                <action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE"/>
                <action android:name="android.intent.action.MEDIA_SHARED"/>
                <action android:name="android.intent.action.MEDIA_NOFS"/>
                <data android:scheme="file"></data>
            </intent-filter>

実は・・・あまりインテントがよくわかっていない。
actionだけではだめで、いくつかのチェックをかいくぐらないと通知しないそうだ。
dataに"file"ってやってなかったので、最初は全然だめだったのだ。

さて、これでSDを挿抜させると・・・・

08-18 19:12:33.830: DEBUG/IntentCatch(32119): android.intent.action.MEDIA_SCANNER_STARTED
08-18 19:12:35.020: DEBUG/IntentCatch(32119): android.intent.action.MEDIA_SCANNER_FINISHED

これしか出なかった。

logcatの出力も、だいたいこれにあっている。
なので、もうMEDIA_MOUNTEDとかで判定するよりも、MEDIA_SCANNER_FINISHEDが来たら何か起きたんだ、と思って調べる方がいいのかもね。

Eject SDはまだ何もしていないので、今度やろう。

[a500]mount/umountのインテントが飛んでこない

Acer A500にEject SDをインストールしている。
まあ、だいたい動いている。

Android3.0/3.1だけなのか、A500がそうカスタマイズされているのかわからないが、設定アプリにSDのフォーマットやmount/umountの項目がなくなっている。
logcatで抜いてみると、voldががんばってなんとかしているし、MediaStoreImporterみたいなものも動いている。
だから、通知は受けとることができるのだろうと思う。


しかし、Intent.ACTION_MEDIA_MOUNTEDみたいなインテントは飛んでこないのだ。
あ、仲間がいた
飛んでこないのだよ。
Android 2くらいでは飛んでいたと思うので、バージョンが上がったためなのか、A500だけなのか。。


SDカードの挿抜は、ホスト側では割り込みとして検知される。
もし割り込みとして処理していないのなら、ポーリングするしかない。
Linuxの場合は、とにかく最後はsysfsでの通知になっている。
ueventに"add"とか"remove"とかするのだ。

これをVoldCmdListenerが受けとって、あれこれやっている。
出てくる人は「Vold」「VoldCmdListener」「MediaScanner」「MediaStoreImportService」「MediaStoreImporter」だ。
PowerManagerServiceもちょくちょく出てくるが、気にしなくていいのかな?
ただ、PowerManagerServiceがMediaScannerServiceをWakeLockさせているように見える。
WakeLockだから、眠らないようにさせる?→そういう認識でよさそうだ

ここにはMountServiceが出てこない。
だからACTION_MEDIA_MOUNTEDがないのかな?

[app]「Eject SD」を公開しました

アプリをまた作りました。
といっても、こっちは焼き直し。

https://market.android.com/details?id=com.blogpost.hiro99ma.EjectSD

以前作っていた「EjectSD」のバージョンアップだけさせる予定だったけど、アップしたときのkeystoreがわからなくなってね。。。
やむなく作り直した次第である。

やっている内容は、こちらを参照してくだされ。


以前との違いは、対象とするディレクトリを選択できるようにしたこと。

前は、

Environment.getExternalStorageDirectory().toString()

で取れたのだが、A500でやると内蔵しているパーティションのどこかを指しているようだ。
A500だけでなく、他のAndroidも最近はそういう傾向にあるようだ。

傾向にあるのはいいが、Androidではその対策が入っていないので、統一ルールがない。
無法地帯だ!
ネットで見ると、どうも環境変数で表しているところが多そうだった。
ならば私もまねしよう。

  • Environment.getExternalStorageDirectory().toString()
  • System.getenv("EXTERNAL_STORAGE")
  • System.getenv("SECOND_VOLUME_STORAGE")
  • System.getenv("THIRD_VOLUME_STORAGE")

何も考えず、これらを列挙してユーザに選ばせるという手段にした。
だって、ルールがないから考えても仕方ないのだ。

A500では、上から順に

  • /mnt/sdcard
  • /mnt/sdcard
  • /mnt/external_sd
  • /mnt/usb_storage

となった。


そしてー、そういうのを確認しているうちにバグを発見・・・。
さっさと修正しましょう。

[a500]Android3.1ではゾンビが出ずに蜂が出た

Android 2.3.4のときは、ゾンビが出た

あのときは私もびっくりしたのだが、もう驚かない。
というわけで、Android3.1でも同じことをしてみた。

おお、出た出た。

Androidの蜂(なんて名前だ?)が、どーん、と出てくる。
「REZZZZZZZ...」ってtoastが出てくるが、それが名前なのかな?

「Androidバージョン」を連打するんだな。

2011/08/17

[app]「RC-S370をつなぐ」を公開しました

宣伝、というか、動作確認してほしいというか。
そんな気持ちです。

Android Marketに「RC-S370をつなぐ」というアプリを公開しました。
Android 3.1以上だと思います。
(API 11ではリンク先につながらないようです。)


こんな設定になっています。

<uses-feature android:name="android.hardware.usb.host" />
<uses-sdk android:minSdkVersion="12" />

機能はシンプルで、PaSoRiのRC-S370(330?もいけるか)を挿すとアプリが起動します。
FeliCaかMifareのカードをかざしてボタンを押すと、IDmなりUIDなりを読んで表示します。

モバイルFeliCaとFeliCa Lite(NDEF版とそうじゃないやつ)、Mifare 1Kは確認したけど、それ以外はやってません。
UIDが4byte以上だったらどうなるんだ?など気になるところはあるところ。


公開してから知ったけど、USB Hostを使うだけだったら、

このアプリケーションの実行に特別な権限は必要ありません。

なのだな。
ちょっとした驚きだ。


よく考えると、Android Marketに接続できる端末で確認してからアップしたのは初めてだ。
記念すべきアプリなのだが、使う人が少なさそうなのが残念だ。。。

まあ、元からメジャーなアプリは作ってないけどね。

[java]ビット演算

hiro99maさんからのお便りです。

こんにちは。
InListPassiveTargetでFeliCaのSystemCodeを取ってきたのですが、88b4がffb4になってしまいます。
なして?


なんでと言われてもねぇ。。。


private static short hl16(byte h8, byte l8) {
  return (short)(h8 << 8) | l8);
}

上位8bitになる変数を8bit左シフトして、下位8bitになる変数とORする。
そんだけだ。
さて、これでなぜだめなのだろうか?


ポイントは、引数がunsignedじゃないところ。
0x88も0xb4も、どちらもマイナス値として扱われてしまう。
-120と-76。

-120はビットシフトするので、0x8800。
これはいい。
問題は-76の方。

どうやら、Javaは演算時にintへ格上げするらしい。
原本を調べているのだが、記載が見つからん。。。

http://java.sun.com/docs/books/jls/second_edition/html/conversions.doc.html#27529

まあ、Cと同じルールと思っていていいんじゃないかな。
intになるのか、大きい方に合わせられるのかはわからんが、今回はどっちでもいい。
0xb4がshortになるのではなく、-76がshortになるのだ。
short型の-76を16進数にすると、0xffb4。
そう、上位8bitが0xffになってしまうのだ。
unsignedだったらこんなことはないのだけどね。。。

そんなわけで、下位8bitをshortに拡張した後で0xffとANDを取ればよい。

private static short hl16(byte h8, byte l8) {
  return (short)(((short)h8 << 8) | ((short)l8 & 0x00ff));
}





2011/08/16

FeliCa LiteのIDmは、独自性が保証されているのか?

保証なのか保障なのかもうよくわからない。
なぜこの質問をあの人にしなかったのか・・・と悔やまれてならん。
まあ、忘れてたんだけどね。

何かというと、FeliCaのIDmユニーク性についてだ。


FeliCa Liteスターターマニュアル Version 1.0

ここの「7.1」に、IDmのユニーク性について記載がある。
FeliCa LiteのIDmはユニーク性を保証していると読める。
FeliCaともユニーク性を持つ、と。

FeliCaカードは、我々が手出しをすることはまずできない。
少なくとも、IDmまでは書き上げられた状態で提供されるのだろうと思う。
もちろんモバイル系については別だが、そこはごにょごにょだ。

IDmのユニーク性は保証しない、というのはどこかで確認したはず。
どこだっただろうか・・・。
FNさんのページでは「連続性は保証しない」になっている。
そういう記述だった…だろうか。自信がない。
昔からそう書かれていても、最近書き換えられていても、私にはわからんということだ。



記憶をさかのぼると・・・(忘れっぽくなってきたが)

IDmは「ユニーク性は保証しないからねー」というのがFN側の主張だったように思う。
まあ、これはFeliCa Liteが出る前だったように思う。
そしてIDmとかだけに関して言えば、FeliCa Liteで重複させることは可能だと思う。
これはもう「思う」の範疇である。やったことないので。
でも、テストしてよいFeliCa Liteカードがあればできると思う。

しかし、それ以外はだめだ。
FeliCa LiteとFeliCaではカードの構造自体が異なる。
唯一共通なのがIDmとPMm、というだけだ。
なので、FeliCa LiteでFeliCaを偽装しようとしても、すぐにばれる。
まあ、システム次第だが。


携帯電話に載っているFeliCaチップでは、そういうのは無理だと思う。
IDmってのは、FeliCaチップに載っているということになっているから。

では、RC-S956はどうだろう?
こいつはPaSoRiにも載っていることからわかるように、R/Wチップだ。
自前では動けない。



・・・おっと、お酒がなくなってきた。
飲みながらじゃないともう書けないので、ここまでだな。
ふふふ。。。

アプリ起動→USB挿し→許可画面→OKとすると、もう1つ開いている

タイトルがわかりにくくてすまん。
今のアプリにある問題点を、動作から書いてみたのだ。
まあ、BTSに登録してようなものだと思ってくれ。


今のアプリは、PaSoRiを挿すと起動して、インテント(ATTACHED)を投げる。
また、許可を得ていない状態でアプリを起動し、PaSoRiが挿さっているのがわかると、インテント(PERMIT)を投げる。
こういうとき、インテントはブロードキャスト(broadcast)する。
そうしないと、誰が受け取れるかわからないからだ。
受けとるアプリが1つにせよ複数にせよ、ダイアログが出てきてユーザに選択させる。

さて、ここら辺がよくわかっていないところだ。
ユーザにアプリを選択させた後、どうなるのか。
それを把握していないから、アプリが2つ起動したようになってしまうのだろう。


あ、アプリが2つ起動したように、というのは、今動かしていたアプリのアクティブなアクティビティが表示されている状態で「BACK」としたのに、同じようなアクティビティが表示されている状態だ。
まったく同じではなく。
今回で言えば、アクティブなアクティビティでは「PaSoRiあるよ」と表示しているのに、BACKしたときに表示されたアクティビティは「PaSoRiないよ」と表示しているのだ。

これは、現在接続されていないから「ないよ」といっているわけではない。
そう作っているのならいいんだけど、私はアクティビティ1つしか持ってないアプリを作ったのだ。
それなら、BACKしたらHOME画面に戻ってほしいのだ。

ここまで読んで気付いたと思うが、これはUSB Hostがどうのこうのという話ではない。
おそらく、Androidアプリの回し方に関することだろう。
そんなわけで、USB Hostについて知りたい、と思っておられる方は、ここはここまででお帰りなさいまし。




と、これから長々と考えていこうかと思ったのだが。。。

よくわからん。
あまり苦労せずに回避するために考えついたのは、PaSoRiが接続されていないときはアプリを起動しない、だった。
逃げていると言われればそれまでなのだけど、PaSoRiが接続されていないのにアプリが起動しても困るのは確かだ。
だって、PaSoRiを使って読み込みアプリだし。
接続されていないのなら、接続されたときに起動すればいい。そのしくみはできている。
接続済みであれば、いつものようにonCreate()経由であれば問題ない。

いいのか、私はそれで・・・。
これがC/C++でできていれば、たぶん追求していっただろうと思う。
しかしここにあるのは、Javaだ。
申し訳ないが、ものすごく困らない限りは「回避」で済ませたい。

あとで困ったら、そのとき考えよう。


[a500]Android3.1からPaSoRi RC-S370を使う(中間報告)

はじめに


Android 3.1では、USB Host APIがある。
これを使うと、AndroidアプリからUSBのクライアント機器をかなり操作できる。
ならば、PaSoRiを操作してみよう。


開発環境

  • Acer A500
  • Android 3.1
  • PaSoRi RC-S370
  • デバッグ:Windows XP SP3 32bit(※1)
  • デバッグ:eclipse INDIGO/HERIOS(※1)(※2)
※1:こだわらない。
※2:使い分けたわけではなく、使っていたPCが違うだけ。


動作状況

PaSoRiを挿すか、挿した状態でメニューからアプリを選択すると、PaSoRiが使えるようになる。
ボタンが1つだけ出てくるので、押すとNFCIDが取得できる。
手元に、FeliCaとMifare 1K(UID 4byte)しかカードがないが、両方取れた。
NFC-AとNFC-Fは何とかなると思う。

処理は簡単で、NFC-FでIDが取れなかったらNFC-Aで取ろうとするだけである。
どちらでもだめだったら、エラーとしている。

ソースファイル

githubに置いている。


問題点

  • C++で作ったライブラリをJavaに置き換えるのが面倒→がんばれ
  • A500のタッチパネルが動作しなくなることがある→たぶん初期不良
  • A500の端っこを触ると、バックライトが接触不良ぎみにちかちかする→たぶん初期不良
  • A500を初期不良交換するか悩んでいる→サポートに電話がつながるか?
  • Javaがよくわからん→Effective Javaを買ったんだから、がんばれ
  • メニュー起動で立ち上げてUSBアクセス許可をユーザにしてもらうと、2つ目が起動してしまう。
  • そのまま終わらせると、死ぬ(logcatが「device not found」だから、adbが死ぬ?)

[a500]Acer A500でPaSoRiを動かすことよりも、C++をJavaに移植する方が難しい

盆休み、特にやることもなかったので、C++で作っていたRC-S620/SというかPaSoRiというか、とにかくRC-S956系のチップ用ライブラリをJavaに移植していた。

半分くらい移植できたと思うので、githubにおいた。
うまく動けば、NFCIDを取ってくれると思う。

しかし・・・出来が悪い。
Androidのアプリとしての出来がよくないので、なんか落ちる。
なんか、メニューから起動させると2回に1回落ちるのだが、まあいいや。

いや。。。ここをいつもないがしろにしているから、適当なものしかできあがらないのだ。
あとで調べよう。


苦労しているのが、Java。
メモリの確保をちょくちょくやるのがいやなので、ポインタを使い回したりしているのだ。
そのあたりが、どうにもこうにも。

それと、引数で戻り値をとってくる場合のやり方もよくわかってない。


void func(int[] retval) {
retval[0] = 20;
}

void aaa() {
int[] ret = new int[1];
func(ret);
}

こんなことをやっているのだよ、今のソースファイルは。
元のソースは、こんなのだ。

void func(int* retval)
{
*retval = 20;
}

void aaa()
{
int ret;
func(&ret);
}

たぶんやり方があるんだろうけど、わからんかった。

それ以上にわからなかったのが、ダブルポインタ。
ライブラリ側にバッファを持たせているけど、そこにデータを書き込んでもらうため、引数でポインタを返していたのだ。

namespace {
uint8_t wBuf[50];
}

bool getCmdBuf(uint8_t** p)
{
*p = &wBuf[5];
}

まあ、イメージとしてはこんな感じ。
プロトコルを作るとき、アプリの層は一番下で、そこから上にヘッダを積んでいく形になるではないか。
今回は固定ヘッダ長なので、ユーザ側には書き込んでほしいポインタを渡したかったのだ。

ByteBufferが使えそうだな、と思ってbyte[]を置き換えていったけど、うまくいかなかった。
敗北だ。。。

やむなくローカルでmemcpy()みたいなのを作って、あちこちコピーするように変更。
動くんだけど、格好が悪い。
なんとかしたいなぁ。

2011/08/13

[a500]JNIでUSB転送させるのは難しいと判断した

Android3.1には、USB Host機能がある。
インターフェースなんかを見ていると、たぶんlibusbを呼んでるだけだと思う。
では、JNIでlibusbを呼び出せるようにがんばれば、今までの資産を使えるのではなかろうか?

そう考えて早1週間・・・。
やっぱり無理だな、と思った。
オープンはJava側で行われているので、そのハンドラとかをもらわないといけないと思うのだが、はて、どれがハンドラやら。
JNI側からJava APIを呼び出す方法もあるんだけど、そこまでするんだったら、もうC++で作った資産をJavaに移植した方がいいんじゃなかろうか、と。

決心するまでに時間がかかるのよねぇ。

2011/08/10

[a500]Android3.1とPaSoRi

今日、A500のアップデートが行われ、Android3.1になった。

これでとうとう、USB Host機能が使えるようになってしまった。

というわけで、まずはPaSoRiが使えるかどうか試してみた。
今のところRESETコマンドだけしか動かしていないけど、ACKも返ってきた。
つまり、問題なしだ。


あとは、今まで作ってきたライブラリをどうやって動かすか、ということ。
JNIでやろうとはしているが、あれはRC-S620/Sを対象としていた。
今回のように、Android側でオープンしたデバイスというのは、どうにもやりづらい。
しかし、JNI側でオープンするには権限がない。

内部的には、たぶんlibusbを使っているのだと思う。
なので、そういう作りにしてしまうっていうのもありなのだろうが・・・敗北感が漂う。

今回私は、A500という製品を買ったのだ。
開発用途とはいえ、rootにもしてないし、変なこともしていない。
ならばアプリも、正統派で勝負したいではないか。


そうなると、jniでは今まで作ったパケット作成のみに押さえ込むことになるのかな?
それだけなら、もうJavaに移植してしまいたいが・・・Javaか・・・。
byte型の変数に0xffって代入しようとしただけで怒られるのはめんどくさいなぁ。
うーん、うーん、うーん・・・・。

2011/08/08

過去記事をいくつかアップ

以前のサイトで書いていた記事を、いくつかアップしました。

ブログエディタが違うので、XHTMLをコピーして、今のサイトに貼り付けただけ。
なので、レイアウトとか崩れてるかもしれんです。
すまん。

当時の資料がないと、mount/umountとウィジェットの件がよくわからないのだ。

2011/08/07

アプリからmount/umountさせる

過去記事:2010/08/22 20:31
AppWidgetのおまけだ。
今回作ったのは、アプリからmount/umountする機能を持っている。
参考にしたのは、Settings。
まあ、これしかなかろう。

まず、IMountServiceを取得。
IBinder bnd = ServiceManager.getService("mount");
IMountService srv = IMountService.Stub.asInterface(bnd);

マウントするなら、
srv.mountVolume(path);
アンマウントするなら、
srv.unmountVolume(path, true);
となる。true、は強制アンマウントします、だ。falseだと様子を見てくれるのだろう。ウィジェットとして使うなら、falseの方がよかったか・・・。
pathは、マウントポイントとなる。
String path = Environment.getExternalStorageDirectory().toString();

やってみるとわかるが、たぶんこれはAndroidSDKではビルドできない。
Platform上でビルドする必要がある。
にもかかわらず、できあがったAPKに署名をすれば、Android Marketに登録できた。
ふーん、そういうものなのか。

[widget]PendingIntent

過去記事:2010/08/22 18:50
PendingIntentは、newしない。
staticメソッドの以下のいずれかを使って取得する。
  • getBroadcast()
  • getActivity()
  • getService()
getBoradcast()の場合、intentの送信はブロードキャストされる。
getActivity()の場合、Activity()を起動する。
getService()の場合、Serviceを起動する。
AppWidgetの場合、PendingIntentをRemoteViewsと結びつける。
  • setOnClickPendingIntent()
これしかなかった。
つまり、AppWidgetのコントロールとPendingIntentを結びつけ、クリックされたときにIntentを発行するのだ。
他にも、AlarmManagerで、時間が経ったらIntentを発行する、という使い方もできるそうだ。

上記の3メソッドだが、とる引数は同じだ。
その中でよくわからんのが、2番目と4番目。
2番目は、requestCode。
「Private request code for the sender (currently not used)」とあるから、使ってないのか?
ソース上は、Parcelにセットされている。しかし、どこでどのように使われているのかはよくわからん。
ActivityManagerNative.getDefault().getIntentSender()の引数で渡され、Parcelに詰められ、IBinder::transact()に投げられ・・・そこからよくわからん。
Activity関係のソースには、requestCodeってのがよく出てくるので、そこと関連があるのかもしれん。
ないのかもしれん。
4番目は、flagだ。
Intent.fillIn()で使えるフラグか、PendingIntent用のフラグ4種。
  • FLAG_NO_CREATE : PendingIntentがまだないなら、nullを返す(生成しない)
  • FLAG_ONE_SHOT : 1回しか使えない。このフラグが設定されていた場合、send()が呼ばれると自動的にキャンセルされ、それ以降はこのPendingIntentを使ってsend()できない。
  • FLAG_CANCEL_CURRENT : 既にPendingIntentがあるなら、生成する前に現在のPendingIntentをキャンセルする。
  • FLAG_UPDATE_CURRENT : 既にPendingIntnetがあるなら、破棄せずにextra dataを新しいintentに置き換える。前のPendingIntentが新しいextra dataだろうと気にしなくていい場合に使える。
かなりいい加減な訳だ。。
きっちりと前のPendingIntentを破棄したい場合はFLAG_CANCEL_CURRENTを使い、どうでもいい場合はFLAG_UPDATE_CURRENTを使えばいい、ということだろうか。
たぶん、FLAG_UPDATE_CURRENTの方が軽いのだろう。
これらは、現在PendingIntentが存在するかどうかを気にしている。ということは、FLAG_XXX_CURRENTを使用した場合、PendingIntentは最後の1つだけが有効になる、と考えてよいか。
FLAG_ONE_SHOTは、send()後どうなるのだろう?
ネットで探していると、クリックイベントでFLAG_ONE_SHOTを指定した場合、連打すると落ちる、というようなことが書かれていた。
送信できないなら、無視してほしいのだが、これは今でもそうなのだろうか。
あー、例外が発生するね。
E/RemoteViews( 1955): Cannot send pending intent:

E/RemoteViews( 1955): android.content.IntentSender$SendIntentException

E/RemoteViews( 1955):   at android.app.ContextImpl.startIntentSender(ContextImpl.java:640)

E/RemoteViews( 1955):   at android.widget.RemoteViews$SetOnClickPendingIntent$1.onClick(RemoteViews.java:157)

E/RemoteViews( 1955):   at android.view.View.performClick(View.java:2408)

E/RemoteViews( 1955):   at android.view.View$PerformClick.run(View.java:8816)

E/RemoteViews( 1955):   at android.os.Handler.handleCallback(Handler.java:587)

E/RemoteViews( 1955):   at android.os.Handler.dispatchMessage(Handler.java:92)

E/RemoteViews( 1955):   at android.os.Looper.loop(Looper.java:123)

E/RemoteViews( 1955):   at android.app.ActivityThread.main(ActivityThread.java:4627)

E/RemoteViews( 1955):   at java.lang.reflect.Method.invokeNative(Native Method)

E/RemoteViews( 1955):   at java.lang.reflect.Method.invoke(Method.java:521)

E/RemoteViews( 1955):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)

E/RemoteViews( 1955):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)

E/RemoteViews( 1955):   at dalvik.system.NativeStart.main(Native Method)
まあ、死にはしないから、いいか(いいのか?)。
引数のintentは、Intent.fillIn()を使ってコピーされるのだろう。
と思ったが、これはcontentsのコピーをするらしい。
intent自身のコピーではないのは何でだろう。。。
理由はあるのだろうが、推測できるような知識がありません。

[widget]AppWidgetManager

過去記事:2010/08/22 17:22
話の流れ上、こちらを先に扱わねばなるまい。
今回作ったAppWidgetではAppWidgetManagerを使っているのだが、まだ何をしているのかよくわかっていない。。
ちゃんと把握しよう!

まず、起動後すぐに呼ばれるのはonUpdate()。
そのまま載せよう。
    @Override

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        Log.v(TAG, "onUpdate");



        final int APP_NUM = appWidgetIds.length;

        for(int i=0; i<APP_NUM; i++) {

            RemoteViews remoteView = getClickRemoteView(context);

            updateIcon(remoteView);

            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteView);

        }

    }
    private RemoteViews getClickRemoteView(Context context) {

        Log.d(TAG, "getClickRemoteView");

        

        Intent intent = new Intent();

        intent.setAction(ACTION_WIDGET_CONTROL);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        

        // register click event

        RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.main);

        remoteView.setOnClickPendingIntent(R.id.ImageView, pendingIntent);

        return remoteView;

    }
updateIcon()は、アイコンを変更するだけの処理だから省く。
ここでは、各AppWidgetに対してupdateAppWidget()しているように見える。
しかし、印象ではいかんな。
appWidgetManagerは、引数だ。
ドキュメントによると、「updateAppWidget()を呼び出せるオブジェクトだ」とのこと。
オブジェクトなのかインスタンスなのかとか、そんなのはまあいいとしよう(私の中では、オブジェクトが具象化したものがインスタンス、としているので、引数はインスタンス扱いだ)。
第3引数のappWidgetIdsは、「アップデートが必要なappWidgetIds」とのこと。appWidgetIdsの説明に、それはなかろう・・・。
AppWidgetにはそれぞれIDがあるようなので、それは後で調べよう。
とにかく、onUpdate()が呼び出される場合には、対象となるWidgetについていろいろとやってやらなくてはならないということらしい。
そういうことからすると、掲載したonUpdate()でそれぞれのappWidgetIds[]に対してRemoteViewsをnewして割り当ててやるのは、正しいと思われる。
ここ以外では、RemoteViewsを割り当てる機会はなさそうだ。
割り当てる?
AppWidgetManager::updateAppWidget()は何だろうか。
Set the RemoteViews to use for the specified appWidgetId.
「RemoteViewsを指定したappWidgetIdに設定する」でいいのかな。
applyではなく、setか。
どこで呼んでもいいわけではなく、ACTION_APPWIDGET_UPDATEか、"outside of the handler"らしい。handlerの外部? the?
ACTION_APPWIDGET_UPDATEのハンドラ外だったら、領域UとUということになり、U∩Uなので全領域になるのではないか?
このメソッドは、UIDがAppWidget providerを所有(owner)しているところから呼ばれないと動作しないらしい。
「呼ぶことはできるけど、動作するには制限がありますよ」ということなのか。
今までを見ていると、zygoteが起動しているから、ownerはzygoteなのだろう。
とにかく、アプリレベルで呼び出せるものでないことだけは確かだ。
自然?に呼び出されるACTION_APPWIDGET_UPDATEは、ownerから呼び出されているとおもってもよさそうだ。
自分で強制的にACTION_APPWIDGET_UPDATEを使っても動かないよ、ということでよいのかな。

もう1つの呼び出し方がある。
これはonUpdate()ではないブロードキャストを受信しての動作だ。
            RemoteViews remoteView = getClickRemoteView(context);

            remoteView.setImageViewResource(R.id.ImageView, R.drawable.mounted);



            // update AppWidget

            ComponentName thisWidget = new ComponentName(context, EjectSD.class);

            AppWidgetManager.getInstance(context).updateAppWidget(thisWidget, remoteView);
前半はアイコン描画なので、どうでもいい。
ポイントは後半の2行だ。
>AppWidgetManager.getInstance(context)
これは、contextが提供するAppWidgetManagerを取得するものだ。
contextは、onReceive()の引数でもらったものになる。
ドキュメントによると、receiverが動作しているcontextらしい。
取得できるのはAppWidgetManagerのインスタンスなので、これも結局のところはAppWidgetManager::updateAppWidget()を呼び出すことになる。
さっきの「ownerから呼び出されないと」は、この意味なのか。
そうであれば、AppWidgetManagerのルールはこうなる。
  • onUpdate()の場合は、引数を使う
  • onReceive()の場合は、AppWidgetManager.getInstance(context)を使う
たぶん、これ以外では呼び出せないのだろう。

あとは、updateAppWidget()の引数だけか。
onUpdate()の場合は、各AppWidgetに対して(AppWidgetのIDに対して)RemoteViewsを設定する必要がある。
ここでは、ImageViewをタッチしたときに飛ばすPendingIntentを設定している。
不思議なのは、onReceive()の場合だ。
今回のAppWidgetだが、1つタッチすると全部のAppWidgetアイコンがちゃんと変化してくれる。
AppWidgetのIDなど何も気にしていないのに、だ。
これをどう解釈するとよいだろうか?
一番あっさりした解釈としては、複数のAppWidgetがあったとしても、全部同じRemoteViewsを参照している、だろう。
位置情報は別々に持っていて、その中身だけは一緒、と。
そうでなければ、updateAppWidget()が全AppWidgetのRemoteViewsを更新して回っている、ということになってしまう。
さすがにそれはないだろう。
もしそうなら、onUpdate()ではforの中でRemoteViewsをnewせず、外でnewしたものをupdateAppWidget()で渡してやればいいことになる。
うん、試してみたが、それでもちゃんと動いているようだ。

しかし、疑問は残る。
最初は全AppWidgetに対してnewしたRemoteViewsを設定しているのに、何でタッチすると一斉にアイコンが変化するのか?
推測するに、こうではなかろうか。
onUpdate()内で各AppWidgetごとにnew RemoteViewsしていたのは、こんなイメージ。

1回だけnew RemoteViewsした場合は、こんなイメージ。

これならば、納得できる。
psで見たとき、AppWidgetを増やしてもメモリがそんなに変化してないのも、リソースが増えていないからという理屈になる。
根拠がないのは、リソースが1つ、としているところだ。
各AppWidgetごとに異なる描画を行わせることもできるので、リソースは別々になっているものと思うのだ。
COWか?という気もするが、確証がない。

あ、updateAppWidget()の説明を読んでなかった!
なんと、引数にComponentNameをとる場合は、特定のAppWidgetインスタンスではなく全AppWidgetインスタンスが対象になると書いてある。
そうすると、最初の図が間違っていることになる。
onUpdate()時は、こうなっていたのだ。

今回のAppWidgetでは、個別に設定しなくていいので、onUpdate()はこれでよかろう。
    @Override

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        RemoteViews remoteView = getClickRemoteView(context);

        updateIcon(remoteView);

        ComponentName thisWidget = new ComponentName(context, EjectSD.class);

        appWidgetManager.updateAppWidget(thisWidget, remoteView);

    }
これで、このAppWidget内では参照がこのような関係になる。

うむ、気が晴れた。

[widget]RemoteViews

過去記事:2010/08/21 16:47
"view"は打ちやすいけど、"views"は打ちにくい。
いつも"w"を抜かしてしまう。
と、どうでもいい話はなしにしよう。
AppWidgetで残る大物は、RemoteViewsとPendingIntentだろう。
RemoteViewsを先にやろう。
あー、AppWidgetManagerってのもいるな。まあ、これは気分次第で。

AppWidgetのために何か描画させたい場合、普通のViewではなくRemoteViewsを使う。
RemoteViewsはViewから派生されたわけではない。ParcelableとFilterをimplementsしてはいるが、親クラスはいない(Objectね)。
特徴は、別プロセスから表示させることができる(can be displayed in another process)だろう。
何がどう"another process"なのかがよくわからん。
起動させるのはHomeアプリだから、この場合はHomeアプリプロセスを指しているのだろうか。
違う。訳が違う。
in another process、だから、別のプロセスで表示できる、となるのか。
別プロセスを立ち上げて、そこでRemoteViewsを表示させると言うことか?
psで見ると、AppWidgetの親はzygoteになっていた。USERはapp_XX(XXは数字)。そうなっているのは、emailやmms、inputmethodのsimejiなんかもだ。
起動直後(Homeには登録していない)

USER      PID   PPID  VSIZE  RSS     WCHAN    PC         NAME

root      1772  1     78708  11124 c0108254 afd0dc74 S zygoteapp_45    2151  1772  100056 12484 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD



Homeアプリ表示(Homeには登録していない)app_45    2151  1772  100576 12408 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD

ちょっと時間経過

app_45    2151  1772  100576 12388 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSDHome登録時のウィジェット一覧表示中app_45    2151  1772  100576 11428 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSDHome登録app_45    2151  1772  100576 11120 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD

ちょっと時間経過app_45    2151  1772  100576 11292 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSDHomeにもう1つウィジェット登録app_45    2151  1772  100576 10924 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD1回タップしてみたapp_45    2151  1772  100576 10924 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD9個置いてみた。app_45    2151  1772  100576 10700 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD
RSSってのが変化しているのでちょこちょこpsて見てみたが、RSSは常駐セットの大きさ、らしい。swapされてない物理メモリだとか。
PCはprogram count?
とにかく、増やしても違いが見えない。
これは単に、今回作ったウィジェットがそうだからなのかも。全部同じ見栄えであってほしいので、ウィジェットIDを管理していないのだ。
app_45は、数値だと10045みたいだ。
USER+α、という計算なのかな?

RemoteViewsのコンストラクタは2つあるが、今回使っているのはpackageNameとlayoutIdをとる方。
これは単にメンバ変数に代入するだけで終わっている。
クリックイベントを受けとってもらうPendingIntentを登録するメソッドのsetOnClickPendingIntent()も、内部で保持するだけ。
内部のActionリストに追加するだけなのだ。
コンストラクタで登録したパッケージ名とレイアウトIDはいつ使うのか?
apply()が一番それっぽく使っているようだ。
apply()関係のメソッドがいくつかあるが、呼ぶと例外を投げるらしい。
システムから呼び出されることを前提にしているのだろうか?
うーん、何一つ疑問が解決しない・・・。
ウィジェットの起動はActivityManagerが行っているようなので、そこから見ていくか。
ログはamのもののようだ。services/java/com/android/server/am/ActivityManagerService.java。
まあ、そういうものか。
プロセス起動は、zygoteが行っている。
これは、Homeアプリがウィジェットとして使おうと使うまいと、関係がない。
では、Homeアプリに9つAppWidgetを登録したときの起動時logcatを見てみよう。
I/ActivityManager( 1868): Start proc jp.typepad.hirokuma.EjectSD for broadcast jp.typepad.hirokuma.EjectSD/.EjectSD: pid=2137 uid=10045 gids={}
V/EjectSD ( 2137): onReceive:android.appwidget.action.APPWIDGET_ENABLED
V/EjectSD ( 2137): onReceive:android.appwidget.action.APPWIDGET_UPDATE
V/EjectSD ( 2137): onUpdate
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon

V/EjectSD ( 2137): onReceive:android.intent.action.MEDIA_MOUNTED
D/EjectSD ( 2137): mount
D/EjectSD ( 2137): getClickRemoteView
これだけ見てもわからないと思うが・・・。
最初の1行は、起動。
次にintentとしてENABLED、続いてUPDATEが来ているので、処理している。
実装しているのはonUpdate()で、こんなことをしている。
final int APP_NUM = appWidgetIds.length;
for(int i=0; i<APP_NUM; i++) {
 RemoteViews remoteView = getClickRemoteView(context);
 updateIcon(remoteView);
 appWidgetManager.updateAppWidget(appWidgetIds[i], remoteView);
}

引数でappWidgetIds[]が渡されるので、それぞれにRemoteViewsを生成し(中でnewを呼び、クリック時のインテントを登録している)、アイコンを描画し、反映させている。
9つ分やっているのは、ここだったのだ。
自分で思いついたわけではなく、どこかのサイトからもらってきた処理だが、そういう意味があったのだな。
では、アイコンをタップしたときのlogcatも見ておこう。
V/EjectSD ( 2137): onReceive:jp.typepad.hirokumaEjectSD.WIDGET_CONTROL
D/EjectSD ( 2137): click
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): onReceive:android.intent.action.MEDIA_EJECT
V/EjectSD ( 2137): onReceive:android.intent.action.MEDIA_UNMOUNTED
D/EjectSD ( 2137): umount
D/EjectSD ( 2137): getClickRemoteView

先頭部分がタップ時のintent受信。
ログに出てこないが、ここでMountServiceに対してmount/umount要求を投げている。
要求が帰ってくるまでの間、アイコンを変化させるためにRemoteViewsを作成し、アイコンをセットしている。
アイコンの変更は、こんな処理。
remoteView.setImageViewResource(R.id.ImageView, R.drawable.icon);
remoteViewはRemoteViewsのインスタンスね。
レイアウトXMLでImageViewという名前のImageViewを置いているので、それをiconというリソースにします、というだけだ。
実は、これがわからずに調べ回っていたのだ。View関係だからRemoteViewsの説明を見ればいいのだろうけど、思いつかなかったのだよ。
その後ここでやっているのは、onUpdate()と違ってこんなことになっている。
ComponentName thisWidget = new ComponentName(context, EjectSD.class);
AppWidgetManager.getInstance(context).updateAppWidget(thisWidget, remoteView);

これもどこかからもらってきた処理だ。
しかし、これも処理がわからずに困っていたのだよ。
後半はUNMOUNTEDとなったintentを受信しての処理だ。
やっていることはタップ時とほとんど同じ。
アイコンを変更させ、updateAppWidget()している。
久しぶりにAndroid関係の本を購入。
「AndroidSDK開発のレシピ」。
カバーはいりません、と言ったつもりなのに、されてしまった。。
何となく悔しい。

[widget]intent-filter

過去記事:2010/08/21 11:54
intent-filterのマッチングについては、「他もアクティビティ図にしたので、見たい人は持っていってくだされ。」ということにした。
これは複雑だ!
しかし、Uriだけのマッチングはもう少しシンプルで、hostやportの一致を見ている。
つまり、こう言えよう。
intent-filterの比較は、intent-filterごとに行われる、と。
今回でいえば、
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="jp.typepad.hirokumaEjectSD.WIDGET_CONTROL"/>
</intent-filter>

と、
<intent-filter>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_EJECT"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTABLE"/>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<data android:scheme="file" />
</intent-filter>

だ。
前者はactionのみのため、match()はどれかのactionと一致し(matchAction)、dataはないのでMATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMALとなり(matchData)、categoryもないのでmatchData()の結果がそのまま戻り値となる。
MATCH_ADJUSTMENT_MASK でANDとって非0だからマッチした、となるのかな。
後者はactionとschemeがあるので、まずactionがどれかと一致し(matchAction)、schemeがあるのでどれかと一致してMATCH_CATEGORY_SCHEMEとなり(matchData)、categoryはないのでmatchData()の結果がそのまま戻り値となる。
これは MATCH_CATEGORY_MASK でANDするのか?
match()の戻り値をどう使うか、よくわからん。
が、そこはいいとしよう。
intent-filterの分け方としては、
  • actionのみのintentを受け付けるのであれば、同じintent-filterにまとめてよい
  • actionとschemeがあるintentを受け付けるのであれば、同じintent-filgerにまとめてよい
  • categoryを含む場合は完全一致となるので注意すべし
ということになるだろうか。
試していないので、そうなるかわからんが、そうなるんではないかね。

MOUNT/UNMOUNTなどのintentは、schemeとして"file"が付加されている。
送信されるintentはこんな感じだ。
in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
むむ、ということは、schemeだけでなくpathもあることになる。
しかしご安心を。
matchData()でpathが比較されるのは、addDataAuthority()した場合のみだ。
XMLだと、<auth>タグがそれに該当するはずなのだが・・・ドキュメントにはないな。
ちなみに、IntentFilterクラスには、writeToXml()というメソッドがある。
試してないけど、どうやらXMLに吐きだしてくれるみたいだ。
何か使えるかな?

なお、MOUNT/UNMOUNTを受け付けるには許可が必要となる。
AndroidManifest.xmlに、
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
がいるのだ。
まあ、忘れていてもlogcatで例外を投げてくれるので、わかりやすいがね。

[widget]AppWidgetProvider

過去記事:2010/08/20 21:00
AppWidgetProviderは、BroadcastReceiverをextendsしている。
コンストラクタもからっぽ。
特徴としては、onReceive()をオーバーライドして、onUpdate(), onDeleted(), onEnabled(), onDisabled()を呼び出せるようにしているところ。
なので、onReceive()を自分でオーバーライドした場合には、superを呼び出すタイミングを考えないと、うまく動かないことがあるかもしれない。
まあ、onUpdate()だけ派生している分には影響ないのだが。
BroadcastReceiverを派生しているので、普通のブロードキャストも受信できる。
そうなると、onReceive()を派生させないと意味がない。
onReceive()内でintent.getAction()によってswitch ...はできないので、if-elseifしき、どこにも引っかからなかったらsuperに処理させる、ということになるか。

私が処理したかったのは、
  • アイコンのタップ
  • マウント通知
  • アンマウント通知
なので、
  • ACTION_WIDGET_CONTROL (自前で定義)
  • Intent.ACTION_MEDIA_MOUNTED
  • Intent.ACTION_MEDIA_UNMOUNTED
を処理した。それ以外は、super.onReceive()にお任せ。
ソースに書くだけでは受け取ってくれないので、AndroidManifest.xmlに追記。
<receiver android:name=".EjectSD">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="jp.typepad.hirokumaEjectSD.WIDGET_CONTROL"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/app_widget"/>
</receiver>

<receiver android:name=".EjectSD">
<intent-filter>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_EJECT"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTABLE"/>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<data android:scheme="file" />
</intent-filter>
</receiver>


前半は、ウィジェット更新とタップを受けるintent-filter、後半は、SDカード関連のintent-filter。
receiverは同じだから、まとめていいのだろうか?
分け方がよくわかっていない。

以下、Android Documentの訳も含めて書いておく。
intent-filterには、action, category, nameの3つのIntent特性(Intent characteristics)がある。
これらは文字列だ。
メソッドでは、setAction(), のような「set」ではなく、addAction(), のような「add」になっている。
つまり、追加していくしくみなのだ。
となると、intent-filterをまとめていいのかどうかは、intent-filterがどのようにintentをマッチさせるかのしくみによることになる。
actionとcategoryはマッチしなくてはならない。
そしてdata(data typeとdata scheme+authority+path)はマッチしなくてはならない。
なら、全部やん。
まあ上記は基本ルールらしいので、もう少し見ていこう。
actionは、Intent actionにマッチする。 "if any of the given values match XXX"とあるので、「もしXXXと一致するなら」くらいの意味になるのだろうか。
それが嫌なら、actionは書くな。
「嫌なら」とは書いてないけど、そんな意味でよかろうか。
次は、Data Type, Data Scheme, Data Authority, Data Pathと続く。
Dataシリーズだろうか。
これらにはそれぞれ、addDataType()やaddDataScheme()のようなメソッドがある。

Data Type。Intent typeにマッチする。MIMEっぽいワイルドカードが使えるらしい。"audio/*"とすると、"audio/mpeg"とか"audio/aiff"などに対応できるとか。大文字・小文字は関係するのだと。小文字使っとけ、と。actionは関係しないのか?

Data Scheme。Intent data's schemeにマッチする。これも大文字・小文字があるので、小文字使っとけ、と。

Data Authority。data authorityと schemeの1つとマッチするか、書かないでおくか。
ならschemeは必ず書く必要があるのかというと、実際はそんなこともない。いるのかいらんのか、表にまとめられんのか、君らは!
これも大文字・小文字あり、と。以下省略。

Data Path。Intentのdata pathと、schemeとauthorityの両方にマッチする。か、書かないでおくか。
そしてCategory。
・・・訳せんかった。

Extra categories in the filter that are not in the Intent will not cause the match to fail.
Intentにないintent-filterの余計なカテゴリーはマッチしない、ということ?
つまり、カテゴリーは完全一致でしか引っかからないということだろうか。
actionがない場合、カテゴリーのないintent-filterは、カテゴリーのないintentしかマッチできない、とある。
悔しいが、よくわからん。

では、intentをマッチさせているソースを見てみたいものだ。
あんまり見てないけど、IntentFilter::match()だろう。
引数が長い方のmatchね。
戻り値は、
  • MATCH_CATEGORY_MASK : 0xfff0000
  • MATCH_ADJUSTMENT_MASK : 0x000ffff
  • MATCH_ADJUSTMENT_NORMAL : 0x8000
  • MATCH_CATEGORY_EMPTY : 0x0100000
  • MATCH_CATEGORY_SCHEME : 0x0200000
  • MATCH_CATEGORY_HOST : 0x0300000
  • MATCH_CATEGORY_PORT : 0x0400000
  • MATCH_CATEGORY_PATH : 0x0500000
  • MATCH_CATEGORY_TYPE : 0x0600000
  • NO_MATCH_TYPE : -1
  • NO_MATCH_DATA : -2
  • NO_MATCH_ACTION : -3
  • NO_MATCH_CATEGORY : -1
とのこと。
actionがある場合には、matchAction()を呼び出し、マッチしなければ終了。
次にmatchData()。戻りが0未満なら、終了。
次にmatchCategories()。非nullなら、終了。
ここまでくぐり抜けたら、matchData()の値を返して終了。
では、順に見ていこう。

matchAction(action)
まず、引数action(おそらくintent.getAction()と同じものが来るだろう)がnullか、mActionsすなわちaddAction()していないか、mActionsの大きさが0の場合は、falseを返す。
そうでなければ、actionがmActionsに含まれればtrue、含まれなければfalseを返す。
実にシンプルだ。
actionに相当するものがあれば比較し、なければ比較しない。
呼び出し元では、引数actionが非nullであることが条件になっている。
つまりIntentにactionがなければ、ここは通らないことになる。
actionがあるならば、!matchAction()なので、trueを返さないとマッチ失敗となる。

matchDAta(type, scheme, data)
ここは処理が長い。ループはないので、上から下に流れるだけなのだが、長いな。
まず、addDataType()とaddDataScheme()をしてない場合。
typeもdataもnullなら、マッチしたことになる。
MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL = 0x108000
そうでなければ、NO_MATCH_DATA。
addDataScheme()していた場合・・・・

と書いていこうとしたが、文章ではわかりにくいことがわかった。
図にしよう。
今回は、メインの流れのみ。
(紛失)
他もアクティビティ図にしたので、見たい人は持っていってくだされ(紛失)