Safepoint Card Reading
This commit is contained in:
parent
d7617186a6
commit
124da43a1e
@ -45,19 +45,14 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
|
||||
implementation libs.appcompat
|
||||
implementation libs.material
|
||||
implementation libs.activity
|
||||
implementation libs.constraintlayout
|
||||
implementation libs.cardview
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
|
||||
// Existing PayLib
|
||||
// implementation(name: 'PayLib-release-2.0.17', ext: 'aar')
|
||||
|
||||
// Tambahkan dependencies yang kompatibel dari referensi
|
||||
implementation 'com.sunmi:printerlibrary:1.0.15'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
||||
|
||||
// Test dependencies
|
||||
testImplementation libs.junit
|
||||
|
@ -24,6 +24,7 @@
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
@ -71,7 +72,8 @@
|
||||
android:name=".HistoryDetailActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".CreditCardActivity"
|
||||
android:name=".kredit.CreditCardActivity"
|
||||
style="@style/Theme.AppCompat"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
|
27
app/src/main/java/com/example/bdkipoc/CacheHelper.java
Normal file
27
app/src/main/java/com/example/bdkipoc/CacheHelper.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class CacheHelper {
|
||||
|
||||
private static final String PREFERENCE_FILE_NAME = "sm_pay_demo_obj";
|
||||
|
||||
private static final String KEY_LANGUAGE = "key_language";
|
||||
|
||||
public static void saveCurrentLanguage(int language) {
|
||||
SharedPreferences sharedPreferences = MyApplication.app.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
|
||||
int value = sharedPreferences.getInt(KEY_LANGUAGE, Constant.LANGUAGE_AUTO);
|
||||
if (value == language) return;
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(KEY_LANGUAGE, language);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static int getCurrentLanguage() {
|
||||
SharedPreferences sharedPreferences = MyApplication.app.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getInt(KEY_LANGUAGE, Constant.LANGUAGE_AUTO);
|
||||
}
|
||||
|
||||
|
||||
}
|
17
app/src/main/java/com/example/bdkipoc/Constant.java
Normal file
17
app/src/main/java/com/example/bdkipoc/Constant.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.example.bdkipoc;
|
||||
|
||||
public class Constant {
|
||||
|
||||
public static final String TAG = "SDKTestDemo";
|
||||
|
||||
public static final int LANGUAGE_AUTO = 0;
|
||||
public static final int LANGUAGE_ZH_CN = 1;
|
||||
public static final int LANGUAGE_EN_US = 2;
|
||||
public static final int LANGUAGE_JA_JP = 3;
|
||||
|
||||
public static final int SCAN_MODEL_NONE = 100;
|
||||
public static final int SCAN_MODEL_P2Lite = 101;
|
||||
|
||||
public static final String SCAN_MODEL_NONE_VALUE = "NONE";
|
||||
public static final String SCAN_MODEL_P2Lite_VALUE = "P2Lite";
|
||||
}
|
@ -19,6 +19,8 @@ import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import com.example.bdkipoc.kredit.CreditCardActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private boolean isExpanded = false; // False = showing only 9 main menus, True = showing all 15 menus
|
||||
@ -158,7 +160,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (cardView != null) {
|
||||
cardView.setOnClickListener(v -> {
|
||||
if (cardId == R.id.card_kartu_kredit) {
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
startActivity(new Intent(MainActivity.this, CreditCardActivity.class));
|
||||
} else if (cardId == R.id.card_kartu_debit) {
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
} else if (cardId == R.id.card_qris) {
|
||||
|
196
app/src/main/java/com/example/bdkipoc/MyApplication.java
Normal file
196
app/src/main/java/com/example/bdkipoc/MyApplication.java
Normal file
@ -0,0 +1,196 @@
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.IBinder;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.example.bdkipoc.emv.EmvTTS;
|
||||
import com.example.bdkipoc.utils.LogUtil;
|
||||
import com.example.bdkipoc.utils.Utility;
|
||||
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.etc.ETCOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.print.PrinterOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.readcard.ReadCardOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.rfid.RFIDOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.security.BiometricManagerV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.security.DevCertManagerV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.security.NoLostKeyManagerV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.tax.TaxOptV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.test.TestOptV2;
|
||||
import com.sunmi.pay.hardware.wrapper.HCEManagerV2Wrapper;
|
||||
import com.sunmi.peripheral.printer.InnerPrinterCallback;
|
||||
import com.sunmi.peripheral.printer.InnerPrinterException;
|
||||
import com.sunmi.peripheral.printer.InnerPrinterManager;
|
||||
import com.sunmi.peripheral.printer.SunmiPrinterService;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import sunmi.paylib.SunmiPayKernel;
|
||||
|
||||
public class MyApplication extends Application {
|
||||
public static MyApplication app;
|
||||
|
||||
public BasicOptV2 basicOptV2; // 获取基础操作模块
|
||||
public ReadCardOptV2 readCardOptV2; // 获取读卡模块
|
||||
public PinPadOptV2 pinPadOptV2; // 获取PinPad操作模块
|
||||
public SecurityOptV2 securityOptV2; // 获取安全操作模块
|
||||
public EMVOptV2 emvOptV2; // 获取EMV操作模块
|
||||
public TaxOptV2 taxOptV2; // 获取税控操作模块
|
||||
public ETCOptV2 etcOptV2; // 获取ETC操作模块
|
||||
public PrinterOptV2 printerOptV2; // 获取打印操作模块
|
||||
public TestOptV2 testOptV2; // 获取测试操作模块
|
||||
public DevCertManagerV2 devCertManagerV2; // 设备证书操作模块
|
||||
public NoLostKeyManagerV2 noLostKeyManagerV2; // NoLostKey操作模块
|
||||
public HCEManagerV2Wrapper hceV2Wrapper; // HCE操作模块
|
||||
public RFIDOptV2 rfidOptV2; // RFID操作模块
|
||||
public SunmiPrinterService sunmiPrinterService; // 打印模块
|
||||
//public IScanInterface scanInterface; // 扫码模块 (commented out)
|
||||
public BiometricManagerV2 mBiometricManagerV2; // 生物特征模块
|
||||
|
||||
private boolean connectPaySDK;//是否已连接PaySDK
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
app = this;
|
||||
initLocaleLanguage();
|
||||
initEmvTTS();
|
||||
bindPrintService();
|
||||
bindPaySDKService();
|
||||
//bindScannerService(); // Commented out scanner service binding
|
||||
}
|
||||
|
||||
public static void initLocaleLanguage() {
|
||||
Resources resources = app.getResources();
|
||||
DisplayMetrics dm = resources.getDisplayMetrics();
|
||||
Configuration config = resources.getConfiguration();
|
||||
int showLanguage = CacheHelper.getCurrentLanguage();
|
||||
if (showLanguage == Constant.LANGUAGE_AUTO) {
|
||||
LogUtil.e(Constant.TAG, config.locale.getCountry() + "---这是系统语言");
|
||||
config.locale = Resources.getSystem().getConfiguration().locale;
|
||||
} else if (showLanguage == Constant.LANGUAGE_ZH_CN) {
|
||||
LogUtil.e(Constant.TAG, "这是中文");
|
||||
config.locale = Locale.SIMPLIFIED_CHINESE;
|
||||
} else if (showLanguage == Constant.LANGUAGE_EN_US) {
|
||||
LogUtil.e(Constant.TAG, "这是英文");
|
||||
config.locale = Locale.ENGLISH;
|
||||
} else if (showLanguage == Constant.LANGUAGE_JA_JP) {
|
||||
LogUtil.e(Constant.TAG, "这是日文");
|
||||
config.locale = Locale.JAPAN;
|
||||
}
|
||||
resources.updateConfiguration(config, dm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LogUtil.e(Constant.TAG, "onConfigurationChanged");
|
||||
}
|
||||
|
||||
public boolean isConnectPaySDK() {
|
||||
return connectPaySDK;
|
||||
}
|
||||
|
||||
/**
|
||||
* bind PaySDK service
|
||||
*/
|
||||
public void bindPaySDKService() {
|
||||
final SunmiPayKernel payKernel = SunmiPayKernel.getInstance();
|
||||
payKernel.initPaySDK(this, new SunmiPayKernel.ConnectCallback() {
|
||||
@Override
|
||||
public void onConnectPaySDK() {
|
||||
LogUtil.e(Constant.TAG, "onConnectPaySDK...");
|
||||
emvOptV2 = payKernel.mEMVOptV2;
|
||||
basicOptV2 = payKernel.mBasicOptV2;
|
||||
pinPadOptV2 = payKernel.mPinPadOptV2;
|
||||
readCardOptV2 = payKernel.mReadCardOptV2;
|
||||
securityOptV2 = payKernel.mSecurityOptV2;
|
||||
taxOptV2 = payKernel.mTaxOptV2;
|
||||
etcOptV2 = payKernel.mETCOptV2;
|
||||
printerOptV2 = payKernel.mPrinterOptV2;
|
||||
testOptV2 = payKernel.mTestOptV2;
|
||||
devCertManagerV2 = payKernel.mDevCertManagerV2;
|
||||
noLostKeyManagerV2 = payKernel.mNoLostKeyManagerV2;
|
||||
mBiometricManagerV2 = payKernel.mBiometricManagerV2;
|
||||
hceV2Wrapper = payKernel.mHCEManagerV2Wrapper;
|
||||
rfidOptV2 = payKernel.mRFIDOptV2;
|
||||
connectPaySDK = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectPaySDK() {
|
||||
LogUtil.e(Constant.TAG, "onDisconnectPaySDK...");
|
||||
connectPaySDK = false;
|
||||
emvOptV2 = null;
|
||||
basicOptV2 = null;
|
||||
pinPadOptV2 = null;
|
||||
readCardOptV2 = null;
|
||||
securityOptV2 = null;
|
||||
taxOptV2 = null;
|
||||
etcOptV2 = null;
|
||||
printerOptV2 = null;
|
||||
devCertManagerV2 = null;
|
||||
noLostKeyManagerV2 = null;
|
||||
mBiometricManagerV2 = null;
|
||||
rfidOptV2 = null;
|
||||
Utility.showToast(R.string.connect_fail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* bind printer service
|
||||
*/
|
||||
private void bindPrintService() {
|
||||
try {
|
||||
InnerPrinterManager.getInstance().bindService(this, new InnerPrinterCallback() {
|
||||
@Override
|
||||
protected void onConnected(SunmiPrinterService service) {
|
||||
sunmiPrinterService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisconnected() {
|
||||
sunmiPrinterService = null;
|
||||
}
|
||||
});
|
||||
} catch (InnerPrinterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* bind scanner service (commented out)
|
||||
*/
|
||||
/*
|
||||
public void bindScannerService() {
|
||||
Intent intent = new Intent();
|
||||
intent.setPackage("com.sunmi.scanner");
|
||||
intent.setAction("com.sunmi.scanner.IScanInterface");
|
||||
bindService(intent, new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
scanInterface = IScanInterface.Stub.asInterface(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
scanInterface = null;
|
||||
}
|
||||
}, Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
*/
|
||||
|
||||
private void initEmvTTS() {
|
||||
EmvTTS.getInstance().init();
|
||||
}
|
||||
}
|
145
app/src/main/java/com/example/bdkipoc/emv/EmvTTS.java
Normal file
145
app/src/main/java/com/example/bdkipoc/emv/EmvTTS.java
Normal file
@ -0,0 +1,145 @@
|
||||
package com.example.bdkipoc.emv;
|
||||
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.bdkipoc.MyApplication;
|
||||
import com.example.bdkipoc.utils.LogUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class EmvTTS extends UtteranceProgressListener {
|
||||
private static final String TAG = "EmvTTS";
|
||||
private TextToSpeech textToSpeech;
|
||||
private boolean supportTTS;
|
||||
private ITTSProgressListener listener;
|
||||
|
||||
private EmvTTS() {
|
||||
|
||||
}
|
||||
|
||||
public static EmvTTS getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public void setTTSListener(ITTSProgressListener l) {
|
||||
listener = l;
|
||||
}
|
||||
|
||||
public void removeTTSListener() {
|
||||
listener = null;
|
||||
}
|
||||
|
||||
private static final class SingletonHolder {
|
||||
private static final EmvTTS INSTANCE = new EmvTTS();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
//初始化TTS对象
|
||||
destroy();
|
||||
textToSpeech = new TextToSpeech(MyApplication.app, this::onTTSInit);
|
||||
textToSpeech.setOnUtteranceProgressListener(this);
|
||||
}
|
||||
|
||||
public void play(String text) {
|
||||
play(text, "0");
|
||||
}
|
||||
|
||||
public void play(String text, String utteranceId) {
|
||||
if (!supportTTS) {
|
||||
Log.e(TAG, "PinPadTTS: play TTS failed, TTS not support...");
|
||||
return;
|
||||
}
|
||||
if (textToSpeech == null) {
|
||||
Log.e(TAG, "PinPadTTS: play TTS slipped, textToSpeech not init..");
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "play() text: [" + text + "]");
|
||||
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(String utteranceId) {
|
||||
Log.e(TAG, "播放开始,utteranceId:" + utteranceId);
|
||||
if (listener != null) {
|
||||
listener.onStart(utteranceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone(String utteranceId) {
|
||||
Log.e(TAG, "播放结束,utteranceId:" + utteranceId);
|
||||
if (listener != null) {
|
||||
listener.onDone(utteranceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String utteranceId) {
|
||||
Log.e(TAG, "播放出错,utteranceId:" + utteranceId);
|
||||
if (listener != null) {
|
||||
listener.onError(utteranceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(String utteranceId, boolean interrupted) {
|
||||
Log.e(TAG, "播放停止,utteranceId:" + utteranceId + ",interrupted:" + interrupted);
|
||||
if (listener != null) {
|
||||
listener.onStop(utteranceId, interrupted);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (textToSpeech != null) {
|
||||
int code = textToSpeech.stop();
|
||||
Log.e(TAG, "tts stop() code:" + code);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isSpeaking() {
|
||||
if (textToSpeech != null) {
|
||||
return textToSpeech.isSpeaking();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (textToSpeech != null) {
|
||||
textToSpeech.stop();
|
||||
textToSpeech.shutdown();
|
||||
textToSpeech = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** TTS初始化回调 */
|
||||
private void onTTSInit(int status) {
|
||||
if (status != TextToSpeech.SUCCESS) {
|
||||
LogUtil.e(TAG, "PinPadTTS: init TTS failed, status:" + status);
|
||||
supportTTS = false;
|
||||
return;
|
||||
}
|
||||
updateTtsLanguage();
|
||||
if (supportTTS) {
|
||||
textToSpeech.setPitch(1.0f);
|
||||
textToSpeech.setSpeechRate(1.0f);
|
||||
LogUtil.e(TAG, "onTTSInit() success,locale:" + textToSpeech.getVoice().getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新TTS语言 */
|
||||
private void updateTtsLanguage() {
|
||||
Locale locale = Locale.ENGLISH;
|
||||
int result = textToSpeech.setLanguage(locale);
|
||||
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
supportTTS = false; //系统不支持当前Locale对应的语音播报
|
||||
LogUtil.e(TAG, "updateTtsLanguage() failed, TTS not support in locale:" + locale);
|
||||
} else {
|
||||
supportTTS = true;
|
||||
LogUtil.e(TAG, "updateTtsLanguage() success, TTS locale:" + locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
package com.example.bdkipoc.emv;
|
||||
|
||||
import android.speech.tts.TextToSpeech;
|
||||
|
||||
public interface ITTSProgressListener {
|
||||
|
||||
/**
|
||||
* Called when an utterance "starts" as perceived by the caller. This will
|
||||
* be soon before audio is played back in the case of a {@link TextToSpeech#speak}
|
||||
* or before the first bytes of a file are written to the file system in the case
|
||||
* of {@link TextToSpeech#synthesizeToFile}.
|
||||
*
|
||||
* @param utteranceId The utterance ID of the utterance.
|
||||
*/
|
||||
void onStart(String utteranceId);
|
||||
|
||||
/**
|
||||
* Called when an utterance has successfully completed processing.
|
||||
* All audio will have been played back by this point for audible output, and all
|
||||
* output will have been written to disk for file synthesis requests.
|
||||
* <p>
|
||||
* This request is guaranteed to be called after {@link #onStart(String)}.
|
||||
*
|
||||
* @param utteranceId The utterance ID of the utterance.
|
||||
*/
|
||||
void onDone(String utteranceId);
|
||||
|
||||
/**
|
||||
* Called when an error has occurred during processing. This can be called
|
||||
* at any point in the synthesis process. Note that there might be calls
|
||||
* to {@link #onStart(String)} for specified utteranceId but there will never
|
||||
* be a call to both {@link #onDone(String)} and {@link #onError(String)} for
|
||||
* the same utterance.
|
||||
*
|
||||
* @param utteranceId The utterance ID of the utterance.
|
||||
* @deprecated Use {@link #onError(String, int)} instead
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #onError(String, int)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
void onError(String utteranceId);
|
||||
|
||||
/**
|
||||
* Called when an utterance has been stopped while in progress or flushed from the
|
||||
* synthesis queue. This can happen if a client calls {@link TextToSpeech#stop()}
|
||||
* or uses {@link TextToSpeech#QUEUE_FLUSH} as an argument with the
|
||||
* {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.
|
||||
*
|
||||
* @param utteranceId The utterance ID of the utterance.
|
||||
* @param interrupted If true, then the utterance was interrupted while being synthesized
|
||||
* and its output is incomplete. If false, then the utterance was flushed
|
||||
* before the synthesis started.
|
||||
*/
|
||||
void onStop(String utteranceId, boolean interrupted);
|
||||
}
|
@ -1,3 +1,341 @@
|
||||
public class CreditCardActivity {
|
||||
package com.example.bdkipoc.kredit;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.bdkipoc.MyApplication;
|
||||
import com.example.bdkipoc.R;
|
||||
import com.example.bdkipoc.utils.ByteUtil;
|
||||
import com.example.bdkipoc.utils.Utility;
|
||||
import com.sunmi.pay.hardware.aidl.AidlConstants.CardType;
|
||||
import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2;
|
||||
|
||||
public class CreditCardActivity extends AppCompatActivity {
|
||||
private TextView tvResult;
|
||||
private Button btnCheckCard;
|
||||
private boolean checkingCard;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
android.util.Log.d("CreditCard", "onCreate called");
|
||||
setContentView(R.layout.activity_credit_card);
|
||||
initView();
|
||||
|
||||
// Check PaySDK status
|
||||
if (MyApplication.app != null) {
|
||||
android.util.Log.d("CreditCard", "MyApplication.app exists");
|
||||
android.util.Log.d("CreditCard", "PaySDK connected: " + MyApplication.app.isConnectPaySDK());
|
||||
android.util.Log.d("CreditCard", "readCardOptV2 null: " + (MyApplication.app.readCardOptV2 == null));
|
||||
} else {
|
||||
android.util.Log.e("CreditCard", "MyApplication.app is null");
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
android.util.Log.d("CreditCard", "initView called");
|
||||
|
||||
// Setup Toolbar as ActionBar
|
||||
androidx.appcompat.widget.Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
if (toolbar != null) {
|
||||
setSupportActionBar(toolbar);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.card_test_credit_card);
|
||||
}
|
||||
}
|
||||
|
||||
tvResult = findViewById(R.id.tv_result);
|
||||
btnCheckCard = findViewById(R.id.btn_check_card);
|
||||
|
||||
if (btnCheckCard != null) {
|
||||
android.util.Log.d("CreditCard", "Button found, setting click listener");
|
||||
btnCheckCard.setOnClickListener(v -> {
|
||||
android.util.Log.d("CreditCard", "Button clicked!");
|
||||
switchCheckCard();
|
||||
});
|
||||
} else {
|
||||
android.util.Log.e("CreditCard", "Button not found!");
|
||||
}
|
||||
|
||||
if (tvResult != null) {
|
||||
tvResult.setText("Ready to scan card...");
|
||||
android.util.Log.d("CreditCard", "TextView initialized");
|
||||
} else {
|
||||
android.util.Log.e("CreditCard", "TextView not found!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void switchCheckCard() {
|
||||
android.util.Log.d("CreditCard", "switchCheckCard called, checkingCard: " + checkingCard);
|
||||
try {
|
||||
if (checkingCard) {
|
||||
android.util.Log.d("CreditCard", "Stopping card check");
|
||||
MyApplication.app.readCardOptV2.cancelCheckCard();
|
||||
btnCheckCard.setText(R.string.card_start_check_card);
|
||||
checkingCard = false;
|
||||
} else {
|
||||
android.util.Log.d("CreditCard", "Starting card check");
|
||||
checkCreditCard();
|
||||
checkingCard = true;
|
||||
btnCheckCard.setText(R.string.card_stop_check_card);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
android.util.Log.e("CreditCard", "Error in switchCheckCard: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCreditCard() {
|
||||
try {
|
||||
// Ensure PaySDK is bound first
|
||||
if (MyApplication.app == null) {
|
||||
tvResult.setText("Error: Application not initialized");
|
||||
android.util.Log.e("CreditCard", "MyApplication.app is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// If not connected, try to bind first
|
||||
if (!MyApplication.app.isConnectPaySDK()) {
|
||||
tvResult.setText("Connecting to PaySDK...");
|
||||
android.util.Log.d("CreditCard", "PaySDK not connected, binding service...");
|
||||
MyApplication.app.bindPaySDKService();
|
||||
|
||||
// Wait a bit and retry
|
||||
btnCheckCard.postDelayed(() -> {
|
||||
if (MyApplication.app.isConnectPaySDK() && MyApplication.app.readCardOptV2 != null) {
|
||||
startCardScan();
|
||||
} else {
|
||||
tvResult.setText("Error: Failed to connect to PaySDK");
|
||||
checkingCard = false;
|
||||
btnCheckCard.setText(R.string.card_start_check_card);
|
||||
}
|
||||
}, 2000); // Wait 2 seconds
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyApplication.app.readCardOptV2 == null) {
|
||||
tvResult.setText("Error: Card reader not initialized");
|
||||
android.util.Log.e("CreditCard", "readCardOptV2 is null");
|
||||
return;
|
||||
}
|
||||
|
||||
startCardScan();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
android.util.Log.e("CreditCard", "Error in checkCreditCard: " + e.getMessage());
|
||||
tvResult.setText("Error starting card scan: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private void startCardScan() {
|
||||
try {
|
||||
int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue();
|
||||
tvResult.setText("Starting card scan...\nPlease insert or swipe your card");
|
||||
|
||||
// Log for debugging
|
||||
android.util.Log.d("CreditCard", "Starting checkCard with cardType: " + cardType);
|
||||
|
||||
MyApplication.app.readCardOptV2.checkCard(cardType, mCheckCardCallback, 60);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
android.util.Log.e("CreditCard", "Error in startCardScan: " + e.getMessage());
|
||||
tvResult.setText("Error starting card scan: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private final CheckCardCallbackV2 mCheckCardCallback = new CheckCardCallbackV2.Stub() {
|
||||
@Override
|
||||
public void findMagCard(Bundle info) throws RemoteException {
|
||||
runOnUiThread(() -> handleMagCardResult(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findICCard(String atr) throws RemoteException {
|
||||
Bundle info = new Bundle();
|
||||
info.putString("atr", atr);
|
||||
runOnUiThread(() -> handleICCardResult(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findRFCard(String uuid) throws RemoteException {
|
||||
// Handle RF card detection - changed to single parameter
|
||||
Bundle info = new Bundle();
|
||||
info.putString("uuid", uuid);
|
||||
runOnUiThread(() -> handleRFCardResult(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int code, String message) throws RemoteException {
|
||||
Bundle info = new Bundle();
|
||||
info.putInt("code", code);
|
||||
info.putString("message", message);
|
||||
runOnUiThread(() -> handleErrorResult(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findICCardEx(Bundle info) throws RemoteException {
|
||||
runOnUiThread(() -> handleICCardResult(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findRFCardEx(Bundle info) throws RemoteException {
|
||||
runOnUiThread(() -> handleRFCardResult(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onErrorEx(Bundle info) throws RemoteException {
|
||||
runOnUiThread(() -> handleErrorResult(info));
|
||||
}
|
||||
};
|
||||
|
||||
private void handleMagCardResult(Bundle info) {
|
||||
android.util.Log.d("CreditCard", "=== MAGNETIC CARD DATA ===");
|
||||
|
||||
String track1 = Utility.null2String(info.getString("TRACK1"));
|
||||
String track2 = Utility.null2String(info.getString("TRACK2"));
|
||||
String track3 = Utility.null2String(info.getString("TRACK3"));
|
||||
|
||||
// Log detailed track data
|
||||
android.util.Log.d("CreditCard", "Track1: " + track1);
|
||||
android.util.Log.d("CreditCard", "Track2: " + track2);
|
||||
android.util.Log.d("CreditCard", "Track3: " + track3);
|
||||
|
||||
// Log error codes if available
|
||||
int track1ErrorCode = info.getInt("track1ErrorCode", 0);
|
||||
int track2ErrorCode = info.getInt("track2ErrorCode", 0);
|
||||
int track3ErrorCode = info.getInt("track3ErrorCode", 0);
|
||||
|
||||
android.util.Log.d("CreditCard", "Track1 Error Code: " + track1ErrorCode);
|
||||
android.util.Log.d("CreditCard", "Track2 Error Code: " + track2ErrorCode);
|
||||
android.util.Log.d("CreditCard", "Track3 Error Code: " + track3ErrorCode);
|
||||
|
||||
// Log additional info if available
|
||||
String pan = info.getString("pan", "");
|
||||
String serviceCode = info.getString("servicecode", "");
|
||||
if (!pan.isEmpty()) android.util.Log.d("CreditCard", "PAN: " + pan);
|
||||
if (!serviceCode.isEmpty()) android.util.Log.d("CreditCard", "Service Code: " + serviceCode);
|
||||
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append(getString(R.string.card_mag_card_detected)).append("\n")
|
||||
.append("Track1:").append(track1).append("\n")
|
||||
.append("Track2:").append(track2).append("\n")
|
||||
.append("Track3:").append(track3);
|
||||
tvResult.setText(sb);
|
||||
switchCheckCard();
|
||||
}
|
||||
|
||||
private void handleICCardResult(Bundle info) {
|
||||
android.util.Log.d("CreditCard", "=== IC CARD DATA ===");
|
||||
|
||||
String atr = info.getString("atr", "");
|
||||
int cardType = info.getInt("cardType", -1);
|
||||
|
||||
android.util.Log.d("CreditCard", "ATR: " + atr);
|
||||
android.util.Log.d("CreditCard", "Card Type: " + cardType);
|
||||
android.util.Log.d("CreditCard", "Full IC Card Data: " + bundleToString(info));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getString(R.string.card_ic_card_detected)).append("\n")
|
||||
.append("ATR:").append(atr).append("\n");
|
||||
if (cardType != -1) {
|
||||
sb.append("Card Type:").append(cardType).append("\n");
|
||||
}
|
||||
tvResult.setText(sb);
|
||||
switchCheckCard();
|
||||
}
|
||||
|
||||
private void handleRFCardResult(Bundle info) {
|
||||
android.util.Log.d("CreditCard", "=== RF/NFC CARD DATA ===");
|
||||
|
||||
String uuid = info.getString("uuid", "");
|
||||
String ats = info.getString("ats", "");
|
||||
int cardType = info.getInt("cardType", -1);
|
||||
int sak = info.getInt("sak", -1);
|
||||
int cardCategory = info.getInt("cardCategory", -1);
|
||||
byte[] atqa = info.getByteArray("atqa");
|
||||
|
||||
android.util.Log.d("CreditCard", "UUID: " + uuid);
|
||||
android.util.Log.d("CreditCard", "ATS: " + ats);
|
||||
android.util.Log.d("CreditCard", "Card Type: " + cardType);
|
||||
android.util.Log.d("CreditCard", "SAK: " + sak);
|
||||
android.util.Log.d("CreditCard", "Card Category: " + cardCategory);
|
||||
if (atqa != null) {
|
||||
android.util.Log.d("CreditCard", "ATQA: " + ByteUtil.bytes2HexStr(atqa));
|
||||
}
|
||||
android.util.Log.d("CreditCard", "Full RF Card Data: " + bundleToString(info));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("RF Card Detected").append("\n")
|
||||
.append("UUID: ").append(uuid).append("\n");
|
||||
if (!ats.isEmpty()) {
|
||||
sb.append("ATS: ").append(ats).append("\n");
|
||||
}
|
||||
if (sak != -1) {
|
||||
sb.append("SAK: ").append(String.format("0x%02X", sak)).append("\n");
|
||||
}
|
||||
tvResult.setText(sb);
|
||||
switchCheckCard();
|
||||
}
|
||||
|
||||
private void handleErrorResult(Bundle info) {
|
||||
int code = info.getInt("code");
|
||||
String msg = info.getString("message");
|
||||
String error = "Error: " + msg + " (Code: " + code + ")";
|
||||
tvResult.setText(error);
|
||||
switchCheckCard();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
cancelCheckCard();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void cancelCheckCard() {
|
||||
try {
|
||||
MyApplication.app.readCardOptV2.cardOff(CardType.NFC.getValue());
|
||||
MyApplication.app.readCardOptV2.cardOff(CardType.IC.getValue());
|
||||
MyApplication.app.readCardOptV2.cancelCheckCard();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert Bundle to readable string for logging
|
||||
*/
|
||||
private String bundleToString(Bundle bundle) {
|
||||
if (bundle == null) return "null";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
for (String key : bundle.keySet()) {
|
||||
Object value = bundle.get(key);
|
||||
sb.append(key).append("=");
|
||||
if (value instanceof byte[]) {
|
||||
sb.append(ByteUtil.bytes2HexStr((byte[]) value));
|
||||
} else {
|
||||
sb.append(value);
|
||||
}
|
||||
sb.append(", ");
|
||||
}
|
||||
if (sb.length() > 1) {
|
||||
sb.setLength(sb.length() - 2); // Remove last ", "
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
265
app/src/main/java/com/example/bdkipoc/utils/ByteUtil.java
Normal file
265
app/src/main/java/com/example/bdkipoc/utils/ByteUtil.java
Normal file
@ -0,0 +1,265 @@
|
||||
package com.example.bdkipoc.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ByteUtil {
|
||||
|
||||
/** 打印内容 */
|
||||
public static String byte2PrintHex(byte[] raw, int offset, int count) {
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
if (offset < 0 || offset > raw.length) {
|
||||
offset = 0;
|
||||
}
|
||||
int end = offset + count;
|
||||
if (end > raw.length) {
|
||||
end = raw.length;
|
||||
}
|
||||
StringBuilder hex = new StringBuilder();
|
||||
for (int i = offset; i < end; i++) {
|
||||
int v = raw[i] & 0xFF;
|
||||
String hv = Integer.toHexString(v);
|
||||
if (hv.length() < 2) {
|
||||
hex.append(0);
|
||||
}
|
||||
hex.append(hv);
|
||||
hex.append(" ");
|
||||
}
|
||||
if (hex.length() > 0) {
|
||||
hex.deleteCharAt(hex.length() - 1);
|
||||
}
|
||||
return hex.toString().toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换成16进制字符串
|
||||
*
|
||||
* @param bytes 源字节数组
|
||||
* @return 转换后的16进制字符串
|
||||
*/
|
||||
public static String bytes2HexStr(byte... bytes) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return "";
|
||||
}
|
||||
return bytes2HexStr(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换成16进制字符串
|
||||
*
|
||||
* @param src 源字节数组
|
||||
* @param offset 偏移量
|
||||
* @param len 数据长度
|
||||
* @return 转换后的16进制字符串
|
||||
*/
|
||||
public static String bytes2HexStr(byte[] src, int offset, int len) {
|
||||
int end = offset + len;
|
||||
if (src == null || src.length == 0 || offset < 0 || len < 0 || end > src.length) {
|
||||
return "";
|
||||
}
|
||||
byte[] buffer = new byte[len * 2];
|
||||
int h = 0, l = 0;
|
||||
for (int i = offset, j = 0; i < end; i++) {
|
||||
h = src[i] >> 4 & 0x0f;
|
||||
l = src[i] & 0x0f;
|
||||
buffer[j++] = (byte) (h > 9 ? h - 10 + 'A' : h + '0');
|
||||
buffer[j++] = (byte) (l > 9 ? l - 10 + 'A' : l + '0');
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
public static byte[] hexStr2Bytes(String hexStr) {
|
||||
if (TextUtils.isEmpty(hexStr)) {
|
||||
return new byte[0];
|
||||
}
|
||||
int length = hexStr.length() / 2;
|
||||
char[] chars = hexStr.toCharArray();
|
||||
byte[] b = new byte[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
b[i] = (byte) (char2Byte(chars[i * 2]) << 4 | char2Byte(chars[i * 2 + 1]));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public static byte hexStr2Byte(String hexStr) {
|
||||
return (byte) Integer.parseInt(hexStr, 16);
|
||||
}
|
||||
|
||||
public static String hexStr2Str(String hexStr) {
|
||||
String vi = "0123456789ABC DEF".trim();
|
||||
char[] array = hexStr.toCharArray();
|
||||
byte[] bytes = new byte[hexStr.length() / 2];
|
||||
int temp;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
char c = array[2 * i];
|
||||
temp = vi.indexOf(c) * 16;
|
||||
c = array[2 * i + 1];
|
||||
temp += vi.indexOf(c);
|
||||
bytes[i] = (byte) (temp & 0xFF);
|
||||
}
|
||||
return new String(bytes);
|
||||
}
|
||||
|
||||
public static String hexStr2AsciiStr(String hexStr) {
|
||||
String vi = "0123456789ABC DEF".trim();
|
||||
hexStr = hexStr.trim().replace(" ", "").toUpperCase(Locale.US);
|
||||
char[] array = hexStr.toCharArray();
|
||||
byte[] bytes = new byte[hexStr.length() / 2];
|
||||
int temp = 0x00;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
char c = array[2 * i];
|
||||
temp = vi.indexOf(c) << 4;
|
||||
c = array[2 * i + 1];
|
||||
temp |= vi.indexOf(c);
|
||||
bytes[i] = (byte) (temp & 0xFF);
|
||||
}
|
||||
return new String(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将无符号short转换成int,大端模式(高位在前)
|
||||
*/
|
||||
public static int unsignedShort2IntBE(byte[] src, int offset) {
|
||||
return (src[offset] & 0xff) << 8 | (src[offset + 1] & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将无符号short转换成int,小端模式(低位在前)
|
||||
*/
|
||||
public static int unsignedShort2IntLE(byte[] src, int offset) {
|
||||
return (src[offset] & 0xff) | (src[offset + 1] & 0xff) << 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将无符号byte转换成int
|
||||
*/
|
||||
public static int unsignedByte2Int(byte[] src, int offset) {
|
||||
return src[offset] & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换成int,大端模式(高位在前)
|
||||
*/
|
||||
public static int unsignedInt2IntBE(byte[] src, int offset) {
|
||||
int result = 0;
|
||||
for (int i = offset; i < offset + 4; i++) {
|
||||
result |= (src[i] & 0xff) << (offset + 3 - i) * 8;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换成int,小端模式(低位在前)
|
||||
*/
|
||||
public static int unsignedInt2IntLE(byte[] src, int offset) {
|
||||
int value = 0;
|
||||
for (int i = offset; i < offset + 4; i++) {
|
||||
value |= (src[i] & 0xff) << (i - offset) * 8;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将int转换成byte数组,大端模式(高位在前)
|
||||
*/
|
||||
public static byte[] int2BytesBE(int src) {
|
||||
byte[] result = new byte[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result[i] = (byte) (src >> (3 - i) * 8);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将int转换成byte数组,小端模式(低位在前)
|
||||
*/
|
||||
public static byte[] int2BytesLE(int src) {
|
||||
byte[] result = new byte[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result[i] = (byte) (src >> i * 8);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将short转换成byte数组,大端模式(高位在前)
|
||||
*/
|
||||
public static byte[] short2BytesBE(short src) {
|
||||
byte[] result = new byte[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
result[i] = (byte) (src >> (1 - i) * 8);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将short转换成byte数组,小端模式(低位在前)
|
||||
*/
|
||||
public static byte[] short2BytesLE(short src) {
|
||||
byte[] result = new byte[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
result[i] = (byte) (src >> i * 8);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组列表合并成单个字节数组
|
||||
*/
|
||||
public static byte[] concatByteArrays(byte[]... list) {
|
||||
if (list == null || list.length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
return concatByteArrays(Arrays.asList(list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组列表合并成单个字节数组
|
||||
*/
|
||||
public static byte[] concatByteArrays(List<byte[]> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return new byte[0];
|
||||
}
|
||||
int totalLen = 0;
|
||||
for (byte[] b : list) {
|
||||
if (b == null || b.length == 0) {
|
||||
continue;
|
||||
}
|
||||
totalLen += b.length;
|
||||
}
|
||||
byte[] result = new byte[totalLen];
|
||||
int index = 0;
|
||||
for (byte[] b : list) {
|
||||
if (b == null || b.length == 0) {
|
||||
continue;
|
||||
}
|
||||
System.arraycopy(b, 0, result, index, b.length);
|
||||
index += b.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert char to byte
|
||||
*
|
||||
* @param c char
|
||||
* @return byte
|
||||
*/
|
||||
private static int char2Byte(char c) {
|
||||
if (c >= 'a') {
|
||||
return (c - 'a' + 10) & 0x0f;
|
||||
}
|
||||
if (c >= 'A') {
|
||||
return (c - 'A' + 10) & 0x0f;
|
||||
}
|
||||
return (c - '0') & 0x0f;
|
||||
}
|
||||
|
||||
|
||||
}
|
89
app/src/main/java/com/example/bdkipoc/utils/LogUtil.java
Normal file
89
app/src/main/java/com/example/bdkipoc/utils/LogUtil.java
Normal file
@ -0,0 +1,89 @@
|
||||
package com.example.bdkipoc.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public class LogUtil {
|
||||
|
||||
public static final int VERBOSE = 1;
|
||||
public static final int DEBUG = 2;
|
||||
public static final int INFO = 3;
|
||||
public static final int WARN = 4;
|
||||
public static final int ERROR = 5;
|
||||
public static final int NOTHING = 6;
|
||||
public static int LEVEL = VERBOSE;
|
||||
|
||||
public static void setLevel(int Level) {
|
||||
LEVEL = Level;
|
||||
}
|
||||
|
||||
public static void v(String TAG, String msg) {
|
||||
if (LEVEL <= VERBOSE && !TextUtils.isEmpty(msg)) {
|
||||
MyLog(VERBOSE, TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String TAG, String msg) {
|
||||
if (LEVEL <= DEBUG && !TextUtils.isEmpty(msg)) {
|
||||
MyLog(DEBUG, TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String TAG, String msg) {
|
||||
if (LEVEL <= INFO && !TextUtils.isEmpty(msg)) {
|
||||
MyLog(INFO, TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String TAG, String msg) {
|
||||
if (LEVEL <= WARN && !TextUtils.isEmpty(msg)) {
|
||||
MyLog(WARN, TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(String TAG, String msg) {
|
||||
if (LEVEL <= ERROR && !TextUtils.isEmpty(msg)) {
|
||||
MyLog(ERROR, TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void MyLog(int type, String TAG, String msg) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
int index = 4;
|
||||
String className = stackTrace[index].getFileName();
|
||||
String methodName = stackTrace[index].getMethodName();
|
||||
int lineNumber = stackTrace[index].getLineNumber();
|
||||
methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("[ (")
|
||||
.append(className)
|
||||
.append(":")
|
||||
.append(lineNumber)
|
||||
.append(")#")
|
||||
.append(methodName)
|
||||
.append(" ] ");
|
||||
stringBuilder.append(msg);
|
||||
String logStr = stringBuilder.toString();
|
||||
switch (type) {
|
||||
case VERBOSE:
|
||||
Log.v(TAG, logStr);
|
||||
break;
|
||||
case DEBUG:
|
||||
Log.d(TAG, logStr);
|
||||
break;
|
||||
case INFO:
|
||||
Log.i(TAG, logStr);
|
||||
break;
|
||||
case WARN:
|
||||
Log.w(TAG, logStr);
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e(TAG, logStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
107
app/src/main/java/com/example/bdkipoc/utils/Utility.java
Normal file
107
app/src/main/java/com/example/bdkipoc/utils/Utility.java
Normal file
@ -0,0 +1,107 @@
|
||||
package com.example.bdkipoc.utils;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.bdkipoc.MyApplication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class Utility {
|
||||
private Utility() {
|
||||
throw new AssertionError("Create instance of Utility is forbidden.");
|
||||
}
|
||||
|
||||
/** Bundle对象转换成字符串 */
|
||||
public static String bundle2String(Bundle bundle) {
|
||||
return bundle2String(bundle, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key排序后将Bundle内容拼接成字符串
|
||||
*
|
||||
* @param bundle 要处理的bundle
|
||||
* @param order 排序规则,0-不排序,1-升序,2-降序
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String bundle2String(Bundle bundle, int order) {
|
||||
if (bundle == null || bundle.keySet().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<String> list = new ArrayList<>(bundle.keySet());
|
||||
if (order == 1) { //升序
|
||||
Collections.sort(list, String::compareTo);
|
||||
} else if (order == 2) {//降序
|
||||
Collections.sort(list, Collections.reverseOrder());
|
||||
}
|
||||
for (String key : list) {
|
||||
sb.append(key);
|
||||
sb.append(":");
|
||||
Object value = bundle.get(key);
|
||||
if (value instanceof byte[]) {
|
||||
sb.append(ByteUtil.bytes2HexStr((byte[]) value));
|
||||
} else {
|
||||
sb.append(value);
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** 将null转换成空串 */
|
||||
public static String null2String(String str) {
|
||||
return str == null ? "" : str;
|
||||
}
|
||||
|
||||
public static String formatStr(String format, Object... params) {
|
||||
return String.format(Locale.ENGLISH, format, params);
|
||||
}
|
||||
|
||||
/** check whether src is hex format */
|
||||
public static boolean checkHexValue(String src) {
|
||||
return Pattern.matches("[0-9a-fA-F]+", src);
|
||||
}
|
||||
|
||||
/** 显示Toast */
|
||||
public static void showToast(final String msg) {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(() -> Toast.makeText(MyApplication.app, msg, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
/** 显示Toast */
|
||||
public static void showToast(int resId) {
|
||||
showToast(MyApplication.app.getString(resId));
|
||||
}
|
||||
|
||||
/** 根据结果码获取成功失败信息 */
|
||||
public static String getStateString(int code) {
|
||||
return code == 0 ? "success" : "failed, code:" + code;
|
||||
}
|
||||
|
||||
/** 根据结果状态获取成功失败信息 */
|
||||
public static String getStateString(boolean state) {
|
||||
return state ? "success" : "failed";
|
||||
}
|
||||
|
||||
/** 将dp转成px */
|
||||
public static int dp2px(int dp) {
|
||||
float density = MyApplication.app.getResources().getDisplayMetrics().density;
|
||||
return Math.round(dp * density);
|
||||
}
|
||||
|
||||
/** 将px转成dp */
|
||||
public static int px2dp(int px) {
|
||||
float density = MyApplication.app.getResources().getDisplayMetrics().density;
|
||||
return Math.round(px / density);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.example.bdkipoc.wrapper;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2;
|
||||
|
||||
|
||||
public class CheckCardCallbackV2Wrapper extends CheckCardCallbackV2.Stub {
|
||||
@Override
|
||||
public void findMagCard(Bundle info) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findICCard(String atr) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findRFCard(String uuid) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int code, String message) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findICCardEx(Bundle info) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findRFCardEx(Bundle info) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onErrorEx(Bundle info) throws RemoteException {
|
||||
|
||||
}
|
||||
}
|
@ -1,120 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
tools:context=".CreditCardActivity">
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Credit Card Payment"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvConnectionStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PaySDK Status: Connecting..."
|
||||
android:textSize="16sp"
|
||||
android:padding="12dp"
|
||||
android:background="@drawable/bg_status"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCheckStatus"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Check Status"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnReconnect"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Reconnect"
|
||||
android:layout_marginStart="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/tv_result"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="16dp"
|
||||
android:text="Ready to scan card..."
|
||||
android:gravity="top" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPaymentMethods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Methods"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnReadCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Read Card"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEMVPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="EMV Payment"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPinPad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PIN Pad Test"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPrintTest"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Print Test"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:enabled="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDebugInfo"
|
||||
<Button
|
||||
android:id="@+id/btn_check_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Debug info will appear here..."
|
||||
android:textSize="12sp"
|
||||
android:background="#f5f5f5"
|
||||
android:padding="8dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end" />
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/card_start_check_card" />
|
||||
|
||||
</LinearLayout>
|
@ -1,7 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="colorOrange">#FF6600</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
|
||||
<color name="colorBackground">#F0F2F5</color>
|
||||
<color name="colorTextTitle">#222222</color>
|
||||
<color name="colorTextContent">#666666</color>
|
||||
<color name="colorTextHelp">#999999</color>
|
||||
<color name="colorLineColor">#d7d7d7</color>
|
||||
<color name="FD5A52">#FD5A52</color>
|
||||
<color name="CE6E6E6">#E6E6E6</color>
|
||||
|
||||
<color name="FF3C00">#FF3C00</color>
|
||||
<color name="C999999">#999999</color>
|
||||
<color name="black">#000000</color>
|
||||
|
||||
|
||||
<!-- Banking App Theme Colors -->
|
||||
<color name="primary_blue">#1976D2</color>
|
||||
|
8
app/src/main/res/values/dimens.xml
Normal file
8
app/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="smallSize">24dp</dimen>
|
||||
<dimen name="itemSize">48dp</dimen>
|
||||
<dimen name="titleSize">56dp</dimen>
|
||||
|
||||
</resources>
|
@ -13,6 +13,11 @@
|
||||
<string name="payment_status_success">Payment Successful!</string>
|
||||
<string name="return_main">Return to Main Screen</string>
|
||||
<string name="main_title">POC</string>
|
||||
<!-- In res/values/strings.xml -->
|
||||
<string name="connect_fail">Connection to payment service failed</string>
|
||||
<!-- In res/values/strings.xml -->
|
||||
<string name="card_test_credit_card">Credit Card Test</string>
|
||||
<string name="card_mag_card_detected">Magnetic Card Detected</string>
|
||||
<string name="card_ic_card_detected">IC Card Detected</string>
|
||||
<string name="card_start_check_card">Start Check Card</string>
|
||||
<string name="card_stop_check_card">Stop Check Card</string>
|
||||
<string name="connect_fail">Connection failed</string>
|
||||
</resources>
|
@ -29,6 +29,11 @@
|
||||
<item name="titleTextAppearance">@style/ToolbarTitleStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="Toolbar.TitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
|
||||
<item name="android:textSize">18sp</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<!-- Numpad Button Style -->
|
||||
<style name="NumpadButton">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user