Detailed summary of Android NFC development and analysis of NFC card reading examples


foreword

The development of the Android software layer of the Internet of Things enterprise must have the step of communicating with the hardware. Whether it is logistics, security, medical care, education and other industries, except for the method of directly calling the SDK, the technology directly related to hardware communication is only the most commonly used one. Several types, namely serial communication, USB communication, Bluetooth, infrared, NFC, etc.

Serial port and USB communication technologies have been introduced in the previous articles [Summary of Serial Communication Development and Example Analysis , Summary of USB Communication Development and Example Analysis of Thermal Printer Development] . This article records the technology related to NFC development and the detours it has taken, hoping to help people in need while helping themselves to consolidate their knowledge.

In addition to the development summary, the case of this article is NFC reading M1 card.
In fact, for the requirement of card reading, our research and development is generally divided into calling the SDK and using the NFC function that comes with android
.


1. What is NFC?

NFC is currently a mainstream configuration hardware item for Android mobile phones. The full name is Near Field Communication, and the middle is near field communication, also known as short-range wireless communication technology. Devices using NFC technology (such as mobile phones) can exchange data when they are close to each other, which evolved from the integration of non-contact radio frequency identification (RFID) and interconnection technologies.

2. Basic knowledge

What you must know before starting development

1. What is NDEF?

Data stored in NFC tags can be written in a variety of formats, but many Android framework APIs are based on an NFC Forum standard called NDEF (NFC Data Exchange Format). .

Simply put, it is a common data format standard

2. Operation mode of NFC technology

(1) Reader/Writer Mode: Supports NFC devices to read and/or write passive NFC tags and stickers.
(2) Peer-to-peer mode: supports NFC devices to exchange data with other NFC peer devices; Android Beam uses this mode of operation.
(3) Card emulation mode: support the NFC device itself as an NFC card. The emulated NFC card can then be accessed by an external NFC reader such as an NFC point-of-sale.

The main use of this case is to read and write cards , which is the normal demand for reading and writing cards. Later, if you have the opportunity to come into contact with the needs of point-to-point and card simulation, you will make supplements in this article.

3. The technical type of the label

Usually, each category of tags (cards) supports one or more technologies, and
the corresponding relationship is as follows

technology describe card type
NfcA Provides access to NFC-A (ISO 14443-3A) capabilities and I/O operations. M1 card
NfcB Provides NFC-B (ISO 14443-3B) capabilities and access to I/O operations.
NFC Provides access to NFC-F (JIS 6319-4) capabilities and I/O operations.
NfcV Provides access to NFC-V (ISO 15693) capabilities and I/O operations. 15693 cards
IsoDep Provides access to ISO-DEP (ISO 14443-4) capabilities and I/O operations. CPU card
Ndef Provides access to data and operations on NFC tags that have been formatted as NDEF.
NdefFormatable A tag that provides a formattable that may be formatted as NDEF.
MifareClassic If this Android device supports MIFARE, it provides access to MIFARE Classic performance and I/O operations. m1 card
MifareUltralight If this Android device supports MIFARE, it provides access to MIFARE for ultra-light performance and I/O operations.

As shown in the figure below, this is the information of the NFC tag displayed by the Demo.
The part circled by me is the technology supported by this NFC tag, which will be used when parsing the data later. After getting these, you can use the corresponding class to parse the tag data.
insert image description here
During development, we have corresponding methods to obtain the parsing methods supported by this tag, which I will introduce later.

4. Classification of implementation methods

(1) Manifest registration method: This method is mainly to configure filters under the activity corresponding to the Manifest file to respond to different types of NFC Actions. Using this method, when swiping a card, if there are multiple applications in the mobile phone that have the NFC implementation, the system will pop up a list of applications that can respond to NFC events for the user to choose, and the user needs to click on the target application to respond to this NFC card swiping event.

(2) The foreground response method does not require the Manifest to reconfigure the filter, and directly uses the foreground activity to capture NFC events and respond.

The differences are as follows:
Different response methods: NFC events registered in the Manifest are distributed by the system, and an application needs to be selected to respond to the event. The
       foreground response method is captured by the foreground activity to respond to NFC events.
