Hatena::ブログ(Diary)

明日の鍵

2013-01-02

2013年

今年の抱負でもつらつらと…

去年の反省

2012年 - 明日の鍵

http://d.hatena.ne.jp/tomorrowkey/20120126/132758834

ものづくり

結局昨年と同じくらい。

NFCがんばってた。

Androidアプリ以外もやる

サーバ系もやった。

自分サーバもたてて、Redmineを使うようになった。

毎月どれくらいの収支あるのか把握しておきたい

moneylook 使い始めてだいたい増えてるのか減ってるのか分かるようになった。

レシート受け取りは継続中。

運用的にはもともと経費で落とせそうなヤツだけもらうようにしてたが

とりあえずもらっておいて、あとで仕分けるスタイルにした。

フルマラソン完走

マラソンたくさん申し込むけど、結局一つもでれなかった。

ジムでのランニングはストレス発散によく使ってた。

ジムはいい。

Google I/Oいく

行った。

楽しかった。

しかしGoogle I/Oよりも観光の方が楽しかった感が否めない。

イベント自体よりもエンジニアがたくさん集まるということが楽しい。

つまり楽しい。

今年もがんばって行きたい。

名古屋いく

行けてない。

つ部行ってみたいなぁ、、。

福岡いく

NFCのイベントに行った。

まみちゃんも行った。

1年ぶりだったけど、覚えててくれて嬉しかった。

屋台がとっても楽しい!また行きたい。

モグ屋いく

行けた。

年明けて間もなくと、年越しに行けた。

ビール大ジョッキがはかどってしょうがない。

レバがうまい。

その他

新しい事を学べたかと思い起こすと、そんなに新しいことしていなくて

環境を整える方に没頭してしまっている。

また、アウトプットぜんぜんない。

プライベートであればメモ程度のものがいくつかあるが

公開しないと意味ないなー。

ブログ書く量が減っているぞ。

今年の抱負

英語

英語を学ぶことは継続が大切だと、多方から聞く。

今年の前半は語学留学に行くので、それをがんばるのはもちろんだが

帰ってきた後に「どのように英語と付き合うか」を考えたい。

仕事

そろそろ「俺が自分でやったほうが早いじゃん」っていうのはやめたい。

自分が痛い目を見る。

仕事を人に任せられるようになりたい。

旅行

ヨーロッパ行きたい。

鹿児島帰りたい。

引越し

練馬いいけど、会社まで遠いわーーーー

移動時間に時間取られるのはもう勘弁><

お金

最近お財布がゆるい。

がんばろう。

#

成長しよう

2012-12-07

0次発行FeliCa LiteにNDEFを書き込む

まえがき

Android Advent Calendar 2012 (表)の8日目担当の@tomorrowkey です!

裏は @rukiadia さんです。

がんばります!

いきさつ

0次発行状態のFeliCa LiteにNDEFを書き込めるソフトウェアがなかったので、自分で作りました。

WindowsではPaSoRi+NDEFWriterで、1次発行状態にすると同時にNDEFを書き込むことができます。

NDEFを書き込むために1次発行は必須ではないので、0次発行の状態でNDEFを書き込めるようにしました。

某イベントで2,000枚のFeliCa LiteにSmartPosterを書き込む必要がありました。

当初はPaSoRi+NDEFWriterでやろうかと思ったのですが

1枚でも書き込むためにURLを入力したり、ダイアログのOKボタンを押したりと

オペレーションが煩雑だったのでアプリを作り、連続で書き込めるようにしたのです。

その時はただ同じ値をかければよかったので、マジックナンバーの嵐だったのですが

今回、値を動的に変えられるように、あとわりと綺麗なソースになるように書き直しました。

ライブラリは使っておらず、スクラッチしています。

まだまだ手を抜いてるところがたくさんあるので、なんとかしたいです。

てきとーですが、ここからは各所の解説など書きます。

アプリがFeliCa Liteに反応するようにする

use-permission

