투케이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 [클래스 종료]
 
반응형
Comments