Different priorities: the priority of the foreground response method is higher than that registered in the Manifest. Mode
     (if multiple Manifest-registered Apps and an App in the foreground capture mode are installed, the one with the highest priority after swiping the card is captured in the foreground, if the corresponding App in the foreground is not opened, a list will pop up for the user to choose to register in the Manifest Qualified App)

The first type is more suitable for the APP that needs to be called by swiping the card, and the device does not have multiple IoT devices that respond to the NFC tag program (because the card package APP, WeChat, etc. When using the App, the operation will be cumbersome)

The second type is more suitable for card reading in the front-end interface, and when there are multiple applications,
choose the appropriate implementation method according to your own project requirements.

5. Process

First of all, under the premise that the device supports NFC authorization, no matter which method is used, the card is swiped first, and the system distributes the corresponding Activity to get the Tag or the foreground Activity captures the Tag. Then parse the data according to the technology supported by this tag.

3. Get the label content

1. Check the environment

First add permissions in Manifest

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

Determine whether to support NFC, and open the function

 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
        if (null == adapter) {
    
    
            Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show();
        } else if (!adapter.isEnabled()) {
    
    
            Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
            // 根据包名打开对应的设置界面
            startActivity(intent);
        }

 

2. Get NFC tag

2.1 Obtain Tag by registering in Manifest

Here are three kinds of intent filters.
The characteristics of this method are introduced in the previous [Classification of Implementation Modes]. This method of distribution by the tag scheduling system needs to define a fixed intent filter in the Manifest. The tag scheduling system defines three Intents, which are listed as follows in order of priority from high to low:

ACTION_NDEF_DISCOVERED : If a tag containing an NDEF payload is scanned and its type is recognized, use this Intent to start the Activity. This is the Intent with the highest priority. The label scheduling system will try to use this Intent to start the Activity as much as possible, and will try to use other Intents when it fails.

ACTION_TECH_DISCOVERED : If there is no Activity registered to handle the ACTION_NDEF_DISCOVERED Intent, the tab dispatch system will attempt to use this Intent to launch the application. Additionally, if a scanned tag contains NDEF data that cannot be mapped to a MIME type or URI, or if the tag contains no NDEF data but uses a known tag technology, then this Intent is also fired directly (without first firing ACTION_NDEF_DISCOVERED ) .

ACTION_TAG_DISCOVERED : If there is no Activity that handles ACTION_NDEF_DISCOVERED or ACTION_TECH_DISCOVERED Intent, use this Intent to start the Activity.

Add an intent filter
This is the first simplest and highest priority one, which already meets the needs

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            </intent-filter>
        </activity>

Of course, you can also choose the second

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
            </intent-filter>
            <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/filter_nfc" />
        </activity>

The filter_nfc
file is what TECH_DISCOVERED needs to configure. Among them, the logic or relationship between tech-lists, and the logic and relationship between tech, are similar to the principle and use of techLists in Scheme 2.

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
 
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
        <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>
</resources>

last one left

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

It doesn't make much sense that it's generally useless.

Then you can get the label in the onCreate method of the corresponding Activity

class NfcActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_nfc)
        val adapter = NfcAdapter.getDefaultAdapter(this)
        if (null == adapter) {
    
    
            Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show()
        } else if (!adapter.isEnabled) {
    
    
            val intent = Intent(Settings.ACTION_NFC_SETTINGS)
            // 根据包名打开对应的设置界面
            startActivity(intent)
        }
        val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
    }
}

2.1 Obtain Tag by way of foreground Activity capture