NFCを使うためにpermissionの設定が必要です。

/AndroidManifest.xml

<uses-permission android:name="android.permission.NFC" />
launchMode

NFCのIntentはNEW_TASKがついた状態で飛んできます。

いちいち新しいActivityが起動されてはうざいので

launchModeにsingleTaskもしくはsingleInstanceを指定して防ぎます。

android:launchMode="singleTask"
enableForegroundDispatch

NFCが反応したら優先して自分のアプリが起動されるようにforegroundDispatchという機能を使います。

アプリがフォアグラウンドに表示されている状態でNFCをフックしたいので、onResumeに処理を書きます。

あとでonPauseに解除するコードを書けばOKです。

/src/jp/tomorrowkey/android/felicalitewriter/WriteActivity.java

@Override
protected void onResume() {
  super.onResume();

  mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
  if (mNfcAdapter == null) {
    Toast.makeText(getApplicationContext(), "not found NFC feature", Toast.LENGTH_SHORT)
        .show();
    finish();
    return;
  }

  if (!mNfcAdapter.isEnabled()) {
    Toast.makeText(getApplicationContext(), "NFC feature is not available",
        Toast.LENGTH_SHORT).show();
    finish();
    return;
  }

  PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
      getClass()), 0);
  IntentFilter[] intentFilter = new IntentFilter[] {
    new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
  };
  String[][] techList = new String[][] {
    {
      android.nfc.tech.NfcF.class.getName()
    }
  };
  mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilter, techList);

}

pendingIntentにはNFCタグを検出した時に投げて欲しいIntentを入れてあげます。

IntentFilterとtechListには検出したいNFCタグの種類を指定します。

IntentFilterのActionには

  • ACTION_NDEF_DISCOVERED
    • NDEFフォーマットされたNFCタグ
  • ACTION_TECH_DISCOVERED
    • NDEFフォーマットされていないタグの中で、タグの種類指定ができる
  • ACTION_TAG_DISCOVERED
    • どんなタグでもいいから検出する

techListにはタグの種類を指定します。

今回はFeliCa Liteだけに対応したいので

  • ActionにACTION_TECH_DISCOVERED
  • techListにNfcF

を指定します。

disableForegroundDispatch

アプリがバックグラウンドに入ったときには、NFCフックしなくていいので

有効にしたforegroundDispatchを無効にします。

@Override
public void onPause() {
  super.onPause();

  mNfcAdapter.disableForegroundDispatch(this);
}

FeliCa の仕様について

ここまで作るとActivity#onNewIntent(:intent)にNFCタグの情報が飛んでくるようになります。

ExtraからTagを引っ張ってNDEFを書いたりするわけですが

中には書けないFeliCa Liteタグがあります。

それはNDEFフラグがたっていないものです。*1

通販等で購入したFeliCa Liteタグはたいていたっていない状態です(あたりまえですね)

つまり、NDEFフラグを立てればいいわけですが

Android SDKでは、FeliCa LiteのNFCフラグをたてる機能がついていません。

自分でポチポチたてる必要があります。

そのためにFeliCa Liteの仕様について知る必要があります。

がんばろう。

FeliCa仕様書

FeliCaの仕様書はSonyのサイトにあります。

FeliCaカード ユーザーズマニュアル 抜粋版
http://www.sony.co.jp/Products/felica/business/tech-support/index.html?j-short=tech-support#Standard01
FeliCa Liteユーザーズマニュアル
http://www.sony.co.jp/Products/felica/business/tech-support/index.html?j-short=tech-support#Lite01

一般的なコマンドの体型

コマンド長 : 1 Byte

先頭に全体のコマンド長が入ります。

それはコマンド長を格納する1Byteも含みます。

よく忘れます。

コマンド : 1 Byte

コマンドを指定します。

FeliCa Liteの仕様書に載っているコマンドは以下のもののみです。

  • Polling
    • 0x00
  • Read Without Encryption
    • 0x06
  • Write Without Encryption
    • 0x08

