728x90

HCE (host-based card emulation)을 이용하면 nfc 태그가 없더라도 nfc통신이 가능하다.



리더기는 acr 1251제품을 사용하였으며 리더기 제어에 관한 코드는 여기에 있다.


hce를 사용하기 위해서는 기본적으로 HostApduService를 상속받아야 하는데 이름에서 알 수 있듯이 Service이기 때문에

매니페스트 파일에 추가해 주어야 한다.


AndroidMainifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hyunseo.hce_sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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>

        <service android:name=".CardService"
            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"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </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/aid_list"/>
        </service>
    </application>

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

</manifest>


AID란 것이 존재하는데 여러 어플리케이션에서 어떤 어플리케이션과 nfc통신을 해야 하는지를 구분하기 위한 값으로 임의로 설정 가능하다.

매니페스트 파일에 aid값이 있는 xml파일을 명시해 주어야 한다. 또 android.permission.BIND_NFC_SERVICE권한을 꼭 추가해 주어야 한다.


MainActivity.java

package com.example.hyunseo.hce_sample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import org.w3c.dom.Text; public class MainActivity extends AppCompatActivity { private TextView _tv; private Intent _cardService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _tv = (TextView) findViewById(R.id._textView); _cardService = new Intent(this, CardService.class); startService(_cardService); } @Override protected void onDestroy() { stopService(_cardService); } public void onClickUpdate(View v) { _tv.setText("Count : " + Counter.GetCurrentCout()); } }


메인 액티비티에서 하는 일은 간단한데 사용자가 버튼을 누르면 Count클래스에 있는 cnt값을 화면에 표시한다.



CardService.java

package com.example.hyunseo.hce_sample; import android.content.Intent; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import java.util.Arrays; public class CardService extends HostApduService { private static final String TAG = "CardService"; // 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 = 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 byte[] SELECT_APDU = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID); //////////////////////////////////////////////////////////////////////////////////// private Messenger _handler; @Override public int onStartCommand(Intent intent, int flags, int startId) { // UI thread 핸들 얻음 Bundle extras = intent.getExtras(); // 서비스를 앱 종료시까지 계속 실행상태로 return START_STICKY; } // 외부로부터의 APDU명령을 받았을때 호출 @Override public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { if (Arrays.equals(SELECT_APDU, commandApdu)) { Log.i(TAG, "Application selected"); Counter.AddOne(); return SELECT_OK_SW; } else { return UNKNOWN_CMD_SW; } } // 연결을 잃었을때 호출됨 @Override public void onDeactivated(int reason) { Log.i(TAG, "Deactivated: " + reason); } 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); } 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; } }


이 클래스가 nfc통신을 담당할 클래스이다. 외부로부터 이 어플리케이션에 apdu 명령을 할 경우 processCommandApdu() 메소드가 호출되며


카운터 클래스의 Counter.AddOne() 메소드를 호출한다.


기본적으로 Select AID란 작업이 완료되면 리더기에서 핸드폰을 떼기 전까지는 이 어플리케이션에만 apdu명령이 들어온다.


현재 aid는 f2222222로 설정하였고 Select AID의 apdu헤더는 00A40400이며 aid의 값은 5바이트(바이트로 변환하기 때문에 16진수로 끊어서)이기 때문에


여기서의 Select AID의 apdu명령은 "00A40400 05 f2222222"의 바이트열로 구성된다. 외부 카드리더기로부터 저 명령이 왔을때부터 핸드폰을 리더기에서


뗄때까지 이 어플리케이션과 통신이 이루어진다. 위 코드에는 select aid외 작업을 하지 않지만 원하면 다른 바이트를 주고 받을 수 있다.


select aid가 완료된 이후에는 따로 헤더가 필요치 않는다. processCommandApdu()의 리턴값은 리더기에 보낼 바이트열 값이다.



Counter.java

package com.example.hyunseo.hce_sample; public class Counter { private static int _cnt = 0; public static void AddOne() {_cnt++;} public static int GetCurrentCout() {return _cnt;} }


카운터 클래스는 간단하게 UI thread와 Service 두곳에서 접근 가능하도록 static으로 구성하였다. 카드서비스에서 _cnt의 값을 올리면


메인액티비티에서 증가된 값을 표시한다.



aid_list.xml

<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/service_name"
    android:requireDeviceUnlock="false">

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


aid가 적혀있다. 외부로부터 이 값을 이용하여 들어온다.



실행결과 :

리더기에 찍고 UPDATE버튼을 누르면 카운트값이 변경이 된다.


자세한 설명은 구글 개발자 페이지를 확인하시면 됩니다.

https://developer.android.com/samples/CardEmulation/index.html

728x90

+ Recent posts