Android SDK Sample(一) CardReader

Android SDK Sample(一) CardReader

导语

本Sample主要是介绍NFC三种模式之一读卡器(Reader)模式的使用,学习本Sample需要对ISO 7816-4报文协议有一定了解。运行本Sample也需要一张智能卡,例如:羊城通。(NFC读取羊城通卡信息)

效果图

效果图

Mainifest.xml

minSdk >=19

效果图
本Sample要求的最Sdk必须是19,因为在API 19之后,Google提供NFC的API有所改变,本Sample是根据API 19之后的API所写,不兼容API 19以下的机型

所需要的权限

<!-- NFC Reader Mode was added in API 19. -->
<!-- Min/target SDK versions (<uses-sdk>) managed by build.gradle -->
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />

首先要求手机或者设备需要有NFC功能,其次需要NFC权限

工程目录

效果图

核心类

CardReaderFragment

注册NFC

private void enableReaderMode() {
    Log.i(TAG, "Enabling reader mode");
    Activity activity = getActivity();
    NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity);
    if (nfc != null) {
        nfc.enableReaderMode(activity, mLoyaltyCardReader, READER_FLAGS, null);
    }
}

1.获取NfcAdapter,一般直接使用DefaultAdapter;
2.调用enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)函数进行注册Nfc,手机发现Nfc标签后会回调ReaderCallBack,第三个参数flags可以进行标签类型过滤。第四个参数可以传入一些配置,例如读卡延迟时间等。

注销NFC

private void disableReaderMode() {
    Log.i(TAG, "Disabling reader mode");
    Activity activity = getActivity();
    NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity);
    if (nfc != null) {
        nfc.disableReaderMode(activity);
    }
}

没啥好说的,直接调用disableReaderMode(Activity activity)

生命周期的调用

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
        View v = inflater.inflate(R.layout.main_fragment, container, false);
        if (v != null) {
            mAccountField = (TextView) v.findViewById(R.id.card_account_field);
            mAccountField.setText("Waiting...");

            mLoyaltyCardReader = new LoyaltyCardReader(this);

            // Disable Android Beam and register our card reader callback
            enableReaderMode();
        }

        return v;
    }

    @Override
    public void onPause() {
        super.onPause();
        disableReaderMode();
    }

    @Override
    public void onResume() {
        super.onResume();
        enableReaderMode();
    }

本类实现的回调接口

public class CardReaderFragment extends Fragment implements LoyaltyCardReader.AccountCallback {
@Override
    public void onAccountReceived(final String account) {
        // This callback is run on a background thread, but updates to UI elements must be performed
        // on the UI thread.
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mAccountField.setText(account);
            }
        });
    }
}

主要是实现了一个接口:LoyaltyCardReader.AccountCallback,这个接口在读卡成功后会进行回调。

再来关注下LoyaltyCardReader,本类实现了刚刚所说的注册NFC所需要的回调ReaderCallback,具体分析如下:
成员变量:
具体7816-4指令可以查看相关文档,APDU指令执行成功,返回数据的最后两个字节为9000。

private static final String TAG = "LoyaltyCardReader";
    // 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";
    // "OK" status word sent in response to SELECT AID command (0x9000)
    private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00};

接口实现函数:

@Override
    public void onTagDiscovered(Tag tag) {
        //Nfc标签被检测到后回调这个函数
        Log.i(TAG, "New tag discovered");
        // Android's Host-based Card Emulation (HCE) feature implements the ISO-DEP (ISO 14443-4)
        // protocol.
        //
        // In order to communicate with a device using HCE, the discovered tag should be processed
        // using the IsoDep class.
        //使用IsoDep完成后续操作
        IsoDep isoDep = IsoDep.get(tag);
        if (isoDep != null) {
            try {
                // Connect to the remote NFC device
                //Nfc与卡片进行连接,抛出IO异常,常见为Tag was Lost
                isoDep.connect();
                // Build SELECT AID command for our loyalty card service.
                // This command tells the remote device which service we wish to communicate with.
                Log.i(TAG, "Requesting remote AID: " + SAMPLE_LOYALTY_CARD_AID);
                //构建APDU指令
                byte[] command = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID);
                // Send command to remote device
                Log.i(TAG, "Sending: " + ByteArrayToHexString(command));
                //nfc执行APDU指令
                byte[] result = isoDep.transceive(command);
                // If AID is successfully selected, 0x9000 is returned as the status word (last 2
                // bytes of the result) by convention. Everything before the status word is
                // optional payload, which is used here to hold the account number.
                //判断指令是否执行成功,最后两个字节为9000则是成功,成功后则回调给Fragment更新UI
                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)) {
                    // The remote NFC device will immediately respond with its stored account number
                    String accountNumber = new String(payload, "UTF-8");
                    Log.i(TAG, "Received: " + accountNumber);
                    // Inform CardReaderFragment of received account number
                    mAccountCallback.get().onAccountReceived(accountNumber);
                } else {
                    Log.i(TAG, "Received: " + ByteArrayToHexString(statusWord));
                    System.out.println(ByteArrayToHexString(statusWord));
                }

            } catch (IOException e) {
                Log.e(TAG, "Error communicating with card: " + e.toString());
            }
        }
    }