FeliCa Standardにはもうすこしあります。

IDm : 8 Byte

コマンドによりますが、そのあとにはだいたいIDmが入ります。

IDmとはFeliCaタグひとつひとつで一意に識別できるIDのことです。

製造番号みたいなもので、同じ通信領域内に複数のFeliCaタグがあった際に

通信相手を選ぶために使われます。

書き込みコマンドについて

FeliCa Liteには以下のような制限があります。

  • 一度に書き込めるブロック数は1つまで
  • サービスコードは0x0009に固定
  • アクセスモードは000に固定
  • サービスコード順番は0000に固定

FeliCa Standardと比べれば考えることが少なくて楽です。

以上のことを踏まえて書き込みコマンドを実装すると以下のようになります。

/**
 * Write Without Encryptionコマンドを発行します<br>
 * FeliCa Liteなので、1度のコマンド発行で1ブロックだけ書き込めます
 * 
 * @param idm IDm
 * @param blockNumber ブロック番号
 * @param data 書き込みデータ
 * @return レスポンス
 * @throws TagLostException
 * @throws IOException
 */
public byte[] writeWithoutEncryption(byte[] idm, int blockNumber, byte[] data)
    throws TagLostException, IOException {
  if (idm == null || idm.length == 0)
    throw new IllegalArgumentException();

  ByteBuffer byteBuffer = ByteBuffer.allocate(31);

  // Write Without Encryption
  byteBuffer.put((byte)0x08);

  // IDm
  byteBuffer.put(idm);

  // サービス数
  // FeliCa Liteなので1に固定
  byteBuffer.put((byte)0x01);

  // サービスコード(リトルエンディアン)
  // 0x00 0x09
  byteBuffer.put((byte)0x09);
  byteBuffer.put((byte)0x00);

  // ブロックリスト
  // 長さ 2Byteなので1b
  // アクセスモード FeliCa Liteなので000bに固定
  // サービスコード順番 FeliCa Liteなので0000bに固定
  // ブロック番号 引数から指定
  byteBuffer.put((byte)0x80);
  byteBuffer.put((byte)blockNumber);

  // 書き込みデータ
  byteBuffer.put(data);

  byte[] command = byteBuffer.array();
  byte[] response = executeCommand(command);

  return response;
}

コマンド長はコマンド発行直前に付加するためはぶいてあります。

FeliCa Liteのメモリマップについて

これで書き込みコマンド組立は完成しました。

次にどこになにを書けばいいか調べます。

FeliCa Liteのメモリマップは以下のようになっています。

(これもFeliCa Liteの仕様書に書かれています)

f:id:tomorrowkey:20121205224921p:image

S_PADと書かれた部分が、ユーザーブロックと呼ばれる場所で

ここにさまざまなデータを書き込みます。

そのほかの領域はいろいろな役割や機能があるので詳しくは仕様書を参考にしてください。

ユーザブロック以外にこんかい必要になるのが

MCブロック(メモリーコンフィグレーションブロック)です。

MCブロックのSYS_OP(System Option)に0x01を指定することで

FeliCa LiteがNDEF化されます。

通常は0x00です。

FeliCa LiteのNDEFフラグをたてる

さっそく書き込みコマンドを使ってNDEF化します。

