TransWikia.com

Androidでボタンを押している間だけNFCタグのデータを読み込ませたい

スタック・オーバーフロー Asked on November 22, 2021

Androidで画面上にボタンがあり、そのボタンを押している間だけNFCタグを読み込むようにしたいです

下記のコードで実現しようとしているのですが、ボタンを押している間にNFCカードを近づけても表示が変わらず、うまくいきません。
どうやらNFCタグが近くにあってもonNewIntentが発生しないようです。
なぜでしょうか?

読み取り用のボタンを押すと"Now Reading A tag"は表示されるのでOnTouchは補足できているようです。

Pixel3(日本モデルのためFelicaカード対応)上のAndroid 10 (API 29)で動かそうとしています


Update

enableForegroundDispatchのtechlistを明示的に指定してみましたがうまく行きませんでした。

techlist = arrayOf(
            arrayOf(android.nfc.tech.NfcA::class.java.name),
            arrayOf(android.nfc.tech.NfcB::class.java.name),
            arrayOf(android.nfc.tech.IsoDep::class.java.name),
            arrayOf(android.nfc.tech.MifareClassic::class.java.name),
            arrayOf(android.nfc.tech.NfcV::class.java.name),
            arrayOf(android.nfc.tech.NfcF::class.java.name),
            arrayOf(android.nfc.tech.NdefFormatable::class.java.name),
            arrayOf(android.nfc.tech.MifareUltralight::class.java.name)
        )

Update 2

Kouki. WさんがおっしゃるようにenableForegroundDispatchでは実現が難しそうだったため、enableReaderModeに変えてみましたがやはりうまくできません。

nfc_adapt!!.enableReaderMode(this, {t ->
                        tview!!.text = t.toString()
                        tview!!.setBackgroundColor(Color.YELLOW)
                        tview!!.setTextColor(Color.rgb(0,0,0))
                        nfc_adapt!!.disableReaderMode(this)
                        read_button!!.isEnabled = false // disable read button until user push the delete button
                        delete_button!!.isEnabled = true

                    },(
                            NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK or
                            NfcAdapter.FLAG_READER_NFC_A or
                            NfcAdapter.FLAG_READER_NFC_B or
                            NfcAdapter.FLAG_READER_NFC_BARCODE or
                            NfcAdapter.FLAG_READER_NFC_F or
                            NfcAdapter.FLAG_READER_NFC_V
                            ),
                    null)

Update 2おわり


AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.youtube_dler">
    <uses-permission android:name="android.permission.NFC"></uses-permission>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MainActivity.kt:

class MainActivity : AppCompatActivity() {
    var pendintent: PendingIntent? = null
    var nfc_adapt: NfcAdapter? = null
    var iFilter: IntentFilter? = null
    var tview: TextView? = null
    var read_button:  Button? = null // 読み込むときに押すボタン
    var delete_button: Button? = null // TextView(tview)の内容を消すときに押すボタン
    @SuppressLint("ClickableViewAccessibility")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tview = findViewById(R.id.textView) // 表示用のTextView
        nfc_adapt = NfcAdapter.getDefaultAdapter(applicationContext)
        pendintent = PendingIntent.getActivity(this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0
        )
        iFilter = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
        val iFilters = arrayOf<IntentFilter>(iFilter!!)

        read_button = findViewById(R.id.button)
        delete_button = findViewById(R.id.button2)
        delete_button!!.isEnabled = false

        read_button!!.setOnTouchListener(fun(v: View, m: MotionEvent) : Boolean {
            when (m.action) {
                MotionEvent.ACTION_DOWN -> {
                    tview!!.setBackgroundColor(Color.rgb(0,0,0))
                    tview!!.setTextColor(Color.rgb(0, 255, 0))
                    tview!!.text = "Now Reading A Tag"
                    Log.d(null, "ACTION UPPED")
                    nfc_adapt!!.enableForegroundDispatch(this, pendintent,iFilters, null)
                    return false
                }
                MotionEvent.ACTION_UP -> {
                    nfc_adapt!!.disableForegroundDispatch(this)
                    tview!!.text = ""
                    return false
                }
            }
            return false
        })

        // Delete contents of TextView

        delete_button!!.setOnClickListener {
            tview!!.text = ""
            tview!!.setBackgroundColor(Color.rgb(0, 0, 0))
            tview!!.setTextColor(Color.rgb(0, 255, 0))
            read_button!!.isEnabled = true
            it.isEnabled = false
        }

    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        val action: String? = intent.action
        if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
            var nfc_tag_array = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_TAG)
            if (nfc_tag_array == null) {
                tview?.setBackgroundColor(Color.YELLOW)
                tview?.setTextColor(Color.rgb(0,0,0))
                tview?.text =  "read a tag but does not contain tag data"
                return
            }
            val t = nfc_tag_array[0] as Tag?
            if (t == null) {
                tview!!.text = "Read a tag but it a null"
            } else {
                tview!!.text = t.toString() // TAGの内容(データではない)を文字列で表示する
            }
            tview!!.setBackgroundColor(Color.YELLOW)
            tview!!.setTextColor(Color.rgb(0,0,0))
            nfc_adapt!!.disableForegroundDispatch(this)
            read_button!!.isEnabled = false // 読んだら一旦読み込みボタンを無効にしてdelete_buttonが押されたら再度読み込みできるようにする
            delete_button!!.isEnabled = true


        }
        else {
            // nothing to do
        }
    }

}

One Answer

こんにちは、少し古い端末(Android 4.1.2/API Level16)で頂いたコードを拝見/確認させていただきました。

enableForegrondDipatchがうまく動作しないとのことですが、こちらドキュメントを確認すると以下のようにあります。

This method must be called from the main thread, and only when the activity is in the foreground (resumed).

このメソッドですが、フォアグラウンドになっているアプリを最優先にしてNFCカードのイベントに反応させるために用います。(当然OS内の他のアプリやサービスもNFCはスキャンしてるのでスキャンを止めることはできません)。そのためライフサイクルイベント内のonResumeで開始、onPauseで停止という用法が正しいです。実際そのように実装した場合は当方では問題なく動作しています。

ボタンを押した時のみなど一時的に反応させたい理由があるのであれば、ボタンで読み取り用のアクティビティ/フラグメントを起動、読み取り後、前のページに戻すなど、別の実装方法を検討されてはいかがでしょうか?

スレッド関係で、頑張れば動かせる可能性はありますが、あまり推奨されないやりかたに思えます。

https://developer.android.com/guide/topics/connectivity/nfc/advanced-nfc?hl=ja#foreground-dispatch

https://developer.android.com/reference/android/nfc/NfcAdapter#enableForegroundDispatch(android.app.Activity,%20android.app.PendingIntent,%20android.content.IntentFilter[],%20java.lang.String[][])

追記

NfcAdapter.enableReadMode (Android 4.4から実装) を利用してonResume以外のタイミングからNFCのActivityによる読み取りが行えたとの旨コメントをいただきました。

https://qiita.com/zaburo/items/6a34dfd8f87d7ffba56a

Answered by Kouki.W on November 22, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP