Android NFC之仿真卡模式


NFC的工作模式
读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。

仿真卡模式:仿真卡模式就是将支持NFC的手机或其它电子设备当成借记卡、信用卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息(支付凭证)封装成数据包存储在支持NFC的手机中,且它是一个独立设备。

仿真卡需要root权限,需要在AndroidManifest.xml中添加android:sharedUserId=“android.uid.system”,并使用系统签名。

1.申请权限

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

    <!--API 9 设备可以使用近场通信(NFC)进行通信。-->
    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />
    <!--API 19 该设备支持基于主机的NFC卡仿真。-->
    <uses-feature
        android:name="android.hardware.nfc.hcef"
        android:required="true" />
    <!--API 24 该设备支持基于主机的NFC-F卡仿真。-->
    <uses-feature
        android:name="android.hardware.nfc.hce"
        android:required="true" />

2.HostApduService实现

processCommandApdu() 只要NFC阅读器向您的服务发送应用程序协议数据单元(APDU),就会调用此方法。APDU也在ISO / IEC 7816-4规范中定义。APDU是NFC读取器和HCE服务之间交换的应用程序级数据包。该应用程序级协议是半双工的:NFC读取器将向您发送命令APDU,它将等待您发送响应APDU,若里面操作时间较长可以先返回null,然后通过sendResponseApdu(byte[] responseApdu)发送。

onDeactivated() 卡片移走或断开连接时调用。


import android.nfc.cardemulation.HostApduService;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;


import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * This is a sample APDU Service which demonstrates how to interface with the card emulation support
 * added in Android 4.4, KitKat.
 *
 * <p>This sample replies to any requests sent with the string "Hello World". In real-world
 * situations, you would need to modify this code to implement your desired communication
 * protocol.
 *
 * <p>This sample will be invoked for any terminals selecting AIDs of 0xF11111111, 0xF22222222, or
 * 0xF33333333. See src/main/res/xml/aid_list.xml for more details.
 *
 * <p class="note">Note: This is a low-level interface. Unlike the NdefMessage many developers
 * are familiar with for implementing Android Beam in apps, card emulation only provides a
 * byte-array based communication channel. It is left to developers to implement higher level
 * protocol support as needed.
 *
 * <p>交互Apdu格式:[apdu header]+[dataLength]+[data]+[status]
 */
public class CardEmulationService extends HostApduService {
    private static final String TAG = "CardEmulation";
    // AID for our loyalty card service.
    private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222";
    // ISO-DEP command HEADER for selecting an AID.
    // Format: [Class | Instruction | Parameter 1 | Parameter 2]
    private static final String SELECT_APDU_HEADER = "00A40400";
    private static final String UPDATE_APDU_HEADER = "00B40400";
    // Format: [Class | Instruction | Parameter 1 | Parameter 2]
    private static final String GET_DATA_APDU_HEADER = "00CA0000";
    // "OK" status word sent in response to SELECT AID command (0x9000)
    private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000");
    // "UNKNOWN" status word sent in response to invalid APDU command (0x0000)
    private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000");

    private static final String WRITE_DATA_APDU_HEADER = "00DA0000";
    private static final String READ_DATA_APDU_HEADER = "00EA0000";
    private static String dataStr = null;

    /*File IO Stuffs*/
    File sdcard = Environment.getExternalStorageDirectory();
    File file = new File(sdcard, "file.txt");
    StringBuilder text = new StringBuilder();
    int pointer;

    @Override
    public void onDeactivated(int reason) {
    }

    @Override
    public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
        Log.i(TAG, "Received APDU: " + ByteArrayToHexString(commandApdu));
        if (commandApdu.length < 6) {
            return UNKNOWN_CMD_SW;
        }
        StringBuffer buffer = new StringBuffer();
        byte[] header = new byte[4];
        int pos = 0;
        System.arraycopy(commandApdu, pos, header, 0, header.length);
        buffer.append("header:").append(ByteArrayToHexString(header));
        pos += header.length;
        if (commandApdu.length == 6) {
            return SELECT_OK_SW;
        }
        int dataLen = Integer.parseInt(ByteArrayToHexString(new byte[]{commandApdu[pos++]}));
        buffer.append("\ndataLen:").append(dataLen);
        if (commandApdu.length < pos + dataLen) {
            return ConcatArrays(buffer.toString().getBytes(), SELECT_OK_SW);
        }
        byte[] data = new byte[dataLen];
        System.arraycopy(commandApdu, pos, data, 0, dataLen);
        buffer.append("\ndata:").append(ByteArrayToHexString(data));
        return ConcatArrays(buffer.toString().getBytes(), SELECT_OK_SW);
    }

    /**
     * Build APDU for SELECT AID command. This command indicates which service a reader is
     * interested in communicating with. See ISO 7816-4.
     *
     * @param aid Application ID (AID) to select
     * @return APDU for SELECT AID command
     */
    public static byte[] BuildSelectApdu(String aid) {
        // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
        return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid);
    }


    /**
     * Utility method to convert a byte array to a hexadecimal string.
     *
     * @param bytes Bytes to convert
     * @return String, containing hexadecimal representation.
     */
    public static String ByteArrayToHexString(byte[] bytes) {
        final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] hexChars = new char[bytes.length * 2]; // Each byte has two hex characters (nibbles)
        int v;
        for (int j = 0; j < bytes.length; j++) {
            v = bytes[j] & 0xFF; // Cast bytes[j] to int, treating as unsigned value
            hexChars[j * 2] = hexArray[v >>> 4]; // Select hex character from upper nibble
            hexChars[j * 2 + 1] = hexArray[v & 0x0F]; // Select hex character from lower nibble
        }
        return new String(hexChars);
    }

    /**
     * Utility method to convert a hexadecimal string to a byte string.
     *
     * <p>Behavior with input strings containing non-hexadecimal characters is undefined.
     *
     * @param s String containing hexadecimal characters to convert
     * @return Byte array generated from input
     * @throws java.lang.IllegalArgumentException if input length is incorrect
     */
    public static byte[] HexStringToByteArray(String s) throws IllegalArgumentException {
        int len = s.length();
        if (len % 2 == 1) {
            throw new IllegalArgumentException("Hex string must have even number of characters");
        }
        byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters
        for (int i = 0; i < len; i += 2) {
            // Convert each character into a integer (base-16), then bit-shift into place
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * Utility method to concatenate two byte arrays.
     *
     * @param first First array
     * @param rest  Any remaining arrays
     * @return Concatenated copy of input arrays
     */
    public static byte[] ConcatArrays(byte[] first, byte[]... rest) {
        int totalLength = first.length;
        for (byte[] array : rest) {
            totalLength += array.length;
        }
        byte[] result = Arrays.copyOf(first, totalLength);
        int offset = first.length;
        for (byte[] array : rest) {
            System.arraycopy(array, 0, result, offset, array.length);
            offset += array.length;
        }
        return result;
    }

    private void readFromFile() {
//        try {
//            BufferedReader br = new BufferedReader(new FileReader(file));
//            String line;
//
//            while ((line = br.readLine()) != null) {
//                text.append(line);
//                text.append('\n');
//            }
//        }
//        catch (IOException e) {
//            e.printStackTrace();
//        }
        text.append("some string random data some string random data some string random data some string random data some string random data \n");
        text.append("some string random data some string random data some string random data some string random data some string random data \n");
        text.append("some string random data some string random data some string random data some string random data some string random data \n");
        text.append("some string random data some string random data some string random data some string random data some string random data \n");
        text.append("some string random data some string random data some string random data some string random data some string random data \n");
        text.append("some string random data some string random data some string random data some string random data some string random data \n");
    }


}


3.注册Service

       <service
            android:name=".CardEmulationService"
            android:exported="true"
            android:permission="android.permission.BIND_NFC_SERVICE">
            <!-- Intent filter indicating that we support card emulation. -->
            <intent-filter>
                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
            </intent-filter>
            <!-- Required XML configuration file, listing the AIDs that we are emulating cards
                 for. This defines what protocols our card emulation service supports. -->
            <meta-data
                android:name="android.nfc.cardemulation.host_apdu_service"
                android:resource="@xml/apduservice" />
        </service>

4.apduservice.xml配置

apudservice.xml中配置AID,可以有一个或多个,aid是这个service标识,需要跟读卡器端约定好,通过aid来找到这个service;这里也可以不配置aid,在activity/fragment中动态注册aid。

requireDeviceUnlock: 可用于指定在调用此服务以处理APDU之前必须解锁设备。

<?xml version="1.0" encoding="utf-8"?>

<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/apdu_service_description"
    android:requireDeviceUnlock="false">

    <aid-group
        android:category="other"
        android:description="@string/apdu_service_description">
<!--        <aid-filter android:name="F222222222" />-->
    </aid-group>
</host-apdu-service>

5.动态注册AID


import android.content.ComponentName;
import android.nfc.NfcAdapter;
import android.nfc.cardemulation.CardEmulation;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.util.ArrayList;
import java.util.List;

public class CardEmulationFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_card_emulation, container, false);
    }

    private static final List<String> AIDS = new ArrayList<>();

    static {
        AIDS.add("F222222222");
        AIDS.add("F223344556");
    }


    private CardEmulation mCardEmulation;
    private ComponentName mService;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(getContext());
        mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
        mService = new ComponentName(getActivity(), CardEmulationService.class);
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d("CardEmulation", "registerAidsForService");
        mCardEmulation.setPreferredService(getActivity(), mService);
        mCardEmulation.registerAidsForService(mService, "other", AIDS);
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d("CardEmulation", "removeAidsForService");
        mCardEmulation.removeAidsForService(mService, "other");
        mCardEmulation.unsetPreferredService(getActivity());
    }
}

