【Kotlin】スマホ単体でFelicaのデータを読み取る

#はじめに
スマホ単体でSuicaなどのICカードを読み取ります。Android4.4以降で追加された方式で記述します。
まずはIDが読み取れるかどうかを簡単なサンプルで試して見ます。

#準備
FelicaやNFCが読み取れるようにmanifestファイルに以下を追加します。

```manifest.xml
    <uses-permission android:name="android.permission.NFC" />
    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />
```

#スマホ側の準備
設定でNFCが使えるようにします。

#注意点
ICカードを読み取る部分がスマホのどこにあるのかは機種で異なる可能性があります。
こちらではスマホの画面とカメラの間ぐらいに置くと反応しました。
onResumeとonPauseで読み取りを開始したり中止したりしていますが、デバッグ中にonPauseが呼ばれずに終了すると、ずっとアダプターを持ったままになるかもしれません。
また、デバッグ実行を繰り返しているとデバッグが出来なくなったり、AndroidStudioにログが表示されなくなったりすることもあります。
その場合はスマホを再起動して下さい。
各種ICカードやNFCタグはこれで読み取れるようです。ガラケーの場合も読み取れますが、スマホの場合は「おサイフケータイ」などのFelicaを利用できる場合には読み取れるようです（未確認）。読み取れない場合はIDとして 0x08 0x00 0x00 0x00の4バイトが返ってきました。 

#ソース
Felica読み取りクラスを別ファイルに出来るようにしていますが、ソースはそれをあわせたものにしています。
書いている途中にコンパイラが推奨する方式に書き直しているため訳のわからない部分もあります。

読み取ることが出来たら**TextView**のID**tvMain**にログにNfcFのIDを出すようにしています。
ICカードに読み書き可能であればonConectedがログに残ります。

```MainActivity.kt
class MainActivity : AppCompatActivity() {
    val felica = FelicaReader(this, this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        felica.setListener(felicaListener)
    }
    override fun onResume() {
        super.onResume()
        felica.start()
    }
    override fun onPause() {
        super.onPause()
        felica.stop()
    }

    private val felicaListener = object : FelicaReaderInterface{
        override fun onReadTag(tag : Tag) {                     // データ受信イベント
            val tvMain = findViewById<TextView>(R.id.tvMain)
            val idm : ByteArray = tag.id
            tag.techList
            tvMain.text = byteToHex(idm)
            Log.d("Sample","${byteToHex(idm)}")
        }

        override fun onConnect() {
            Log.d("Sample","onConnected")
        }
    }

    private fun byteToHex(b : ByteArray) : String{
        var s : String = ""
        for (i in 0..b.size-1){
            s += "[%02X]".format(b[i])
        }
        return s
    }
}
interface FelicaReaderInterface : FelicaReader.Listener {
    fun onReadTag(tag : Tag)                        // タグ受信イベント
    fun onConnect()
}

class FelicaReader(private val context: Context,private val activity : Activity) : android.os.Handler() {
    private var nfcmanager : NfcManager? = null
    private var nfcadapter : NfcAdapter? = null
    private var callback : CustomReaderCallback? = null

    private var listener: FelicaReaderInterface? = null
    interface Listener {}

    fun start(){
        callback = CustomReaderCallback()
        callback?.setHandler(this)
        nfcmanager = context.getSystemService(Context.NFC_SERVICE) as NfcManager?
        nfcadapter = nfcmanager!!.getDefaultAdapter()
        nfcadapter!!.enableReaderMode(activity,callback
            ,NfcAdapter.FLAG_READER_NFC_F or
                    NfcAdapter.FLAG_READER_NFC_A or
                    NfcAdapter.FLAG_READER_NFC_B or
                    NfcAdapter.FLAG_READER_NFC_V or
            NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,null)
    }
    fun stop(){
        nfcadapter!!.disableReaderMode(activity)
        callback = null
    }

    override fun handleMessage(msg: Message) {                  // コールバックからのメッセージクラス
        if (msg.arg1 == 1){                                     // 読み取り終了
            listener?.onReadTag(msg.obj as Tag)                 // 拡張用
        }
        if (msg.arg1 == 2){                                     // 読み取り終了
            listener?.onConnect()                               // 拡張用
        }
    }
    
    fun setListener(listener: FelicaReader.Listener?) {         // イベント受け取り先を設定
        if (listener is FelicaReaderInterface) {
            this.listener = listener as FelicaReaderInterface
        }
    }
    
    private class CustomReaderCallback : ReaderCallback {
        private var handler : android.os.Handler? = null
        override fun onTagDiscovered(tag: Tag) {
            Log.d("Sample", tag.id.toString())
            val msg = Message.obtain()
            msg.arg1 = 1
            msg.obj = tag
            if (handler != null) handler?.sendMessage(msg)
            val nfc : NfcF = NfcF.get(tag) ?: return
            try {
                nfc.connect()
                //nfc.transceive()
                nfc.close()
                msg.arg1 = 2
                msg.obj = tag
                if (handler != null) handler?.sendMessage(msg)
            }catch (e : Exception){
                nfc.close()
            }
        }
        fun setHandler(handler  : android.os.Handler){
            this.handler = handler
        }
    }
}
```

#参考
下記のサイトを参考にしています。
[Android 4.4で追加されたNFC reader mode](https://qiita.com/androhi/items/e20b9ac7f7173f12b484)