class MainActivity : AppCompatActivity() {
    
    
    var mNfcAdapter: NfcAdapter? = null
    var pIntent: PendingIntent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
       super.onCreate(savedInstanceState)
       initNfc()
 

    }
    private fun initNfc() {
    
    
        mNfcAdapter = M1CardUtils.isNfcAble(this)
        pIntent = PendingIntent.getActivity(this, 0,  
        //在Manifest里或者这里设置当前activity启动模式,否则每次响应NFC事件,activity会重复创建
        Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
    }
    
    override fun onResume() {
    
    
        super.onResume()
        mNfcAdapter?.let {
    
    
            val ndef = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
            val tag = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
            val tech = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
            val filters = arrayOf(ndef, tag, tech)
            val techList = arrayOf(
                arrayOf(
                    "android.nfc.tech.Ndef",
                    "android.nfc.tech.NfcA",
                    "android.nfc.tech.NfcB",
                    "android.nfc.tech.NfcF",
                    "android.nfc.tech.NfcV",
                    "android.nfc.tech.NdefFormatable",
                    "android.nfc.tech.MifareClassic",
                    "android.nfc.tech.MifareUltralight",
                    "android.nfc.tech.NfcBarcode"
                )
            )
            it.enableForegroundDispatch(this, pIntent, filters, techList)
            XLog.d("开始捕获NFC数据")
        }
    }
    override fun onPause() {
    
    
        super.onPause()
        mNfcAdapter?.disableForegroundDispatch(this)
    }
    override fun onNewIntent(intent: Intent?) {
    
    
        super.onNewIntent(intent)
        //这里必须setIntent,set  NFC事件响应后的intent才能拿到数据
        setIntent(intent)
        val tag = getIntent().getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
        //M1CardUtils 我后面会贴出来的
        if (M1CardUtils.isMifareClassic(tag)) {
    
    
            try {
    
    
                val reader = M1CardUtils.readCard(tag)
                XLog.d("读卡内容:$reader")
                val data = reader.split("|")
            } catch (e: IOException) {
    
    
                e.printStackTrace()
            }
        }
    }
}

Fourth, analyze the label data

No matter which method is used, when we get the TAG tag, the parsing method is the same, and we need to choose the corresponding parsing method according to different card types. As shown in the figure, we can get the
insert image description here
information of the card. Corresponding to:
supported technology type
MifareClassic type
sector storage space
number of sectors number of
blocks in a sector

1. M1 card analysis

Let me talk about the basics here. Whether it is NFC or card reader module reading, the analysis process is to find the card first, then verify the password of the sector, and get the data of the sector. For example, if the data to be read is known to be in sector 2, then search When post-card verification, pass the sector number to be verified, the password of the sector, and the verification password type A/B of the sector. After the verification is passed, the data can be read.


import android.app.Activity
import android.nfc.NfcAdapter
import android.nfc.Tag
import com.hjq.toast.ToastUtils
import kotlin.Throws
import android.nfc.tech.MifareClassic
import com.elvishew.xlog.XLog
import java.io.IOException
import java.lang.StringBuilder
import java.nio.charset.Charset


