Android NFC(一) M1卡讀寫

原料:Android 帶NFC功能手機(jī)、M1卡

參考官方文檔: https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf

怕你們沒耐心先上demo

gayhub: https://github.com/soulListener/NFCDemo

1.在AndroidManifest中添加權(quán)限控制

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

activity中需要添加

        <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
            android:resource="@xml/tag_type" />

需要使用前臺(tái)調(diào)度的話,就需要在<intent-filter>中添加需要過濾的標(biāo)簽,這里使用自定義過濾

<action android:name="android.nfc.action.TECH_DISCOVERED"/>

2.開始編寫代碼

最主要的是在OnNewIntent中添加對卡片控制的代碼。主要的流程大概是這樣滴:

1.判斷是否支持NFC、是否打開NFC
2.通過Intent獲取卡類型,進(jìn)行類型判斷
3.獲得Adapter對象、獲得Tag對象、獲得MifareClassic對象
4.讀寫卡操作分為兩個(gè)步驟:密碼校驗(yàn)、讀寫卡。只有對當(dāng)前要讀寫的塊數(shù)據(jù)所在的扇區(qū)進(jìn)行密碼校驗(yàn)之后才能進(jìn)行接下來的讀寫操作

/**
 * @author kuan
 * Created on 2019/2/25.
 * @description
 */
public class NfcActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mNfcAdapter = M1CardUtils.isNfcAble(this);
    M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
            getClass()), 0));
}

@Override
public void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    mNfcAdapter = M1CardUtils.isNfcAble(this);
    M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
            getClass()), 0));

    Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    M1CardUtils.isMifareClassic(tag,this);
    try {
        if (M1CardUtils.writeBlock(tag, 25,"9966332211445566".getBytes())){
            Log.e("onNewIntent","寫入成功");
        } else {
            Log.e("onNewIntent","寫入失敗");
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        M1CardUtils.readCard(tag);
    } catch (IOException e) {
        e.printStackTrace();
    }

    }

    @Override
    public void onPause() {
    super.onPause();
    if (mNfcAdapter != null) {
        mNfcAdapter.disableForegroundDispatch(this);
    }
    }
    @Override
    public void onResume() {
    super.onResume();
    if (mNfcAdapter != null) {
        mNfcAdapter.enableForegroundDispatch(this, M1CardUtils.getPendingIntent(),
                null, null);
    }
    }
    }

下面是抽離的工具類

/**
 * @author kuan
 * Created on 2019/2/26.
 * @description MifareClassic卡片讀寫工具類
 */
public class M1CardUtils {

private static PendingIntent pendingIntent;
public static PendingIntent getPendingIntent(){
    return pendingIntent;
}

public static void setPendingIntent(PendingIntent pendingIntent){
    M1CardUtils.pendingIntent = pendingIntent;
}

/**
 * 判斷是否支持NFC
 * @return
 */
public static NfcAdapter isNfcAble(Activity mContext){
    NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext);
    if (mNfcAdapter == null) {
        Toast.makeText(mContext, "設(shè)備不支持NFC!", Toast.LENGTH_LONG).show();
    }
    if (!mNfcAdapter.isEnabled()) {
        Toast.makeText(mContext, "請?jiān)谙到y(tǒng)設(shè)置中先啟用NFC功能!", Toast.LENGTH_LONG).show();
    }
    return mNfcAdapter;
}

/**
 * 監(jiān)測是否支持MifareClassic
 * @param tag
 * @param activity
 * @return
 */
public static boolean isMifareClassic(Tag tag,Activity activity){
    String[] techList = tag.getTechList();
    boolean haveMifareUltralight = false;
    for (String tech : techList) {
        if (tech.contains("MifareClassic")) {
            haveMifareUltralight = true;
            break;
        }
    }
    if (!haveMifareUltralight) {
        Toast.makeText(activity, "不支持MifareClassic", Toast.LENGTH_LONG).show();
        return false;
    }
    return true;
}

/**
 * 讀取卡片信息
 * @return
 */