工具函数:
构建APDU指令,返回byte[]数组,AID的长度用16进制表示,长度为2个字节

/**
     * 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);
    }

byte[]数组转16进制字符串

    /**
     * Utility class 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];
        int v;
        for ( int j = 0; j < bytes.length; j++ ) {
            v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

16进制字符串转byte[]数组

    /**
     * Utility class 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
     */
    public static byte[] HexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

单元测试

本Sample的单元测试继承了ActivityInstrumentationTestCase2,所以需要运行在真机或者模拟器中

@Override
    protected void setUp() throws Exception {
        super.setUp();

        // Starts the activity under test using the default Intent with:
        // action = {@link Intent#ACTION_MAIN}
        // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
        // All other fields are null or empty.
        mTestActivity = getActivity();
        mTestFragment = (CardReaderFragment)
            mTestActivity.getSupportFragmentManager().getFragments().get(1);
    }

    /**
    * Test if the test fixture has been set up correctly.
    */
    public void testPreconditions() {
        //Try to add a message to add context to your assertions. These messages will be shown if
        //a tests fails and make it easy to understand why a test failed
        assertNotNull("mTestActivity is null", mTestActivity);
        assertNotNull("mTestFragment is null", mTestFragment);
    }

    /**
     * Test building SELECT APDU from AID string.
     */
    public void testBuildSelectApdu() {
        final String aid = "1234";
        final byte[] expectedResult = {(byte) 0x00, (byte) 0xA4, 04, (byte) 0x00, (byte) 0x02,
                (byte) 0x12, (byte) 0x34};
        final byte[] result = LoyaltyCardReader.BuildSelectApdu(aid);

        assertEquals(expectedResult.length, result.length);
        for (int i = 0; i < expectedResult.length; i++) {
            assertEquals(expectedResult[i], result[i]);
        }
    }

    /**
     * Test converting from a hex string to binary.
     */
    public void testHexToBinary() {
        final byte[] testData = {(byte) 0xc0, (byte) 0xff, (byte) 0xee};
        final byte[] output = LoyaltyCardReader.HexStringToByteArray("C0FFEE");
        for (int i = 0; i < testData.length; i++) {
            assertEquals(testData[i], output[i]);
        }
    }

    /**
     * Test converting from binary to a hex string
     */
    public void testBinaryToHex() {
        final byte[] input = {(byte) 0xc0, (byte) 0xff, (byte) 0xee};
        final String output = LoyaltyCardReader.ByteArrayToHexString(input);
        assertEquals("C0FFEE", output);
    }

单元测试执行结果

效果图

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
List of Sample Apps The list below provides a summary of the sample applications that are available with the Android SDK. Using the links on this page, you can view the source files of the sample applications in your browser. You can also download the source of these samples into your SDK, then modify and reuse it as you need. For more information, see Getting the Samples. API Demos A variety of small applications that demonstrate an extensive collection of framework topics. Backup and Restore A simple example that illustrates a few different ways for an application to implement support for the Android data backup and restore mechanism. Bluetooth Chat An application for two-way text messaging over Bluetooth. BusinessCard An application that demonstrates how to launch the built-in contact picker from within an activity. This sample also uses reflection to ensure that the correct version of the contacts API is used, depending on which API level the application is running under. Contact Manager An application that demonstrates how to query the system contacts provider using the ContactsContract API, as well as insert contacts into a specific account. Home A home screen replacement application. JetBoy A game that demonstrates the SONiVOX JET interactive music technology, with JetPlayer. Live Wallpaper An application that demonstrates how to create a live wallpaper and bundle it in an application that users can install on their devices. Lunar Lander A classic Lunar Lander game. Multiple Resolutions A sample application that shows how to use resource directory qualifiers to provide different resources for different screen configurations. Note Pad An application for saving notes. Similar (but not identical) to the Notepad tutorial. SampleSyncAdapter Demonstrates how an application can communicate with a cloud-based service and synchronize its data with data stored locally in a content provider. The sample uses two related parts of the Android framework — the account manager and the synchronization manager (through a sync adapter). Searchable Dictionary A sample application that demonstrates Android's search framework, including how to provide search suggestions for Quick Search Box. Snake An implementation of the classic game "Snake." Soft Keyboard An example of writing an input method for a software keyboard. Spinner A simple application that serves as an application-under-test for the SpinnerTest sample application. SpinnerTest An example test application that contains test cases run against the Spinner sample application. To learn more about the application and how to run it, please read the Activity Testing tutorial. TicTacToeLib An example of an Android library project that provides a game-play Activity to any dependent application project. For an example of how an application can use the code and resources in an Android library project, see the TicTacToeMain sample application. TicTacToeMain An example of an Android application that makes use of code and resources provided in an Android library project. Specifically, this application uses code and resources provided in the TicTacToeLib library project. Wiktionary An example of creating interactive widgets for display on the Android home screen. Wiktionary (Simplified) A simple Android home screen widgets example.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值