object M1CardUtils {
    
    
    /**
     * 判断是否支持NFC
     *
     * @return
     */
    fun isNfcAble(mContext: Activity?): NfcAdapter? {
    
    
        val mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext)
        if (mNfcAdapter == null) {
    
    
            ToastUtils.show("设备不支持NFC!")
        }
        if (!mNfcAdapter!!.isEnabled) {
    
    
            ToastUtils.show("请在系统设置中先启用NFC功能!")
        }
        return mNfcAdapter
    }

    /**
     * 监测是否支持MifareClassic
     *
     * @param tag
     * @return
     */
    fun isMifareClassic(tag: Tag): Boolean {
    
    
        val techList = tag.techList
        var haveMifareUltralight = false
        for (tech in techList) {
    
    
            if (tech.contains("MifareClassic")) {
    
    
                haveMifareUltralight = true
                break
            }
        }
        if (!haveMifareUltralight) {
    
    
            ToastUtils.show("不支持MifareClassic")
            return false
        }
        return true
    }

    /**
     * 读取卡片信息
     *
     * @return
     */
    @Throws(IOException::class)
    fun readCard(tag: Tag?): String {
    
    
        val mifareClassic = MifareClassic.get(tag)
        return try {
    
    
            mifareClassic.connect()
            val metaInfo = StringBuilder()
            val gbk = Charset.forName("gbk")

            // 获取TAG中包含的扇区数
            val sectorCount = mifareClassic.sectorCount
            //            for (int j = 0; j < sectorCount; j++) {
    
    
            val bCount: Int //当前扇区的块数
            var bIndex: Int //当前扇区第一块
            if (m1Auth(mifareClassic, 2)) {
    
    
                bCount = mifareClassic.getBlockCountInSector(2)
                bIndex = mifareClassic.sectorToBlock(2)
                var length = 0
                for (i in 0 until bCount) {
    
    
                    val data = mifareClassic.readBlock(bIndex)
                    for (i1 in data.indices) {
    
    
                        if (data[i1] == 0.toByte()) {
    
    
                            length = i1
                        }
                    }
                    val dataString = String(data, 0, length, gbk).trim {
    
     it <= ' ' }
                    metaInfo.append(dataString)
                    bIndex++
                }
            } else {
    
    
                XLog.e("密码校验失败")
            }
            //            }
            metaInfo.toString()
        } catch (e: IOException) {
    
    
            throw IOException(e)
        } finally {
    
    
            try {
    
    
                mifareClassic.close()
            } catch (e: IOException) {
    
    
                throw IOException(e)
            }
        }
    }

    /**
     * 改写数据
     *
     * @param block
     * @param blockbyte
     */
    @Throws(IOException::class)
    fun writeBlock(tag: Tag?, block: Int, blockbyte: ByteArray?): Boolean {
    
    
        val mifareClassic = MifareClassic.get(tag)
        try {
    
    
            mifareClassic.connect()
            if (m1Auth(mifareClassic, block / 4)) {
    
    
                mifareClassic.writeBlock(block, blockbyte)
                XLog.e("writeBlock", "写入成功")
            } else {
    
    
                XLog.e("密码是", "没有找到密码")
                return false
            }
        } catch (e: IOException) {
    
    
            throw IOException(e)
        } finally {
    
    
            try {
    
    
                mifareClassic.close()
            } catch (e: IOException) {
    
    
                throw IOException(e)
            }
        }
        return true
    }

    /**
     * 密码校验
     *
     * @param mTag
     * @param position
     * @return
     * @throws IOException
     */
    @Throws(IOException::class)
    fun m1Auth(mTag: MifareClassic, position: Int): Boolean {
    
    
        if (mTag.authenticateSectorWithKeyA(position, MifareClassic.KEY_DEFAULT)) {
    
    
            return true
        } else if (mTag.authenticateSectorWithKeyB(position, MifareClassic.KEY_DEFAULT)) {
    
    
            return true
        }
        return false
    }


}

2. Analysis of iso15693 card

This is not used in this case, but M1 is needed, so this is not needed. This is a class encapsulated by other big guys for reference.

import android.nfc.tech.NfcV;
 
import com.haiheng.core.util.ByteUtils;
 
import java.io.IOException;
 
/**
 * NfcV(ISO 15693)读写操作
 *   用法
 *  NfcV mNfcV = NfcV.get(tag);
 *  mNfcV.connect();
 * <p>
 *  NfcVUtils mNfcVutil = new NfcVUtils(mNfcV);
 *  取得UID
 *  mNfcVutil.getUID();
 *  读取block在1位置的内容
 *  mNfcVutil.readOneBlock(1);
 *  从位置7开始读2个block的内容
 *  mNfcVutil.readBlocks(7, 2);
 *  取得block的个数
 *  mNfcVutil.getBlockNumber();
 *  取得1个block的长度
 *  mNfcVutil.getOneBlockSize();
 *  往位置1的block写内容
 *  mNfcVutil.writeBlock(1, new byte[]{0, 0, 0, 0})
 *
 * @author Kelly
 * @version 1.0.0
 * @filename NfcVUtils.java
 * @time 2018/10/30 10:29
 * @copyright(C) 2018 song
 */
public class NfcVUtils {
    
    
    private NfcV mNfcV;
    /**
     * UID数组行式
     */
    private byte[] ID;
    private String UID;
    private String DSFID;
    private String AFI;
    /**
     * block的个数
     */
    private int blockNumber;
    /**
     * 一个block长度
     */
    private int oneBlockSize;
    /**
     * 信息
     */
    private byte[] infoRmation;
 