// NDEF化します
byte[] data = new byte[] {
    (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
};
felicaLiteTag.writeWithoutEncryption(idm, 0x88, data);

MCブロックに書き込みたいので、ブロック番号に0x88を指定し

データに1ブロック分のデータを指定します。

一度の書き込みで1ブロックすべてを指定しなければなりません。

このブロックの中には一度特定の値を書き込むと、二度とその値を変更できない箇所があります。

ですので、気をつけて書き込みましょう。

本来であれば現在のブロックデータを読み込んでSYS_OPの部分だけ変更したほうがいいんだけど

手を抜きました。

NDEFデータを作る

タグのメタデータっぽいところはNDEFに対応できたから

今度はデータ本体を作ります。

AndroidはNFC対応!だなんて謳ってるくらいだから、NDEFの各フォーマットビルダーくらい用意されてるよなーって

思ったんですが、ありません。

仕様書を読んで自分で作るか、どこかからひろってきましょう。

NDEF仕様書はNFC Forumにあります。

NDEFについては'NFC Data Exchange Format (NDEF) Technical Specification' という仕様書

RTDについては'Record Type Definition Technical Specifications' の各仕様書

を参考にしてください。

NFC Forum : Technical Specifications 
http://www.nfc-forum.org/specs/spec_list/

以前NDEFについて発表したときの資料はここから

避けては通れないバイナリ地獄 - NDEFってなんだろう - 
http://www.slideshare.net/tomorrowkey/ndef-13784268
避けては通れないバイナリ地獄 - NDEFってなんだろう - 
http://www.slideshare.net/tomorrowkey/ndef-13784268

ちなみに身近なところでNDEFデータを作るサンプルとしては

ApiDemosにRTD-Textを作るコードが入っています。

NDEFデータができたら書き込みます。

NDEFフラグを変更した時と同じ要領です。

NDEFヘッダを作成する

データができたところで、S_PAD(スクラッチパッド)に書き込みたいところですが

そのまま書いてもNDEFだと認識してくれません。

実はS_PAD0をAttribute Information Blockにしないといけないためです。

そのへんの仕様はNFC Forumに定義されています。

FeliCa系のタグはNFC ForumではType 3となっているので

NFC Forum Type 3 Tag Operation Specification

という仕様書を見てみると書いてあります。

NFC Forum : Technical Specifications 
http://www.nfc-forum.org/specs/spec_list/

f:id:tomorrowkey:20121206205014p:image

  • Ver

バージョンを指定します。

1.0を表す0x10を指定します。

  • Nbr

タグ読み取り時に、一度に読めるブロック数を指定します。

FeliCa Liteなので0x04を指定します。

  • Nbw

タグ書き込み時に、一度に書き込めるブロック数を指定します。

FeliCa Liteなので0x01を指定します。

  • Nmaxb

NDEFデータとして使えるブロック数を指定します。

13ブロックなので0x00 0x0Dを指定します。

  • WriteF

データは一つのタグに収まるので、0x00を指定します。

  • RW Flag

今後も書き込みできる状態なので、0x01を指定します。

  • Ln

NDEFデータの長さを指定します。

実際のデータ長から設定されます。

  • Checksum

チェックサムです。

ブロック0のすべてを加算した値を設定します。

private byte[] createNdefHeader(int ndefLength) {
  ByteBuffer buffer = ByteBuffer.allocate(16);

  // Ver
  buffer.put((byte)0x10);

  // Nbr
  // Read Without Encrypitonで一度に読めるブロック数を指定します
  // FeliCa Liteなので、一度に4ブロック読み込める
  buffer.put((byte)0x04);

  // Nbw
  // Write Without Encryptionで一度に書き込めるブロック数を指定します
  // FeliCa Liteなので、一度に1ブロック書き込める
  buffer.put((byte)0x01);

  // Nmaxb
  // NDEFとして使用できるブロック数
  // FeliCa Liteなので、データ領域は13ブロックまで
  buffer.put((byte)0x00);
  buffer.put((byte)0x0d);

  // unused
  buffer.put((byte)0x00);
  buffer.put((byte)0x00);
  buffer.put((byte)0x00);
  buffer.put((byte)0x00);

  // WriteF
  // 一枚で完結しているので、0x00
  buffer.put((byte)0x00);

  // RW Flag
  // Read Writeなので0x01
  buffer.put((byte)0x01);

  // Ln
  // NDEFデータの長さを指定します
  buffer.put((byte)((ndefLength >>> 16) & 0xff));
  buffer.put((byte)((ndefLength >>> 8) & 0xff));
  buffer.put((byte)(ndefLength & 0xff));

  // Checksum
  // チェックサムを指定します
  buffer.put(checksum(buffer.array()));

  return buffer.array();
}

/**
 * チェックサムを作成します<br>
 * すべてのバイト配列の合計を計算します
 * 
 * @param byteArray
 * @return
 */
private byte[] checksum(byte[] byteArray) {
  int sum = 0;
  for (byte b : byteArray) {
    sum += b & 0xff;
  }
  return new byte[] {
    (byte)((sum >>> 8) & 0xff), (byte)(sum & 0xff)
  };
}
NDEFデータを書き込む

これで、すべての材料は揃いました。

ユーザーブロックの0から順にデータを書き込みます。

/**
 * NdefMessageを書き込みます
 * 
 * @param idm IDm
 * @param ndefMessage NDEF
 * @throws SizeOverflowException NdefMessageのサイズが大きすぎる場合に発生します
 * @throws TagLostException
 * @throws IOException
 */
public void writeNdefMessage(byte[] idm, NdefMessage ndefMessage) throws SizeOverflowException,
    TagLostException, IOException {
  if (idm == null || idm.length == 0)
    throw new IllegalArgumentException();
  if (ndefMessage == null)
    throw new IllegalArgumentException();

  byte[][] datas = mappingBlock(ndefMessage);

  for (int blockNumber = 0; blockNumber <= 13; blockNumber++) {
    // FIXME レスポンスを握りつぶしているので、どうにかする
    writeWithoutEncryption(idm, blockNumber, datas[blockNumber]);
  }
}

/**
 * NdefMessageからFeliCa Liteの各ブロックにマッピングします
 * 
 * @param ndefMessage
 * @return
 * @throws SizeOverflowException
 * @throws IOException
 */
private byte[][] mappingBlock(NdefMessage ndefMessage) throws SizeOverflowException,
    IOException {
  byte[] ndefMessageBytes = ndefMessage.toByteArray();
  int ndefMessageBytesLength = ndefMessageBytes.length;
  int blockCount = (int)Math.ceil(ndefMessageBytesLength / 16.0);
  if (blockCount > 13)
    throw new SizeOverflowException(ndefMessageBytesLength, 16 * 13);

  byte[][] datas = new byte[14][16];
  datas[0] = createNdefHeader(ndefMessageBytesLength);

  ByteArrayInputStream inputStream = new ByteArrayInputStream(ndefMessageBytes);
  try {
    inputStream = new ByteArrayInputStream(ndefMessageBytes);
    int readLength;
    for (int i = 1; i < datas.length; i++) {
      readLength = inputStream.read(datas[i]);
      if (readLength == -1)
        break;
    }
  } finally {
    try {
      if (inputStream != null)
        inputStream.close();
    } catch (IOException e) {
      // ignore
    }
  }

  return datas;
}

完成

これでNDEFを書きこめるようになりました。

たのしいですね。

ソースコードはここに晒しておきます。

https://github.com/tomorrowkey/FeliCaLiteWriter

最後に

明日はNexus 7がなかなか届かないことで有名な@sekitoba さんと

@sugimotoak さんです。

みんながんばれー

*1:ユーザーエリアがReadOnlyに変更されている場合もあります

2012-02-26

フッタにボタンを表示する4.0

4.0以前はこちら

フッタにボタンを表示する - 明日の鍵 
http://d.hatena.ne.jp/tomorrowkey/20110809/1312869978

ボタン2つ

f:id:tomorrowkey:20120226173416p:image:w250

この画面のレイアウトファイルはどうなっているかソースを探す

uninstall_confirm.xml

<!-- OK confirm and cancel buttons.  -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:divider="?android:attr/dividerHorizontal"
    android:showDividers="beginning"
    android:paddingTop="16dip">

  <LinearLayout
      style="?android:attr/buttonBarStyle"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      android:measureWithLargestChild="true">

    <LinearLayout android:id="@+id/leftSpacer"
      android:layout_weight="0.25"
      android:layout_width="0dip"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      android:visibility="gone" />

    <Button android:id="@+id/cancel_button"
      android:layout_width="0dip"
      android:layout_height="wrap_content"
      android:layout_gravity="left"
      android:layout_weight="1"
      android:text="@string/cancel"
      android:maxLines="2"
      style="?android:attr/buttonBarButtonStyle" />

    <Button android:id="@+id/ok_button"
      android:layout_width="0dip"
      android:layout_height="wrap_content"
      android:layout_gravity="right"
      android:layout_weight="1"
      android:text="@string/ok"
      android:maxLines="2"
      style="?android:attr/buttonBarButtonStyle" />

    <LinearLayout android:id="@+id/rightSpacer"
      android:layout_width="0dip"
      android:layout_weight="0.25"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      android:visibility="gone" />

  </LinearLayout>
</LinearLayout>

ボタン1つ

軽く探したけど、だいたいActionBarで実現してるみたい

レイアウトで実装したいなら以下でそれっぽくできる

<LinearLayout
  style="?android:attr/buttonBarStyle"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:measureWithLargestChild="true"
  android:orientation="horizontal" >

  <Button
    android:id="@+id/done_button"
    style="?android:attr/buttonBarButtonStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:drawableLeft="@drawable/ok"
    android:drawableRight="@drawable/space"
    android:maxLines="2"
    android:text="@string/done" />
</LinearLayout>

2012-02-10

乗換案内を検索するアプリを作ったよ

f:id:tomorrowkey:20120210123528p:image:h400

簡単に使える乗換案内アプリを作りました。


毎回駅名を入力して、検索をするなんてめんどうです。

いつも使うルートを予め登録しておけば、あとは選択するだけで

今の時間で検索してくれます


検索結果をわざわざ保存なんてめんどうです。

検索したら自動的に保存してくれます

ダウンロード

https://market.android.com/details?id=jp.tomorrowkey.android.transitguide

サイト

Androidアプリ 乗換案内

https://sites.google.com/site/androidtrainguide/

その他スクリーンショット

f:id:tomorrowkey:20120210123529p:image:h400

f:id:tomorrowkey:20120210123531p:image:h400

f:id:tomorrowkey:20120210123530p:image:h400

2012-02-01

Javaの正規表現をテストするサイトを作ったよ

f:id:tomorrowkey:20120201225135p:image

作った

Java regex tester 
http://java-regex-tester.appspot.com/

Javaの正規表現を評価するサイトを作ったよ。


使ったライブラリとか

Slim3

https://sites.google.com/site/slim3appengine/

GAEだとslim3一択

jQuery: The Write Less, Do More, JavaScript Library

http://jquery.com/

jQueryないとjavascriptがめんどい

zClip :: jQuery ZeroClipboard

http://www.steamdev.com/zclip/

クリップボードにコピーするためのjQueryプラグイン

Adventures in Programming » Blog Archive » Toast Notifications Using jQuery

http://shawntabai.com/wp/2011/09/06/toast-notifications-using-jquery/

AndroidのToastライクな表示をするためのコード

Google Web Fonts

http://www.google.com/webfonts

Gudeaというフォントを使いました。

チェックボックスをつけて、コードをコピーするだけなんでとても簡単です。

見た目

がんばってCSS書いたけど、Bootstrap使ってかっこよくしたいなー。

あー、faviconださい。

あとは脱GAEしたい。

更新しました

  • 正規表現エラーだった場合にレスポンスが帰ってこない不具合を直しました
  • Bootstrap使いました
  • Matchesも表示するようにしました
  • Groupのハイライトを色分けしました

2012-02-05 20:45 追記

更新しました

  • Findがすべて抽出されないバグを修正しました

2012-02-07 08:49 追記

ソースを公開しました

tomorrowkey/RegularExpressionTester - GitHub 
https://github.com/tomorrowkey/RegularExpressionTester

いい加減ライセンスの表記をちゃんとしないとなー

2012-02-09 08:40 追記