refactor transaction
This commit is contained in:
parent
0af0e836b1
commit
ece79942c1
@ -73,12 +73,8 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".kredit.CreateTransactionActivity"
|
android:name=".transaction.CreateTransactionActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".kredit.EmvTransactionActivity"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".kredit.CreditCardActivity"
|
android:name=".kredit.CreditCardActivity"
|
||||||
|
@ -19,8 +19,7 @@ import androidx.core.view.WindowInsetsCompat;
|
|||||||
|
|
||||||
import com.google.android.material.button.MaterialButton;
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
|
||||||
import com.example.bdkipoc.kredit.CreateTransactionActivity;
|
import com.example.bdkipoc.transaction.CreateTransactionActivity;
|
||||||
import com.example.bdkipoc.kredit.EmvTransactionActivity;
|
|
||||||
import com.example.bdkipoc.kredit.CreditCardActivity;
|
import com.example.bdkipoc.kredit.CreditCardActivity;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
package com.example.bdkipoc.kredit;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import com.example.bdkipoc.R;
|
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CreateTransactionActivity - Handle amount input and transaction creation
|
|
||||||
*/
|
|
||||||
public class CreateTransactionActivity extends AppCompatActivity {
|
|
||||||
private static final String TAG = "CreateTransaction";
|
|
||||||
|
|
||||||
// UI Components
|
|
||||||
private TextView tvAmountDisplay;
|
|
||||||
private TextView tvModeIndicator;
|
|
||||||
private Button btnConfirm;
|
|
||||||
private Button btnToggleMode;
|
|
||||||
|
|
||||||
// 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; // Default to EMV mode
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_create_transaction);
|
|
||||||
|
|
||||||
initViews();
|
|
||||||
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 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);
|
|
||||||
|
|
||||||
// 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 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
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,6 +32,8 @@ import java.util.TreeMap;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.example.bdkipoc.transaction.CreateTransactionActivity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CreditCardActivity - Display detailed transaction results and TLV data
|
* CreditCardActivity - Display detailed transaction results and TLV data
|
||||||
*/
|
*/
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -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<EMVCandidateV2> 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<EMVCandidateV2> 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,203 +1,276 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fillViewport="true"
|
|
||||||
android:background="@color/colorBackground"
|
android:background="@color/colorBackground"
|
||||||
tools:context=".kredit.CreateTransactionActivity">
|
tools:context=".kredit.CreateTransactionActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Main Content -->
|
||||||
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:fillViewport="true">
|
||||||
|
|
||||||
<!-- Toolbar -->
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:background="@color/primary_blue"
|
|
||||||
android:theme="@style/CustomToolbarTheme"
|
|
||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
|
||||||
|
|
||||||
<!-- Content Container -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<!-- Header Section -->
|
<!-- Toolbar -->
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@color/primary_blue"
|
||||||
|
android:theme="@style/CustomToolbarTheme"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
|
<!-- Content Container -->
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="24dp"
|
android:orientation="vertical"
|
||||||
app:cardCornerRadius="12dp"
|
android:padding="16dp">
|
||||||
app:cardElevation="4dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Header Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_marginBottom="24dp"
|
||||||
android:padding="20dp">
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="MASUKKAN NOMINAL"
|
android:orientation="vertical"
|
||||||
android:textSize="18sp"
|
android:padding="20dp">
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="#333333"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_marginBottom="16dp" />
|
|
||||||
|
|
||||||
<!-- Amount Display -->
|
<TextView
|
||||||
<TextView
|
android:layout_width="match_parent"
|
||||||
android:id="@+id/tv_amount_display"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:text="MASUKKAN NOMINAL"
|
||||||
android:layout_height="wrap_content"
|
android:textSize="18sp"
|
||||||
android:text="Rp 0,00"
|
android:textStyle="bold"
|
||||||
style="@style/AmountDisplayStyle"
|
android:textColor="#333333"
|
||||||
android:layout_marginBottom="8dp" />
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
<!-- Mode Indicator -->
|
<!-- Amount Display -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_mode_indicator"
|
android:id="@+id/tv_amount_display"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Current Mode: EMV Mode (Full Card Data)"
|
android:text="Rp 0,00"
|
||||||
style="@style/ModeIndicatorStyle" />
|
style="@style/AmountDisplayStyle"
|
||||||
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
<!-- Mode Indicator -->
|
||||||
</androidx.cardview.widget.CardView>
|
<TextView
|
||||||
|
android:id="@+id/tv_mode_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Current Mode: EMV Mode (Full Card Data)"
|
||||||
|
style="@style/ModeIndicatorStyle" />
|
||||||
|
|
||||||
<!-- Mode Toggle Button -->
|
</LinearLayout>
|
||||||
<Button
|
</androidx.cardview.widget.CardView>
|
||||||
android:id="@+id/btn_toggle_mode"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:text="Switch to Simple"
|
|
||||||
style="@style/OutlineButton" />
|
|
||||||
|
|
||||||
<!-- Keypad Section -->
|
<!-- Mode Toggle Button -->
|
||||||
<androidx.cardview.widget.CardView
|
<Button
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn_toggle_mode"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_height="48dp"
|
||||||
app:cardCornerRadius="12dp"
|
android:layout_marginBottom="24dp"
|
||||||
app:cardElevation="4dp">
|
android:text="Switch to Simple"
|
||||||
|
style="@style/OutlineButton" />
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Keypad Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_marginBottom="24dp"
|
||||||
android:padding="16dp">
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Keypad Input"
|
android:orientation="vertical"
|
||||||
android:textSize="16sp"
|
android:padding="16dp">
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="#333333"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_marginBottom="16dp" />
|
|
||||||
|
|
||||||
<!-- Keypad Grid -->
|
<TextView
|
||||||
<GridLayout
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Keypad Input"
|
||||||
android:columnCount="3"
|
android:textSize="16sp"
|
||||||
android:rowCount="4"
|
android:textStyle="bold"
|
||||||
android:layout_gravity="center">
|
android:textColor="#333333"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
<!-- Row 1: 1, 2, 3 -->
|
<!-- Keypad Grid -->
|
||||||
<Button
|
<GridLayout
|
||||||
android:id="@+id/btn_1"
|
android:layout_width="match_parent"
|
||||||
style="@style/KeypadButton"
|
android:layout_height="wrap_content"
|
||||||
android:text="1" />
|
android:columnCount="3"
|
||||||
|
android:rowCount="4"
|
||||||
|
android:layout_gravity="center">
|
||||||
|
|
||||||
<Button
|
<!-- Row 1: 1, 2, 3 -->
|
||||||
android:id="@+id/btn_2"
|
<Button
|
||||||
style="@style/KeypadButton"
|
android:id="@+id/btn_1"
|
||||||
android:text="2" />
|
style="@style/KeypadButton"
|
||||||
|
android:text="1" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_3"
|
android:id="@+id/btn_2"
|
||||||
style="@style/KeypadButton"
|
style="@style/KeypadButton"
|
||||||
android:text="3" />
|
android:text="2" />
|
||||||
|
|
||||||
<!-- Row 2: 4, 5, 6 -->
|
<Button
|
||||||
<Button
|
android:id="@+id/btn_3"
|
||||||
android:id="@+id/btn_4"
|
style="@style/KeypadButton"
|
||||||
style="@style/KeypadButton"
|
android:text="3" />
|
||||||
android:text="4" />
|
|
||||||
|
|
||||||
<Button
|
<!-- Row 2: 4, 5, 6 -->
|
||||||
android:id="@+id/btn_5"
|
<Button
|
||||||
style="@style/KeypadButton"
|
android:id="@+id/btn_4"
|
||||||
android:text="5" />
|
style="@style/KeypadButton"
|
||||||
|
android:text="4" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_6"
|
android:id="@+id/btn_5"
|
||||||
style="@style/KeypadButton"
|
style="@style/KeypadButton"
|
||||||
android:text="6" />
|
android:text="5" />
|
||||||
|
|
||||||
<!-- Row 3: 7, 8, 9 -->
|
<Button
|
||||||
<Button
|
android:id="@+id/btn_6"
|
||||||
android:id="@+id/btn_7"
|
style="@style/KeypadButton"
|
||||||
style="@style/KeypadButton"
|
android:text="6" />
|
||||||
android:text="7" />
|
|
||||||
|
|
||||||
<Button
|
<!-- Row 3: 7, 8, 9 -->
|
||||||
android:id="@+id/btn_8"
|
<Button
|
||||||
style="@style/KeypadButton"
|
android:id="@+id/btn_7"
|
||||||
android:text="8" />
|
style="@style/KeypadButton"
|
||||||
|
android:text="7" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_9"
|
android:id="@+id/btn_8"
|
||||||
style="@style/KeypadButton"
|
style="@style/KeypadButton"
|
||||||
android:text="9" />
|
android:text="8" />
|
||||||
|
|
||||||
<!-- Row 4: 00, 0, Clear -->
|
<Button
|
||||||
<Button
|
android:id="@+id/btn_9"
|
||||||
android:id="@+id/btn_00"
|
style="@style/KeypadButton"
|
||||||
style="@style/KeypadButton"
|
android:text="9" />
|
||||||
android:text="00" />
|
|
||||||
|
|
||||||
<Button
|
<!-- Row 4: 00, 0, Clear -->
|
||||||
android:id="@+id/btn_0"
|
<Button
|
||||||
style="@style/KeypadButton"
|
android:id="@+id/btn_00"
|
||||||
android:text="0" />
|
style="@style/KeypadButton"
|
||||||
|
android:text="00" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_clear"
|
android:id="@+id/btn_0"
|
||||||
style="@style/KeypadButtonSecondary"
|
style="@style/KeypadButton"
|
||||||
android:text="⌫" />
|
android:text="0" />
|
||||||
|
|
||||||
</GridLayout>
|
<Button
|
||||||
</LinearLayout>
|
android:id="@+id/btn_clear"
|
||||||
</androidx.cardview.widget.CardView>
|
style="@style/KeypadButtonSecondary"
|
||||||
|
android:text="⌫" />
|
||||||
|
|
||||||
<!-- Confirm Button -->
|
</GridLayout>
|
||||||
<Button
|
</LinearLayout>
|
||||||
android:id="@+id/btn_confirm"
|
</androidx.cardview.widget.CardView>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:text="KONFIRMASI NOMINAL"
|
|
||||||
style="@style/PrimaryButton"
|
|
||||||
android:layout_marginBottom="16dp" />
|
|
||||||
|
|
||||||
<!-- Instructions -->
|
<!-- Confirm Button -->
|
||||||
<TextView
|
<Button
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn_confirm"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="• Gunakan keypad untuk memasukkan nominal\n• Minimal transaksi Rp 1,00\n• Pilih mode EMV untuk data lengkap kartu"
|
android:layout_height="56dp"
|
||||||
style="@style/HintTextStyle" />
|
android:text="KONFIRMASI NOMINAL"
|
||||||
|
style="@style/PrimaryButton"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<!-- Instructions -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="• Gunakan keypad untuk memasukkan nominal\n• Minimal transaksi Rp 1,00\n• Pilih mode EMV untuk data lengkap kartu"
|
||||||
|
style="@style/HintTextStyle" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</ScrollView>
|
||||||
</ScrollView>
|
|
||||||
|
<!-- Hidden Progress Bar for Card Scanning -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/primary_blue" />
|
||||||
|
|
||||||
|
<!-- Modal Overlay with Blur Effect for Card Scanning -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/modal_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/modal_card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="32dp"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="8dp"
|
||||||
|
app:cardBackgroundColor="@android:color/white">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="32dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<!-- Card Icon -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/modal_icon"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:src="@drawable/ic_card_insert"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
app:tint="@color/primary_blue" />
|
||||||
|
|
||||||
|
<!-- Main Text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/modal_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"
|
||||||
|
style="@style/StatusTextStyle"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lineSpacingExtra="4dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -1,160 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@color/colorBackground"
|
|
||||||
tools:context=".kredit.EmvTransactionActivity">
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/main_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- Toolbar -->
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:background="@color/primary_blue"
|
|
||||||
android:theme="@style/CustomToolbarTheme"
|
|
||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
|
||||||
|
|
||||||
<!-- Main Content Container with Center Alignment -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="24dp">
|
|
||||||
|
|
||||||
<!-- Status Modal Card - Centered and Compact -->
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
app:cardCornerRadius="16dp"
|
|
||||||
app:cardElevation="8dp"
|
|
||||||
app:cardBackgroundColor="@android:color/white">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="32dp"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<!-- Header Title -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Card Scanner"
|
|
||||||
style="@style/HeaderTextStyle"
|
|
||||||
android:layout_marginBottom="24dp" />
|
|
||||||
|
|
||||||
<!-- Card Reader Animation/Icon -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_card_reader"
|
|
||||||
android:layout_width="120dp"
|
|
||||||
android:layout_height="120dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:src="@drawable/ic_card_insert"
|
|
||||||
android:scaleType="centerInside"
|
|
||||||
app:tint="@color/primary_blue" />
|
|
||||||
|
|
||||||
<!-- Status Text -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Ready for card scanning..."
|
|
||||||
style="@style/StatusTextStyle"
|
|
||||||
android:layout_marginBottom="24dp" />
|
|
||||||
|
|
||||||
<!-- Progress Indicator -->
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress_bar"
|
|
||||||
style="?android:attr/progressBarStyle"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="visible"
|
|
||||||
android:indeterminateTint="@color/primary_blue"
|
|
||||||
android:layout_marginBottom="16dp" />
|
|
||||||
|
|
||||||
<!-- Instructions Text -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Please insert, tap, or swipe your card\nScanning will start automatically"
|
|
||||||
style="@style/HintTextStyle"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Modal Overlay with Blur Effect -->
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/modal_overlay"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#80000000"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<!-- Modal Content -->
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/modal_card"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_margin="32dp"
|
|
||||||
app:cardCornerRadius="16dp"
|
|
||||||
app:cardElevation="8dp"
|
|
||||||
app:cardBackgroundColor="@android:color/white">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="32dp"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<!-- Card Icon -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/modal_icon"
|
|
||||||
android:layout_width="80dp"
|
|
||||||
android:layout_height="80dp"
|
|
||||||
android:src="@drawable/ic_card_insert"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
app:tint="@color/primary_blue" />
|
|
||||||
|
|
||||||
<!-- Main Text -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/modal_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"
|
|
||||||
style="@style/StatusTextStyle"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:lineSpacingExtra="4dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
Loading…
x
Reference in New Issue
Block a user