RubotoではどのようにNFCを読み取ればよいかを調べ、Androidのメソッドを利用して情報を読み取ったときのメモ。
環境
Platform | JDK | ant | Ruby | ruboto | jruby-jars | Device | API level |
---|---|---|---|---|---|---|---|
Windows7 x64 | 1.7.0_25 | 1.9.1 | RubyInstaller 1.9.3-p448 | 0.13.0 | 1.7.4 | Nexus7 2012 | android-17 |
アプリの動き
Nexus7にNFCを近づけると、IDmなどを表示します。
手元には以下のものがあったため、それぞれで試してみました。
規格名 | 実際のもの |
---|---|
NFC Type A | 週刊アスキー(2013/9/10増刊号) の付録 |
NFC Type B | 運転免許証 |
Felica (Type F) | Edy |
なお、身近なものでの規格例は、以下にありました。
http://yumewaza.yumemi.co.jp/2011/03/nexuss_nfc_gettechlist.html
AndroidManifest.xmlへの追記
NFCを使うパーミッションの設定
<uses-permission android:name="android.permission.NFC" />
- GooglePlayで公開する時に、ハードウェアにNFCが無い場合は表示されない設定
<uses-feature android:name="android.hardware.nfc" android:required="true" />
- intent filterの設定
外部からintentを投げられた時(=NFCを読み取った時)に反応できるよう、intent filterを設定します。
<intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter"/>
category.DEFAULTについては、以下の記事が参考になりました。
Yukiの枝折: Android:IntentFilterにDEFAULT_CATEGORYが必要な理由
また、実際のfilterの内容は、meta-dataタグの中で、「@xml/nfc_tech_filter」と拡張子無しのファイル名を指定します。
intent filter用xmlファイルの作成
nfc_read\res\xml ディレクトリを作成し、以下のファイルを保存します。
nfc_tech_filter.xml
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.IsoDep</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcA</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcB</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcF</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcV</tech> </tech-list> <tech-list> <tech>android.nfc.tech.Ndef</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NdefFormatable</tech> </tech-list> <tech-list> <tech>android.nfc.tech.MifareClassic</tech> </tech-list> <tech-list> <tech>android.nfc.tech.MifareUltralight</tech> </tech-list> </resources>
Activityまわり
IDmの取得について
intent.getParcelableExtra()とintent.getByteArrayExtra()を使うサンプルがありましたが、今回はAPI10以降かつタグの情報を色々と使うこともあり、前者を使うことにしました。
AndroidアプリでNFCタグを読み書きするための基礎知識:Androidで動く携帯Javaアプリ作成入門(38) - @IT
バイナリデータの16進数化について
Tag.getIdでIDmを取得するとバイト配列(JRubyではFixnum配列)が返ってきますが、Java同様マイナス値となることがありました。
JRubyなのでJavaと同じくマイナス値になるのだろうと考え、以下を参考に0xffの論理積を考慮しました。
getTechListの戻り値について
String配列で戻ってくるので
tech_list = tag.getTechList().join(',')
のように連結しようとしたところ、
undefined method 'join' for java.lang.String.[android.nfc.tech.NfcF]
とエラーになりました。
そのため、
getTechList.to_ary.join(',')
と、to_aryメソッドを経由して配列として扱えるようにしました。
to_aryとto_aのどちらを使うべきなのかで悩みましたが、以下を読んで、配列として扱えればよく、配列への変換までは不要と思い、前者を利用しました*1。
ソース
# encoding: utf-8 require 'ruboto/widget' ruboto_import_widgets :LinearLayout, :TextView # NFCまわり java_import 'android.nfc.NfcAdapter' java_import 'android.nfc.tech.NfcA' java_import 'android.nfc.tech.NfcB' java_import 'android.nfc.tech.NfcF' java_import 'android.nfc.tech.IsoDep' java_import 'android.nfc.tech.MifareUltralight' class NfcReadActivity def onCreate(bundle) super set_title 'NFC Reader Sample' intent = getIntent case intent.getAction when NfcAdapter::ACTION_TECH_DISCOVERED || NfcAdapter::ACTION_TAG_DISCOVERED tag = intent.getParcelableExtra(NfcAdapter::EXTRA_TAG) view = linear_layout orientation: :vertical do @idm_view = text_view text: tag.nil? ? 'No IDm' : to_hex_string(tag.getId), width: :match_parent, gravity: :center, text_size: 48.0 @tag_info_view = text_view text: tag.nil? ? '' : tag.toString # getTechList.joinだとエラーになるため、to_aryを経由する @tech_list_view = text_view text: tag.nil? ? '' : tag.getTechList.to_ary.join(',') end self.content_view = view tech_view = create_tech_view(@tech_list_view.text, tag) view.addView tech_view unless tech_view.nil? end end def create_tech_view(tech_list, tag) view = linear_layout orientation: :vertical do @option_view = text_view text: add_delimiter + '以降はNFCタイプごとの項目' + add_delimiter end tech_list.split(',').each do |tech| # いろいろ簡略化したことによりActivityへ集約するサンプル # その分、条件ごとにメソッドを作るという、あまり良くない書き方になってしまった case tech when 'android.nfc.tech.NfcA' tech_view = create_nfca_view(tag) when 'android.nfc.tech.NfcB' tech_view = create_nfcb_view(tag) when 'android.nfc.tech.NfcF' tech_view = create_nfcf_view(tag) when 'android.nfc.tech.IsoDep' tech_view = create_isodep_view(tag) when 'android.nfc.tech.MifareUltralight' tech_view = create_mifare_ultralight_view(tag) else next end view.addView(tech_view) end view end def create_nfca_view(tag) nfc = NfcA.get(tag) view = linear_layout orientation: :vertical do @atqa = text_view text: '[NFC-A] atqa: ' + to_hex_string(nfc.getAtqa) @sak = text_view text: '[NFC-A] sak: ' + nfc.getSak.to_s end end def create_nfcb_view(tag) nfc = NfcB.get(tag) view = linear_layout orientation: :vertical do @application_data = text_view text: '[NFC-B] application_data: ' + to_hex_string(nfc.getApplicationData) @protocol_info = text_view text: '[NFC-B] protocol_info: ' + to_hex_string(nfc.getProtocolInfo) end end def create_nfcf_view(tag) nfc = NfcF.get(tag) view = linear_layout orientation: :vertical do @manufacturer = text_view text: '[NFC-F] manufacturer: ' + to_hex_string(nfc.getManufacturer) @system_code = text_view text: '[NFC-F] system_code: ' + to_hex_string(nfc.getSystemCode) end end def create_isodep_view(tag) nfc = IsoDep.get(tag) view = linear_layout orientation: :vertical do @hi_layer_response = text_view text: '[ISO Dep] hi_layer_response: ' + to_hex_string(nfc.getHiLayerResponse) @historical_bytes = text_view text: '[ISO Dep] historical_bytes: ' + to_hex_string(nfc.getHistoricalBytes) end end def create_mifare_ultralight_view(tag) nfc = MifareUltralight.get(tag) view = linear_layout orientation: :vertical do @mifare_ultralight = text_view text: '[Mifare Ultralight] mifare_ultralight_type: ' + nfc.getType.to_s end end def to_hex_string(bytes) bytes.nil? ? '' : bytes.map{ |byte| "%02X" % (byte & 0xff) }.join end def add_delimiter '-' * 6 end end
AndroidManifest.xml、nfc_tech_filter.xml、nfc_read_activity.rbについては、Gistにも上げてあります。
RubotoでNFCを読み込むサンプル · GitHub
公式情報
*1:認識が誤っていたら、すみません