public static String[][] readCard(Tag tag)  throws IOException{
    MifareClassic mifareClassic = MifareClassic.get(tag);
    try {
        mifareClassic.connect();
        String[][] metaInfo = new String[16][4];
        // 獲取TAG中包含的扇區(qū)數(shù)
        int sectorCount = mifareClassic.getSectorCount();
        for (int j = 0; j < sectorCount; j++) {
            int bCount;//當(dāng)前扇區(qū)的塊數(shù)
            int bIndex;//當(dāng)前扇區(qū)第一塊
            if (m1Auth(mifareClassic,j)) {
                bCount = mifareClassic.getBlockCountInSector(j);
                bIndex = mifareClassic.sectorToBlock(j);
                for (int i = 0; i < bCount; i++) {
                    byte[] data = mifareClassic.readBlock(bIndex);
                    String dataString = bytesToHexString(data);
                    metaInfo[j][i] = dataString;
                    Log.e("獲取到信息",dataString);
                    bIndex++;
                }
            } else {
                Log.e("readCard","密碼校驗(yàn)失敗");
            }
        }
        return metaInfo;
    } catch (IOException e){
        throw new IOException(e);
    } finally {
        try {
            mifareClassic.close();
        }catch (IOException e){
            throw new IOException(e);
        }
    }
}

/**
 * 改寫數(shù)據(jù)
 * @param block
 * @param blockbyte
 */
public static boolean writeBlock(Tag tag, int block, byte[] blockbyte) throws IOException {
    MifareClassic mifareClassic = MifareClassic.get(tag);
    try {
        mifareClassic.connect();
        if (m1Auth(mifareClassic,block/4)) {
            mifareClassic.writeBlock(block, blockbyte);
            Log.e("writeBlock","寫入成功");
        } else {
            Log.e("密碼是", "沒有找到密碼");
            return false;
        }
    } catch (IOException e){
        throw new IOException(e);
    } finally {
        try {
            mifareClassic.close();
        }catch (IOException e){
            throw new IOException(e);
        }
    }
    return true;

}

/**
 * 密碼校驗(yàn)
 * @param mTag
 * @param position
 * @return
 * @throws IOException
 */
public static boolean m1Auth(MifareClassic mTag,int position) throws IOException {
    if (mTag.authenticateSectorWithKeyA(position, MifareClassic.KEY_DEFAULT)) {
        return true;
    } else if (mTag.authenticateSectorWithKeyB(position, MifareClassic.KEY_DEFAULT)) {
        return true;
    }
    return false;
}

private static String bytesToHexString(byte[] src) {
    StringBuilder stringBuilder = new StringBuilder();
    if (src == null || src.length <= 0) {
        return null;
    }
    char[] buffer = new char[2];
    for (int i = 0; i < src.length; i++) {
        buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);
        buffer[1] = Character.forDigit(src[i] & 0x0F, 16);
        System.out.println(buffer);
        stringBuilder.append(buffer);
    }
    return stringBuilder.toString();
}
}

遇坑指南

1.鄙人所用的M1卡數(shù)據(jù)一個(gè)塊為16字節(jié),卡數(shù)據(jù)存儲(chǔ)的是16進(jìn)制的byte數(shù)組。讀取的時(shí)候要將16進(jìn)制byte數(shù)組轉(zhuǎn)換為10進(jìn)制的;寫卡的時(shí)候要進(jìn)行轉(zhuǎn)換為16進(jìn)制的byte數(shù)組,而且數(shù)據(jù)必須為16字節(jié)
2.第3塊一般不進(jìn)行數(shù)據(jù)存儲(chǔ)(0、1、2、3塊)
3.一般來說第0個(gè)扇區(qū)的第0塊為卡商初始化數(shù)據(jù),不能進(jìn)行寫操作
4.要關(guān)注Activity的聲明周期。onNewIntent中要進(jìn)行掃描卡片的處理,onResume要禁止前臺(tái)卡片活動(dòng)的調(diào)度處理, onPause要啟用前臺(tái)卡片活動(dòng)的調(diào)度處理。
5.要修改密鑰需要先校驗(yàn)密鑰之后修改控制位數(shù)據(jù)、密鑰數(shù)據(jù)。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容