    /**
     *  * 初始化
     *  * @param mNfcV NfcV对象
     *  * @throws IOException
     *  
     */
    public NfcVUtils(NfcV mNfcV) throws IOException {
    
    
        this.mNfcV = mNfcV;
        ID = this.mNfcV.getTag().getId();
        byte[] uid = new byte[ID.length];
        int j = 0;
        for (int i = ID.length - 1; i >= 0; i--) {
    
    
            uid[j] = ID[i];
            j++;
        }
        this.UID = ByteUtils.byteArrToHexString(uid);
        getInfoRmation();
    }
 
    public String getUID() {
    
    
        return UID;
    }
 
    /**
     *  * 取得标签信息 
     *  
     */
    private byte[] getInfoRmation() throws IOException {
    
    
        byte[] cmd = new byte[10];
        cmd[0] = (byte) 0x22; // flag
        cmd[1] = (byte) 0x2B; // command
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        infoRmation = mNfcV.transceive(cmd);
        blockNumber = infoRmation[12];
        oneBlockSize = infoRmation[13];
        AFI = ByteUtils.byteArrToHexString(new byte[]{
    
    infoRmation[11]});
        DSFID = ByteUtils.byteArrToHexString(new byte[]{
    
    infoRmation[10]});
        return infoRmation;
    }
 
    public String getDSFID() {
    
    
        return DSFID;
    }
 
    public String getAFI() {
    
    
        return AFI;
    }
 
    public int getBlockNumber() {
    
    
        return blockNumber + 1;
    }
 
    public int getOneBlockSize() {
    
    
        return oneBlockSize + 1;
    }
 
    /**
     *  * 读取一个位置在position的block
     *  * @param position 要读取的block位置
     *  * @return 返回内容字符串
     *  * @throws IOException
     *  
     */
    public String readOneBlock(int position) throws IOException {
    
    
        byte cmd[] = new byte[11];
        cmd[0] = (byte) 0x22;
        cmd[1] = (byte) 0x20;
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        cmd[10] = (byte) position;
        byte res[] = mNfcV.transceive(cmd);
        if (res[0] == 0x00) {
    
    
            byte block[] = new byte[res.length - 1];
            System.arraycopy(res, 1, block, 0, res.length - 1);
            return ByteUtils.byteArrToHexString(block);
        }
        return null;
    }
 
    /**
     *  * 读取从begin开始end个block
     *  * begin + count 不能超过blockNumber
     *  * @param begin block开始位置
     *  * @param count 读取block数量
     *  * @return 返回内容字符串
     *  * @throws IOException
     *  
     */
    public String readBlocks(int begin, int count) throws IOException {
    
    
        if ((begin + count) > blockNumber) {
    
    
            count = blockNumber - begin;
        }
        StringBuffer data = new StringBuffer();
        for (int i = begin; i < count + begin; i++) {
    
    
            data.append(readOneBlock(i));
        }
        return data.toString();
    }
 
 
    /**
     *  * 将数据写入到block,
     *  * @param position 要写内容的block位置
     *  * @param data 要写的内容,必须长度为blockOneSize
     *  * @return false为写入失败,true为写入成功
     *  * @throws IOException 
     *  
     */
    public boolean writeBlock(int position, byte[] data) throws IOException {
    
    
        byte cmd[] = new byte[15];
        cmd[0] = (byte) 0x22;
        cmd[1] = (byte) 0x21;
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        //block
        cmd[10] = (byte) position;
        //value
        System.arraycopy(data, 0, cmd, 11, data.length);
        byte[] rsp = mNfcV.transceive(cmd);
        if (rsp[0] == 0x00)
            return true;
        return false;
    }
}

Summarize

The above is what I want to talk about today. If there are any mistakes or improvements in the article, welcome to add and correct. This article only introduces the use of NFC and the reading and analysis scenarios of M1 cards. The history of NFC, card types, and Intent filter types are detailed. You can refer to more documents for descriptions, other usage scenarios, etc. Here are a few helpful articles that I have seen, and you are welcome to make more references.

NFC various card types, differences, history introduction
https://zhuanlan.zhihu.com/p/344426747
Chinese description of various official materials
https://blog.csdn.net/u013164293/article/details/124474247?spm=1001.2014 .3001.5506

Guess you like

Origin blog.csdn.net/qq_39178733/article/details/129850034