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
'프로그래밍 > Java' 카테고리의 다른 글
너비 우선 탐색(BFS) (0) | 2019.10.21 |
---|---|
깊이 우선 탐색 (DFS), 인접 행렬, 인접 리스트 (0) | 2019.10.18 |
[Android] 사설 SSL 인증서를 이용한 https 통신 (4) | 2018.05.30 |
[JAVA] NFC 제어 (2) | 2017.10.15 |
[Android] 안드로이드 소켓 프로그래밍 (1) | 2016.11.12 |