NfcFeliCaTagFragmentを作る

リファレンスドキュメントにもあるが、Fragmentは別にUIを持つ必要は無くActivityで共通に使える処理自体をコンポーネント化するのに使えそうなのだ。
[android][sdk]UIを持たないFragment - Kazzzの日記

ということでサンプルを考えてみた。

最も手軽ですぐに使えそうなものとして拙作の"nfc-felica"におけるアプリケーション"NFCFeliCaReader"はメインのActivityにFeliCa/FeliCaLiteのタグを読み込む処理が書かれているが、これをFragmentとして分離してみることにする。上手くいけばその後他の種類のNFCタグにも対応できるように派生のFragmentを用意することにする。※

クラスの仕様
    • 自身でAndroid 2.3.3(4)のNFC-Fのタグをハンドリングする
    • FeliCa及びFeliCaLiteのタグデータを読込んでカプセル化したクラスを生成する
    • タグを検知した場合には単純なイベントとしてイベントリスナに通知する
    • Android 2.3.3から導入された"Foreground Dispatch"を制御する

Fragmentの利点はActivityに動的に追加/削除できること、Activityとライフサイクルを共有できることであり、ライフサイクルイベント(onCreate/onDestroy/onStop/onResume/onPause)をActivityと同期することができる。

NFCの"Foreground Dispatch"はActivityのResume時とPause時にそれぞれ有効/無効にするので、Fragmentで制御することができると踏んだわけだ。

なお、FeliCaとFeliCaLiteを別にタグとして捉えることもできるが、NFC-Fという分類では同じ中に含まれるのでNfcTagクラスとしては分離しないこととした。ただしデータクラスは別になっているので内部にFeliCaLiteなのかの判定メソッドを入れることにした。

NfcFeliCaTagFragment.java
package net.kazzz;

import java.util.ArrayList;

import net.kazzz.felica.FeliCaException;
import net.kazzz.felica.FeliCaLiteTag;
import net.kazzz.felica.FeliCaTag;
import net.kazzz.felica.lib.FeliCaLib;
import net.kazzz.felica.lib.FeliCaLib.IDm;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NfcAdapter;
import android.nfc.tech.NfcF;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.util.Log;

public class NfcFeliCaTagFragment extends Fragment {
    public static final String TAG = "NfcFeliCaTagFragment";
    private String _techLists;
    private IntentFilter _filters;
    private Parcelable _nfcTag;

    private ArrayList> _listnerList = 
            new ArrayList>(); 

    public static interface INfcTagListener {
        void onTagDiscovered(Intent intent, Parcelable nfcTag, F fragment);
    }
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    
        // FeliCa及びFeliCaLiteは NFC-F のみ
        _techLists = new String { new String { NfcF.class.getName() } };
        IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        try {
            ndef.addDataType("*/*");
        } catch (MalformedMimeTypeException e) {
            throw new RuntimeException("fail", e);
        }
        _filters = new IntentFilter[] {ndef};

    }
    /**
     * インテントを捕捉する
     * @param intent Activityで捕捉したインテントがセットされます
     */
    public void onNewIntent(Intent intent) {
        String action = intent.getAction();
        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
            _nfcTag = intent.getParcelableExtra("android.nfc.extra.TAG");
            if ( _nfcTag != null ) {
                Log.d(TAG, "** nfcTag = " + _nfcTag.toString() );
                for ( INfcTagListener listener : _listnerList ) {
                    //リスナに通知
                    listener.onTagDiscovered(intent, _nfcTag, this);
                }
            }
        }    
    }

    @Override
    public void onPause() {
        super.onPause();
        
        //foregrandDispathch無効
        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this.getActivity());
        adapter.disableForegroundDispatch(this.getActivity());
    }

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

        //foregrandDispathch有効
        Activity a = this.getActivity();
        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(a);
        PendingIntent pendingIntent = PendingIntent.getActivity(a, 0,
                new Intent(a, a.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

        adapter.enableForegroundDispatch(this.getActivity()
                , pendingIntent, _filters, _techLists);
    }
    public boolean isFeliCaLite() throws FeliCaException {
        FeliCaTag f = new FeliCaTag(_nfcTag); 
        IDm idm = f.pollingAndGetIDm(FeliCaLib.SYSTEMCODE_FELICA_LITE);
        return idm != null;
    }
    public FeliCaTag createFeliCaTag() {
        return new FeliCaTag(_nfcTag);
    }
    public FeliCaLiteTag createFeliCaLiteTag() {
        return new FeliCaLiteTag(_nfcTag);
    }
    public void addNfcTagListener(INfcTagListener listener) {
        _listnerList.add(listener);
    }
    public void removeNfcTagListener(INfcTagListener listener) {
        _listnerList.remove(listener);
    }
    public Parcelable getNfcTag() {
        return _nfcTag;
    }
    public void set_nfcTag(Parcelable _nfcTag) {
        this._nfcTag = _nfcTag;
    }
}

NFCタグの処理の殆どをこのFragmentに逃がすことによって、Activity側の処理をシンプルにするのが狙い。インテントフィルタとNFCのTechListを設定することでマニフェストに頼らず処理するタグを指定できる。 タグ検知の処理はシンプルなイベント"onTagDiscovered"で通知する。

次回はこのFragmentを組み込んで使うAtivityのコードを書いてみよう。


※全てのタグがNDEFを使えればタグを分類する必要が無い。理想ではその通りだが現状全てNDEFで対応できる訳ではない。