6.读卡器端Apdu处理

这里简单介绍一下读卡器端对Tag的处理,详细的请看Android NFC之读卡器模式

import android.nfc.Tag;
import android.nfc.tech.IsoDep;

import java.io.IOException;
import java.util.Arrays;


public class CustomIsoDepReader extends CardReader {
    private static final String TAG = "IsoDepReader";

    private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222";
    // ISO-DEP command HEADER for selecting an AID.
    // Format: [Class | Instruction | Parameter 1 | Parameter 2]
    private static final String SELECT_APDU_HEADER = "00A40400";
    // "OK" status word sent in response to SELECT AID command (0x9000)
    private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00};


    public String parse(Tag tag) {
        IsoDep isoDep = IsoDep.get(tag);
        try {
            isoDep.connect();
            byte[] command = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID);
            byte[] result = isoDep.transceive(command);
            int resultLength = result.length;
            byte[] statusWord = {result[resultLength - 2], result[resultLength - 1]};
            byte[] payload = Arrays.copyOf(result, resultLength - 2);
            if (Arrays.equals(SELECT_OK_SW, statusWord)) {
                return new String(payload, "UTF-8");
            }
            return "Select failed";
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        } finally {
            close(isoDep);
        }
    }


    /**
     * Build APDU for SELECT AID command. This command indicates which service a reader is
     * interested in communicating with. See ISO 7816-4.
     *
     * @param aid Application ID (AID) to select
     * @return APDU for SELECT AID command
     */
    private byte[] BuildSelectApdu(String aid) {
        // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
        return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid);
    }


}

读卡器模式

