From 2ea0792d284b8c779d4e3dc5c2313fa5e9b0656f Mon Sep 17 00:00:00 2001 From: riz081 Date: Wed, 18 Jun 2025 14:41:18 +0700 Subject: [PATCH] Implement EMV --- .../bdkipoc/kredit/CreditCardActivity.java | 1661 ++++++++++++++--- .../main/res/layout/activity_credit_card.xml | 70 +- 2 files changed, 1423 insertions(+), 308 deletions(-) diff --git a/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java b/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java index 44cfa3a..2df7feb 100644 --- a/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java +++ b/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java @@ -1,42 +1,239 @@ package com.example.bdkipoc.kredit; +import android.content.ClipboardManager; +import android.content.ClipData; +import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.RemoteException; +import android.text.TextUtils; import android.widget.Button; import android.widget.TextView; +import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import com.example.bdkipoc.MyApplication; import com.example.bdkipoc.R; import com.example.bdkipoc.utils.ByteUtil; import com.example.bdkipoc.utils.Utility; +import com.sunmi.emv.l2.utils.iso8583.TLV; +import com.sunmi.emv.l2.utils.iso8583.TLVUtils; import com.sunmi.pay.hardware.aidl.AidlConstants.CardType; +import com.sunmi.pay.hardware.aidl.bean.CardInfo; +import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2; +import com.sunmi.pay.hardware.aidlv2.AidlErrorCodeV2; +import com.sunmi.pay.hardware.aidlv2.bean.EMVCandidateV2; +import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2; +import com.sunmi.pay.hardware.aidlv2.emv.EMVListenerV2; +import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2; +import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2; +import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2; import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Enhanced CreditCardActivity with EMV capabilities + * This activity can detect cards and read complete EMV data including PAN, card type, etc. + */ public class CreditCardActivity extends AppCompatActivity { private static final String TAG = "CreditCard"; + // UI Components private TextView tvResult; + private TextView tvModeIndicator; private Button btnCheckCard; + private Button btnToggleMode; + private Button btnCopyData; + private Button btnClearData; private boolean checkingCard; + + // EMV Components + private EMVOptV2 mEMVOptV2; + private PinPadOptV2 mPinPadOptV2; + + // EMV Process Variables + private int mCardType; + private String mCardNo; + private int mPinType; + private String mCertInfo; + private int mSelectIndex; + private int mProcessStep; + private AlertDialog mAppSelectDialog; + private boolean isEMVMode = false; // Flag to control EMV vs simple card detection + + // EMV Process Constants + private static final int EMV_APP_SELECT = 1; + private static final int EMV_FINAL_APP_SELECT = 2; + private static final int EMV_CONFIRM_CARD_NO = 3; + private static final int EMV_CERT_VERIFY = 4; + private static final int EMV_SHOW_PIN_PAD = 5; + private static final int EMV_ONLINE_PROCESS = 6; + private static final int EMV_SIGNATURE = 7; + private static final int EMV_TRANS_SUCCESS = 888; + private static final int EMV_TRANS_FAIL = 999; + + private static final int PIN_CLICK_NUMBER = 50; + private static final int PIN_CLICK_PIN = 51; + private static final int PIN_CLICK_CONFIRM = 52; + private static final int PIN_CLICK_CANCEL = 53; + private static final int PIN_ERROR = 54; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case EMV_FINAL_APP_SELECT: + importFinalAppSelectStatus(0); + break; + case EMV_APP_SELECT: + String[] candiNames = (String[]) msg.obj; + showAppSelectDialog(candiNames); + break; + case EMV_CONFIRM_CARD_NO: + showCardNumberConfirmation(); + break; + case EMV_CERT_VERIFY: + showCertificateVerification(); + break; + case EMV_SHOW_PIN_PAD: + initPinPad(); + break; + case EMV_ONLINE_PROCESS: + mockOnlineProcess(); + break; + case EMV_SIGNATURE: + importSignatureStatus(0); + break; + case PIN_CLICK_PIN: + importPinInputStatus(0); + break; + case PIN_CLICK_CONFIRM: + importPinInputStatus(2); + break; + case PIN_CLICK_CANCEL: + showToast("User cancelled PIN input"); + importPinInputStatus(1); + break; + case PIN_ERROR: + showToast("PIN Error: " + msg.obj + " -- " + msg.arg1); + importPinInputStatus(3); + break; + case EMV_TRANS_FAIL: + resetUI(); + showToast("Transaction failed: " + msg.obj + " -- " + msg.arg1); + break; + case EMV_TRANS_SUCCESS: + resetUI(); + getTlvData(); // Get complete card data + showToast("Transaction successful: " + msg.obj + " -- " + msg.arg1); + break; + } + } + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); android.util.Log.d(TAG, "onCreate called"); setContentView(R.layout.activity_credit_card); - initView(); - // Check PaySDK status + initEMVComponents(); + initView(); + initEMVData(); checkPaySDKStatus(); } + private void initEMVComponents() { + if (MyApplication.app != null) { + mEMVOptV2 = MyApplication.app.emvOptV2; + mPinPadOptV2 = MyApplication.app.pinPadOptV2; + android.util.Log.d(TAG, "EMV components initialized"); + } else { + android.util.Log.e(TAG, "MyApplication.app is null"); + } + } + + private void initEMVData() { + try { + if (mEMVOptV2 != null) { + // Initialize EMV process + mEMVOptV2.initEmvProcess(); + + // Set EMV TLV data for different card schemes + initEmvTlvData(); + + android.util.Log.d(TAG, "EMV data initialized successfully"); + } + } catch (Exception e) { + android.util.Log.e(TAG, "Error initializing EMV data: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void initEmvTlvData() { + try { + // Set PayPass (MasterCard) TLV data + String[] tagsPayPass = {"DF8117", "DF8118", "DF8119", "DF811F", "DF811E", "DF812C", + "DF8123", "DF8124", "DF8125", "DF8126", "DF811B", "DF811D", "DF8122", "DF8120", "DF8121"}; + String[] valuesPayPass = {"E0", "F8", "F8", "E8", "00", "00", + "000000000000", "000000100000", "999999999999", "000000100000", + "30", "02", "0000000000", "000000000000", "000000000000"}; + mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS, tagsPayPass, valuesPayPass); + + // Set AMEX (American Express) TLV data + String[] tagsAE = {"9F6D", "9F6E", "9F33", "9F35", "DF8168", "DF8167", "DF8169", "DF8170"}; + String[] valuesAE = {"C0", "D8E00000", "E0E888", "22", "00", "00", "00", "60"}; + mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_AE, tagsAE, valuesAE); + + // Set JCB TLV data + String[] tagsJCB = {"9F53", "DF8161"}; + String[] valuesJCB = {"708000", "7F00"}; + mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_JCB, tagsJCB, valuesJCB); + + // Set DPAS TLV data + String[] tagsDPAS = {"9F66"}; + String[] valuesDPAS = {"B600C000"}; + mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_DPAS, tagsDPAS, valuesDPAS); + + // Set Flash TLV data + String[] tagsFLASH = {"9F58", "9F59", "9F5A", "9F5D", "9F5E"}; + String[] valuesFLASH = {"03", "D88700", "00", "000000000000", "E000"}; + mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_FLASH, tagsFLASH, valuesFLASH); + + // Set Pure TLV data + String[] tagsPURE = {"DF7F", "DF8134", "DF8133"}; + String[] valuesPURE = {"A0000007271010", "DF", "36006043F9"}; + mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PURE, tagsPURE, valuesPURE); + + Bundle bundle = new Bundle(); + bundle.putBoolean("optOnlineRes", true); + mEMVOptV2.setTermParamEx(bundle); + + } catch (RemoteException e) { + android.util.Log.e(TAG, "Error setting EMV TLV data: " + e.getMessage()); + e.printStackTrace(); + } + } + private void checkPaySDKStatus() { if (MyApplication.app != null) { android.util.Log.d(TAG, "MyApplication.app exists"); android.util.Log.d(TAG, "PaySDK connected: " + MyApplication.app.isConnectPaySDK()); android.util.Log.d(TAG, "readCardOptV2 null: " + (MyApplication.app.readCardOptV2 == null)); + android.util.Log.d(TAG, "emvOptV2 null: " + (MyApplication.app.emvOptV2 == null)); } else { android.util.Log.e(TAG, "MyApplication.app is null"); } @@ -51,31 +248,133 @@ public class CreditCardActivity extends AppCompatActivity { setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.card_test_credit_card); + getSupportActionBar().setTitle("Enhanced Card Reader with EMV"); } } + // Initialize required UI components tvResult = findViewById(R.id.tv_result); btnCheckCard = findViewById(R.id.btn_check_card); + // Initialize optional UI components (may not exist in current layout) + tvModeIndicator = findViewById(R.id.tv_mode_indicator); + btnToggleMode = findViewById(R.id.btn_toggle_mode); + btnCopyData = findViewById(R.id.btn_copy_data); + btnClearData = findViewById(R.id.btn_clear_data); + + // Setup button listeners + if (btnToggleMode != null) { + btnToggleMode.setOnClickListener(v -> toggleEMVMode()); + } else { + android.util.Log.w(TAG, "btnToggleMode not found in layout"); + } + if (btnCheckCard != null) { android.util.Log.d(TAG, "Button found, setting click listener"); btnCheckCard.setOnClickListener(v -> { android.util.Log.d(TAG, "Button clicked!"); switchCheckCard(); }); + + // Add long click listener as alternative to toggle EMV mode (if toggle button doesn't exist) + if (btnToggleMode == null) { + btnCheckCard.setOnLongClickListener(v -> { + toggleEMVMode(); + return true; + }); + android.util.Log.d(TAG, "Long press listener added for EMV toggle"); + } } else { android.util.Log.e(TAG, "Button not found!"); } + if (btnCopyData != null) { + btnCopyData.setOnClickListener(v -> copyCardDataToClipboard()); + } else { + android.util.Log.w(TAG, "btnCopyData not found in layout"); + } + + if (btnClearData != null) { + btnClearData.setOnClickListener(v -> clearCardData()); + } else { + android.util.Log.w(TAG, "btnClearData not found in layout"); + } + if (tvResult != null) { - tvResult.setText("Ready to scan card..."); + updateModeDisplay(); android.util.Log.d(TAG, "TextView initialized"); } else { android.util.Log.e(TAG, "TextView not found!"); } } + private void toggleEMVMode() { + isEMVMode = !isEMVMode; + updateModeDisplay(); + android.util.Log.d(TAG, "EMV Mode toggled to: " + isEMVMode); + + // If no toggle button exists, show toast to inform user + if (btnToggleMode == null) { + showToast("Mode switched to: " + (isEMVMode ? "EMV Mode" : "Simple Mode")); + } + } + + // Alternative method to toggle EMV mode programmatically or via menu + public void switchToEMVMode() { + isEMVMode = true; + updateModeDisplay(); + android.util.Log.d(TAG, "Switched to EMV Mode"); + showToast("Switched to EMV Mode (Full Card Data)"); + } + + public void switchToSimpleMode() { + isEMVMode = false; + updateModeDisplay(); + android.util.Log.d(TAG, "Switched to Simple Mode"); + showToast("Switched to Simple Mode (Basic Detection)"); + } + + private void updateModeDisplay() { + String mode = isEMVMode ? "EMV Mode (Full Card Data)" : "Simple Mode (Basic Detection)"; + + if (tvModeIndicator != null) { + tvModeIndicator.setText("Current Mode: " + mode); + } + + if (tvResult != null) { + tvResult.setText("Ready to scan card...\nCurrent Mode: " + mode + "\nPlace your card on the reader"); + } + } + + // ====== COPY AND CLEAR FUNCTIONALITY ====== + private void copyCardDataToClipboard() { + if (tvResult != null) { + String cardData = tvResult.getText().toString(); + if (!cardData.isEmpty() && !cardData.contains("Ready to scan")) { + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Card Data", cardData); + clipboard.setPrimaryClip(clip); + showToast("Card data copied to clipboard"); + } else { + showToast("No card data to copy"); + } + } + } + + private void clearCardData() { + updateModeDisplay(); + + // Hide copy and clear buttons (if they exist) + if (btnCopyData != null) { + btnCopyData.setVisibility(android.view.View.GONE); + } + if (btnClearData != null) { + btnClearData.setVisibility(android.view.View.GONE); + } + + showToast("Card data cleared"); + } + @Override public boolean onSupportNavigateUp() { onBackPressed(); @@ -101,47 +400,34 @@ public class CreditCardActivity extends AppCompatActivity { private void startCardCheck() { try { - checkCreditCard(); + if (isEMVMode) { + startEMVCardCheck(); + } else { + startSimpleCardCheck(); + } checkingCard = true; - btnCheckCard.setText(R.string.card_stop_check_card); + btnCheckCard.setText("Stop Scanning"); } catch (Exception e) { android.util.Log.e(TAG, "Error starting card check: " + e.getMessage()); updateUI("Error starting card check: " + e.getMessage(), false); } } - private void stopCardCheck() { + private void startSimpleCardCheck() { + // Original simple card detection logic try { - if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { - MyApplication.app.readCardOptV2.cancelCheckCard(); - } - checkingCard = false; - btnCheckCard.setText(R.string.card_start_check_card); - updateUI("Card check stopped", false); - } catch (Exception e) { - android.util.Log.e(TAG, "Error stopping card check: " + e.getMessage()); - checkingCard = false; - btnCheckCard.setText(R.string.card_start_check_card); - } - } - - private void checkCreditCard() { - try { - // Validate application state if (!validateApplicationState()) { return; } - // If not connected, try to bind first if (!MyApplication.app.isConnectPaySDK()) { tvResult.setText("Connecting to PaySDK..."); android.util.Log.d(TAG, "PaySDK not connected, binding service..."); MyApplication.app.bindPaySDKService(); - // Wait and retry btnCheckCard.postDelayed(() -> { if (MyApplication.app.isConnectPaySDK() && MyApplication.app.readCardOptV2 != null) { - startCardScan(); + startSimpleCardScan(); } else { updateUI("Error: Failed to connect to PaySDK", false); } @@ -155,15 +441,76 @@ public class CreditCardActivity extends AppCompatActivity { return; } - startCardScan(); + startSimpleCardScan(); } catch (Exception e) { e.printStackTrace(); - android.util.Log.e(TAG, "Error in checkCreditCard: " + e.getMessage()); + android.util.Log.e(TAG, "Error in startSimpleCardCheck: " + e.getMessage()); updateUI("Error starting card scan: " + e.getMessage(), false); } } + private void startEMVCardCheck() { + try { + if (!validateApplicationState()) { + return; + } + + if (mEMVOptV2 == null) { + updateUI("Error: EMV not initialized", false); + android.util.Log.e(TAG, "EMV not initialized"); + return; + } + + // Initialize EMV process before card check + mEMVOptV2.initEmvProcess(); + initEmvTlvData(); + + tvResult.setText("EMV Mode: Starting card scan...\nPlease insert, swipe, or tap your card"); + + int cardType = AidlConstantsV2.CardType.NFC.getValue() | AidlConstantsV2.CardType.IC.getValue(); + android.util.Log.d(TAG, "Starting EMV checkCard with cardType: " + cardType); + + MyApplication.app.readCardOptV2.checkCard(cardType, mEMVCheckCardCallback, 60); + + } catch (Exception e) { + e.printStackTrace(); + android.util.Log.e(TAG, "Error in startEMVCardCheck: " + e.getMessage()); + updateUI("Error starting EMV card scan: " + e.getMessage(), false); + } + } + + private void startSimpleCardScan() { + try { + int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue(); + tvResult.setText("Simple Mode: Starting card scan...\nPlease insert, swipe, or tap your card"); + + android.util.Log.d(TAG, "Starting simple checkCard with cardType: " + cardType); + + MyApplication.app.readCardOptV2.checkCard(cardType, mSimpleCheckCardCallback, 60); + + } catch (Exception e) { + e.printStackTrace(); + android.util.Log.e(TAG, "Error in startSimpleCardScan: " + e.getMessage()); + updateUI("Error starting card scan: " + e.getMessage(), false); + } + } + + private void stopCardCheck() { + try { + if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { + MyApplication.app.readCardOptV2.cancelCheckCard(); + } + checkingCard = false; + btnCheckCard.setText("Start Scanning"); + updateModeDisplay(); + } catch (Exception e) { + android.util.Log.e(TAG, "Error stopping card check: " + e.getMessage()); + checkingCard = false; + btnCheckCard.setText("Start Scanning"); + } + } + private boolean validateApplicationState() { if (MyApplication.app == null) { updateUI("Error: Application not initialized", false); @@ -172,101 +519,124 @@ public class CreditCardActivity extends AppCompatActivity { } return true; } - - private void startCardScan() { - try { - int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue(); - tvResult.setText("Starting card scan...\nPlease insert, swipe, or tap your card"); - - android.util.Log.d(TAG, "Starting checkCard with cardType: " + cardType); - android.util.Log.d(TAG, "CardType values - MAGNETIC: " + CardType.MAGNETIC.getValue() + - ", IC: " + CardType.IC.getValue() + ", NFC: " + CardType.NFC.getValue()); - - MyApplication.app.readCardOptV2.checkCard(cardType, mCheckCardCallback, 60); - - } catch (Exception e) { - e.printStackTrace(); - android.util.Log.e(TAG, "Error in startCardScan: " + e.getMessage()); - updateUI("Error starting card scan: " + e.getMessage(), false); - } - } - private void updateUI(String message, boolean isCardDetected) { - runOnUiThread(() -> { - if (tvResult != null) { - tvResult.setText(message); - } - if (isCardDetected) { - checkingCard = false; - btnCheckCard.setText(R.string.card_start_check_card); - } - }); - } - - // ====== CALLBACK IMPLEMENTATION ====== - private final CheckCardCallbackV2 mCheckCardCallback = new CheckCardCallbackV2.Stub() { + // ====== SIMPLE CARD DETECTION CALLBACK ====== + private final CheckCardCallbackV2 mSimpleCheckCardCallback = new CheckCardCallbackV2.Stub() { @Override public void findMagCard(Bundle info) throws RemoteException { - android.util.Log.d(TAG, "findMagCard callback triggered"); - runOnUiThread(() -> handleMagCardResult(info)); + android.util.Log.d(TAG, "Simple Mode: findMagCard callback triggered"); + runOnUiThread(() -> handleSimpleMagCardResult(info)); } @Override public void findICCard(String atr) throws RemoteException { - android.util.Log.d(TAG, "findICCard callback triggered with ATR: " + atr); + android.util.Log.d(TAG, "Simple Mode: findICCard callback triggered with ATR: " + atr); Bundle info = new Bundle(); info.putString("atr", atr); - runOnUiThread(() -> handleICCardResult(info)); + runOnUiThread(() -> handleSimpleICCardResult(info)); } @Override public void findRFCard(String uuid) throws RemoteException { - android.util.Log.d(TAG, "findRFCard callback triggered with UUID: " + uuid); + android.util.Log.d(TAG, "Simple Mode: findRFCard callback triggered with UUID: " + uuid); Bundle info = new Bundle(); info.putString("uuid", uuid); - runOnUiThread(() -> handleRFCardResult(info)); + runOnUiThread(() -> handleSimpleRFCardResult(info)); } @Override public void onError(int code, String message) throws RemoteException { - android.util.Log.e(TAG, "onError callback triggered - Code: " + code + ", Message: " + message); + android.util.Log.e(TAG, "Simple Mode: onError callback triggered - Code: " + code + ", Message: " + message); Bundle info = new Bundle(); info.putInt("code", code); info.putString("message", message); - runOnUiThread(() -> handleErrorResult(info)); + runOnUiThread(() -> handleSimpleErrorResult(info)); } @Override public void findICCardEx(Bundle info) throws RemoteException { - android.util.Log.d(TAG, "findICCardEx callback triggered"); - runOnUiThread(() -> handleICCardResult(info)); + android.util.Log.d(TAG, "Simple Mode: findICCardEx callback triggered"); + runOnUiThread(() -> handleSimpleICCardResult(info)); } @Override public void findRFCardEx(Bundle info) throws RemoteException { - android.util.Log.d(TAG, "findRFCardEx callback triggered"); - runOnUiThread(() -> handleRFCardResult(info)); + android.util.Log.d(TAG, "Simple Mode: findRFCardEx callback triggered"); + runOnUiThread(() -> handleSimpleRFCardResult(info)); } @Override public void onErrorEx(Bundle info) throws RemoteException { - android.util.Log.e(TAG, "onErrorEx callback triggered"); - runOnUiThread(() -> handleErrorResult(info)); + android.util.Log.e(TAG, "Simple Mode: onErrorEx callback triggered"); + runOnUiThread(() -> handleSimpleErrorResult(info)); } }; - // ====== CARD RESULT HANDLERS ====== - private void handleMagCardResult(Bundle info) { - // Log dengan detail lengkap + // ====== EMV CARD DETECTION CALLBACK ====== + private final CheckCardCallbackV2 mEMVCheckCardCallback = new CheckCardCallbackV2.Stub() { + @Override + public void findMagCard(Bundle info) throws RemoteException { + android.util.Log.d(TAG, "EMV Mode: findMagCard callback triggered"); + runOnUiThread(() -> handleEMVMagCardResult(info)); + } + + @Override + public void findICCard(String atr) throws RemoteException { + android.util.Log.d(TAG, "EMV Mode: findICCard callback triggered with ATR: " + atr); + // IC card detected, start EMV process + MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0); + mCardType = AidlConstantsV2.CardType.IC.getValue(); + runOnUiThread(() -> startEMVTransactionProcess()); + } + + @Override + public void findRFCard(String uuid) throws RemoteException { + android.util.Log.d(TAG, "EMV Mode: findRFCard callback triggered with UUID: " + uuid); + // NFC card detected, start EMV process + mCardType = AidlConstantsV2.CardType.NFC.getValue(); + runOnUiThread(() -> startEMVTransactionProcess()); + } + + @Override + public void onError(int code, String message) throws RemoteException { + android.util.Log.e(TAG, "EMV Mode: onError callback triggered - Code: " + code + ", Message: " + message); + Bundle info = new Bundle(); + info.putInt("code", code); + info.putString("message", message); + runOnUiThread(() -> handleEMVErrorResult(info)); + } + + @Override + public void findICCardEx(Bundle info) throws RemoteException { + android.util.Log.d(TAG, "EMV Mode: findICCardEx callback triggered"); + mCardType = AidlConstantsV2.CardType.IC.getValue(); + runOnUiThread(() -> startEMVTransactionProcess()); + } + + @Override + public void findRFCardEx(Bundle info) throws RemoteException { + android.util.Log.d(TAG, "EMV Mode: findRFCardEx callback triggered"); + mCardType = AidlConstantsV2.CardType.NFC.getValue(); + runOnUiThread(() -> startEMVTransactionProcess()); + } + + @Override + public void onErrorEx(Bundle info) throws RemoteException { + android.util.Log.e(TAG, "EMV Mode: onErrorEx callback triggered"); + runOnUiThread(() -> handleEMVErrorResult(info)); + } + }; + + // ====== SIMPLE CARD RESULT HANDLERS ====== + private void handleSimpleMagCardResult(Bundle info) { logMagneticCardData(info); - // Update UI String track1 = Utility.null2String(info.getString("TRACK1")); String track2 = Utility.null2String(info.getString("TRACK2")); String track3 = Utility.null2String(info.getString("TRACK3")); StringBuilder sb = new StringBuilder() - .append(getString(R.string.card_mag_card_detected)).append("\n") + .append("MAGNETIC CARD DETECTED (Simple Mode)\n") .append("Track1: ").append(track1.isEmpty() ? "N/A" : track1).append("\n") .append("Track2: ").append(track2.isEmpty() ? "N/A" : track2).append("\n") .append("Track3: ").append(track3.isEmpty() ? "N/A" : track3); @@ -274,16 +644,14 @@ public class CreditCardActivity extends AppCompatActivity { updateUI(sb.toString(), true); } - private void handleICCardResult(Bundle info) { - // Log dengan detail lengkap + private void handleSimpleICCardResult(Bundle info) { logICCardData(info); - // Update UI String atr = info.getString("atr", ""); int cardType = info.getInt("cardType", -1); StringBuilder sb = new StringBuilder(); - sb.append(getString(R.string.card_ic_card_detected)).append("\n") + sb.append("IC CARD DETECTED (Simple Mode)\n") .append("ATR: ").append(atr.isEmpty() ? "N/A" : atr).append("\n"); if (cardType != -1) { sb.append("Card Type: ").append(cardType).append("\n"); @@ -292,17 +660,15 @@ public class CreditCardActivity extends AppCompatActivity { updateUI(sb.toString(), true); } - private void handleRFCardResult(Bundle info) { - // Log dengan detail lengkap + private void handleSimpleRFCardResult(Bundle info) { logRFCardData(info); - // Update UI String uuid = info.getString("uuid", ""); String ats = info.getString("ats", ""); int sak = info.getInt("sak", -1); StringBuilder sb = new StringBuilder(); - sb.append("RF/NFC Card Detected").append("\n") + sb.append("RF/NFC CARD DETECTED (Simple Mode)\n") .append("UUID: ").append(uuid.isEmpty() ? "N/A" : uuid).append("\n"); if (!ats.isEmpty()) { sb.append("ATS: ").append(ats).append("\n"); @@ -315,188 +681,681 @@ public class CreditCardActivity extends AppCompatActivity { updateUI(sb.toString(), true); } - private void handleErrorResult(Bundle info) { - // Log dengan detail lengkap + private void handleSimpleErrorResult(Bundle info) { logCardError(info); - // Update UI int code = info.getInt("code", -1); String msg = info.getString("message", "Unknown error"); String error = "Error: " + msg + " (Code: " + code + ")"; updateUI(error, true); } - // ====== ENHANCED LOGGING METHODS ====== - private void logMagneticCardData(Bundle info) { - android.util.Log.d(TAG, "======================================="); - android.util.Log.d(TAG, " MAGNETIC CARD DETECTED "); - android.util.Log.d(TAG, "======================================="); - - // Track Data - String track1 = Utility.null2String(info.getString("TRACK1")); - String track2 = Utility.null2String(info.getString("TRACK2")); - String track3 = Utility.null2String(info.getString("TRACK3")); - - android.util.Log.d(TAG, "TRACK DATA:"); - android.util.Log.d(TAG, " Track1: " + (track1.isEmpty() ? "N/A" : track1)); - android.util.Log.d(TAG, " Track2: " + (track2.isEmpty() ? "N/A" : track2)); - android.util.Log.d(TAG, " Track3: " + (track3.isEmpty() ? "N/A" : track3)); - - // Error Codes - int track1ErrorCode = info.getInt("track1ErrorCode", 0); - int track2ErrorCode = info.getInt("track2ErrorCode", 0); - int track3ErrorCode = info.getInt("track3ErrorCode", 0); - - android.util.Log.d(TAG, "ERROR CODES:"); - android.util.Log.d(TAG, " Track1 Error: " + track1ErrorCode); - android.util.Log.d(TAG, " Track2 Error: " + track2ErrorCode); - android.util.Log.d(TAG, " Track3 Error: " + track3ErrorCode); - - // Additional Data - String pan = info.getString("pan", ""); - String servicecode = info.getString("servicecode", ""); - String expiryDate = info.getString("expiry", ""); - String cardholderName = info.getString("cardholder", ""); - - android.util.Log.d(TAG, "PARSED DATA:"); - android.util.Log.d(TAG, " PAN: " + (pan.isEmpty() ? "N/A" : maskPAN(pan))); - android.util.Log.d(TAG, " Service Code: " + (servicecode.isEmpty() ? "N/A" : servicecode)); - android.util.Log.d(TAG, " Expiry Date: " + (expiryDate.isEmpty() ? "N/A" : expiryDate)); - android.util.Log.d(TAG, " Cardholder: " + (cardholderName.isEmpty() ? "N/A" : cardholderName)); - - // Raw Bundle Data - android.util.Log.d(TAG, "RAW BUNDLE DATA:"); - android.util.Log.d(TAG, " " + bundleToString(info)); - - android.util.Log.d(TAG, "======================================="); + // ====== EMV CARD RESULT HANDLERS ====== + private void handleEMVMagCardResult(Bundle info) { + // For magnetic cards in EMV mode, fall back to simple processing + handleSimpleMagCardResult(info); + // Add EMV suffix to indicate mode + String currentText = tvResult.getText().toString(); + tvResult.setText(currentText.replace("(Simple Mode)", "(EMV Mode - Magnetic)")); } - private void logICCardData(Bundle info) { - android.util.Log.d(TAG, "======================================="); - android.util.Log.d(TAG, " IC CARD DETECTED "); - android.util.Log.d(TAG, "======================================="); - - String atr = info.getString("atr", ""); - int cardType = info.getInt("cardType", -1); - String cardTypeStr = info.getString("cardTypeStr", ""); - int errorCode = info.getInt("errorCode", 0); - - android.util.Log.d(TAG, "BASIC INFO:"); - android.util.Log.d(TAG, " ATR: " + (atr.isEmpty() ? "N/A" : atr)); - android.util.Log.d(TAG, " Card Type: " + cardType); - android.util.Log.d(TAG, " Card Type String: " + (cardTypeStr.isEmpty() ? "N/A" : cardTypeStr)); - android.util.Log.d(TAG, " Error Code: " + errorCode); - - // Parse ATR if available - if (!atr.isEmpty()) { - android.util.Log.d(TAG, "ATR ANALYSIS:"); - android.util.Log.d(TAG, " ATR Length: " + atr.length() + " characters"); - android.util.Log.d(TAG, " ATR Bytes: " + atr.length()/2 + " bytes"); - } - - // Check for additional fields - String[] possibleKeys = {"voltage", "protocol", "clockRate", "baudRate", - "historicalBytes", "interfaceBytes", "checkByte"}; - - android.util.Log.d(TAG, "ADDITIONAL DATA:"); - for (String key : possibleKeys) { - if (info.containsKey(key)) { - android.util.Log.d(TAG, " " + key + ": " + info.get(key)); - } - } - - // Raw Bundle Data - android.util.Log.d(TAG, "RAW BUNDLE DATA:"); - android.util.Log.d(TAG, " " + bundleToString(info)); - - android.util.Log.d(TAG, "======================================="); - } - - private void logRFCardData(Bundle info) { - android.util.Log.d(TAG, "======================================="); - android.util.Log.d(TAG, " RF/NFC CARD DETECTED "); - android.util.Log.d(TAG, "======================================="); - - String uuid = info.getString("uuid", ""); - String ats = info.getString("ats", ""); - int cardType = info.getInt("cardType", -1); - int sak = info.getInt("sak", -1); - int cardCategory = info.getInt("cardCategory", -1); - byte[] atqa = info.getByteArray("atqa"); - int errorCode = info.getInt("errorCode", 0); - - android.util.Log.d(TAG, "BASIC INFO:"); - android.util.Log.d(TAG, " UUID: " + (uuid.isEmpty() ? "N/A" : uuid)); - android.util.Log.d(TAG, " ATS: " + (ats.isEmpty() ? "N/A" : ats)); - android.util.Log.d(TAG, " Card Type: " + cardType); - android.util.Log.d(TAG, " SAK: " + (sak == -1 ? "N/A" : String.format("0x%02X (%d)", sak, sak))); - android.util.Log.d(TAG, " Card Category: " + cardCategory); - android.util.Log.d(TAG, " Error Code: " + errorCode); - - if (atqa != null && atqa.length > 0) { - android.util.Log.d(TAG, " ATQA: " + ByteUtil.bytes2HexStr(atqa) + - " (" + atqa.length + " bytes)"); - } else { - android.util.Log.d(TAG, " ATQA: N/A"); - } - - // Analyze card type based on SAK - if (sak != -1) { - String cardTypeAnalysis = analyzeCardTypeBySAK(sak); - android.util.Log.d(TAG, "CARD TYPE ANALYSIS:"); - android.util.Log.d(TAG, " " + cardTypeAnalysis); - } - - // Check for additional NFC fields - String[] possibleKeys = {"historicalBytes", "applicationData", "protocolInfo", - "maxDataRate", "supportedProtocols", "manufacturerData"}; - - android.util.Log.d(TAG, "ADDITIONAL DATA:"); - for (String key : possibleKeys) { - if (info.containsKey(key)) { - Object value = info.get(key); - if (value instanceof byte[]) { - android.util.Log.d(TAG, " " + key + ": " + - ByteUtil.bytes2HexStr((byte[]) value)); - } else { - android.util.Log.d(TAG, " " + key + ": " + value); - } - } - } - - // Raw Bundle Data - android.util.Log.d(TAG, "RAW BUNDLE DATA:"); - android.util.Log.d(TAG, " " + bundleToString(info)); - - android.util.Log.d(TAG, "======================================="); - } - - private void logCardError(Bundle info) { - android.util.Log.e(TAG, "======================================="); - android.util.Log.e(TAG, " CARD READ ERROR "); - android.util.Log.e(TAG, "======================================="); + private void handleEMVErrorResult(Bundle info) { + logCardError(info); int code = info.getInt("code", -1); - String message = info.getString("message", ""); - String details = info.getString("details", ""); + String msg = info.getString("message", "Unknown error"); + String error = "EMV Error: " + msg + " (Code: " + code + ")"; + updateUI(error, true); + } + + // ====== EMV TRANSACTION PROCESS ====== + private void startEMVTransactionProcess() { + android.util.Log.d(TAG, "Starting EMV transaction process"); + try { + Bundle bundle = new Bundle(); + bundle.putString("amount", "100"); // Default amount 1.00 + bundle.putString("transType", "00"); + + if (mCardType == AidlConstantsV2.CardType.NFC.getValue()) { + bundle.putInt("flowType", AidlConstantsV2.EMV.FlowType.TYPE_EMV_STANDARD); + } else { + bundle.putInt("flowType", AidlConstantsV2.EMV.FlowType.TYPE_EMV_STANDARD); + } + bundle.putInt("cardType", mCardType); + + tvResult.setText("EMV processing started...\nReading card data..."); + + mEMVOptV2.transactProcessEx(bundle, mEMVListener); + } catch (Exception e) { + android.util.Log.e(TAG, "Error starting EMV transaction: " + e.getMessage()); + e.printStackTrace(); + updateUI("Error starting EMV transaction: " + e.getMessage(), true); + } + } + + // ====== EMV LISTENER ====== + private final EMVListenerV2 mEMVListener = new EMVListenerV2.Stub() { - android.util.Log.e(TAG, "ERROR INFO:"); - android.util.Log.e(TAG, " Error Code: " + code); - android.util.Log.e(TAG, " Error Message: " + (message.isEmpty() ? "N/A" : message)); - android.util.Log.e(TAG, " Error Details: " + (details.isEmpty() ? "N/A" : details)); + @Override + public void onWaitAppSelect(List appNameList, boolean isFirstSelect) throws RemoteException { + android.util.Log.d(TAG, "onWaitAppSelect isFirstSelect:" + isFirstSelect); + mProcessStep = EMV_APP_SELECT; + String[] candidateNames = getCandidateNames(appNameList); + mHandler.obtainMessage(EMV_APP_SELECT, candidateNames).sendToTarget(); + } + + @Override + public void onAppFinalSelect(String tag9F06Value) throws RemoteException { + android.util.Log.d(TAG, "onAppFinalSelect tag9F06Value:" + tag9F06Value); + mProcessStep = EMV_FINAL_APP_SELECT; + mHandler.obtainMessage(EMV_FINAL_APP_SELECT, tag9F06Value).sendToTarget(); + } + + @Override + public void onConfirmCardNo(String cardNo) throws RemoteException { + android.util.Log.d(TAG, "onConfirmCardNo cardNo:" + maskCardNumber(cardNo)); + mCardNo = cardNo; + mProcessStep = EMV_CONFIRM_CARD_NO; + mHandler.obtainMessage(EMV_CONFIRM_CARD_NO).sendToTarget(); + } + + @Override + public void onRequestShowPinPad(int pinType, int remainTime) throws RemoteException { + android.util.Log.d(TAG, "onRequestShowPinPad pinType:" + pinType + " remainTime:" + remainTime); + mPinType = pinType; + if (mCardNo == null) { + mCardNo = getCardNo(); + } + mProcessStep = EMV_SHOW_PIN_PAD; + mHandler.obtainMessage(EMV_SHOW_PIN_PAD).sendToTarget(); + } + + @Override + public void onRequestSignature() throws RemoteException { + android.util.Log.d(TAG, "onRequestSignature"); + mProcessStep = EMV_SIGNATURE; + mHandler.obtainMessage(EMV_SIGNATURE).sendToTarget(); + } + + @Override + public void onCertVerify(int certType, String certInfo) throws RemoteException { + android.util.Log.d(TAG, "onCertVerify certType:" + certType + " certInfo:" + certInfo); + mCertInfo = certInfo; + mProcessStep = EMV_CERT_VERIFY; + mHandler.obtainMessage(EMV_CERT_VERIFY).sendToTarget(); + } + + @Override + public void onOnlineProc() throws RemoteException { + android.util.Log.d(TAG, "onOnlineProcess"); + mProcessStep = EMV_ONLINE_PROCESS; + mHandler.obtainMessage(EMV_ONLINE_PROCESS).sendToTarget(); + } + + @Override + public void onCardDataExchangeComplete() throws RemoteException { + android.util.Log.d(TAG, "onCardDataExchangeComplete"); + if (mCardType == AidlConstantsV2.CardType.NFC.getValue()) { + MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0); + } + } + + @Override + public void onTransResult(int code, String desc) throws RemoteException { + if (mCardNo == null) { + mCardNo = getCardNo(); + } + android.util.Log.d(TAG, "onTransResult code:" + code + " desc:" + desc); + + if (code == 1 || code == 2 || code == 5 || code == 6) { + mHandler.obtainMessage(EMV_TRANS_SUCCESS, code, code, desc).sendToTarget(); + } else { + mHandler.obtainMessage(EMV_TRANS_FAIL, code, code, desc).sendToTarget(); + } + } + + @Override + public void onConfirmationCodeVerified() throws RemoteException { + android.util.Log.d(TAG, "onConfirmationCodeVerified"); + // Handle See Phone flow + } + + @Override + public void onRequestDataExchange(String cardNo) throws RemoteException { + android.util.Log.d(TAG, "onRequestDataExchange,cardNo:" + cardNo); + mEMVOptV2.importDataExchangeStatus(0); + } + + @Override + public void onTermRiskManagement() throws RemoteException { + android.util.Log.d(TAG, "onTermRiskManagement"); + mEMVOptV2.importTermRiskManagementStatus(0); + } + + @Override + public void onPreFirstGenAC() throws RemoteException { + android.util.Log.d(TAG, "onPreFirstGenAC"); + mEMVOptV2.importPreFirstGenACStatus(0); + } + + @Override + public void onDataStorageProc(String[] containerID, String[] containerContent) throws RemoteException { + android.util.Log.d(TAG, "onDataStorageProc"); + String[] tags = new String[0]; + String[] values = new String[0]; + mEMVOptV2.importDataStorage(tags, values); + } + }; + + // ====== EMV DIALOG HANDLERS ====== + private void showAppSelectDialog(String[] candidateNames) { + mAppSelectDialog = new AlertDialog.Builder(this) + .setTitle("Please select application") + .setNegativeButton("Cancel", (dialog, which) -> importAppSelect(-1)) + .setPositiveButton("OK", (dialog, which) -> importAppSelect(mSelectIndex)) + .setSingleChoiceItems(candidateNames, 0, (dialog, which) -> mSelectIndex = which) + .create(); + mSelectIndex = 0; + mAppSelectDialog.show(); + } + + private void showCardNumberConfirmation() { + tvResult.setText("Card Number: " + maskCardNumber(mCardNo) + "\n\nPlease confirm card number"); + btnCheckCard.setText("Confirm"); + btnCheckCard.setOnClickListener(v -> { + importCardNoStatus(0); + btnCheckCard.setText("Processing..."); + btnCheckCard.setOnClickListener(null); + }); + } + + private void showCertificateVerification() { + tvResult.setText("Certificate Info: " + mCertInfo + "\n\nPlease confirm certificate"); + btnCheckCard.setText("Confirm"); + btnCheckCard.setOnClickListener(v -> { + importCertStatus(0); + btnCheckCard.setText("Processing..."); + btnCheckCard.setOnClickListener(null); + }); + } + + // ====== TLV DATA RETRIEVAL ====== + private void getTlvData() { + android.util.Log.d(TAG, "======== RETRIEVING COMPLETE CARD DATA ========"); - // Interpret common error codes - String interpretation = interpretErrorCode(code); - android.util.Log.e(TAG, " Interpretation: " + interpretation); - - // Raw Bundle Data - android.util.Log.e(TAG, "RAW ERROR DATA:"); - android.util.Log.e(TAG, " " + bundleToString(info)); - - android.util.Log.e(TAG, "======================================="); + try { + // Comprehensive TLV tag list based on EMV specifications + String[] standardTagList = { + "4F", "50", "57", "5A", "5F20", "5F24", "5F25", "5F28", "5F2A", "5F2D", "5F30", "5F34", + "82", "84", "87", "88", "8A", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", "95", + "9A", "9B", "9C", "9D", "9F01", "9F02", "9F03", "9F04", "9F05", "9F06", "9F07", "9F08", "9F09", + "9F0D", "9F0E", "9F0F", "9F10", "9F11", "9F12", "9F13", "9F14", "9F15", "9F16", "9F17", "9F18", + "9F1A", "9F1B", "9F1C", "9F1D", "9F1E", "9F1F", "9F20", "9F21", "9F22", "9F23", "9F26", "9F27", + "9F2D", "9F2E", "9F2F", "9F32", "9F33", "9F34", "9F35", "9F36", "9F37", "9F38", "9F39", "9F3A", + "9F3B", "9F3C", "9F3D", "9F40", "9F41", "9F42", "9F43", "9F44", "9F45", "9F46", "9F47", "9F48", + "9F49", "9F4A", "9F4B", "9F4C", "9F4D", "9F4E", "9F53", "9F54", "9F55", "9F56", "9F57", "9F58", + "9F59", "9F5A", "9F5B", "9F5C", "9F5D", "9F5E", "9F61", "9F62", "9F63", "9F64", "9F65", "9F66", + "9F67", "9F68", "9F69", "9F6A", "9F6B", "9F6C", "9F6D", "9F6E", "9F70", "9F71", "9F72", "9F73", + "9F74", "9F75", "9F76", "9F77", "9F78", "9F79", "9F7A", "9F7B", "9F7C", "9F7D", "9F7E", "9F7F" + }; + + // PayPass specific tags + String[] payPassTagList = { + "DF810C", "DF8117", "DF8118", "DF8119", "DF811A", "DF811B", "DF811C", "DF811D", "DF811E", "DF811F", + "DF8120", "DF8121", "DF8122", "DF8123", "DF8124", "DF8125", "DF8126", "DF8127", "DF8128", "DF8129", + "DF812A", "DF812B", "DF812C", "DF812D", "DF812E", "DF812F", "DF8130", "DF8131", "DF8132", "DF8133", + "DF8134", "DF8135", "DF8136", "DF8137", "DF8138", "DF8139", "DF813A", "DF813B", "DF813C", "DF813D", + "DF8161", "DF8167", "DF8168", "DF8169", "DF8170" + }; + + byte[] outData = new byte[4096]; + Map allTlvMap = new TreeMap<>(); + + // Determine TLV operation code based on card type + int tlvOpCode = AidlConstantsV2.EMV.TLVOpCode.OP_NORMAL; + if (mCardType == AidlConstantsV2.CardType.NFC.getValue()) { + tlvOpCode = AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS; + } + + android.util.Log.d(TAG, "Using TLV OpCode: " + tlvOpCode); + android.util.Log.d(TAG, "Requesting " + standardTagList.length + " standard tags"); + + // Get standard EMV tags + int len = mEMVOptV2.getTlvList(tlvOpCode, standardTagList, outData); + if (len > 0) { + byte[] bytes = Arrays.copyOf(outData, len); + String hexStr = ByteUtil.bytes2HexStr(bytes); + android.util.Log.d(TAG, "Retrieved " + len + " bytes of standard TLV data"); + + Map tlvMap = TLVUtils.buildTLVMap(hexStr); + allTlvMap.putAll(tlvMap); + android.util.Log.d(TAG, "Parsed " + tlvMap.size() + " standard TLV tags"); + } + + // Get PayPass specific tags (if NFC card) + if (mCardType == AidlConstantsV2.CardType.NFC.getValue()) { + android.util.Log.d(TAG, "Requesting " + payPassTagList.length + " PayPass specific tags"); + + len = mEMVOptV2.getTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS, payPassTagList, outData); + if (len > 0) { + byte[] bytes = Arrays.copyOf(outData, len); + String hexStr = ByteUtil.bytes2HexStr(bytes); + android.util.Log.d(TAG, "Retrieved " + len + " bytes of PayPass TLV data"); + + Map payPassTlvMap = TLVUtils.buildTLVMap(hexStr); + allTlvMap.putAll(payPassTlvMap); + android.util.Log.d(TAG, "Parsed " + payPassTlvMap.size() + " PayPass TLV tags"); + } + } + + // Build comprehensive display output + StringBuilder sb = new StringBuilder(); + + // ====== DISPLAY ALL TLV DATA LIKE DEMO APP ====== + android.util.Log.d(TAG, "======== ALL TLV DATA ========"); + + Set keySet = allTlvMap.keySet(); + for (String key : keySet) { + TLV tlv = allTlvMap.get(key); + String value = tlv != null ? tlv.getValue() : ""; + String description = getTlvDescription(key); + + // Format like the demo app + sb.append(key); + if (!description.equals("Unknown")) { + sb.append(" (").append(description).append(")"); + } + sb.append(": "); + + // Special formatting for specific tags + if (key.equals("5A") && !value.isEmpty()) { + // PAN - show full number + sb.append(value); + } else if (key.equals("50") && !value.isEmpty()) { + // Application Label - convert hex to string + String decodedLabel = hexToString(value); + sb.append(value).append(" (").append(decodedLabel).append(")"); + } else if (key.equals("5F20") && !value.isEmpty()) { + // Cardholder Name - convert hex to string + String decodedName = hexToString(value); + sb.append(value).append(" (").append(decodedName.trim()).append(")"); + } else if (key.equals("9F06") && !value.isEmpty()) { + // Application Identifier - show scheme + String scheme = identifyPaymentScheme(value); + sb.append(value).append(" (").append(scheme).append(")"); + } else if (key.equals("5F24") && !value.isEmpty()) { + // Expiry Date - format YYMMDD + sb.append(value); + if (value.length() == 6) { + sb.append(" (").append(value.substring(2, 4)).append("/").append(value.substring(0, 2)).append(")"); + } + } else if (key.equals("9F02") && !value.isEmpty()) { + // Amount - format as currency + sb.append(value); + try { + long amount = Long.parseLong(value, 16); + sb.append(" (").append(String.format("%.2f", amount / 100.0)).append(")"); + } catch (Exception e) { + // Keep original value + } + } else { + sb.append(value); + } + + sb.append("\n"); + + // Log each TLV + android.util.Log.d(TAG, "Tag " + key + " (" + description + "): " + value); + } + + sb.append("\nTotal TLV tags retrieved: ").append(keySet.size()); + + android.util.Log.d(TAG, "Total TLV tags retrieved: " + keySet.size()); + android.util.Log.d(TAG, "=================================="); + + // Also create a summary view + StringBuilder summary = new StringBuilder(); + summary.append("==== CARD SUMMARY ====\n"); + + // Card Number (show full number) + TLV panTlv = allTlvMap.get("5A"); + if (panTlv != null && !TextUtils.isEmpty(panTlv.getValue())) { + summary.append("Card Number: ").append(panTlv.getValue()).append("\n"); + } + + // Application Label + TLV labelTlv = allTlvMap.get("50"); + if (labelTlv != null && !TextUtils.isEmpty(labelTlv.getValue())) { + summary.append("App Label: ").append(hexToString(labelTlv.getValue())).append("\n"); + } + + // Cardholder Name + TLV nameTlv = allTlvMap.get("5F20"); + if (nameTlv != null && !TextUtils.isEmpty(nameTlv.getValue())) { + summary.append("Cardholder: ").append(hexToString(nameTlv.getValue()).trim()).append("\n"); + } + + // Expiry Date + TLV expiryTlv = allTlvMap.get("5F24"); + if (expiryTlv != null && !TextUtils.isEmpty(expiryTlv.getValue())) { + String expiry = expiryTlv.getValue(); + if (expiry.length() == 6) { + summary.append("Expiry: ").append(expiry.substring(2, 4)).append("/").append(expiry.substring(0, 2)).append("\n"); + } + } + + // Card Scheme + TLV aidTlv = allTlvMap.get("9F06"); + if (aidTlv != null && !TextUtils.isEmpty(aidTlv.getValue())) { + summary.append("Scheme: ").append(identifyPaymentScheme(aidTlv.getValue())).append("\n"); + } + + summary.append("\n").append("==== ALL TLV DATA ====\n").append(sb.toString()); + + android.util.Log.d(TAG, "Complete card data retrieved successfully"); + updateUI(summary.toString(), true); + + } catch (Exception e) { + android.util.Log.e(TAG, "Error retrieving TLV data: " + e.getMessage()); + e.printStackTrace(); + updateUI("Error retrieving card data: " + e.getMessage(), true); + } } // ====== HELPER METHODS ====== + + /** + * Get comprehensive TLV tag description for better logging and display + */ + private String getTlvDescription(String tag) { + switch (tag.toUpperCase()) { + // Standard EMV Tags + case "4F": return "Application Identifier"; + case "50": return "Application Label"; + case "57": return "Track 2 Equivalent Data"; + case "5A": return "Application PAN"; + case "5F20": return "Cardholder Name"; + case "5F24": return "Application Expiry Date"; + case "5F25": return "Application Effective Date"; + case "5F28": return "Issuer Country Code"; + case "5F2A": return "Transaction Currency Code"; + case "5F2D": return "Language Preference"; + case "5F30": return "Service Code"; + case "5F34": return "PAN Sequence Number"; + case "82": return "Application Interchange Profile"; + case "84": return "Dedicated File Name"; + case "87": return "Application Priority Indicator"; + case "88": return "Short File Identifier"; + case "8A": return "Authorization Response Code"; + case "8C": return "Card Risk Management Data Object List 1"; + case "8D": return "Card Risk Management Data Object List 2"; + case "8E": return "Cardholder Verification Method List"; + case "8F": return "Certification Authority Public Key Index"; + case "90": return "Issuer Public Key Certificate"; + case "91": return "Issuer Authentication Data"; + case "92": return "Issuer Public Key Remainder"; + case "93": return "Signed Static Application Data"; + case "94": return "Application File Locator"; + case "95": return "Terminal Verification Results"; + case "9A": return "Transaction Date"; + case "9B": return "Transaction Status Information"; + case "9C": return "Transaction Type"; + case "9D": return "Directory Definition File Name"; + case "9F01": return "Acquirer Identifier"; + case "9F02": return "Amount Authorized"; + case "9F03": return "Amount Other"; + case "9F04": return "Amount Other (Binary)"; + case "9F05": return "Application Discretionary Data"; + case "9F06": return "Application Identifier"; + case "9F07": return "Application Usage Control"; + case "9F08": return "Application Version Number"; + case "9F09": return "Application Version Number"; + case "9F0D": return "Issuer Action Code - Default"; + case "9F0E": return "Issuer Action Code - Denial"; + case "9F0F": return "Issuer Action Code - Online"; + case "9F10": return "Issuer Application Data"; + case "9F11": return "Issuer Code Table Index"; + case "9F12": return "Application Preferred Name"; + case "9F13": return "Last Online Application Transaction Counter"; + case "9F14": return "Lower Consecutive Offline Limit"; + case "9F15": return "Merchant Category Code"; + case "9F16": return "Merchant Identifier"; + case "9F17": return "PIN Try Counter"; + case "9F18": return "Issuer Script Identifier"; + case "9F1A": return "Terminal Country Code"; + case "9F1B": return "Terminal Floor Limit"; + case "9F1C": return "Terminal Identification"; + case "9F1D": return "Terminal Risk Management Data"; + case "9F1E": return "Interface Device Serial Number"; + case "9F1F": return "Track 1 Discretionary Data"; + case "9F20": return "Track 2 Discretionary Data"; + case "9F21": return "Transaction Time"; + case "9F22": return "Certification Authority Public Key Index"; + case "9F23": return "Upper Consecutive Offline Limit"; + case "9F26": return "Application Cryptogram"; + case "9F27": return "Cryptogram Information Data"; + case "9F2D": return "ICC PIN Encipherment Public Key Certificate"; + case "9F2E": return "ICC PIN Encipherment Public Key Exponent"; + case "9F2F": return "ICC PIN Encipherment Public Key Remainder"; + case "9F32": return "Issuer Public Key Exponent"; + case "9F33": return "Terminal Capabilities"; + case "9F34": return "CVM Results"; + case "9F35": return "Terminal Type"; + case "9F36": return "Application Transaction Counter"; + case "9F37": return "Unpredictable Number"; + case "9F38": return "Processing Options Data Object List"; + case "9F39": return "Point-of-Service Entry Mode"; + case "9F3A": return "Amount Reference Currency"; + case "9F3B": return "Currency Code Application"; + case "9F3C": return "Transaction Reference Currency Code"; + case "9F3D": return "Transaction Reference Currency Exponent"; + case "9F40": return "Additional Terminal Capabilities"; + case "9F41": return "Transaction Sequence Counter"; + case "9F42": return "Application Currency Code"; + case "9F43": return "Application Reference Currency Exponent"; + case "9F44": return "Application Currency Exponent"; + case "9F45": return "Data Authentication Code"; + case "9F46": return "ICC Public Key Certificate"; + case "9F47": return "ICC Public Key Exponent"; + case "9F48": return "ICC Public Key Remainder"; + case "9F49": return "Dynamic Data Authentication Data Object List"; + case "9F4A": return "Static Data Authentication Tag List"; + case "9F4B": return "Signed Dynamic Application Data"; + case "9F4C": return "ICC Dynamic Number"; + case "9F4D": return "Log Entry"; + case "9F4E": return "Merchant Name and Location"; + case "9F53": return "Transaction Category Code"; + case "9F54": return "Cumulative Total Transaction Amount Limit"; + case "9F55": return "Geographic Indicator"; + case "9F56": return "Issuer Authentication Indicator"; + case "9F57": return "Issuer Country Code"; + case "9F58": return "Lower Cumulative Offline Transaction Amount Limit"; + case "9F59": return "Upper Cumulative Offline Transaction Amount Limit"; + case "9F5A": return "Application Program Identifier"; + case "9F5B": return "Issuer Script Results"; + case "9F5C": return "Cumulative Total Transaction Amount Upper Limit"; + case "9F5D": return "Available Offline Spending Amount"; + case "9F5E": return "Consecutive Transaction Limit (International)"; + case "9F61": return "Point-of-Service Environment"; + case "9F62": return "PCVC3 Track1"; + case "9F63": return "PUNATC Track1"; + case "9F64": return "NATC Track1"; + case "9F65": return "PCVC3 Track2"; + case "9F66": return "Terminal Transaction Qualifiers"; + case "9F67": return "NATC Track2"; + case "9F68": return "Mag Stripe CVM List"; + case "9F69": return "Unpredictable Number Data Object List"; + case "9F6A": return "Unpredictable Number"; + case "9F6B": return "Track 2 Data"; + case "9F6C": return "Card Transaction Qualifiers"; + case "9F6D": return "Mag Stripe Application Version Number"; + case "9F6E": return "Unknown"; + case "9F70": return "Protected Data Envelope 1"; + case "9F71": return "Protected Data Envelope 2"; + case "9F72": return "Protected Data Envelope 3"; + case "9F73": return "Protected Data Envelope 4"; + case "9F74": return "Protected Data Envelope 5"; + case "9F75": return "Unprotected Data Envelope 1"; + case "9F76": return "Unprotected Data Envelope 2"; + case "9F77": return "Unprotected Data Envelope 3"; + case "9F78": return "Unprotected Data Envelope 4"; + case "9F79": return "Unprotected Data Envelope 5"; + case "9F7A": return "VLP Issuer Authorization Code"; + case "9F7B": return "VLP Terminal Transaction Limit"; + case "9F7C": return "Customer Exclusive Data"; + case "9F7D": return "Unknown"; + case "9F7E": return "Application Life Cycle Data"; + case "9F7F": return "Card Production Life Cycle Data"; + + // PayPass/Contactless specific tags (DF81xx range) + case "DF810C": return "PayPass - Mag Stripe CVM Capability No CVM Required"; + case "DF8117": return "PayPass - Terminal Capabilities"; + case "DF8118": return "PayPass - Additional Terminal Capabilities"; + case "DF8119": return "PayPass - Terminal Type"; + case "DF811A": return "PayPass - Kernel Configuration"; + case "DF811B": return "PayPass - Mag Stripe Application Version Number"; + case "DF811C": return "PayPass - Mag Stripe CVM Capability CVM Required"; + case "DF811D": return "PayPass - Mag Stripe CVM Capability No CVM Required"; + case "DF811E": return "PayPass - Message Hold Time"; + case "DF811F": return "PayPass - Security Capability"; + case "DF8120": return "PayPass - Kernel Identifier"; + case "DF8121": return "PayPass - Kernel Configuration"; + case "DF8122": return "PayPass - Maximum Torn Transaction Log Records"; + case "DF8123": return "PayPass - Torn Transaction Log Data Element"; + case "DF8124": return "PayPass - Max Lifetime of Torn Transaction Log Record"; + case "DF8125": return "PayPass - Max Number of Torn Transaction Log Records"; + case "DF8126": return "PayPass - Mag Stripe CVM Capability"; + case "DF8127": return "PayPass - CVM Capability - CVM Required"; + case "DF8128": return "PayPass - CVM Capability - No CVM Required"; + case "DF8129": return "PayPass - Card Data Input Capability"; + case "DF812A": return "PayPass - CVM Required Limit"; + case "DF812B": return "PayPass - No CVM Required Limit"; + case "DF812C": return "PayPass - Transaction Limit (No On-device CVM)"; + case "DF812D": return "PayPass - Transaction Limit (On-device CVM)"; + case "DF812E": return "PayPass - CVM Required Limit"; + case "DF812F": return "PayPass - No CVM Required Limit"; + case "DF8130": return "PayPass - Floor Limit Check"; + case "DF8131": return "PayPass - Terminal Action Code - Default"; + case "DF8132": return "PayPass - Terminal Action Code - Denial"; + case "DF8133": return "PayPass - Terminal Action Code - Online"; + case "DF8134": return "PayPass - Reader Contactless Transaction Limit (No On-device CVM)"; + case "DF8135": return "PayPass - Reader Contactless Transaction Limit (On-device CVM)"; + case "DF8136": return "PayPass - Reader CVM Required Limit"; + case "DF8137": return "PayPass - Reader Contactless Floor Limit"; + case "DF8138": return "PayPass - Max Torn Record Lifetime"; + case "DF8139": return "PayPass - Mag-stripe CVM Capability - CVM Required"; + case "DF813A": return "PayPass - Mag-stripe CVM Capability - No CVM Required"; + case "DF813B": return "PayPass - Kernel Configuration"; + case "DF813C": return "PayPass - Mag Stripe Application Version Number"; + case "DF813D": return "PayPass - Mag Stripe CVM Capability"; + case "DF8161": return "JCB - Terminal Transaction Qualifiers"; + case "DF8167": return "AMEX - Terminal Capabilities"; + case "DF8168": return "AMEX - Additional Terminal Capabilities"; + case "DF8169": return "AMEX - Terminal Type"; + case "DF8170": return "AMEX - Message Hold Time"; + + default: return "Unknown"; + } + } + + private String getCardNo() { + try { + String[] tagList = {"57", "5A"}; + byte[] outData = new byte[256]; + int len = mEMVOptV2.getTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_NORMAL, tagList, outData); + if (len <= 0) { + return ""; + } + byte[] bytes = Arrays.copyOf(outData, len); + Map tlvMap = TLVUtils.buildTLVMap(bytes); + + if (tlvMap.get("57") != null && !TextUtils.isEmpty(Objects.requireNonNull(tlvMap.get("57")).getValue())) { + TLV tlv57 = tlvMap.get("57"); + CardInfo cardInfo = parseTrack2(tlv57.getValue()); + return cardInfo.cardNo; + } + if (tlvMap.get("5A") != null && !TextUtils.isEmpty(Objects.requireNonNull(tlvMap.get("5A")).getValue())) { + return Objects.requireNonNull(tlvMap.get("5A")).getValue(); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + return ""; + } + + public static CardInfo parseTrack2(String track2) { + String track_2 = stringFilter(track2); + int index = track_2.indexOf("="); + if (index == -1) { + index = track_2.indexOf("D"); + } + CardInfo cardInfo = new CardInfo(); + if (index == -1) { + return cardInfo; + } + String cardNumber = ""; + if (track_2.length() > index) { + cardNumber = track_2.substring(0, index); + } + String expiryDate = ""; + if (track_2.length() > index + 5) { + expiryDate = track_2.substring(index + 1, index + 5); + } + String serviceCode = ""; + if (track_2.length() > index + 8) { + serviceCode = track_2.substring(index + 5, index + 8); + } + cardInfo.cardNo = cardNumber; + cardInfo.expireDate = expiryDate; + cardInfo.serviceCode = serviceCode; + return cardInfo; + } + + static String stringFilter(String str) { + String regEx = "[^0-9=D]"; + Pattern p = Pattern.compile(regEx); + Matcher matcher = p.matcher(str); + return matcher.replaceAll("").trim(); + } + + private String maskCardNumber(String cardNo) { + if (cardNo == null || cardNo.length() < 8) { + return cardNo; + } + String first4 = cardNo.substring(0, 4); + String last4 = cardNo.substring(cardNo.length() - 4); + StringBuilder middle = new StringBuilder(); + for (int i = 0; i < cardNo.length() - 8; i++) { + middle.append("*"); + } + return first4 + middle.toString() + last4; + } + + private String identifyPaymentScheme(String aid) { + if (aid == null) return "Unknown"; + + if (aid.startsWith("A000000333")) return "UnionPay"; + else if (aid.startsWith("A000000003")) return "Visa"; + else if (aid.startsWith("A000000004") || aid.startsWith("A000000005")) return "MasterCard"; + else if (aid.startsWith("A000000025")) return "American Express"; + else if (aid.startsWith("A000000065")) return "JCB"; + else if (aid.startsWith("A000000524")) return "RuPay"; + else return "Unknown (" + aid + ")"; + } + + private String hexToString(String hex) { + try { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hex.length(); i += 2) { + String str = hex.substring(i, i + 2); + sb.append((char) Integer.parseInt(str, 16)); + } + return sb.toString().trim(); + } catch (Exception e) { + return hex; + } + } + private String analyzeCardTypeBySAK(int sak) { switch (sak & 0xFF) { case 0x00: return "MIFARE Ultralight"; @@ -513,61 +1372,267 @@ public class CreditCardActivity extends AppCompatActivity { } } - private String maskPAN(String pan) { - if (pan == null || pan.length() < 8) { - return pan; + private String[] getCandidateNames(List candiList) { + if (candiList == null || candiList.size() == 0) return new String[0]; + String[] result = new String[candiList.size()]; + for (int i = 0; i < candiList.size(); i++) { + EMVCandidateV2 candi = candiList.get(i); + String name = candi.appPreName; + name = TextUtils.isEmpty(name) ? candi.appLabel : name; + name = TextUtils.isEmpty(name) ? candi.appName : name; + name = TextUtils.isEmpty(name) ? "" : name; + result[i] = name; } - - String firstFour = pan.substring(0, 4); - String lastFour = pan.substring(pan.length() - 4); - StringBuilder middle = new StringBuilder(); - for (int i = 0; i < pan.length() - 8; i++) { - middle.append("*"); - } - - return firstFour + middle.toString() + lastFour; + return result; } - private String interpretErrorCode(int errorCode) { - switch (errorCode) { - case 0: return "No Error"; - case -1: return "General Error"; - case -2: return "Timeout"; - case -3: return "User Cancelled"; - case -4: return "Card Removed"; - case -5: return "Hardware Error"; - case -6: return "Communication Error"; - case -7: return "Card Not Supported"; - case -8: return "Invalid Parameter"; - case -9: return "Service Not Available"; - case -10: return "Permission Denied"; - default: return "Unknown Error Code: " + errorCode; + // ====== EMV IMPORT METHODS ====== + private void importAppSelect(int selectIndex) { + try { + mEMVOptV2.importAppSelect(selectIndex); + } catch (Exception e) { + e.printStackTrace(); } } - /** - * Helper method to convert Bundle to readable string for logging - */ - private String bundleToString(Bundle bundle) { - if (bundle == null) return "null"; - - StringBuilder sb = new StringBuilder(); - sb.append("{"); - for (String key : bundle.keySet()) { - Object value = bundle.get(key); - sb.append(key).append("="); - if (value instanceof byte[]) { - sb.append(ByteUtil.bytes2HexStr((byte[]) value)); + private void importFinalAppSelectStatus(int status) { + try { + mEMVOptV2.importAppFinalSelectStatus(status); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private void importCardNoStatus(int status) { + try { + mEMVOptV2.importCardNoStatus(status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void importCertStatus(int status) { + try { + mEMVOptV2.importCertStatus(status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void importPinInputStatus(int inputResult) { + try { + mEMVOptV2.importPinInputStatus(mPinType, inputResult); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void importSignatureStatus(int status) { + try { + mEMVOptV2.importSignatureStatus(status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // ====== PIN PAD METHODS ====== + private void initPinPad() { + try { + PinPadConfigV2 pinPadConfig = new PinPadConfigV2(); + pinPadConfig.setPinPadType(0); + pinPadConfig.setPinType(mPinType); + pinPadConfig.setOrderNumKey(false); + byte[] panBytes = mCardNo.substring(mCardNo.length() - 13, mCardNo.length() - 1).getBytes("US-ASCII"); + pinPadConfig.setPan(panBytes); + pinPadConfig.setTimeout(60 * 1000); + pinPadConfig.setPinKeyIndex(12); + pinPadConfig.setMaxInput(12); + pinPadConfig.setMinInput(0); + pinPadConfig.setKeySystem(0); + pinPadConfig.setAlgorithmType(0); + + mPinPadOptV2.initPinPad(pinPadConfig, mPinPadListener); + } catch (Exception e) { + e.printStackTrace(); + mHandler.obtainMessage(PIN_ERROR, Integer.MIN_VALUE, Integer.MIN_VALUE, "initPinPad() failure").sendToTarget(); + } + } + + private final PinPadListenerV2 mPinPadListener = new PinPadListenerV2.Stub() { + @Override + public void onPinLength(int len) { + android.util.Log.d(TAG, "onPinLength:" + len); + mHandler.obtainMessage(PIN_CLICK_NUMBER, len).sendToTarget(); + } + + @Override + public void onConfirm(int i, byte[] pinBlock) { + if (pinBlock != null) { + String hexStr = ByteUtil.bytes2HexStr(pinBlock); + android.util.Log.d(TAG, "onConfirm pin block:" + hexStr); + mHandler.obtainMessage(PIN_CLICK_PIN, pinBlock).sendToTarget(); } else { - sb.append(value); + mHandler.obtainMessage(PIN_CLICK_CONFIRM).sendToTarget(); } - sb.append(", "); } - if (sb.length() > 1) { - sb.setLength(sb.length() - 2); // Remove last ", " + + @Override + public void onCancel() { + android.util.Log.d(TAG, "onCancel"); + mHandler.obtainMessage(PIN_CLICK_CANCEL).sendToTarget(); } - sb.append("}"); - return sb.toString(); + + @Override + public void onError(int code) { + android.util.Log.e(TAG, "onError:" + code); + String msg = AidlErrorCodeV2.valueOf(code).getMsg(); + mHandler.obtainMessage(PIN_ERROR, code, code, msg).sendToTarget(); + } + + @Override + public void onHover(int event, byte[] data) throws RemoteException { + android.util.Log.d(TAG, "onHover(), event:" + event + ", data:" + ByteUtil.bytes2HexStr(data)); + } + }; + + // ====== MOCK ONLINE PROCESS ====== + private void mockOnlineProcess() { + new Thread(() -> { + try { + runOnUiThread(() -> tvResult.setText("Processing online authorization...")); + Thread.sleep(2000); + + try { + String[] tags = {"71", "72", "91", "8A", "89"}; + String[] values = {"", "", "", "", ""}; + byte[] out = new byte[1024]; + int len = mEMVOptV2.importOnlineProcStatus(0, tags, values, out); + if (len < 0) { + android.util.Log.e(TAG, "importOnlineProcessStatus error,code:" + len); + } + } catch (Exception e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + + // ====== UI UPDATE METHODS ====== + private void updateUI(String message, boolean isCardDetected) { + runOnUiThread(() -> { + if (tvResult != null) { + tvResult.setText(message); + } + if (isCardDetected) { + checkingCard = false; + btnCheckCard.setText("Start Scanning"); + btnCheckCard.setOnClickListener(v -> switchCheckCard()); + + // Show copy and clear buttons when card data is available (if buttons exist) + if (btnCopyData != null && message.contains("====")) { + btnCopyData.setVisibility(android.view.View.VISIBLE); + } + if (btnClearData != null && message.contains("====")) { + btnClearData.setVisibility(android.view.View.VISIBLE); + } + } + }); + } + + private void resetUI() { + runOnUiThread(() -> { + mProcessStep = 0; + btnCheckCard.setText("Start Scanning"); + btnCheckCard.setOnClickListener(v -> switchCheckCard()); + dismissAppSelectDialog(); + updateModeDisplay(); + + // Hide copy and clear buttons (if they exist) + if (btnCopyData != null) { + btnCopyData.setVisibility(android.view.View.GONE); + } + if (btnClearData != null) { + btnClearData.setVisibility(android.view.View.GONE); + } + }); + } + + private void dismissAppSelectDialog() { + if (mAppSelectDialog != null) { + try { + mAppSelectDialog.dismiss(); + } catch (Exception e) { + e.printStackTrace(); + } + mAppSelectDialog = null; + } + } + + private void showToast(String message) { + runOnUiThread(() -> Toast.makeText(this, message, Toast.LENGTH_SHORT).show()); + } + + // ====== LOGGING METHODS (kept from original) ====== + private void logMagneticCardData(Bundle info) { + android.util.Log.d(TAG, "======================================="); + android.util.Log.d(TAG, " MAGNETIC CARD DETECTED "); + android.util.Log.d(TAG, "======================================="); + + String track1 = Utility.null2String(info.getString("TRACK1")); + String track2 = Utility.null2String(info.getString("TRACK2")); + String track3 = Utility.null2String(info.getString("TRACK3")); + + android.util.Log.d(TAG, "TRACK DATA:"); + android.util.Log.d(TAG, " Track1: " + (track1.isEmpty() ? "N/A" : track1)); + android.util.Log.d(TAG, " Track2: " + (track2.isEmpty() ? "N/A" : track2)); + android.util.Log.d(TAG, " Track3: " + (track3.isEmpty() ? "N/A" : track3)); + android.util.Log.d(TAG, "======================================="); + } + + private void logICCardData(Bundle info) { + android.util.Log.d(TAG, "======================================="); + android.util.Log.d(TAG, " IC CARD DETECTED "); + android.util.Log.d(TAG, "======================================="); + + String atr = info.getString("atr", ""); + int cardType = info.getInt("cardType", -1); + + android.util.Log.d(TAG, "BASIC INFO:"); + android.util.Log.d(TAG, " ATR: " + (atr.isEmpty() ? "N/A" : atr)); + android.util.Log.d(TAG, " Card Type: " + cardType); + android.util.Log.d(TAG, "======================================="); + } + + private void logRFCardData(Bundle info) { + android.util.Log.d(TAG, "======================================="); + android.util.Log.d(TAG, " RF/NFC CARD DETECTED "); + android.util.Log.d(TAG, "======================================="); + + String uuid = info.getString("uuid", ""); + String ats = info.getString("ats", ""); + int sak = info.getInt("sak", -1); + + android.util.Log.d(TAG, "BASIC INFO:"); + android.util.Log.d(TAG, " UUID: " + (uuid.isEmpty() ? "N/A" : uuid)); + android.util.Log.d(TAG, " ATS: " + (ats.isEmpty() ? "N/A" : ats)); + android.util.Log.d(TAG, " SAK: " + (sak == -1 ? "N/A" : String.format("0x%02X (%d)", sak, sak))); + android.util.Log.d(TAG, "======================================="); + } + + private void logCardError(Bundle info) { + android.util.Log.e(TAG, "======================================="); + android.util.Log.e(TAG, " CARD READ ERROR "); + android.util.Log.e(TAG, "======================================="); + + int code = info.getInt("code", -1); + String message = info.getString("message", ""); + + android.util.Log.e(TAG, "ERROR INFO:"); + android.util.Log.e(TAG, " Error Code: " + code); + android.util.Log.e(TAG, " Error Message: " + (message.isEmpty() ? "N/A" : message)); + android.util.Log.e(TAG, "======================================="); } // ====== LIFECYCLE METHODS ====== @@ -582,7 +1647,6 @@ public class CreditCardActivity extends AppCompatActivity { protected void onPause() { super.onPause(); android.util.Log.d(TAG, "onPause called"); - // Optionally cancel card check when app goes to background if (checkingCard) { android.util.Log.d(TAG, "App paused, stopping card check"); stopCardCheck(); @@ -593,7 +1657,6 @@ public class CreditCardActivity extends AppCompatActivity { protected void onResume() { super.onResume(); android.util.Log.d(TAG, "onResume called"); - // Check PaySDK status when returning to app checkPaySDKStatus(); } diff --git a/app/src/main/res/layout/activity_credit_card.xml b/app/src/main/res/layout/activity_credit_card.xml index 1b99d0e..b6ee995 100644 --- a/app/src/main/res/layout/activity_credit_card.xml +++ b/app/src/main/res/layout/activity_credit_card.xml @@ -1,30 +1,82 @@ + + + android:orientation="vertical" + android:padding="16dp"> + + android:background="?attr/colorPrimary" /> + +