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