Android NFC之读卡器模式

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Android调用NFC读取NFC片的过程如下:首先,需要确认设备的硬件支持NFC功能,并且设备的操作系统版本必须是Android 4.0或更高版本。然后,在Android应用程序中,需要在AndroidManifest.xml文件中添加必要的权限和NFC相关的配置。 接下来,需要在应用程序中注册一个NFC适配器对象,以便与设备的NFC芯片进行通信。然后,可以通过检测NFC设备是否处于范围内来启动应用程序的特定操作,例如读取片中的数据。 读取NFC片的过程包括以下步骤:首先,需要创建一个PendingIntent对象,以便在检测到NFC设备时接收通知。然后,使用NFC适配器对象调用enableForegroundDispatch()方法,将PendingIntent对象传递给它。这将使应用程序在检测到NFC设备时接收到通知,并初始化NFC操作。 在读取NFC片之前,需要创建一个IntentFilter对象,以过滤掉不相关的NFC标签。可以使用ACTION_NDEF_DISCOVERED和ACTION_TECH_DISCOVERED等值来设置过滤器。然后,将IntentFilter对象传递给NFC适配器的enableForegroundDispatch()方法。 当NFC适配器检测到NFC设备时,将调用应用程序的onNewIntent()方法。在onNewIntent()方法中,可以通过调用getParcelableExtra()方法获取传递给PendingIntent对象的Intent,并从中提取出NFC标签的数据。 最后,可以使用读取到的NFC标签数据进行其他的处理操作,例如解析数据、显示在应用界面上或将其发送到远程服务器等。 在读取NFC片的过程中需要注意的是,NFC设备必须处于活动状态,并且应用程序必须拥有前台调度权限,才能成功读取NFC片。此外,由于不同的NFC片类型和厂商可能有不同的协议和数据格式,因此需要根据具体的NFC片来实现相应的处理逻辑。 ### 回答2: Android调用NFC读取NFC片的过程如下: 首先,需要在AndroidManifest.xml文件中添加必要的权限和特性声明,以确保应用能够访问NFC功能。例如,需要添加以下权限声明: ``` <uses-permission android:name="android.permission.NFC" /> ``` 在应用的Activity中,需要注册一个NFC监听器,以接收NFC设备的读取事件。可以通过以下方式实现: ``` private NfcAdapter mNfcAdapter; ... @Override protected void onCreate(Bundle savedInstanceState) { ... mNfcAdapter = NfcAdapter.getDefaultAdapter(this); ... } ... @Override public void onResume() { super.onResume(); if (mNfcAdapter != null) { Intent intent = new Intent(this, this.getClass()).addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); IntentFilter[] intentFilters = new IntentFilter[]{}; mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null); } } ... @Override public void onPause() { super.onPause(); if (mNfcAdapter != null) { mNfcAdapter.disableForegroundDispatch(this); } } ... @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); if (intent.hasExtra(NfcAdapter.EXTRA_TAG)) { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); // 在此处理读取到的NFC片数据 } } ``` 以上代码中,`mNfcAdapter`是NFC适配器实例,`onResume()`方法用于在应用进入前台时启用前台调度,以便接收NFC设备事件,`onPause()`方法用于在应用进入后台时禁用前台调度,`onNewIntent()`方法用于处理读取到的NFC片数据。 在`onNewIntent()`方法中,可以通过`intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)`来获取到读取到的NFC标签,并进行相应的操作,例如读取和写入片数据等。 需要注意的是,以上代码中只展示了基本的NFC读取流程,具体的读取和处理操作需要根据需求进一步实现。 ### 回答3: Android调用NFC读取NFC片需要经过以下的步骤: 首先,确保设备支持NFC功能。大多数Android设备都已经集成了NFC芯片,但仍然需要在设备的设置中确认是否启用NFC功能。 然后,在Android的Manifest文件中添加必要的权限和配置。这些权限包括NFC权限,以及与NFC相关的Intent过滤器。例如,可以添加以下代码来声明NFC权限: <uses-permission android:name="android.permission.NFC"/> 同时,还需要添加一个Intent过滤器,以便应用程序能够响应NFC相关的意图。以下是示例代码: <intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED"/> </intent-filter> 接下来,需要创建一个能够响应NFC意图的活动或服务。这可以通过继承Android提供的NFC活动类或服务类来实现。在这个活动或服务中,可以使用NFC适配器来检测和连接到NFC标签。以下是示例代码: NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter != null && nfcAdapter.isEnabled()) { // NFC已经启用,可以进行相关操作 } else { // 设备不支持或未启用NFC功能 } 在检测到NFC标签后,可以通过NFC适配器获取NFC标签的ID或数据。例如,可以使用以下代码获取NFC标签的ID: Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); String tagId = bytesToHexString(tag.getId()); 最后,可以根据需要对NFC标签的ID或数据进行进一步的处理。例如,可以将ID与系统中已知的标签ID进行比较,或者读取和写入NFC标签的数据。 总之,Android调用NFC读取NFC片需要确认设备支持NFC功能、添加必要的权限和配置、创建能够响应NFC意图的活动或服务、使用NFC适配器进行NFC标签的检测和连接,并对NFC标签的ID或数据进行处理。以上是一个简单的概述,更详细的实现可以参考Android开发文档中关于NFC的相关内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值