Notice
Recent Posts
Recent Comments
Link
투케이2K
154. (TWOK/UTIL) [Android/Java] C_Nfc_Hce_Module : NFC 기능 활성 및 HCE 호스트 카드 시뮬레이션 수행 유틸 파일 본문
투케이2K 유틸파일
154. (TWOK/UTIL) [Android/Java] C_Nfc_Hce_Module : NFC 기능 활성 및 HCE 호스트 카드 시뮬레이션 수행 유틸 파일
투케이2K 2025. 1. 5. 19:01[설 명]
프로그램 : Android / Java
설 명 : C_Nfc_Hce_Module : NFC 기능 활성 및 HCE 호스트 카드 시뮬레이션 수행 유틸 파일
[소스 코드]
package com.example.javaproject.C_Module;
import android.app.Service;
import android.content.Intent;
import android.nfc.cardemulation.HostApduService;
import android.os.Bundle;
import com.example.javaproject.C_Util;
import com.example.javaproject.S_Log;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class C_Nfc_Hce_Module extends HostApduService {
/**
* // --------------------------------------------------------------------------------------
* TODO [클래스 설명]
* // --------------------------------------------------------------------------------------
* 1. [설명] : NFC 기능 활성 및 HCE 호스트 카드 시뮬레이션 수행 모듈
* // --------------------------------------------------------------------------------------
* 2. HCE 통신 주요 로직 - 호스트 기반 카드 에뮬레이션 [외부 NFC 리더기와 통신 수행]
*
* - onStartCommand() 메소드를 통해 전달할 데이터를 가져온다
*
* - processCommandApdu() 메소드에서 우선, 외부 장치로 부터 통신 수행 명령 APDU 를 응답받는다 (모바일과 기기간 기기통신 정보가 같아야함)
* (헤더 / AID 값 기기 통신 정보 길이 / AID 값 기기 통신 정보)
* (00A40400 / 05 / F1 23 45 67 89) - (ex aid) F123456789
*
* - processCommandApdu() 메소드에서 통신 수행 명령 APDU 를 정상적으로 응답받으면 return 값으로 외부 기기에 바이트 데이터를 전달한다
* (전달할 데이터 / 응답 상태 9000 필수)
* (0x02 / 전달할 데이터 / 응답 상태 9000 필수 / 0x03 - 이런 형태도 가능하다)
* // --------------------------------------------------------------------------------------
* 3. 안드로이드 디벨로퍼 참고 사이트 :
*
* https://developer.android.com/develop/connectivity/nfc/hce?hl=ko
* // --------------------------------------------------------------------------------------
* */
// ------------------------------------------------------------------------------------------
// TODO [NFC 설정 가이드]
// ------------------------------------------------------------------------------------------
// TODO [AndroidManifest.xml 파일] : NFC 퍼미션 설정
// ------------------------------------------------------------------------------------------
/*
<!-- ============================================================= -->
<!-- [NFC에서 HCE(호스트 카드) 통신 (유심 사용) : 퍼미션] -->
<!-- ============================================================= -->
<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc" android:required="true" />
*/
// ------------------------------------------------------------------------------------------
// TODO [AndroidManifest.xml 파일] : NFC 서비스 클래스 등록
// ------------------------------------------------------------------------------------------
/*
<!-- ============================================================= -->
<!-- 서비스 : NFC에서 HCE(호스트 카드) 통신 -->
<!-- ============================================================= -->
<!-- 외부 리더기와 통신 설정을 위해 : android:exported="true" :: false 일 경우 리더기 응답 안됨 -->
<!-- ============================================================= -->
<service android:name=".C_Module.C_Nfc_Hce_Module"
android:exported="true"
android:largeHeap="true"
android:permission="android.permission.BIND_NFC_SERVICE"
android:process=":auth">
<!-- Intent filter 등록 카드 에뮬레이터 설정 -->
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<!-- [aid 값 설정 실시] : aid 값을 통해서 디바이스와 매핑 인증 -->
<meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/apduservice"/>
</service>
*/
// ------------------------------------------------------------------------------------------
// TODO [XML 파일] : apduservice.xml : AID 설정 XML 파일 생성
// ------------------------------------------------------------------------------------------
/*
<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name"
android:requireDeviceUnlock="false">
<!-- [aid 설정 : 외부로부터 이 값을 이용하여 모바일로 들어온다] -->
<aid-group android:description="@string/app_name" android:category="other">
<!-- [AID] : 외부 리더기도 같은 값으로 설정해야한다 -->
<aid-filter android:name="F222222222"/>
</aid-group>
</host-apdu-service>
*/
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// TODO [소스 코드 사용 방법 예시]
// ------------------------------------------------------------------------------------------
/*
try {
// TODO [NFC 서비스 동작 수행]
Intent nfcIntent = new Intent(getApplication(), C_Nfc_Hce_Module.class);
nfcIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
nfcIntent.putExtra(C_Nfc_Hce_Module.KEY_SEND_MSG, "twok"); // TODO [App To Device 전송 메시지 삽입]
startService(nfcIntent);
// TODO [NFC 서비스 중지 수행]
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
new C_Nfc_Hce_Module().stopNfcService();
}
}, 5000);
}
catch (Exception e) {
e.printStackTrace();
}
*/
// ------------------------------------------------------------------------------------------
/**
* // --------------------------------------------------------------------------------------
* TODO [빠른 로직 찾기 : 주석 로직 찾기]
* // --------------------------------------------------------------------------------------
*
* // --------------------------------------------------------------------------------------
*
* // --------------------------------------------------------------------------------------
*
* // --------------------------------------------------------------------------------------
*/
// ------------------------------------------------------------------------------------------
// TODO [전역 변수 선언]
// ------------------------------------------------------------------------------------------
private String ACTIVITY_NAME = "C_Nfc_Hce_Module";
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); // [바이트와 아스키코드간 데이터 포맷을 위한 초기 설정]
private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000");
public static final String KEY_SEND_MSG = "KEY_SEND_MSG"; // [intent key 값]
public String sendMessage = ""; // [App To Device 로 전송할 데이터]
// ------------------------------------------------------------------------------------------
// TODO [onCreate] : [클래스 생성]
// ------------------------------------------------------------------------------------------
@Override
public void onCreate() {
super.onCreate();
S_Log._W_(ACTIVITY_NAME + " :: onCreate :: 클래스 생성", null);
}
// ------------------------------------------------------------------------------------------
// TODO [onStartCommand] : [서비스 시작]
// ------------------------------------------------------------------------------------------
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO [App To Device 전송 하기 위한 인텐트로 전달 받은 데이터 확인]
sendMessage = String.valueOf(intent.getStringExtra(KEY_SEND_MSG));
S_Log._W_(ACTIVITY_NAME + " :: onStartCommand :: NFC 서비스 시작 수행", new String[]{"sendMessage :: " + String.valueOf(sendMessage)});
// TODO [서비스를 앱 종료시까지 계속 실행상태로 유지]
return START_STICKY;
}
// ------------------------------------------------------------------------------------------
// TODO [processCommandApdu] : [외부 기기와 실제 데이터 송수신 처리]
// ------------------------------------------------------------------------------------------
/*
1) 외부 카드리더기로부터 명령이 들어오고 핸드폰을 리더기에서 뗄 때까지 어플리케이션과 통신이 이루어진다.
2) 위 코드에는 select aid 외 작업을 하지 않지만 원하면 다른 바이트를 주고 받을 수 있다.
3) select aid 가 완료된 이후에는 따로 헤더가 필요치 않는다.
4) processCommandApdu()의 리턴값은 리더기에 보낼 바이트열 값이다.
*/
// ------------------------------------------------------------------------------------------
// TODO [리턴 샘플 코드] : sendMessage = hello
// ------------------------------------------------------------------------------------------
/*
I/: ----------------------------------------------------
I/: [LOG :: DESCRIPTION :: A_Intro :: processCommandApdu :: Data Transfer Log]
I/: ----------------------------------------------------
I/: [LOG :: [App >> Device] [responseMsg | Byte] :: [104, 101, 108, 108, 111]]
I/: ----------------------------------------------------
I/: [LOG :: [App >> Device] [responseMsg | Hex] :: 0x68 0x65 0x6c 0x6c 0x6f]
I/: ----------------------------------------------------
I/: [LOG :: [App >> Device] [responseMsg | String] :: hello]
I/: ----------------------------------------------------
I/: [LOG :: [App >> Device] [responseTotal | Byte] :: [104, 101, 108, 108, 111, -112, 0]]
I/: ----------------------------------------------------
I/: [LOG :: [App >> Device] [responseTotal | Hex] :: 0x68 0x65 0x6c 0x6c 0x6f 0x90 0x00]
I/: ----------------------------------------------------
*/
// ------------------------------------------------------------------------------------------
@Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
S_Log._W_(ACTIVITY_NAME + " :: processCommandApdu :: Start", new String[]{ "설명 :: 외부 기기로부터 통신 수행 명령 APDU 를 전달 받음" });
// ----------------------------------------------
// TODO [commandApdu 널 체크 수행]
// ----------------------------------------------
if (commandApdu == null){
S_Log._E_(ACTIVITY_NAME + " :: processCommandApdu :: Error", new String[]{ "commandApdu Is Null" });
return null;
}
// ----------------------------------------------
// TODO [App To Device 데이터 전송 메시지 확인]
// ----------------------------------------------
if (C_Util.stringNotNull(sendMessage) == false){ // [널 인 경우] >> 기본 hello 전송
sendMessage = "hello";
}
byte responseMsg[] = sendMessage.getBytes(StandardCharsets.UTF_8); // [message to byte]
byte returnTotal[] = responseConcatArrays(responseMsg, SELECT_OK_SW); // TODO [최종 외부 기기에 바이트값 전송 (정상 응답)]
// ----------------------------------------------
// TODO [로그 출력 수행]
// ----------------------------------------------
S_Log._W_(ACTIVITY_NAME + " :: processCommandApdu :: Data Transfer Log", new String[]{
"[Device >> App] [commandApdu | Byte] :: " + Arrays.toString(commandApdu),
"[Device >> App] [commandApdu | Hex] :: " + ByteToHex(commandApdu),
"[App >> Device] [responseMsg | Byte] :: " + Arrays.toString(responseMsg),
"[App >> Device] [responseMsg | Hex] :: " + ByteToHex(responseMsg),
"[App >> Device] [responseMsg | String] :: " + sendMessage,
"[App >> Device] [responseTotal | Byte] :: " + Arrays.toString(returnTotal),
"[App >> Device] [responseTotal | Hex] :: " + ByteToHex(returnTotal)
});
// ----------------------------------------------
// TODO [App To Device 리턴 반환 수행 - Byte Array]
// ----------------------------------------------
return returnTotal;
}
// ------------------------------------------------------------------------------------------
// TODO [onDeactivated] : [NFC 리더와 기기 간의 NFC 링크 연결을 잃었을 때 호출]
// ------------------------------------------------------------------------------------------
@Override
public void onDeactivated(int reason) {
S_Log._E_(ACTIVITY_NAME + " :: onDeactivated :: NFC 리더와 기기 간의 NFC 링크 연결 비활성 및 대기 상태", new String[]{"Reason :: " + String.valueOf(reason)});
}
// ------------------------------------------------------------------------------------------
// TODO [onDestroy] : [클래스 종료]
// ------------------------------------------------------------------------------------------
@Override
public void onDestroy() {
super.onDestroy();
S_Log._E_(ACTIVITY_NAME + " :: onDestroy :: 클래스 종료", null);
}
// ------------------------------------------------------------------------------------------
// TODO [stopNfcService] : [서비스 중지 수행 메소드]
// ------------------------------------------------------------------------------------------
public void stopNfcService() {
S_Log._E_(ACTIVITY_NAME + " :: stopService :: NFC 서비스 중지 수행", null);
try { stopSelf(); } catch (Exception e){ }
}
// ------------------------------------------------------------------------------------------
// TODO [유틸 함수] : [HexStringToByteArray] : [Hex 문자열 To Byte 배열 변환]
// ------------------------------------------------------------------------------------------
public static byte[] HexStringToByteArray(String s) throws IllegalArgumentException {
int len = s.length();
if (len % 2 == 1) { // [2로 나눠 떨어 지지 않는 경우]
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;
}
// -----------------------------------------------------------------------------------------
// TODO [유틸 함수] : [ByteToHex] : [Byte 배열 값을 Hex 문자열로 변환]
// -----------------------------------------------------------------------------------------
public static String ByteToHex(byte buf[]) {
String returnData = "";
try {
if (buf != null && buf.length>0){
for(int i=0; i<buf.length; i++) {
returnData += String.format("0x%02x ", buf[i]); // [0xfg]
// returnData += String.format("%02x ", buf[i]); // [fg]
// returnData += String.format("0X%02X ", buf[i]); // [0XFG]
// returnData += String.format("%02X ", buf[i]); // [FG]
}
returnData = returnData.trim();
}
}
catch (Exception e){
e.printStackTrace();
}
return returnData;
}
// ------------------------------------------------------------------------------------------
// TODO [유틸 함수] : [responseConcatArrays] : [App To Device 로 응답 보낼 데이터 결합]
// ------------------------------------------------------------------------------------------
public static byte[] responseConcatArrays(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;
}
} // TODO [클래스 종료]
반응형
'투케이2K 유틸파일' 카테고리의 다른 글
Comments