diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2e7dcfd..f5ad28e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,12 +73,8 @@ android:exported="false" /> - - + android:name=".transaction.CreateTransactionActivity" + android:exported="false" /> { - Button btn = (Button) v; - String number = btn.getText().toString(); - appendToAmount(number); - }; - - btn1.setOnClickListener(numberClickListener); - btn2.setOnClickListener(numberClickListener); - btn3.setOnClickListener(numberClickListener); - btn4.setOnClickListener(numberClickListener); - btn5.setOnClickListener(numberClickListener); - btn6.setOnClickListener(numberClickListener); - btn7.setOnClickListener(numberClickListener); - btn8.setOnClickListener(numberClickListener); - btn9.setOnClickListener(numberClickListener); - btn0.setOnClickListener(numberClickListener); - btn00.setOnClickListener(numberClickListener); - - // Clear button - btnClear.setOnClickListener(v -> clearAmount()); - - // Confirm button - btnConfirm.setOnClickListener(v -> handleConfirmAmount()); - - // Toggle mode button - btnToggleMode.setOnClickListener(v -> toggleEMVMode()); - } - - private void appendToAmount(String number) { - // Remove leading zeros and handle maximum amount - String currentAmount = transactionAmount.equals("0") ? "" : transactionAmount; - String newAmount = currentAmount + number; - - // Limit to reasonable amount (max 999999999 = 9,999,999.99) - if (newAmount.length() <= 9) { - transactionAmount = newAmount; - updateAmountDisplay(); - } - } - - private void clearAmount() { - if (transactionAmount.length() > 1) { - transactionAmount = transactionAmount.substring(0, transactionAmount.length() - 1); - } else { - transactionAmount = "0"; - } - updateAmountDisplay(); - } - - private void updateAmountDisplay() { - if (tvAmountDisplay != null) { - long amountCents = Long.parseLong(transactionAmount); - double amountRupiah = amountCents / 100.0; - NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID")); - String formattedAmount = formatter.format(amountRupiah); - tvAmountDisplay.setText(formattedAmount); - } - } - - private void handleConfirmAmount() { - long amountCents = Long.parseLong(transactionAmount); - if (amountCents < 100) { // Minimum Rp 1.00 - showToast("Minimum amount is Rp 1.00"); - return; - } - - // Start EMV Transaction Activity - Intent intent = new Intent(this, EmvTransactionActivity.class); - intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); - intent.putExtra("EMV_MODE", isEMVMode); - startActivity(intent); - finish(); // Remove this activity from stack - } - - private void toggleEMVMode() { - isEMVMode = !isEMVMode; - updateModeDisplay(); - showToast("Mode switched to: " + (isEMVMode ? "EMV Mode" : "Simple Mode")); - } - - private void updateModeDisplay() { - String mode = isEMVMode ? "EMV Mode (Full Card Data)" : "Simple Mode (Basic Detection)"; - if (tvModeIndicator != null) { - tvModeIndicator.setText("Current Mode: " + mode); - } - if (btnToggleMode != null) { - btnToggleMode.setText(isEMVMode ? "Switch to Simple" : "Switch to EMV"); - } - } - - private void showToast(String message) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } -} \ No newline at end of file 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 2318b79..960a264 100644 --- a/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java +++ b/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java @@ -32,6 +32,8 @@ import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.example.bdkipoc.transaction.CreateTransactionActivity; + /** * CreditCardActivity - Display detailed transaction results and TLV data */ diff --git a/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java b/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java deleted file mode 100644 index 7922332..0000000 --- a/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java +++ /dev/null @@ -1,1307 +0,0 @@ -package com.example.bdkipoc.kredit; - -import android.content.Intent; -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.util.Log; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -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.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.text.NumberFormat; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * EmvTransactionActivity - Handle card reading and EMV processing with Modal - */ -public class EmvTransactionActivity extends AppCompatActivity { - private static final String TAG = "EmvTransaction"; - - // UI Components - SIMPLIFIED + MODAL - private LinearLayout mainContent; - private FrameLayout modalOverlay; - private TextView tvStatus; - private TextView modalText; - private ImageView modalIcon; - private ProgressBar progressBar; - private ImageView ivCardReader; - - // Animation - private Animation fadeIn; - private Animation fadeOut; - - // Transaction Data - private String transactionAmount; - private boolean isEMVMode; - private boolean isProcessing = false; - private boolean isModalShowing = false; - - // 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; - - // 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_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; - - // Modal Display Messages - private static final int SHOW_MODAL_SCAN_CARD = 101; - private static final int SHOW_MODAL_PROCESSING = 102; - private static final int HIDE_MODAL = 103; - - 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: - android.util.Log.d(TAG, "Initializing PIN pad..."); - hideModal(); - initPinPad(); - break; - - case EMV_ONLINE_PROCESS: - showModalProcessing("Memproses Otorisasi Online..."); - mockOnlineProcess(); - break; - - case EMV_SIGNATURE: - importSignatureStatus(0); - break; - - case PIN_CLICK_PIN: - android.util.Log.d(TAG, "PIN input confirmed"); - importPinInputStatus(0); - break; - - case PIN_CLICK_CONFIRM: - android.util.Log.d(TAG, "PIN confirm without input (bypass)"); - importPinInputStatus(2); - break; - - case PIN_CLICK_CANCEL: - showToast("PIN input cancelled by user"); - importPinInputStatus(1); - break; - - case PIN_ERROR: - android.util.Log.e(TAG, "PIN Error: " + msg.obj + " -- " + msg.arg1); - showToast("PIN Error: " + msg.obj); - importPinInputStatus(3); - break; - - case EMV_TRANS_FAIL: - // Handle EMV transaction failure with error code check - int errorCode = msg.arg1; - String errorDesc = (String) msg.obj; - - Log.e(TAG, "EMV Transaction failed: " + errorDesc + " (Code: " + errorCode + ")"); - - hideModal(); - if (errorCode == -50009) { - // EMV process conflict - reset and retry - Log.d(TAG, "EMV process conflict detected - resetting..."); - showToast("EMV busy, resetting..."); - resetEMVAndRetry(); - } else { - // Other errors - show message but don't finish - showToast("Transaction failed: " + errorDesc); - resetScanningState(); - } - break; - - case EMV_TRANS_SUCCESS: - // Navigate to results screen - hideModal(); - navigateToResults(); - break; - - case SHOW_MODAL_SCAN_CARD: - showModalScanCard(); - break; - - case SHOW_MODAL_PROCESSING: - String processingMsg = (String) msg.obj; - showModalProcessing(processingMsg); - break; - - case HIDE_MODAL: - hideModal(); - break; - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_emv_transaction); - - getIntentData(); - initViews(); - initAnimations(); - initEMVComponents(); - initEMVData(); - - // Auto-start scanning after short delay - new Handler(Looper.getMainLooper()).postDelayed(() -> { - autoStartScanning(); - }, 1000); // 1 second delay to let everything initialize - } - - private void getIntentData() { - Intent intent = getIntent(); - transactionAmount = intent.getStringExtra("TRANSACTION_AMOUNT"); - isEMVMode = intent.getBooleanExtra("EMV_MODE", true); - - if (transactionAmount == null) { - transactionAmount = "0"; - finish(); - return; - } - } - - // ====== UPDATED INIT VIEWS METHOD ====== - private void initViews() { - // Setup Toolbar with back navigation - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle("Scan Kartu"); - } - - // Initialize UI components - mainContent = findViewById(R.id.main_content); - modalOverlay = findViewById(R.id.modal_overlay); - tvStatus = findViewById(R.id.tv_status); - progressBar = findViewById(R.id.progress_bar); - ivCardReader = findViewById(R.id.iv_card_reader); - modalText = findViewById(R.id.modal_text); - modalIcon = findViewById(R.id.modal_icon); - - // Set initial state - updateStatusUI("Initializing scanner...", true); - - // Setup modal overlay click to close - modalOverlay.setOnClickListener(v -> { - // Optional: close modal when clicking outside - // hideModal(); - }); - } - - // ====== ANIMATION INITIALIZATION ====== - private void initAnimations() { - // Create fade animations programmatically since we want to avoid new resources - fadeIn = AnimationUtils.loadAnimation(this, android.R.anim.fade_in); - fadeOut = AnimationUtils.loadAnimation(this, android.R.anim.fade_out); - - // Set animation duration - fadeIn.setDuration(300); - fadeOut.setDuration(300); - } - - // ====== MODAL METHODS ====== - private void showModalScanCard() { - if (isModalShowing) return; - - runOnUiThread(() -> { - modalText.setText("Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"); - modalIcon.setImageResource(R.drawable.ic_card_insert); - - modalOverlay.setVisibility(View.VISIBLE); - modalOverlay.startAnimation(fadeIn); - - isModalShowing = true; - - Log.d(TAG, "Modal scan card shown"); - }); - } - - private void showModalProcessing(String message) { - if (!isModalShowing) { - runOnUiThread(() -> { - modalText.setText(message); - modalIcon.setImageResource(R.drawable.ic_card_insert); - - modalOverlay.setVisibility(View.VISIBLE); - modalOverlay.startAnimation(fadeIn); - - isModalShowing = true; - - Log.d(TAG, "Modal processing shown: " + message); - }); - } else { - // Just update text if modal already showing - runOnUiThread(() -> { - modalText.setText(message); - Log.d(TAG, "Modal text updated: " + message); - }); - } - } - - private void hideModal() { - if (!isModalShowing) return; - - runOnUiThread(() -> { - fadeOut.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) {} - - @Override - public void onAnimationEnd(Animation animation) { - modalOverlay.setVisibility(View.GONE); - isModalShowing = false; - } - - @Override - public void onAnimationRepeat(Animation animation) {} - }); - - modalOverlay.startAnimation(fadeOut); - - Log.d(TAG, "Modal hidden"); - }); - } - - // ====== NEW METHOD TO UPDATE STATUS UI ====== - private void updateStatusUI(String statusText, boolean showProgress) { - runOnUiThread(() -> { - tvStatus.setText(statusText); - progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE); - }); - } - - 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"); - showToast("Application error"); - finish(); - } - } - - private void initEMVData() { - try { - if (mEMVOptV2 != null) { - // Reset any existing EMV process first - mEMVOptV2.initEmvProcess(); - - // Small delay to ensure reset is complete - new Handler(Looper.getMainLooper()).postDelayed(() -> { - try { - initEmvTlvData(); - android.util.Log.d(TAG, "EMV data initialized successfully"); - } catch (Exception e) { - android.util.Log.e(TAG, "Error in delayed EMV init: " + e.getMessage()); - } - }, 500); - } - } 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 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(); - } - } - - // ====== UPDATED AUTO-START SCANNING METHOD ====== - private void autoStartScanning() { - Log.d(TAG, "Auto-starting card scanning..."); - - if (isProcessing) { - Log.d(TAG, "Already processing - ignoring auto-start"); - return; - } - - updateStatusUI("Ready for card...\n\nPlease insert, swipe, or tap your card\n\nScanning will start automatically", true); - - // Show modal for card scanning - mHandler.obtainMessage(SHOW_MODAL_SCAN_CARD).sendToTarget(); - - // Start scanning automatically - startCardCheck(); - } - - private void restartAutoScanning() { - Log.d(TAG, "Restarting auto-scanning after delay..."); - - // Wait a bit before restarting - new Handler(Looper.getMainLooper()).postDelayed(() -> { - if (!isFinishing() && !isProcessing) { - autoStartScanning(); - } - }, 2000); // 2 second delay before restart - } - - // ====== UPDATED CARD CHECK METHODS ====== - private void startCardCheck() { - if (isProcessing) { - Log.d(TAG, "Card check already in progress - ignoring call"); - return; - } - - Log.d(TAG, "Starting auto card check - setting isProcessing = true"); - isProcessing = true; - - updateStatusUI("Initializing scanner...\n\nPlease wait...", true); - - try { - if (mEMVOptV2 != null) { - Log.d(TAG, "Forcing EMV reset before card check"); - mEMVOptV2.initEmvProcess(); - - new Handler(Looper.getMainLooper()).postDelayed(() -> { - if (isProcessing && !isFinishing()) { - continueCardCheck(); - } - }, 500); - - } else { - continueCardCheck(); - } - - } catch (Exception e) { - Log.e(TAG, "Error in startCardCheck: " + e.getMessage(), e); - handleScanError("Error: " + e.getMessage()); - } - } - - private void continueCardCheck() { - try { - Log.d(TAG, "continueCardCheck - isProcessing: " + isProcessing); - - if (!isProcessing) { - Log.d(TAG, "continueCardCheck called but not processing - returning"); - return; - } - - String mode = isEMVMode ? "EMV Mode" : "Simple Mode"; - updateStatusUI("Scanning for card...\n\nMode: " + mode + "\n\nPlease insert, swipe, or tap your card", true); - - if (isEMVMode) { - startEMVCardCheck(); - } else { - startSimpleCardCheck(); - } - - } catch (Exception e) { - Log.e(TAG, "Error in continueCardCheck: " + e.getMessage(), e); - handleScanError("Error: " + e.getMessage()); - } - } - - // ====== UPDATED HANDLE SCAN ERROR ====== - private void handleScanError(String errorMessage) { - Log.e(TAG, "Scan error: " + errorMessage); - - isProcessing = false; - mProcessStep = 0; - - updateStatusUI("Scan error: " + errorMessage + "\n\nRetrying in 2 seconds...", false); - showToast(errorMessage); - - // Hide modal on error - hideModal(); - - // Auto-restart scanning after error - restartAutoScanning(); - } - - private void startEMVCardCheck() { - try { - if (mEMVOptV2 == null) { - showToast("EMV not initialized"); - return; - } - - mEMVOptV2.initEmvProcess(); - initEmvTlvData(); - - updateStatusUI("EMV Mode: Starting card scan...\nPlease insert, swipe, or tap your card", true); - - 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()); - showToast("Error starting EMV card scan: " + e.getMessage()); - } - } - - private void startSimpleCardCheck() { - try { - if (!MyApplication.app.isConnectPaySDK()) { - updateStatusUI("Connecting to PaySDK...", true); - MyApplication.app.bindPaySDKService(); - return; - } - - int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue(); - updateStatusUI("Simple Mode: Starting card scan...\nPlease insert, swipe, or tap your card", true); - - MyApplication.app.readCardOptV2.checkCard(cardType, mSimpleCheckCardCallback, 60); - - } catch (Exception e) { - e.printStackTrace(); - showToast("Error starting card scan: " + e.getMessage()); - } - } - - private void stopCardCheck() { - try { - if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { - MyApplication.app.readCardOptV2.cancelCheckCard(); - } - isProcessing = false; - updateStatusUI("Ready for card...\n\nPlease insert, swipe, or tap your card", true); - } catch (Exception e) { - android.util.Log.e(TAG, "Error stopping card check: " + e.getMessage()); - } - } - - // ====== SIMPLE CARD DETECTION CALLBACK ====== - private final CheckCardCallbackV2 mSimpleCheckCardCallback = new CheckCardCallbackV2.Stub() { - @Override - public void findMagCard(Bundle info) throws RemoteException { - android.util.Log.d(TAG, "Simple Mode: findMagCard callback triggered"); - runOnUiThread(() -> { - showModalProcessing("Kartu Ditemukan - Memproses..."); - new Handler(Looper.getMainLooper()).postDelayed(() -> { - handleSimpleCardResult(info, "MAGNETIC"); - }, 1500); - }); - } - - @Override - public void findICCard(String atr) throws RemoteException { - Bundle info = new Bundle(); - info.putString("atr", atr); - runOnUiThread(() -> { - showModalProcessing("Kartu IC Ditemukan - Memproses..."); - new Handler(Looper.getMainLooper()).postDelayed(() -> { - handleSimpleCardResult(info, "IC"); - }, 1500); - }); - } - - @Override - public void findRFCard(String uuid) throws RemoteException { - Bundle info = new Bundle(); - info.putString("uuid", uuid); - runOnUiThread(() -> { - showModalProcessing("Kartu NFC Ditemukan - Memproses..."); - new Handler(Looper.getMainLooper()).postDelayed(() -> { - handleSimpleCardResult(info, "NFC"); - }, 1500); - }); - } - - @Override - public void onError(int code, String message) throws RemoteException { - runOnUiThread(() -> { - showToast("Card error: " + message); - hideModal(); - stopCardCheck(); - }); - } - - @Override - public void findICCardEx(Bundle info) throws RemoteException { - runOnUiThread(() -> { - showModalProcessing("Kartu IC Ditemukan - Memproses..."); - new Handler(Looper.getMainLooper()).postDelayed(() -> { - handleSimpleCardResult(info, "IC"); - }, 1500); - }); - } - - @Override - public void findRFCardEx(Bundle info) throws RemoteException { - runOnUiThread(() -> { - showModalProcessing("Kartu NFC Ditemukan - Memproses..."); - new Handler(Looper.getMainLooper()).postDelayed(() -> { - handleSimpleCardResult(info, "NFC"); - }, 1500); - }); - } - - @Override - public void onErrorEx(Bundle info) throws RemoteException { - runOnUiThread(() -> { - String msg = info.getString("message", "Unknown error"); - showToast("Card error: " + msg); - hideModal(); - stopCardCheck(); - }); - } - }; - - // ====== EMV CARD DETECTION CALLBACK ====== - private final CheckCardCallbackV2 mEMVCheckCardCallback = new CheckCardCallbackV2.Stub() { - @Override - public void findMagCard(Bundle info) throws RemoteException { - runOnUiThread(() -> { - showModalProcessing("Kartu Magnetik Ditemukan - Memproses..."); - new Handler(Looper.getMainLooper()).postDelayed(() -> { - handleSimpleCardResult(info, "MAGNETIC"); - }, 1500); - }); - } - - @Override - public void findICCard(String atr) throws RemoteException { - MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0); - mCardType = AidlConstantsV2.CardType.IC.getValue(); - runOnUiThread(() -> { - showModalProcessing("Kartu IC Ditemukan - Memulai EMV..."); - startEMVTransactionProcess(); - }); - } - - @Override - public void findRFCard(String uuid) throws RemoteException { - mCardType = AidlConstantsV2.CardType.NFC.getValue(); - runOnUiThread(() -> { - showModalProcessing("Kartu NFC Ditemukan - Memulai EMV..."); - startEMVTransactionProcess(); - }); - } - - @Override - public void onError(int code, String message) throws RemoteException { - runOnUiThread(() -> { - showToast("EMV Error: " + message); - hideModal(); - stopCardCheck(); - }); - } - - @Override - public void findICCardEx(Bundle info) throws RemoteException { - mCardType = AidlConstantsV2.CardType.IC.getValue(); - runOnUiThread(() -> { - showModalProcessing("Kartu IC Ditemukan - Memulai EMV..."); - startEMVTransactionProcess(); - }); - } - - @Override - public void findRFCardEx(Bundle info) throws RemoteException { - mCardType = AidlConstantsV2.CardType.NFC.getValue(); - runOnUiThread(() -> { - showModalProcessing("Kartu NFC Ditemukan - Memulai EMV..."); - startEMVTransactionProcess(); - }); - } - - @Override - public void onErrorEx(Bundle info) throws RemoteException { - runOnUiThread(() -> { - String msg = info.getString("message", "Unknown error"); - showToast("EMV Error: " + msg); - hideModal(); - stopCardCheck(); - }); - } - }; - - private void handleSimpleCardResult(Bundle info, String cardType) { - // For simple mode, navigate directly to results - Intent intent = new Intent(this, CreditCardActivity.class); - intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); - intent.putExtra("CARD_TYPE", cardType); - intent.putExtra("CARD_DATA", info); - intent.putExtra("EMV_MODE", isEMVMode); - startActivity(intent); - finish(); - } - - private void startEMVTransactionProcess() { - // Prevent multiple calls - if (mProcessStep != 0) { - Log.d(TAG, "EMV transaction already in progress (step: " + mProcessStep + ") - ignoring call"); - return; - } - - android.util.Log.d(TAG, "Starting EMV transaction process"); - mProcessStep = 1; // Set step to prevent multiple calls - - try { - // Ensure EMV is properly reset before starting - mEMVOptV2.initEmvProcess(); - - // Small delay to ensure reset is complete - new Handler(Looper.getMainLooper()).postDelayed(() -> { - try { - // Double check we're still in the right state - if (mProcessStep <= 0) { - Log.d(TAG, "EMV process was cancelled - not starting"); - return; - } - - Bundle bundle = new Bundle(); - bundle.putString("amount", transactionAmount); - 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); - - updateStatusUI("EMV processing started...\nReading card data...", true); - showModalProcessing("Memproses Data Kartu EMV..."); - - Log.d(TAG, "Starting transactProcessEx with reset EMV"); - mEMVOptV2.transactProcessEx(bundle, mEMVListener); - - } catch (Exception e) { - Log.e(TAG, "Error in delayed EMV start: " + e.getMessage(), e); - hideModal(); - resetScanningState(); - showToast("Error starting EMV: " + e.getMessage()); - } - }, 300); // 300ms delay - - } catch (Exception e) { - android.util.Log.e(TAG, "Error starting EMV transaction: " + e.getMessage()); - e.printStackTrace(); - hideModal(); - resetScanningState(); - showToast("Error starting EMV transaction: " + e.getMessage()); - } - } - - // ====== EMV LISTENER ====== - private final EMVListenerV2 mEMVListener = new EMVListenerV2.Stub() { - - @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); - runOnUiThread(() -> hideModal()); - 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"); - } - - @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(); - } - - // ====== UPDATED EMV DIALOG HANDLERS ====== - private void showCardNumberConfirmation() { - runOnUiThread(() -> { - String displayText = "Card Number Detected:\n" + maskCardNumber(mCardNo) + - "\n\nConfirming automatically..."; - updateStatusUI(displayText, true); - showModalProcessing("Mengkonfirmasi Nomor Kartu..."); - - // Auto-confirm after short delay - new Handler(Looper.getMainLooper()).postDelayed(() -> { - Log.d(TAG, "Auto-confirming card number"); - importCardNoStatus(0); - }, 1500); - - mProcessStep = EMV_CONFIRM_CARD_NO; - }); - } - - private void showCertificateVerification() { - runOnUiThread(() -> { - String displayText = "Certificate Verification:\n" + mCertInfo + - "\n\nProcessing automatically..."; - updateStatusUI(displayText, true); - showModalProcessing("Memverifikasi Sertifikat..."); - - // Auto-confirm after short delay - new Handler(Looper.getMainLooper()).postDelayed(() -> { - Log.d(TAG, "Auto-confirming certificate"); - importCertStatus(0); - }, 1500); - - mProcessStep = EMV_CERT_VERIFY; - }); - } - - // ====== UPDATED PIN PAD METHODS ====== - private void initPinPad() { - android.util.Log.e(TAG, "========== PIN PAD INITIALIZATION =========="); - try { - if (mPinPadOptV2 == null) { - throw new IllegalStateException("PIN Pad service not available"); - } - - if (mCardNo == null || mCardNo.length() < 13) { - throw new IllegalArgumentException("Invalid card number for PIN"); - } - - PinPadConfigV2 pinPadConfig = new PinPadConfigV2(); - pinPadConfig.setPinPadType(0); - pinPadConfig.setPinType(mPinType); - pinPadConfig.setOrderNumKey(true); // Set to true for normal order, false for random - - String panForPin = mCardNo.substring(mCardNo.length() - 13, mCardNo.length() - 1); - byte[] panBytes = panForPin.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); - - updateStatusUI("PIN Input Required\n\nPIN pad is ready\nPlease enter your PIN on the device", true); - - mPinPadOptV2.initPinPad(pinPadConfig, mPinPadListener); - - } catch (Exception e) { - android.util.Log.e(TAG, "PIN pad initialization failed: " + e.getMessage()); - mHandler.obtainMessage(PIN_ERROR, -1, -1, - "PIN Error: " + e.getMessage()).sendToTarget(); - } - } - - // ====== UPDATED PIN PAD LISTENER ====== - private final PinPadListenerV2 mPinPadListener = new PinPadListenerV2.Stub() { - @Override - public void onPinLength(int len) throws RemoteException { - android.util.Log.d(TAG, "PIN input length: " + len); - runOnUiThread(() -> { - String dots = ""; - for (int i = 0; i < len; i++) { - dots += "• "; - } - updateStatusUI("PIN Input: " + dots + "\n\nEntering PIN... (" + len + " digits)", true); - }); - } - - @Override - public void onConfirm(int i, byte[] pinBlock) throws RemoteException { - android.util.Log.d(TAG, "PIN input confirmed"); - - runOnUiThread(() -> { - updateStatusUI("PIN confirmed, processing...", true); - showModalProcessing("PIN Dikonfirmasi - Memproses..."); - }); - - if (pinBlock != null) { - String hexStr = ByteUtil.bytes2HexStr(pinBlock); - android.util.Log.d(TAG, "PIN block received: " + hexStr); - mHandler.obtainMessage(PIN_CLICK_PIN, pinBlock).sendToTarget(); - } else { - android.util.Log.d(TAG, "PIN bypass confirmed"); - mHandler.obtainMessage(PIN_CLICK_CONFIRM).sendToTarget(); - } - } - - @Override - public void onCancel() throws RemoteException { - android.util.Log.d(TAG, "PIN input cancelled by user"); - - runOnUiThread(() -> { - updateStatusUI("PIN input cancelled", false); - hideModal(); - }); - - mHandler.obtainMessage(PIN_CLICK_CANCEL).sendToTarget(); - } - - @Override - public void onError(int code) throws RemoteException { - android.util.Log.e(TAG, "PIN pad error: " + code); - String msg = AidlErrorCodeV2.valueOf(code).getMsg(); - - runOnUiThread(() -> { - updateStatusUI("PIN error: " + msg, false); - hideModal(); - }); - - mHandler.obtainMessage(PIN_ERROR, code, code, msg).sendToTarget(); - } - - @Override - public void onHover(int event, byte[] data) throws RemoteException { - android.util.Log.d(TAG, "PIN pad hover event: " + event); - } - }; - - // ====== EMV RESET METHODS ====== - private void resetEMVAndRetry() { - new Thread(() -> { - try { - Log.d(TAG, "Resetting EMV process..."); - - // Cancel any existing card operations first - if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { - MyApplication.app.readCardOptV2.cancelCheckCard(); - } - - Thread.sleep(500); // Wait for cancel to complete - - // Reset EMV process - if (mEMVOptV2 != null) { - mEMVOptV2.initEmvProcess(); // Reset EMV state - } - - Thread.sleep(1500); // Wait longer for complete reset - - runOnUiThread(() -> { - Log.d(TAG, "EMV reset complete - auto-restarting scan"); - isProcessing = false; - mProcessStep = 0; - - updateStatusUI("EMV reset complete\n\nRestarting auto-scan...", true); - showToast("EMV reset - restarting scan"); - - // Auto-restart scanning - restartAutoScanning(); - }); - - } catch (Exception e) { - Log.e(TAG, "Error resetting EMV: " + e.getMessage(), e); - runOnUiThread(() -> { - handleScanError("Reset failed - " + e.getMessage()); - }); - } - }).start(); - } - - // ====== UPDATED RESET SCANNING STATE ====== - private void resetScanningState() { - Log.d(TAG, "Resetting scanning state for auto-scan mode"); - isProcessing = false; - mProcessStep = 0; - - updateStatusUI("Ready for card...\n\nPlease insert, swipe, or tap your card", true); - hideModal(); - } - - // ====== HELPER METHODS ====== - private void navigateToResults() { - Intent intent = new Intent(this, CreditCardActivity.class); - intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); - intent.putExtra("CARD_TYPE", mCardType == AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"); - intent.putExtra("EMV_MODE", isEMVMode); - intent.putExtra("CARD_NO", mCardNo); - startActivity(intent); - finish(); - } - - // ====== UPDATED MOCK ONLINE PROCESS ====== - private void mockOnlineProcess() { - new Thread(() -> { - try { - runOnUiThread(() -> updateStatusUI("Processing online authorization...", true)); - 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(); - } - - private String getCardNo() { - // Implementation from original code - return ""; - } - - 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[] 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; - } - return result; - } - - // ====== EMV IMPORT METHODS ====== - private void importAppSelect(int selectIndex) { - try { - mEMVOptV2.importAppSelect(selectIndex); - } catch (Exception e) { - e.printStackTrace(); - } - } - - 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(); - } - } - - private void showToast(String message) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - - // ====== UPDATED ON SUPPORT NAVIGATE UP ====== - @Override - public boolean onSupportNavigateUp() { - // Handle back button press - clean up and go back - try { - // Cancel any ongoing operations - if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { - MyApplication.app.readCardOptV2.cancelCheckCard(); - } - - // Reset EMV process - if (mEMVOptV2 != null) { - mEMVOptV2.initEmvProcess(); - } - - isProcessing = false; - mProcessStep = 0; - - hideModal(); - - } catch (Exception e) { - Log.e(TAG, "Error cleaning up on back press: " + e.getMessage()); - } - - finish(); - return true; - } - - @Override - protected void onDestroy() { - Log.d(TAG, "onDestroy - cleaning up auto-scan resources"); - - // Stop auto-scanning - isProcessing = false; - - try { - // Cancel card operations - if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { - MyApplication.app.readCardOptV2.cancelCheckCard(); - } - - // Reset EMV process - if (mEMVOptV2 != null) { - mEMVOptV2.initEmvProcess(); - } - - hideModal(); - - } catch (Exception e) { - Log.e(TAG, "Error cleaning up EMV: " + e.getMessage()); - } - - super.onDestroy(); - } - - @Override - protected void onPause() { - super.onPause(); - Log.d(TAG, "onPause - pausing auto-scan"); - - // Don't stop scanning on pause - let it continue in background - // This allows card detection even if screen goes off briefly - } - - @Override - protected void onResume() { - super.onResume(); - Log.d(TAG, "onResume - resuming auto-scan"); - - // If we're not processing and not finishing, restart auto-scan - if (!isProcessing && !isFinishing() && mProcessStep == 0) { - restartAutoScanning(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java b/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java new file mode 100644 index 0000000..6b1386e --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java @@ -0,0 +1,504 @@ +package com.example.bdkipoc.transaction; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import com.example.bdkipoc.R; +import com.example.bdkipoc.transaction.managers.CardScannerManager; +import com.example.bdkipoc.transaction.managers.EMVManager; +import com.example.bdkipoc.transaction.managers.ModalManager; +import com.example.bdkipoc.transaction.managers.PinPadManager; + +import java.text.NumberFormat; +import java.util.Locale; + +import com.example.bdkipoc.kredit.CreditCardActivity; + +/** + * CreateTransactionActivity - Refactored with Manager Classes + * Handles amount input and card scanning in one screen + * Located in transaction package + */ +public class CreateTransactionActivity extends AppCompatActivity implements + EMVManager.EMVManagerCallback, + CardScannerManager.CardScannerCallback, + PinPadManager.PinPadManagerCallback { + + private static final String TAG = "CreateTransaction"; + + // UI Components - Amount Input + private TextView tvAmountDisplay; + private TextView tvModeIndicator; + private Button btnConfirm; + private Button btnToggleMode; + private ProgressBar progressBar; + + // Amount Input Keypad + private Button btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn00, btnClear; + + // State Management + private String transactionAmount = "0"; + private boolean isEMVMode = true; + + // Manager Classes + private EMVManager emvManager; + private CardScannerManager cardScannerManager; + private PinPadManager pinPadManager; + private ModalManager modalManager; + + // EMV Dialog + private AlertDialog mAppSelectDialog; + private int mSelectIndex; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_create_transaction); + + initViews(); + initManagers(); + setupListeners(); + updateAmountDisplay(); + updateModeDisplay(); + } + + private void initViews() { + // Setup Toolbar + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle("Input Nominal Transaksi"); + } + + // Initialize amount input UI components + tvAmountDisplay = findViewById(R.id.tv_amount_display); + tvModeIndicator = findViewById(R.id.tv_mode_indicator); + btnConfirm = findViewById(R.id.btn_confirm); + btnToggleMode = findViewById(R.id.btn_toggle_mode); + progressBar = findViewById(R.id.progress_bar); + + // Initialize keypad buttons + btn1 = findViewById(R.id.btn_1); + btn2 = findViewById(R.id.btn_2); + btn3 = findViewById(R.id.btn_3); + btn4 = findViewById(R.id.btn_4); + btn5 = findViewById(R.id.btn_5); + btn6 = findViewById(R.id.btn_6); + btn7 = findViewById(R.id.btn_7); + btn8 = findViewById(R.id.btn_8); + btn9 = findViewById(R.id.btn_9); + btn0 = findViewById(R.id.btn_0); + btn00 = findViewById(R.id.btn_00); + btnClear = findViewById(R.id.btn_clear); + } + + private void initManagers() { + // Initialize Modal Manager + FrameLayout modalOverlay = findViewById(R.id.modal_overlay); + TextView modalText = findViewById(R.id.modal_text); + ImageView modalIcon = findViewById(R.id.modal_icon); + modalManager = new ModalManager(modalOverlay, modalText, modalIcon); + + // Initialize other managers + emvManager = new EMVManager(this); + cardScannerManager = new CardScannerManager(this); + pinPadManager = new PinPadManager(this); + + // Initialize EMV data + emvManager.initEMVData(); + + Log.d(TAG, "All managers initialized"); + } + + private void setupListeners() { + // Keypad number listeners + View.OnClickListener numberClickListener = v -> { + Button btn = (Button) v; + String number = btn.getText().toString(); + appendToAmount(number); + }; + + btn1.setOnClickListener(numberClickListener); + btn2.setOnClickListener(numberClickListener); + btn3.setOnClickListener(numberClickListener); + btn4.setOnClickListener(numberClickListener); + btn5.setOnClickListener(numberClickListener); + btn6.setOnClickListener(numberClickListener); + btn7.setOnClickListener(numberClickListener); + btn8.setOnClickListener(numberClickListener); + btn9.setOnClickListener(numberClickListener); + btn0.setOnClickListener(numberClickListener); + btn00.setOnClickListener(numberClickListener); + + // Clear button + btnClear.setOnClickListener(v -> clearAmount()); + + // Confirm button - shows modal and starts scanning + btnConfirm.setOnClickListener(v -> handleConfirmAmount()); + + // Toggle mode button + btnToggleMode.setOnClickListener(v -> toggleEMVMode()); + + // Modal overlay click to close (only if not processing) + findViewById(R.id.modal_overlay).setOnClickListener(v -> { + if (modalManager.isShowing() && !cardScannerManager.isScanning()) { + modalManager.hideModal(); + } + }); + } + + // ====== AMOUNT INPUT METHODS ====== + private void appendToAmount(String number) { + String currentAmount = transactionAmount.equals("0") ? "" : transactionAmount; + String newAmount = currentAmount + number; + + if (newAmount.length() <= 9) { + transactionAmount = newAmount; + updateAmountDisplay(); + } + } + + private void clearAmount() { + if (transactionAmount.length() > 1) { + transactionAmount = transactionAmount.substring(0, transactionAmount.length() - 1); + } else { + transactionAmount = "0"; + } + updateAmountDisplay(); + } + + private void updateAmountDisplay() { + if (tvAmountDisplay != null) { + long amountCents = Long.parseLong(transactionAmount); + double amountRupiah = amountCents / 100.0; + NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID")); + String formattedAmount = formatter.format(amountRupiah); + tvAmountDisplay.setText(formattedAmount); + } + } + + private void handleConfirmAmount() { + long amountCents = Long.parseLong(transactionAmount); + if (amountCents < 100) { // Minimum Rp 1.00 + showToast("Minimum amount is Rp 1.00"); + return; + } + + // Show modal and start card scanning + showModalAndStartScanning(); + } + + private void showModalAndStartScanning() { + Log.d(TAG, "Starting card scanning with modal..."); + + // Show modal first + modalManager.showScanCardModal(); + + // Start scanning after short delay + new Handler(Looper.getMainLooper()).postDelayed(() -> { + cardScannerManager.startScanning(isEMVMode); + }, 1000); + } + + private void toggleEMVMode() { + isEMVMode = !isEMVMode; + updateModeDisplay(); + showToast("Mode switched to: " + (isEMVMode ? "EMV Mode" : "Simple Mode")); + } + + private void updateModeDisplay() { + String mode = isEMVMode ? "EMV Mode (Full Card Data)" : "Simple Mode (Basic Detection)"; + if (tvModeIndicator != null) { + tvModeIndicator.setText("Current Mode: " + mode); + } + if (btnToggleMode != null) { + btnToggleMode.setText(isEMVMode ? "Switch to Simple" : "Switch to EMV"); + } + } + + // ====== CARD SCANNER CALLBACK METHODS ====== + @Override + public void onCardDetected(String cardType, Bundle cardData) { + Log.d(TAG, "Simple card detected: " + cardType); + modalManager.showProcessingModal("Kartu " + cardType + " Ditemukan - Memproses..."); + + // Navigate to results after short delay + new Handler(Looper.getMainLooper()).postDelayed(() -> { + navigateToResults(cardType, cardData, null); + }, 1500); + } + + @Override + public void onEMVCardDetected(int cardType) { + Log.d(TAG, "EMV card detected: " + cardType); + modalManager.showProcessingModal("Kartu Ditemukan - Memulai EMV..."); + + // Start EMV transaction process + emvManager.startEMVTransaction(transactionAmount, cardType); + } + + @Override + public void onScanError(String errorMessage) { + Log.e(TAG, "Scan error: " + errorMessage); + showToast(errorMessage); + modalManager.hideModal(); + + // Auto-restart scanning after error + restartScanningAfterDelay(); + } + + @Override + public void onScanProgress(String message) { + Log.d(TAG, "Scan progress: " + message); + // Can update UI with progress if needed + } + + // ====== EMV MANAGER CALLBACK METHODS ====== + @Override + public void onAppSelect(String[] candidateNames) { + modalManager.hideModal(); + showAppSelectDialog(candidateNames); + } + + @Override + public void onFinalAppSelect() { + emvManager.importFinalAppSelectStatus(0); + } + + @Override + public void onConfirmCardNo(String cardNo) { + modalManager.showProcessingModal("Mengkonfirmasi Nomor Kartu..."); + + // Auto-confirm after short delay + new Handler(Looper.getMainLooper()).postDelayed(() -> { + Log.d(TAG, "Auto-confirming card number"); + emvManager.importCardNoStatus(0); + }, 1500); + } + + @Override + public void onCertVerify(String certInfo) { + modalManager.showProcessingModal("Memverifikasi Sertifikat..."); + + // Auto-confirm after short delay + new Handler(Looper.getMainLooper()).postDelayed(() -> { + Log.d(TAG, "Auto-confirming certificate"); + emvManager.importCertStatus(0); + }, 1500); + } + + @Override + public void onShowPinPad(int pinType) { + Log.d(TAG, "Initializing PIN pad..."); + modalManager.hideModal(); + + String cardNo = emvManager.getCardNo(); + if (cardNo != null && cardNo.length() >= 13) { + pinPadManager.initPinPad(cardNo, pinType); + } else { + showToast("Invalid card number for PIN"); + emvManager.importPinInputStatus(3); // Error + } + } + + @Override + public void onOnlineProcess() { + modalManager.showProcessingModal("Memproses Otorisasi Online..."); + emvManager.mockOnlineProcess(); + } + + @Override + public void onSignature() { + emvManager.importSignatureStatus(0); + } + + @Override + public void onTransactionSuccess(int code, String desc) { + Log.d(TAG, "EMV Transaction successful"); + modalManager.hideModal(); + + String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; + navigateToResults(cardType, null, emvManager.getCardNo()); + } + + @Override + public void onTransactionFailed(int code, String desc) { + Log.e(TAG, "EMV Transaction failed: " + desc + " (Code: " + code + ")"); + + modalManager.hideModal(); + if (code == -50009) { + // EMV process conflict - reset and retry + Log.d(TAG, "EMV process conflict detected - resetting..."); + showToast("EMV busy, resetting..."); + resetEMVAndRetry(); + } else { + // Other errors - show message and restart scanning + showToast("Transaction failed: " + desc); + restartScanningAfterDelay(); + } + } + + // ====== PIN PAD CALLBACK METHODS ====== + @Override + public void onPinInputLength(int length) { + String dots = ""; + for (int i = 0; i < length; i++) { + dots += "• "; + } + // Can show PIN input feedback on UI if needed + Log.d(TAG, "PIN input length: " + length); + } + + @Override + public void onPinInputConfirmed(byte[] pinBlock) { + modalManager.showProcessingModal("PIN Dikonfirmasi - Memproses..."); + + if (pinBlock != null) { + emvManager.importPinInputStatus(0); // PIN entered + } else { + emvManager.importPinInputStatus(2); // PIN bypassed + } + } + + @Override + public void onPinInputCancelled() { + showToast("PIN input cancelled by user"); + modalManager.hideModal(); + emvManager.importPinInputStatus(1); // Cancelled + } + + @Override + public void onPinInputError(int code, String message) { + Log.e(TAG, "PIN Error: " + message); + showToast("PIN Error: " + message); + modalManager.hideModal(); + emvManager.importPinInputStatus(3); // Error + } + + // ====== HELPER METHODS ====== + private void showAppSelectDialog(String[] candidateNames) { + mAppSelectDialog = new AlertDialog.Builder(this) + .setTitle("Please select application") + .setNegativeButton("Cancel", (dialog, which) -> emvManager.importAppSelect(-1)) + .setPositiveButton("OK", (dialog, which) -> emvManager.importAppSelect(mSelectIndex)) + .setSingleChoiceItems(candidateNames, 0, (dialog, which) -> mSelectIndex = which) + .create(); + mSelectIndex = 0; + mAppSelectDialog.show(); + } + + private void navigateToResults(String cardType, Bundle cardData, String cardNo) { + modalManager.hideModal(); + + Intent intent = new Intent(this, CreditCardActivity.class); + intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); + intent.putExtra("CARD_TYPE", cardType); + intent.putExtra("EMV_MODE", isEMVMode); + + if (cardData != null) { + intent.putExtra("CARD_DATA", cardData); + } + if (cardNo != null) { + intent.putExtra("CARD_NO", cardNo); + } + + startActivity(intent); + finish(); + } + + private void restartScanningAfterDelay() { + Log.d(TAG, "Restarting scanning after delay..."); + + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (!isFinishing()) { + showModalAndStartScanning(); + } + }, 2000); + } + + private void resetEMVAndRetry() { + new Thread(() -> { + try { + Log.d(TAG, "Resetting EMV process..."); + + // Cancel any existing operations + cardScannerManager.stopScanning(); + Thread.sleep(500); + + // Reset EMV process + emvManager.resetEMVProcess(); + Thread.sleep(1500); + + runOnUiThread(() -> { + Log.d(TAG, "EMV reset complete - auto-restarting scan"); + showToast("EMV reset - restarting scan"); + restartScanningAfterDelay(); + }); + + } catch (Exception e) { + Log.e(TAG, "Error resetting EMV: " + e.getMessage(), e); + runOnUiThread(() -> { + showToast("Reset failed - " + e.getMessage()); + restartScanningAfterDelay(); + }); + } + }).start(); + } + + private void showToast(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + @Override + public boolean onSupportNavigateUp() { + cleanup(); + finish(); + return true; + } + + @Override + protected void onDestroy() { + Log.d(TAG, "onDestroy - cleaning up resources"); + cleanup(); + super.onDestroy(); + } + + private void cleanup() { + try { + // Stop all operations + if (cardScannerManager != null) { + cardScannerManager.stopScanning(); + } + + if (emvManager != null) { + emvManager.resetEMVProcess(); + } + + if (pinPadManager != null) { + pinPadManager.cancelPinInput(); + } + + if (modalManager != null) { + modalManager.hideModal(); + } + + } catch (Exception e) { + Log.e(TAG, "Error during cleanup: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/CardScannerManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/CardScannerManager.java new file mode 100644 index 0000000..39889da --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/CardScannerManager.java @@ -0,0 +1,258 @@ +package com.example.bdkipoc.transaction.managers; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +import com.example.bdkipoc.MyApplication; +import com.sunmi.pay.hardware.aidl.AidlConstants.CardType; +import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2; +import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2; + +/** + * CardScannerManager - Handles card detection for both EMV and Simple modes + */ +public class CardScannerManager { + private static final String TAG = "CardScannerManager"; + + private CardScannerCallback callback; + private boolean isProcessing = false; + + public interface CardScannerCallback { + void onCardDetected(String cardType, Bundle cardData); + void onEMVCardDetected(int cardType); + void onScanError(String errorMessage); + void onScanProgress(String message); + } + + public CardScannerManager(CardScannerCallback callback) { + this.callback = callback; + } + + public void startScanning(boolean isEMVMode) { + if (isProcessing) { + Log.d(TAG, "Card check already in progress - ignoring call"); + return; + } + + Log.d(TAG, "Starting card check - setting isProcessing = true"); + isProcessing = true; + + try { + // Small delay to ensure everything is ready + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (isProcessing) { + if (isEMVMode) { + startEMVCardCheck(); + } else { + startSimpleCardCheck(); + } + } + }, 500); + + } catch (Exception e) { + Log.e(TAG, "Error in startScanning: " + e.getMessage(), e); + handleScanError("Error: " + e.getMessage()); + } + } + + public void stopScanning() { + try { + if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) { + MyApplication.app.readCardOptV2.cancelCheckCard(); + } + isProcessing = false; + Log.d(TAG, "Card scanning stopped"); + } catch (Exception e) { + Log.e(TAG, "Error stopping card check: " + e.getMessage()); + } + } + + public boolean isScanning() { + return isProcessing; + } + + private void startEMVCardCheck() { + try { + if (callback != null) { + callback.onScanProgress("EMV Mode: Starting card scan..."); + } + + int cardType = AidlConstantsV2.CardType.NFC.getValue() | AidlConstantsV2.CardType.IC.getValue(); + Log.d(TAG, "Starting EMV checkCard with cardType: " + cardType); + + MyApplication.app.readCardOptV2.checkCard(cardType, mEMVCheckCardCallback, 60); + + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "Error in startEMVCardCheck: " + e.getMessage()); + handleScanError("Error starting EMV card scan: " + e.getMessage()); + } + } + + private void startSimpleCardCheck() { + try { + if (!MyApplication.app.isConnectPaySDK()) { + if (callback != null) { + callback.onScanProgress("Connecting to PaySDK..."); + } + MyApplication.app.bindPaySDKService(); + return; + } + + if (callback != null) { + callback.onScanProgress("Simple Mode: Starting card scan..."); + } + + int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue(); + + MyApplication.app.readCardOptV2.checkCard(cardType, mSimpleCheckCardCallback, 60); + + } catch (Exception e) { + e.printStackTrace(); + handleScanError("Error starting card scan: " + e.getMessage()); + } + } + + private void handleScanError(String errorMessage) { + Log.e(TAG, "Scan error: " + errorMessage); + isProcessing = false; + + if (callback != null) { + callback.onScanError(errorMessage); + } + } + + public void resetScanning() { + Log.d(TAG, "Resetting scanning state"); + isProcessing = false; + } + + // Simple Card Detection Callback + private final CheckCardCallbackV2 mSimpleCheckCardCallback = new CheckCardCallbackV2.Stub() { + @Override + public void findMagCard(Bundle info) throws RemoteException { + Log.d(TAG, "Simple Mode: findMagCard callback triggered"); + isProcessing = false; + if (callback != null) { + callback.onCardDetected("MAGNETIC", info); + } + } + + @Override + public void findICCard(String atr) throws RemoteException { + Bundle info = new Bundle(); + info.putString("atr", atr); + isProcessing = false; + if (callback != null) { + callback.onCardDetected("IC", info); + } + } + + @Override + public void findRFCard(String uuid) throws RemoteException { + Bundle info = new Bundle(); + info.putString("uuid", uuid); + isProcessing = false; + if (callback != null) { + callback.onCardDetected("NFC", info); + } + } + + @Override + public void onError(int code, String message) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onScanError("Card error: " + message); + } + } + + @Override + public void findICCardEx(Bundle info) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onCardDetected("IC", info); + } + } + + @Override + public void findRFCardEx(Bundle info) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onCardDetected("NFC", info); + } + } + + @Override + public void onErrorEx(Bundle info) throws RemoteException { + isProcessing = false; + String msg = info.getString("message", "Unknown error"); + if (callback != null) { + callback.onScanError("Card error: " + msg); + } + } + }; + + // EMV Card Detection Callback + private final CheckCardCallbackV2 mEMVCheckCardCallback = new CheckCardCallbackV2.Stub() { + @Override + public void findMagCard(Bundle info) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onCardDetected("MAGNETIC", info); + } + } + + @Override + public void findICCard(String atr) throws RemoteException { + MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0); + isProcessing = false; + if (callback != null) { + callback.onEMVCardDetected(AidlConstantsV2.CardType.IC.getValue()); + } + } + + @Override + public void findRFCard(String uuid) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onEMVCardDetected(AidlConstantsV2.CardType.NFC.getValue()); + } + } + + @Override + public void onError(int code, String message) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onScanError("EMV Error: " + message); + } + } + + @Override + public void findICCardEx(Bundle info) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onEMVCardDetected(AidlConstantsV2.CardType.IC.getValue()); + } + } + + @Override + public void findRFCardEx(Bundle info) throws RemoteException { + isProcessing = false; + if (callback != null) { + callback.onEMVCardDetected(AidlConstantsV2.CardType.NFC.getValue()); + } + } + + @Override + public void onErrorEx(Bundle info) throws RemoteException { + isProcessing = false; + String msg = info.getString("message", "Unknown error"); + if (callback != null) { + callback.onScanError("EMV Error: " + msg); + } + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/EMVManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/EMVManager.java new file mode 100644 index 0000000..9e07462 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/EMVManager.java @@ -0,0 +1,417 @@ +package com.example.bdkipoc.transaction.managers; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +import com.example.bdkipoc.MyApplication; +import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2; +import com.sunmi.pay.hardware.aidlv2.bean.EMVCandidateV2; +import com.sunmi.pay.hardware.aidlv2.emv.EMVListenerV2; +import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2; + +import java.util.List; + +/** + * EMVManager - Handles all EMV related operations + */ +public class EMVManager { + private static final String TAG = "EMVManager"; + + private EMVOptV2 mEMVOptV2; + private EMVManagerCallback callback; + + // EMV Process Variables + private int mCardType; + private String mCardNo; + private int mPinType; + private String mCertInfo; + private int mProcessStep; + + public interface EMVManagerCallback { + void onAppSelect(String[] candidateNames); + void onFinalAppSelect(); + void onConfirmCardNo(String cardNo); + void onCertVerify(String certInfo); + void onShowPinPad(int pinType); + void onOnlineProcess(); + void onSignature(); + void onTransactionSuccess(int code, String desc); + void onTransactionFailed(int code, String desc); + } + + public EMVManager(EMVManagerCallback callback) { + this.callback = callback; + initEMVComponents(); + } + + private void initEMVComponents() { + if (MyApplication.app != null) { + mEMVOptV2 = MyApplication.app.emvOptV2; + Log.d(TAG, "EMV components initialized"); + } else { + Log.e(TAG, "MyApplication.app is null"); + } + } + + public void initEMVData() { + try { + if (mEMVOptV2 != null) { + mEMVOptV2.initEmvProcess(); + + new Handler(Looper.getMainLooper()).postDelayed(() -> { + try { + initEmvTlvData(); + Log.d(TAG, "EMV data initialized successfully"); + } catch (Exception e) { + Log.e(TAG, "Error in delayed EMV init: " + e.getMessage()); + } + }, 500); + } + } catch (Exception e) { + Log.e(TAG, "Error initializing EMV data: " + e.getMessage()); + } + } + + 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 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) { + Log.e(TAG, "Error setting EMV TLV data: " + e.getMessage()); + } + } + + public void startEMVTransaction(String transactionAmount, int cardType) { + if (mProcessStep != 0) { + Log.d(TAG, "EMV transaction already in progress (step: " + mProcessStep + ") - ignoring call"); + return; + } + + Log.d(TAG, "Starting EMV transaction process"); + mProcessStep = 1; + mCardType = cardType; + + try { + mEMVOptV2.initEmvProcess(); + + new Handler(Looper.getMainLooper()).postDelayed(() -> { + try { + if (mProcessStep <= 0) { + Log.d(TAG, "EMV process was cancelled - not starting"); + return; + } + + Bundle bundle = new Bundle(); + bundle.putString("amount", transactionAmount); + bundle.putString("transType", "00"); + bundle.putInt("flowType", AidlConstantsV2.EMV.FlowType.TYPE_EMV_STANDARD); + bundle.putInt("cardType", mCardType); + + Log.d(TAG, "Starting transactProcessEx with reset EMV"); + mEMVOptV2.transactProcessEx(bundle, mEMVListener); + + } catch (Exception e) { + Log.e(TAG, "Error in delayed EMV start: " + e.getMessage(), e); + if (callback != null) { + callback.onTransactionFailed(-1, "Error starting EMV: " + e.getMessage()); + } + } + }, 300); + + } catch (Exception e) { + Log.e(TAG, "Error starting EMV transaction: " + e.getMessage()); + if (callback != null) { + callback.onTransactionFailed(-1, "Error starting EMV transaction: " + e.getMessage()); + } + } + } + + public void resetEMVProcess() { + try { + if (mEMVOptV2 != null) { + mEMVOptV2.initEmvProcess(); + } + mProcessStep = 0; + mCardNo = null; + Log.d(TAG, "EMV process reset"); + } catch (Exception e) { + Log.e(TAG, "Error resetting EMV process: " + e.getMessage()); + } + } + + // EMV Import Methods + public void importAppSelect(int selectIndex) { + try { + mEMVOptV2.importAppSelect(selectIndex); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void importFinalAppSelectStatus(int status) { + try { + mEMVOptV2.importAppFinalSelectStatus(status); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void importCardNoStatus(int status) { + try { + mEMVOptV2.importCardNoStatus(status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void importCertStatus(int status) { + try { + mEMVOptV2.importCertStatus(status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void importPinInputStatus(int inputResult) { + try { + mEMVOptV2.importPinInputStatus(mPinType, inputResult); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void importSignatureStatus(int status) { + try { + mEMVOptV2.importSignatureStatus(status); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void mockOnlineProcess() { + new Thread(() -> { + try { + 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) { + Log.e(TAG, "importOnlineProcessStatus error,code:" + len); + } + } catch (Exception e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + + // Getters + public String getCardNo() { + return mCardNo; + } + + public int getCardType() { + return mCardType; + } + + public int getPinType() { + return mPinType; + } + + // Helper Methods + public 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; + } + + public 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; + } + return result; + } + + // EMV Listener + private final EMVListenerV2 mEMVListener = new EMVListenerV2.Stub() { + + @Override + public void onWaitAppSelect(List appNameList, boolean isFirstSelect) throws RemoteException { + Log.d(TAG, "onWaitAppSelect isFirstSelect:" + isFirstSelect); + mProcessStep = 1; // EMV_APP_SELECT + String[] candidateNames = getCandidateNames(appNameList); + if (callback != null) { + callback.onAppSelect(candidateNames); + } + } + + @Override + public void onAppFinalSelect(String tag9F06Value) throws RemoteException { + Log.d(TAG, "onAppFinalSelect tag9F06Value:" + tag9F06Value); + mProcessStep = 2; // EMV_FINAL_APP_SELECT + if (callback != null) { + callback.onFinalAppSelect(); + } + } + + @Override + public void onConfirmCardNo(String cardNo) throws RemoteException { + Log.d(TAG, "onConfirmCardNo cardNo:" + maskCardNumber(cardNo)); + mCardNo = cardNo; + mProcessStep = 3; // EMV_CONFIRM_CARD_NO + if (callback != null) { + callback.onConfirmCardNo(cardNo); + } + } + + @Override + public void onRequestShowPinPad(int pinType, int remainTime) throws RemoteException { + Log.d(TAG, "onRequestShowPinPad pinType:" + pinType + " remainTime:" + remainTime); + mPinType = pinType; + mProcessStep = 5; // EMV_SHOW_PIN_PAD + if (callback != null) { + callback.onShowPinPad(pinType); + } + } + + @Override + public void onRequestSignature() throws RemoteException { + Log.d(TAG, "onRequestSignature"); + mProcessStep = 7; // EMV_SIGNATURE + if (callback != null) { + callback.onSignature(); + } + } + + @Override + public void onCertVerify(int certType, String certInfo) throws RemoteException { + Log.d(TAG, "onCertVerify certType:" + certType + " certInfo:" + certInfo); + mCertInfo = certInfo; + mProcessStep = 4; // EMV_CERT_VERIFY + if (callback != null) { + callback.onCertVerify(certInfo); + } + } + + @Override + public void onOnlineProc() throws RemoteException { + Log.d(TAG, "onOnlineProcess"); + mProcessStep = 6; // EMV_ONLINE_PROCESS + if (callback != null) { + callback.onOnlineProcess(); + } + } + + @Override + public void onCardDataExchangeComplete() throws RemoteException { + 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 { + Log.d(TAG, "onTransResult code:" + code + " desc:" + desc); + + if (code == 1 || code == 2 || code == 5 || code == 6) { + if (callback != null) { + callback.onTransactionSuccess(code, desc); + } + } else { + if (callback != null) { + callback.onTransactionFailed(code, desc); + } + } + } + + @Override + public void onConfirmationCodeVerified() throws RemoteException { + Log.d(TAG, "onConfirmationCodeVerified"); + } + + @Override + public void onRequestDataExchange(String cardNo) throws RemoteException { + Log.d(TAG, "onRequestDataExchange,cardNo:" + cardNo); + mEMVOptV2.importDataExchangeStatus(0); + } + + @Override + public void onTermRiskManagement() throws RemoteException { + Log.d(TAG, "onTermRiskManagement"); + mEMVOptV2.importTermRiskManagementStatus(0); + } + + @Override + public void onPreFirstGenAC() throws RemoteException { + Log.d(TAG, "onPreFirstGenAC"); + mEMVOptV2.importPreFirstGenACStatus(0); + } + + @Override + public void onDataStorageProc(String[] containerID, String[] containerContent) throws RemoteException { + Log.d(TAG, "onDataStorageProc"); + String[] tags = new String[0]; + String[] values = new String[0]; + mEMVOptV2.importDataStorage(tags, values); + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/ModalManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/ModalManager.java new file mode 100644 index 0000000..262cb27 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/ModalManager.java @@ -0,0 +1,121 @@ +package com.example.bdkipoc.transaction.managers; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import android.util.Log; + +import com.example.bdkipoc.R; + +/** + * ModalManager - Handles modal UI operations + */ +public class ModalManager { + private static final String TAG = "ModalManager"; + + private FrameLayout modalOverlay; + private TextView modalText; + private ImageView modalIcon; + private Animation fadeIn; + private Animation fadeOut; + private boolean isModalShowing = false; + + public ModalManager(FrameLayout modalOverlay, TextView modalText, ImageView modalIcon) { + this.modalOverlay = modalOverlay; + this.modalText = modalText; + this.modalIcon = modalIcon; + initAnimations(); + } + + private void initAnimations() { + fadeIn = AnimationUtils.loadAnimation(modalOverlay.getContext(), android.R.anim.fade_in); + fadeOut = AnimationUtils.loadAnimation(modalOverlay.getContext(), android.R.anim.fade_out); + + fadeIn.setDuration(300); + fadeOut.setDuration(300); + } + + public void showScanCardModal() { + if (isModalShowing) return; + + modalOverlay.post(() -> { + modalText.setText("Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"); + modalIcon.setImageResource(R.drawable.ic_card_insert); + + modalOverlay.setVisibility(View.VISIBLE); + modalOverlay.startAnimation(fadeIn); + + isModalShowing = true; + Log.d(TAG, "Modal scan card shown"); + }); + } + + public void showProcessingModal(String message) { + if (!isModalShowing) { + modalOverlay.post(() -> { + modalText.setText(message); + modalIcon.setImageResource(R.drawable.ic_card_insert); + + modalOverlay.setVisibility(View.VISIBLE); + modalOverlay.startAnimation(fadeIn); + + isModalShowing = true; + Log.d(TAG, "Modal processing shown: " + message); + }); + } else { + // Just update text if modal already showing + modalOverlay.post(() -> { + modalText.setText(message); + Log.d(TAG, "Modal text updated: " + message); + }); + } + } + + public void hideModal() { + if (!isModalShowing) return; + + modalOverlay.post(() -> { + fadeOut.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + + @Override + public void onAnimationEnd(Animation animation) { + modalOverlay.setVisibility(View.GONE); + isModalShowing = false; + } + + @Override + public void onAnimationRepeat(Animation animation) {} + }); + + modalOverlay.startAnimation(fadeOut); + Log.d(TAG, "Modal hidden"); + }); + } + + public boolean isShowing() { + return isModalShowing; + } + + public void updateText(String text) { + if (isModalShowing) { + modalOverlay.post(() -> { + modalText.setText(text); + Log.d(TAG, "Modal text updated: " + text); + }); + } + } + + public void updateIcon(int iconResource) { + if (isModalShowing) { + modalOverlay.post(() -> { + modalIcon.setImageResource(iconResource); + Log.d(TAG, "Modal icon updated"); + }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/PinPadManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/PinPadManager.java new file mode 100644 index 0000000..c31d10f --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/PinPadManager.java @@ -0,0 +1,142 @@ +package com.example.bdkipoc.transaction.managers; + +import android.os.RemoteException; +import android.util.Log; + +import com.example.bdkipoc.MyApplication; +import com.example.bdkipoc.utils.ByteUtil; +import com.sunmi.pay.hardware.aidlv2.AidlErrorCodeV2; +import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2; +import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2; +import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2; + +/** + * PinPadManager - Handles PIN pad operations + */ +public class PinPadManager { + private static final String TAG = "PinPadManager"; + + private PinPadOptV2 mPinPadOptV2; + private PinPadManagerCallback callback; + + public interface PinPadManagerCallback { + void onPinInputLength(int length); + void onPinInputConfirmed(byte[] pinBlock); + void onPinInputCancelled(); + void onPinInputError(int code, String message); + } + + public PinPadManager(PinPadManagerCallback callback) { + this.callback = callback; + initPinPadComponents(); + } + + private void initPinPadComponents() { + if (MyApplication.app != null) { + mPinPadOptV2 = MyApplication.app.pinPadOptV2; + Log.d(TAG, "PIN Pad components initialized"); + } else { + Log.e(TAG, "MyApplication.app is null"); + } + } + + public void initPinPad(String cardNo, int pinType) { + Log.d(TAG, "========== PIN PAD INITIALIZATION =========="); + try { + if (mPinPadOptV2 == null) { + throw new IllegalStateException("PIN Pad service not available"); + } + + if (cardNo == null || cardNo.length() < 13) { + throw new IllegalArgumentException("Invalid card number for PIN"); + } + + PinPadConfigV2 pinPadConfig = new PinPadConfigV2(); + pinPadConfig.setPinPadType(0); + pinPadConfig.setPinType(pinType); + pinPadConfig.setOrderNumKey(true); // Set to true for normal order, false for random + + String panForPin = cardNo.substring(cardNo.length() - 13, cardNo.length() - 1); + byte[] panBytes = panForPin.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); + + Log.d(TAG, "Initializing PIN pad with config"); + mPinPadOptV2.initPinPad(pinPadConfig, mPinPadListener); + + } catch (Exception e) { + Log.e(TAG, "PIN pad initialization failed: " + e.getMessage()); + if (callback != null) { + callback.onPinInputError(-1, "PIN Error: " + e.getMessage()); + } + } + } + + public void cancelPinInput() { + try { + if (mPinPadOptV2 != null) { + // Cancel PIN input if needed + Log.d(TAG, "PIN input cancelled"); + } + } catch (Exception e) { + Log.e(TAG, "Error cancelling PIN input: " + e.getMessage()); + } + } + + // PIN Pad Listener + private final PinPadListenerV2 mPinPadListener = new PinPadListenerV2.Stub() { + @Override + public void onPinLength(int len) throws RemoteException { + Log.d(TAG, "PIN input length: " + len); + if (callback != null) { + callback.onPinInputLength(len); + } + } + + @Override + public void onConfirm(int i, byte[] pinBlock) throws RemoteException { + Log.d(TAG, "PIN input confirmed"); + + if (pinBlock != null) { + String hexStr = ByteUtil.bytes2HexStr(pinBlock); + Log.d(TAG, "PIN block received: " + hexStr); + if (callback != null) { + callback.onPinInputConfirmed(pinBlock); + } + } else { + Log.d(TAG, "PIN bypass confirmed"); + if (callback != null) { + callback.onPinInputConfirmed(null); + } + } + } + + @Override + public void onCancel() throws RemoteException { + Log.d(TAG, "PIN input cancelled by user"); + if (callback != null) { + callback.onPinInputCancelled(); + } + } + + @Override + public void onError(int code) throws RemoteException { + Log.e(TAG, "PIN pad error: " + code); + String msg = AidlErrorCodeV2.valueOf(code).getMsg(); + if (callback != null) { + callback.onPinInputError(code, msg); + } + } + + @Override + public void onHover(int event, byte[] data) throws RemoteException { + Log.d(TAG, "PIN pad hover event: " + event); + } + }; +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_transaction.xml b/app/src/main/res/layout/activity_create_transaction.xml index ec0b3b2..5f20624 100644 --- a/app/src/main/res/layout/activity_create_transaction.xml +++ b/app/src/main/res/layout/activity_create_transaction.xml @@ -1,203 +1,276 @@ - - + + android:layout_height="match_parent" + android:fillViewport="true"> - - - - + android:orientation="vertical"> - - + + + + + android:orientation="vertical" + android:padding="16dp"> - + + android:layout_marginBottom="24dp" + app:cardCornerRadius="12dp" + app:cardElevation="4dp"> - + android:orientation="vertical" + android:padding="20dp"> - - + - - + + - - + + - -