first commit

This commit is contained in:
ahmeddatexpay 2025-09-24 18:33:01 +03:00
commit 7ad09b2b5f
70 changed files with 3335 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

18
.idea/gradle.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="ms-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="ms-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

76
app/build.gradle Normal file
View File

@ -0,0 +1,76 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.test.cardreadtest'
compileSdk 35
signingConfigs {
release {
storeFile file('new-keystore.jks')
storePassword 'MPOS73356xDp'
keyPassword 'MPOS73356xDp'
keyAlias 'mulberrypos'
}
debug {
storeFile file('new-keystore.jks')
storePassword 'MPOS73356xDp'
keyPassword 'MPOS73356xDp'
keyAlias 'mulberrypos'
}
}
defaultConfig {
applicationId "com.test.cardreadtest"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
viewBinding {
enabled = true
}
dataBinding {
enabled = true
}
packagingOptions {
doNotStrip '**/lib/*.so' // SO文件
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
implementation 'androidx.recyclerview:recyclerview:1.3.1' // Use the latest version
implementation 'androidx.viewpager2:viewpager2:1.0.0' // Use the latest version
implementation 'androidx.databinding:databinding-runtime:8.0.0'
implementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:4.0.0'
implementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:4.0.0'
implementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-viewpager2:4.0.0'
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
app/new-keystore.jks Normal file

Binary file not shown.

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package com.test.cardreadtest;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.test.cardreadtest", appContext.getPackageName());
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CardReadTest"
tools:targetApi="31"
tools:node="replace">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".payment.PaymentActivity"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,38 @@
package com.test.cardreadtest;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.test.cardreadtest.payment.PaymentActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Button btnReadCard = findViewById(R.id.btnReadCard);
btnReadCard.setOnClickListener(v -> {
// For testing, you can pass dummy values
Intent intent = new Intent(MainActivity.this, PaymentActivity.class);
intent.putExtra("amount", "5.00");
intent.putExtra("deviceAddress", "UART");
startActivity(intent);
});
}
}

View File

@ -0,0 +1,5 @@
package com.test.cardreadtest.common.enums;
public enum POS_TYPE {
BLUETOOTH, UART, USB, BLUETOOTH_BLE
}

View File

@ -0,0 +1,37 @@
package com.test.cardreadtest.common.enums;
public enum PaymentType {
GOODS("GOODS"),
SERVICES("SERVICES"),
CASH("CASH"),
CASHBACK("CASHBACK"),
PURCHASE_REFUND("PURCHASE_REFUND"),
INQUIRY("INQUIRY"),
TRANSFER("TRANSFER"),
ADMIN("ADMIN"),
PAYMENT("PAYMENT"),
SALE("SALE"),
CHANGE_PIN("CHANGE_PIN"),
BALANCE("BALANCE"),
BALANCE_UPDATE("BALANCE_UPDATE"),
REFOUND("REFUND");
private final String value;
PaymentType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static String[] getValues() {
PaymentType[] types = PaymentType.values();
String[] values = new String[types.length];
for (int i = 0; i < types.length; i++) {
values[i] = types[i].getValue();
}
return values;
}
}

View File

@ -0,0 +1,32 @@
package com.test.cardreadtest.common.enums;
import com.mulberry.xpos.QPOSService;
public enum TransCardMode {
SWIPE_TAP_INSERT_CARD_NOTUP(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_NOTUP),SWIPE_TAP_INSERT_CARD(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD), ONLY_INSERT_CARD(QPOSService.CardTradeMode.ONLY_INSERT_CARD), ONLY_SWIPE_CARD(QPOSService.CardTradeMode.ONLY_SWIPE_CARD), TAP_INSERT_CARD(QPOSService.CardTradeMode.TAP_INSERT_CARD),
TAP_INSERT_CARD_NOTUP(QPOSService.CardTradeMode.TAP_INSERT_CARD_NOTUP), UNALLOWED_LOW_TRADE(QPOSService.CardTradeMode.UNALLOWED_LOW_TRADE), SWIPE_INSERT_CARD(QPOSService.CardTradeMode.SWIPE_INSERT_CARD), SWIPE_TAP_INSERT_CARD_UNALLOWED_LOW_TRADE(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_UNALLOWED_LOW_TRADE),
SWIPE_TAP_INSERT_CARD_NOTUP_UNALLOWED_LOW_TRADE(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_NOTUP_UNALLOWED_LOW_TRADE), ONLY_TAP_CARD(QPOSService.CardTradeMode.ONLY_TAP_CARD),
ONLY_TAP_CARD_QF(QPOSService.CardTradeMode.ONLY_TAP_CARD_QF),
SWIPE_TAP_INSERT_CARD_DOWN(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_DOWN), SWIPE_INSERT_CARD_UNALLOWED_LOW_TRADE(QPOSService.CardTradeMode.SWIPE_INSERT_CARD_UNALLOWED_LOW_TRADE),
SWIPE_TAP_INSERT_CARD_UNALLOWED_LOW_TRADE_NEW(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_UNALLOWED_LOW_TRADE_NEW), ONLY_INSERT_CARD_NOPIN(QPOSService.CardTradeMode.ONLY_INSERT_CARD_NOPIN),
SWIPE_TAP_INSERT_CARD_NOTUP_DELAY(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_NOTUP_DELAY);
protected QPOSService.CardTradeMode cardTradeModeValue;
public QPOSService.CardTradeMode getCardTradeModeValue() {
return cardTradeModeValue;
}
TransCardMode(QPOSService.CardTradeMode i) {
this.cardTradeModeValue = i;
}
public static String[] getCardTradeModes() {
TransCardMode[] types = TransCardMode.values();
String[] values = new String[types.length];
for (int i = 0; i < types.length; i++) {
values[i] = types[i].getCardTradeModeValue().name();
}
return values;
}
}

View File

@ -0,0 +1,203 @@
package com.test.cardreadtest.payment;
import static android.content.Intent.getIntent;
import android.app.Application;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import com.test.cardreadtest.R;
import com.test.cardreadtest.databinding.ActivityPaymentBinding;
import com.test.cardreadtest.posAPI.ConnectionServiceCallback;
import com.test.cardreadtest.posAPI.POSManager;
import com.test.cardreadtest.posAPI.PaymentServiceCallback;
import com.test.cardreadtest.utils.TRACE;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
//import me.goldze.mvvmhabit.utils.ToastUtils;
public class PaymentActivity extends AppCompatActivity {
private static final String TAG = "PaymentActivity";
private String amount;
private String deviceAddress;
private PaymentViewModel viewModel;
private ActivityPaymentBinding binding;
private PaymentServiceCallback paymentServiceCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Simple DataBinding inflation - NO third-party dependencies
binding = ActivityPaymentBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Use standard ViewModelProvider
viewModel = new ViewModelProvider(this).get(PaymentViewModel.class);
binding.setViewModel(viewModel);
initData();
}
/**
* Initialize payment activity data
* Sets up initial UI state and starts transaction
*/
public void initData() {
paymentServiceCallback = new PaymentCallback();
// Get intent data
Intent intent = getIntent();
amount = intent.getStringExtra("amount");
// For testing, use default values if not provided
if (amount == null) amount = "1.00";
viewModel.displayAmount(amount);//display to UI
startTransaction();
}
/**
* Start payment transaction in background thread
* Handles device connection and transaction initialization
*/
private void startTransaction() {
new Thread(() -> {
// Initialize POSManager if not already done
POSManager.init(getApplicationContext());
if(!POSManager.getInstance().isDeviceReady()){
POSManager.getInstance().connect(deviceAddress, new ConnectionServiceCallback() {
@Override
public void onRequestNoQposDetected() {
runOnUiThread(() -> Log.d(TAG, "No device detected"));
}
@Override
public void onRequestQposConnected() {
runOnUiThread(() -> Log.d(TAG, "Device connected"));
}
@Override
public void onRequestQposDisconnected() {
runOnUiThread(() -> {
Log.d(TAG, "Device disconnected");
finish();
});
}
});
}
// Start transaction with callback
POSManager.getInstance().startTransaction(amount, paymentServiceCallback);
}).start();
}
/**
* Inner class to handle payment callbacks
* Implements all payment related events and UI updates
*/
private class PaymentCallback implements PaymentServiceCallback {
@Override
public void onRequestWaitingUser() {
runOnUiThread(() -> {
viewModel.setWaitingStatus(true);
Log.d(TAG, "Please insert/swipe/tap card");
});
}
@Override
public void onRequestTime() {
String terminalTime = new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime());
TRACE.d("onRequestTime: " + terminalTime);
POSManager.getInstance().sendTime(terminalTime);
}
@Override
public void onRequestSelectEmvApp(ArrayList<String> appList) {
TRACE.d("onRequestSelectEmvApp():" + appList.toString());
// Auto-select first app for testing
if (!appList.isEmpty()) {
POSManager.getInstance().selectEmvApp(0);
}
}
@Override
public void onQposRequestPinResult(List<String> dataList, int offlineTime) {
TRACE.d("onQposRequestPinResult = " + dataList + "\nofflineTime: " + offlineTime);
}
@Override
public void onRequestSetPin(boolean isOfflinePin, int tryNum) {
TRACE.d("onRequestSetPin = " + isOfflinePin + "\ntryNum: " + tryNum);
}
@Override
public void onRequestSetPin() {
TRACE.i("onRequestSetPin()");
}
@Override
public void onRequestDisplay(com.mulberry.xpos.QPOSService.Display displayMsg) {
TRACE.d("onRequestDisplay(Display displayMsg):" + displayMsg.toString());
}
@Override
public void onTransactionCompleted(com.test.cardreadtest.posAPI.PaymentResult result) {
runOnUiThread(() -> {
Log.d(TAG, "Transaction completed: " + result.getTransactionType());
// Display basic card info for testing
if (result.getMaskedPAN() != null) {
Log.d(TAG, "Card: " + result.getMaskedPAN());
}
// finish();
});
}
@Override
public void onTransactionFailed(String errorMessage, String data) {
runOnUiThread(() -> {
Log.d(TAG, "Transaction failed: " + errorMessage);
finish();
});
}
@Override
public void onRequestOnlineProcess(final String tlv) {
TRACE.d("onRequestOnlineProcess" + tlv);
// For testing, just send success response
POSManager.getInstance().sendOnlineProcessResult("8A023030");
}
@Override
public void onReturnGetPinInputResult(int num) {
TRACE.i("onReturnGetPinInputResult ===" + num);
}
}
@Override
public void onBackPressed() {
// Cancel transaction and go back
POSManager.getInstance().cancelTransaction();
super.onBackPressed();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (POSManager.getInstance() != null) {
POSManager.getInstance().unregisterCallbacks();
}
}
}

View File

@ -0,0 +1,19 @@
package com.test.cardreadtest.payment;
import androidx.lifecycle.ViewModel;
public class PaymentViewModel extends ViewModel {
public PaymentViewModel() {
super();
}
public void displayAmount(String amount) {
// For testing
System.out.println("Amount to display: " + amount);
}
public void setWaitingStatus(boolean waiting) {
System.out.println("Waiting status: " + waiting);
}
}

View File

@ -0,0 +1,26 @@
package com.test.cardreadtest.posAPI;
/**
* Connection Service Callback Interface
* Handle all connection-related callback methods
*/
public interface ConnectionServiceCallback {
// ==================== Device Connection Status Callbacks ====================
/**
* Request QPOS Connection
*/
default void onRequestQposConnected() {}
/**
* Request QPOS Disconnection
*/
default void onRequestQposDisconnected() {}
/**
* Request No QPOS Detected
*/
default void onRequestNoQposDetected() {}
}

View File

@ -0,0 +1,626 @@
package com.test.cardreadtest.posAPI;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import com.test.cardreadtest.common.enums.POS_TYPE;
import com.test.cardreadtest.common.enums.TransCardMode;
import com.test.cardreadtest.utils.DeviceUtils;
import com.test.cardreadtest.utils.TRACE;
import com.mulberry.xpos.CQPOSService;
import com.mulberry.xpos.QPOSService;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class POSManager {
private static volatile POSManager instance;
private QPOSService pos;
private Context context;
private QPOSServiceListener listener;
// Callback management
private final List<ConnectionServiceCallback> connectionCallbacks = new CopyOnWriteArrayList<>();
private final List<PaymentServiceCallback> transactionCallbacks = new CopyOnWriteArrayList<>();
private Handler mainHandler;
private CountDownLatch connectLatch;
private PaymentResult paymentResult;
private POS_TYPE posType;
private boolean isICC;
private SharedPreferences sharedPreferences;
private static final String PREFS_NAME = "POSManagerPrefs";
private static final String TAG = "POSManager";
private POSManager(Context context) {
this.context = context.getApplicationContext();
this.listener = new QPOSServiceListener();
this.sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
mainHandler = new Handler(Looper.getMainLooper());
paymentResult = new PaymentResult();
Log.d(TAG, "POSManager initialized");
}
/**
* Initialize POSManager with application context
*
* @param context Application context
*/
public static void init(Context context) {
getInstance(context);
}
public static POSManager getInstance(Context context) {
if (instance == null) {
synchronized (POSManager.class) {
if (instance == null) {
instance = new POSManager(context);
Log.d("POSManager", "New POSManager instance created");
}
}
}
return instance;
}
/**
* Get singleton instance of POSManager
*
* @return POSManager instance
*/
public static POSManager getInstance() {
if (instance == null) {
throw new IllegalStateException("POS must be initialized with context first");
}
return instance;
}
/**
* Connect to POS device
*
* @param deviceAddress Device address (Bluetooth address or USB port)
* @param callback Callback to handle connection events
*/
public void connect(String deviceAddress, ConnectionServiceCallback callback) {
Log.d(TAG, "Connecting to device: " + deviceAddress);
connectLatch = new CountDownLatch(1);
registerConnectionCallback(callback);
// start connect
connect("UART");
try {
boolean waitSuccess = connectLatch.await(5, TimeUnit.SECONDS);
if (!waitSuccess) {
Log.w(TAG, "Connection timeout");
TRACE.i("Connection timeout");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Connection interrupted", e);
throw new IllegalStateException("Connection interrupted", e);
}
}
public void connect(String deviceAddress) {
Log.d(TAG, "Starting connection process for: " + deviceAddress);
// if (!deviceAddress.isEmpty()) {
// if (deviceAddress.contains(":")) {
// posType = POS_TYPE.BLUETOOTH;
// Log.d(TAG, "Bluetooth connection detected");
// initMode(QPOSService.CommunicationMode.BLUETOOTH);
// if (pos != null) {
// pos.setDeviceAddress(deviceAddress);
// pos.connectBluetoothDevice(true, 25, deviceAddress);
// }
// } else {
// posType = POS_TYPE.USB;
// Log.d(TAG, "USB connection detected");
// initMode(QPOSService.CommunicationMode.USB);
// }
// } else {
posType = POS_TYPE.UART;
Log.d(TAG, "UART connection detected");
initMode(QPOSService.CommunicationMode.UART);
pos.openUart();
//
// }
}
public void initMode(QPOSService.CommunicationMode mode) {
Log.d(TAG, "Initializing POS mode: " + mode);
pos = QPOSService.getInstance(context, mode);
if (pos == null) {
Log.e(TAG, "Failed to get QPOSService instance for mode: " + mode);
return;
}
if (mode == QPOSService.CommunicationMode.USB_OTG_CDC_ACM) {
pos.setUsbSerialDriver(QPOSService.UsbOTGDriver.CDCACM);
}
pos.setContext(context);
pos.initListener(listener);
Log.d(TAG, "POS mode initialized successfully");
}
public QPOSService getQPOSService() {
return pos;
}
public void clearPosService() {
Log.d(TAG, "Clearing POS service");
pos = null;
}
public void setICC(boolean ICC) {
isICC = ICC;
Log.d(TAG, "ICC set to: " + ICC);
}
/**
* Check if device is ready for transaction
*
* @return true if device is connected and ready
*/
public boolean isDeviceReady() {
boolean ready = pos != null;
Log.d(TAG, "isDeviceReady: " + ready);
return ready;
}
public void setDeviceAddress(String address) {
if (pos != null) {
pos.setDeviceAddress(address);
Log.d(TAG, "Device address set to: " + address);
}
}
public QPOSService.TransactionType getTransType() {
String transactionTypeString = sharedPreferences.getString("transactionType", "");
if (transactionTypeString.isEmpty()) {
transactionTypeString = "GOODS";
Log.d(TAG, "Using default transaction type: GOODS");
} else {
Log.d(TAG, "Retrieved transaction type: " + transactionTypeString);
}
return QPOSService.TransactionType.GOODS;
}
public QPOSService.CardTradeMode getCardTradeMode() {
String modeName = sharedPreferences.getString("cardMode", "");
Log.d(TAG, "Retrieved card mode: " + modeName);
QPOSService.CardTradeMode cardTradeMode = QPOSService.CardTradeMode.TAP_INSERT_CARD_NOTUP;
if (modeName.isEmpty()) {
if (DeviceUtils.isSmartDevices()) {
cardTradeMode = QPOSService.CardTradeMode.TAP_INSERT_CARD_NOTUP;
Log.d(TAG, "Using SWIPE_TAP_INSERT_CARD_NOTUP for smart device");
} else {
cardTradeMode = QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD;
Log.d(TAG, "Using SWIPE_TAP_INSERT_CARD for non-smart device");
}
} else {
Log.d(TAG, "Using configured card mode");
}
return QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_NOTUP ;
}
/**
* Start a payment transaction
*
* @param amount Transaction amount
*/
public void startTransaction(String amount, PaymentServiceCallback callback) {
Log.d(TAG, "Starting transaction with amount: " + amount);
if (!isDeviceReady()) {
Log.e(TAG, "Cannot start transaction - device not ready");
return;
}
getDeviceId();
if (callback != null) {
registerPaymentCallback(callback);
Log.d(TAG, "Payment callback registered");
}
int currencyCode = sharedPreferences.getInt("currencyCode", 156);
Log.d(TAG, "Using currency code: " + currencyCode);
pos.setCardTradeMode(QPOSService.CardTradeMode.SWIPE_TAP_INSERT_CARD_NOTUP);
pos.setAmount("20", "10", "643", QPOSService.TransactionType.GOODS);
pos.doTrade(20); // 20-second timeout
// if (pos != null) {
// pos.setCardTradeMode(getCardTradeMode());
// pos.setAmount(amount, "", String.valueOf(currencyCode), getTransType());
// pos.doTrade(60);
// Log.d(TAG, "Transaction started successfully");
// } else {
// Log.e(TAG, "POS service is null, cannot start transaction");
// }
}
public void getDeviceId() {
Log.d(TAG, "Getting device ID");
if (pos == null) {
Log.e(TAG, "POS service is null, cannot get device ID");
return;
}
Hashtable<String, Object> posIdTable = pos.syncGetQposId(5);
String posId = posIdTable.get("posId") == null ? "" : (String) posIdTable.get("posId");
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("posID", posId);
editor.apply();
Log.d(TAG, "Device ID retrieved and saved: " + posId);
TRACE.i("posid :" + posId);
}
/**
* Cancel ongoing transaction
*/
public void cancelTransaction() {
Log.d(TAG, "Cancelling transaction");
if (pos != null) {
pos.cancelTrade();
Log.d(TAG, "Transaction cancelled");
} else {
Log.w(TAG, "Cannot cancel transaction - POS service is null");
}
}
public void sendTime(String terminalTime) {
Log.d(TAG, "Sending terminal time: " + terminalTime);
if (pos != null) {
pos.sendTime(terminalTime);
}
}
public void selectEmvApp(int position) {
Log.d(TAG, "Selecting EMV app at position: " + position);
if (pos != null) {
pos.selectEmvApp(position);
}
}
public void cancelSelectEmvApp() {
Log.d(TAG, "Cancelling EMV app selection");
if (pos != null) {
pos.cancelSelectEmvApp();
}
}
public void pinMapSync(String value, int timeout) {
Log.d(TAG, "PIN map sync with timeout: " + timeout);
if (pos != null) {
pos.pinMapSync(value, timeout);
}
}
public void cancelPin() {
Log.d(TAG, "Cancelling PIN input");
if (pos != null) {
pos.cancelPin();
}
}
public boolean isOnlinePin() {
boolean onlinePin = pos != null && pos.isOnlinePin();
Log.d(TAG, "isOnlinePin: " + onlinePin);
return onlinePin;
}
public int getCvmPinTryLimit() {
int limit = pos != null ? pos.getCvmPinTryLimit() : 0;
Log.d(TAG, "CVM PIN try limit: " + limit);
return limit;
}
public void bypassPin() {
Log.d(TAG, "Bypassing PIN");
if (pos != null) {
pos.sendPin("".getBytes());
}
}
public void sendCvmPin(String pinBlock, boolean isEncrypted) {
Log.d(TAG, "Sending CVM PIN, encrypted: " + isEncrypted);
if (pos != null) {
pos.sendCvmPin(pinBlock, isEncrypted);
}
}
public Hashtable<String, String> getEncryptData() {
Log.d(TAG, "Getting encrypted data");
return pos != null ? pos.getEncryptData() : new Hashtable<>();
}
public Hashtable<String, String> getNFCBatchData() {
Log.d(TAG, "Getting NFC batch data");
return pos != null ? pos.getNFCBatchData() : new Hashtable<>();
}
public void sendOnlineProcessResult(String tlv) {
Log.d(TAG, "Sending online process result, TLV length: " + (tlv != null ? tlv.length() : 0));
if (pos != null) {
pos.sendOnlineProcessResult(tlv);
}
}
public Hashtable<String, String> anlysEmvIccData(String tlv) {
Log.d(TAG, "Analyzing EMV ICC data, TLV length: " + (tlv != null ? tlv.length() : 0));
return pos != null ? pos.anlysEmvIccData(tlv) : new Hashtable<>();
}
public void updateDeviceFirmware(Activity activity, String blueTootchAddress) {
Log.d(TAG, "Updating device firmware");
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Requesting storage permission");
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1001);
} else {
Log.d(TAG, "Storage permission already granted");
}
}
public void close() {
Log.d(TAG, "Closing POS manager");
TRACE.d("start close");
if (pos == null || posType == null) {
Log.d(TAG, "POS service already closed");
TRACE.d("return close");
} else if (posType == POS_TYPE.BLUETOOTH) {
pos.disconnectBT();
Log.d(TAG, "Bluetooth disconnected");
} else if (posType == POS_TYPE.BLUETOOTH_BLE) {
pos.disconnectBLE();
Log.d(TAG, "BLE disconnected");
} else if (posType == POS_TYPE.UART) {
pos.closeUart();
Log.d(TAG, "UART closed");
} else if (posType == POS_TYPE.USB) {
pos.closeUsb();
Log.d(TAG, "USB closed");
} else {
pos.disconnectBT();
Log.d(TAG, "Default Bluetooth disconnect");
}
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("deviceAddress", "");
editor.apply();
clearPosService();
Log.d(TAG, "POS manager closed successfully");
}
/**
* register payment callback
*/
public void registerPaymentCallback(PaymentServiceCallback callback) {
if (callback != null && !transactionCallbacks.contains(callback)) {
transactionCallbacks.add(callback);
Log.d(TAG, "Payment callback registered, total callbacks: " + transactionCallbacks.size());
}
}
/**
* register connection service callback
*/
public void registerConnectionCallback(ConnectionServiceCallback callback) {
if (callback != null && !connectionCallbacks.contains(callback)) {
connectionCallbacks.add(callback);
Log.d(TAG, "Connection callback registered, total callbacks: " + connectionCallbacks.size());
}
}
public void unregisterCallbacks() {
int connectionCount = connectionCallbacks.size();
int transactionCount = transactionCallbacks.size();
connectionCallbacks.clear();
transactionCallbacks.clear();
Log.d(TAG, "Callbacks unregistered. Connection: " + connectionCount + ", Transaction: " + transactionCount);
}
private void notifyConnectionCallbacks(CallbackAction<ConnectionServiceCallback> action) {
mainHandler.post(() -> {
Log.d(TAG, "Notifying " + connectionCallbacks.size() + " connection callbacks");
for (ConnectionServiceCallback callback : connectionCallbacks) {
try {
action.execute(callback);
} catch (Exception e) {
Log.e(TAG, "Error in connection callback: " + e.getMessage());
TRACE.e("Error in connection callback: " + e.getMessage());
}
}
});
}
private void notifyTransactionCallbacks(CallbackAction<PaymentServiceCallback> action) {
mainHandler.post(() -> {
Log.d(TAG, "Notifying " + transactionCallbacks.size() + " transaction callbacks");
for (PaymentServiceCallback callback : transactionCallbacks) {
try {
action.execute(callback);
} catch (Exception e) {
Log.e(TAG, "Error in transaction callback: " + e.getMessage());
TRACE.e("Error in transaction callback: " + e.getMessage());
}
}
});
}
@FunctionalInterface
private interface CallbackAction<T> {
void execute(T callback) throws Exception;
}
private class QPOSServiceListener extends CQPOSService {
@Override
public void onRequestQposConnected() {
Log.d(TAG, "onRequestQposConnected");
connectLatch.countDown();
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("isConnected", true);
editor.apply();
notifyConnectionCallbacks(cb -> cb.onRequestQposConnected());
}
@Override
public void onRequestQposDisconnected() {
Log.d(TAG, "onRequestQposDisconnected");
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("isConnected", false);
editor.apply();
clearPosService();
connectLatch.countDown();
notifyConnectionCallbacks(cb -> cb.onRequestQposDisconnected());
}
@Override
public void onRequestNoQposDetected() {
Log.d(TAG, "onRequestNoQposDetected");
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("isConnected", false);
editor.apply();
clearPosService();
connectLatch.countDown();
notifyConnectionCallbacks(cb -> cb.onRequestNoQposDetected());
}
@Override
public void onDoTradeResult(QPOSService.DoTradeResult result, Hashtable<String, String> decodeData) {
// Handle ICC card for EMV processing
Log.d(TAG, "onDoTradeResult result:" + result);
Log.d(TAG, "onDoTradeResult decodeData:" + decodeData);
setICC(false);
if (result == QPOSService.DoTradeResult.ICC) {
setICC(true);
paymentResult.setTransactionType(result.name());
if (pos != null) {
pos.doEmvApp(QPOSService.EmvOption.START);
}
} else if (result == QPOSService.DoTradeResult.NFC_OFFLINE || result == QPOSService.DoTradeResult.NFC_ONLINE || result == QPOSService.DoTradeResult.MCR) {
paymentResult.setTransactionType(result.name());
notifyTransactionCallbacks(cb -> cb.onTransactionCompleted(paymentResult));
}
}
@Override
public void onRequestTransactionResult(QPOSService.TransactionResult transactionResult) {
Log.d(TAG, "onRequestTransactionResult: " + transactionResult);
}
@Override
public void onRequestWaitingUser() {
Log.d(TAG, "onRequestWaitingUser");
notifyTransactionCallbacks(cb -> cb.onRequestWaitingUser());
}
@Override
public void onRequestTime() {
Log.d(TAG, "onRequestTime");
notifyTransactionCallbacks(cb -> cb.onRequestTime());
}
@Override
public void onRequestSelectEmvApp(ArrayList<String> appList) {
Log.d(TAG, "onRequestSelectEmvApp, app count: " + (appList != null ? appList.size() : 0));
notifyTransactionCallbacks(cb -> cb.onRequestSelectEmvApp(appList));
}
@Override
public void onRequestOnlineProcess(String tlv) {
Log.d(TAG, "onRequestOnlineProcess, TLV length: " + (tlv != null ? tlv.length() : 0));
notifyTransactionCallbacks(cb -> cb.onRequestOnlineProcess(tlv));
}
@Override
public void onRequestBatchData(String tlv) {
Log.d(TAG, "onRequestBatchData, TLV length: " + (tlv != null ? tlv.length() : 0));
paymentResult.setTlv(tlv);
notifyTransactionCallbacks(cb -> cb.onTransactionCompleted(paymentResult));
}
@Override
public void onRequestSetPin(boolean isOfflinePin, int tryNum) {
Log.d(TAG, "onRequestSetPin, offline: " + isOfflinePin + ", tryNum: " + tryNum);
notifyTransactionCallbacks(cb -> cb.onRequestSetPin(isOfflinePin, tryNum));
}
@Override
public void onRequestDisplay(QPOSService.Display displayMsg) {
Log.d(TAG, "onRequestDisplay: " + displayMsg);
TRACE.i("parent onRequestDisplay");
}
@Override
public void onError(QPOSService.Error errorState) {
Log.e(TAG, "onError: " + errorState);
notifyTransactionCallbacks(cb -> cb.onTransactionFailed(errorState.name(), null));
}
@Override
public void onReturnReversalData(String tlv) {
Log.d(TAG, "onReturnReversalData, TLV length: " + (tlv != null ? tlv.length() : 0));
paymentResult.setTlv(tlv);
notifyTransactionCallbacks(cb -> cb.onTransactionCompleted(paymentResult));
}
@Override
public void onEmvICCExceptionData(String tlv) {
Log.e(TAG, "onEmvICCExceptionData, TLV length: " + (tlv != null ? tlv.length() : 0));
notifyTransactionCallbacks(cb -> cb.onTransactionFailed("Decline", tlv));
}
@Override
public void onGetCardInfoResult(Hashtable<String, String> cardInfo) {
Log.d(TAG, "onGetCardInfoResult, card info keys: " + (cardInfo != null ? cardInfo.keySet().size() : 0));
notifyTransactionCallbacks(cb -> cb.onGetCardInfoResult(cardInfo));
}
@Override
public void onRequestSetPin() {
Log.d(TAG, "onRequestSetPin");
notifyTransactionCallbacks(cb -> cb.onRequestSetPin());
}
@Override
public void onReturnGetPinInputResult(int num) {
Log.d(TAG, "onReturnGetPinInputResult: " + num);
notifyTransactionCallbacks(cb -> cb.onReturnGetPinInputResult(num));
}
@Override
public void onQposRequestPinResult(List<String> dataList, int offlineTime) {
Log.d(TAG, "onQposRequestPinResult, data count: " + (dataList != null ? dataList.size() : 0) + ", offlineTime: " + offlineTime);
notifyTransactionCallbacks(cb -> cb.onQposRequestPinResult(dataList, offlineTime));
}
@Override
public void onTradeCancelled() {
Log.d(TAG, "onTradeCancelled");
notifyTransactionCallbacks(cb -> cb.onTransactionFailed("Cancel", null));
}
}
}

View File

@ -0,0 +1,222 @@
package com.test.cardreadtest.posAPI;
import java.io.Serializable;
public class PaymentResult implements Serializable {
private boolean isConnected;
private String status;
private String amount;
private String formatID;
private String maskedPAN;
private String expiryDate;
private String cardHolderName;
private String serviceCode;
private String track1Length;
private String track2Length;
private String track3Length;
private String encTracks;
private String encTrack1;
private String encTrack2;
private String encTrack3;
private String partialTrack;
private String pinKsn;
private String trackksn;
private String pinBlock;
private String encPAN;
private String trackRandomNumber;
private String pinRandomNumber;
private String tlv;
private String transactionType;
public String getTransactionType() {
return transactionType;
}
public void setTransactionType(String transactionType) {
this.transactionType = transactionType;
}
public boolean isConnected() {
return isConnected;
}
public void setConnected(boolean connected) {
isConnected = connected;
}
public String getTlv() {
return tlv;
}
public void setTlv(String tlv) {
this.tlv = tlv;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public String getCardHolderName() {
return cardHolderName;
}
public void setCardHolderName(String cardHolderName) {
this.cardHolderName = cardHolderName;
}
public String getEncPAN() {
return encPAN;
}
public void setEncPAN(String encPAN) {
this.encPAN = encPAN;
}
public String getEncTrack1() {
return encTrack1;
}
public void setEncTrack1(String encTrack1) {
this.encTrack1 = encTrack1;
}
public String getEncTrack2() {
return encTrack2;
}
public void setEncTrack2(String encTrack2) {
this.encTrack2 = encTrack2;
}
public String getEncTrack3() {
return encTrack3;
}
public void setEncTrack3(String encTrack3) {
this.encTrack3 = encTrack3;
}
public String getEncTracks() {
return encTracks;
}
public void setEncTracks(String encTracks) {
this.encTracks = encTracks;
}
public String getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(String expiryDate) {
this.expiryDate = expiryDate;
}
public String getFormatID() {
return formatID;
}
public void setFormatID(String formatID) {
this.formatID = formatID;
}
public String getMaskedPAN() {
return maskedPAN;
}
public void setMaskedPAN(String maskedPAN) {
this.maskedPAN = maskedPAN;
}
public String getPartialTrack() {
return partialTrack;
}
public void setPartialTrack(String partialTrack) {
this.partialTrack = partialTrack;
}
public String getPinBlock() {
return pinBlock;
}
public void setPinBlock(String pinBlock) {
this.pinBlock = pinBlock;
}
public String getPinKsn() {
return pinKsn;
}
public void setPinKsn(String pinKsn) {
this.pinKsn = pinKsn;
}
public String getPinRandomNumber() {
return pinRandomNumber;
}
public void setPinRandomNumber(String pinRandomNumber) {
this.pinRandomNumber = pinRandomNumber;
}
public String getServiceCode() {
return serviceCode;
}
public void setServiceCode(String serviceCode) {
this.serviceCode = serviceCode;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getTrack1Length() {
return track1Length;
}
public void setTrack1Length(String track1Length) {
this.track1Length = track1Length;
}
public String getTrack2Length() {
return track2Length;
}
public void setTrack2Length(String track2Length) {
this.track2Length = track2Length;
}
public String getTrack3Length() {
return track3Length;
}
public void setTrack3Length(String track3Length) {
this.track3Length = track3Length;
}
public String getTrackksn() {
return trackksn;
}
public void setTrackksn(String trackksn) {
this.trackksn = trackksn;
}
public String getTrackRandomNumber() {
return trackRandomNumber;
}
public void setTrackRandomNumber(String trackRandomNumber) {
this.trackRandomNumber = trackRandomNumber;
}
}

View File

@ -0,0 +1,72 @@
package com.test.cardreadtest.posAPI;
import com.mulberry.xpos.QPOSService;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
/**
* Payment Service Callback Interface
* Handle all transaction-related callback methods
*/
public interface PaymentServiceCallback {
// ==================== Core Transaction Callbacks ====================
/**
* Waiting for user operation (insert/swipe/tap card)
*/
default void onRequestWaitingUser() {}
/**
* Request time
*/
default void onRequestTime() {}
/**
* Request to select EMV application
*/
default void onRequestSelectEmvApp(ArrayList<String> appList) {}
/**
* Request online processing
*/
default void onRequestOnlineProcess(String tlv) {}
/**
* Request to display message
*/
default void onRequestDisplay(QPOSService.Display displayMsg) {}
// ==================== PIN Related Callbacks ====================
/**
* PIN request result
*/
default void onQposRequestPinResult(List<String> dataList, int offlineTime) {}
/**
* Request to set PIN
*/
default void onRequestSetPin(boolean isOfflinePin, int tryNum) {}
/**
* Request to set PIN (no parameters)
*/
default void onRequestSetPin() {}
/**
* Return PIN input result
*/
default void onReturnGetPinInputResult(int num) {}
/**
* Get card information result
*/
default void onGetCardInfoResult(Hashtable<String, String> cardInfo) {}
default void onTransactionCompleted(PaymentResult result) {}
default void onTransactionFailed(String errorMessage,String data) {}
}

View File

@ -0,0 +1,120 @@
package com.test.cardreadtest.utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.UUID;
public class DevUtils {
/**
* Gets the version number
*
* @return The version number of the current app
*/
public static String getPackageVersionName(Context context, String pkgName) {
try {
PackageManager manager = context.getPackageManager();
PackageInfo info = manager.getPackageInfo(pkgName, 0);
//PackageManager.GET_CONFIGURATIONS
return info.versionName;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Get process name
*/
public static String getProcessName(int pid) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline"));
String processName = reader.readLine();
if (!TextUtils.isEmpty(processName)) {
processName = processName.trim();
}
return processName;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* Obtain the unique identifier of the device
*/
public static String getDeviceId(Context context) {
String deviceId = "";
try {
// Obtain the device serial number
String serial = Build.SERIAL;
// Obtain ANDROID_ID
String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
// Obtain device hardware information
String hardware = Build.HARDWARE;
String model = Build.MODEL;
// Obtain device fingerprint
String fingerprint = Build.FINGERPRINT;
String country = DeviceUtils.getDevieCountry(context);
String time = new SimpleDateFormat("yyMMddHHmmss").format(Calendar.getInstance().getTime());
// Generate a unique ID by combining device information
deviceId = model + "-"+hardware + "-"+ country + "-"+ time;
} catch (Exception e) {
e.printStackTrace();
// If the acquisition fails, use UUID as an alternative solution
deviceId = UUID.randomUUID().toString();
}
return deviceId;
}
/**
* SHA256 encryption
*/
private static String SHA256(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (Exception e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* Convert bytes to hex
*/
private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bytes) {
String temp = Integer.toHexString(b & 0xFF);
if (temp.length() == 1) {
stringBuilder.append("0");
}
stringBuilder.append(temp);
}
return stringBuilder.toString();
}
}

View File

@ -0,0 +1,200 @@
package com.test.cardreadtest.utils;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import androidx.annotation.RequiresApi;
import com.test.cardreadtest.common.enums.POS_TYPE;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
/**
* [Describe the functionality of this class in one sentence]
*
* @author : [DH]
* @createTime : [2024/9/3 10:43]
* @updateRemark : [Explain the content of this modification]
*/
public class DeviceUtils {
/**
* Get the current mobile system language
*
* @return Return the current system language. For example, if the current setting is "Chinese-China", return "zh-CN"
*/
public static String getSystemLanguage() {
return Locale.getDefault().getLanguage();
}
/**
* Retrieve the list of languages (Locale list) on the current system
*
* @return Lists of languages
*/
public static Locale[] getSystemLanguageList() {
return Locale.getAvailableLocales();
}
/**
* obtain androidId
*
* @return
*/
public static String getAndroidId(Context context) {
return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}
/**
* Is the camera available
*
* @return
*/
public static boolean isSupportCamera(Context context) {
PackageManager packageManager = context.getPackageManager();
return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
/**
* Obtain mobile phone manufacturers
* HuaWei
*
* @return Mobile phone manufacturers
*/
public static String getPhoneBrand() {
return Build.BRAND;
}
/**
* Get phone model
*
* @return Mobile phone model
*/
public static String getPhoneModel() {
return Build.MODEL;
}
/**
* Get the current mobile system version number
* Android 10
*
* @return System Version Number
*/
public static String getVersionRelease() {
return Build.VERSION.RELEASE;
}
/**
* Get the current mobile device name
* Unified device model, not the device name in 'About Mobile'
*
* @return device name
*/
public static String getDeviceName() {
return Build.DEVICE;
}
/**
* HUAWEI HWELE ELE-AL00 10
*
* @return
*/
public static String getPhoneDetail() {
return "Brand:" + DeviceUtils.getPhoneBrand() + " || Name:" + DeviceUtils.getDeviceName() + " || Model:" + DeviceUtils.getPhoneModel() + " || Version:" + DeviceUtils.getVersionRelease();
}
/**
* Get the name of the phone motherboard
*
* @return Motherboard name
*/
public static String getDeviceBoard() {
return Build.BOARD;
}
public static boolean isSmartDevices() {
if ("D20".equals(Build.MODEL) || "D30".equals(Build.MODEL) || "D50".equals(Build.MODEL) || "D60".equals(Build.MODEL)
|| "D70".equals(Build.MODEL) || "D30M".equals(Build.MODEL) || "S10".equals(Build.MODEL)
|| "D80".equals(Build.MODEL) || "D80K".equals(Build.MODEL) || "M60".equals(Build.MODEL) || "M20".equals(Build.MODEL) || "M70".equals(Build.MODEL)) {
return true;
}
return false;
}
public static boolean isPrinterDevices() {
if ("D30".equals(Build.MODEL) || "D60".equals(Build.MODEL)
|| "D70".equals(Build.MODEL) || "D30M".equals(Build.MODEL) || "D80".equals(Build.MODEL) || "D80K".equals(Build.MODEL) || "M60".equals(Build.MODEL) || "M20".equals(Build.MODEL) || "M70".equals(Build.MODEL)) {
return true;
}
return false;
}
/**
* Obtain the name of the mobile phone manufacturer
* HuaWei
*
* @return Mobile phone manufacturer name
*/
public static String getDeviceManufacturer() {
return Build.MANUFACTURER;
}
public static String getDevieCountry(Context context) {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String code = telephonyManager.getNetworkCountryIso();
return code;
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static Context getGlobalApplicationContext() {
try {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThread = currentActivityThreadMethod.invoke(null);
Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
mInitialApplicationField.setAccessible(true);
Application application = (Application) mInitialApplicationField.get(activityThread);
return application.getApplicationContext();
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
| InvocationTargetException | NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
public static boolean isAppInstalled(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
TRACE.d("[PrinterManager] isAppInstalled ");
return true;
} catch (PackageManager.NameNotFoundException e) {
TRACE.d("not found pacakge == " + e.toString());
return false;
}
}
public static POS_TYPE getDevicePosType(String deviceTypeName) {
if (deviceTypeName.equals(POS_TYPE.UART.name())) {
return POS_TYPE.UART;
} else if (deviceTypeName.equals(POS_TYPE.USB.name())) {
return POS_TYPE.USB;
} else if (deviceTypeName.equals(POS_TYPE.BLUETOOTH.name())) {
return POS_TYPE.BLUETOOTH;
}
return POS_TYPE.BLUETOOTH;
}
public static final String UART_AIDL_SERVICE_APP_PACKAGE_NAME = "com.dspread.sdkservice";//新架构的service包名
}

View File

@ -0,0 +1,413 @@
package com.test.cardreadtest.utils;
import android.content.Context;
import android.media.AudioManager;
import com.mulberry.xpos.Util;
import com.mulberry.xpos.utils.AESUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Hashtable;
public class QPOSUtil {
static final String HEXES = "0123456789ABCDEF";
public static String byteArray2Hex(byte[] raw) {
if (raw == null) {
return null;
}
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}
//根据n和e获取公钥
public static RSAPublicKey getPublicKey(String modulus, String publicExponent) {
BigInteger m = new BigInteger(modulus, 16);
BigInteger e = new BigInteger(publicExponent, 16);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
KeyFactory keyFactory;
RSAPublicKey publicKey = null;
try {
keyFactory = KeyFactory.getInstance("RSA");
publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (Exception e1) {
e1.printStackTrace();
}
return publicKey;
}
/*
* Convert hex value to ascii code
**/
public static String convertHexToString(String hex) {
StringBuilder sb = new StringBuilder();
StringBuilder temp = new StringBuilder();
//49204c6f7665204a617661 split into two characters 49, 20, 4c...
for (int i = 0; i < hex.length() - 1; i += 2) {
//grab the hex in pairs
String output = hex.substring(i, (i + 2));
//convert hex to decimal
int decimal = Integer.parseInt(output, 16);
//convert the decimal to character
sb.append((char) decimal);
temp.append(decimal);
}
return sb.toString();
}
/**
* convert int to byte[]
* @param i need to be converted to byte array
* @return byte array
*/
public static byte[] intToByteArray(int i) {
byte[] result = new byte[2];
// result[0] = (byte)((i >> 24) & 0xFF);
// result[1] = (byte)((i >> 16) & 0xFF);
result[0] = (byte)((i >> 8) & 0xFF);
result[1] = (byte)(i & 0xFF);
return result;
}
//16 byte xor
public static String xor16(byte[] src1, byte[] src2){
byte[] results = new byte[16];
for (int i = 0; i < results.length; i++){
results[i] = (byte)(src1[i] ^ src2[i]);
}
return QPOSUtil.byteArray2Hex(results);
}
/**
* convert a string in hexadecimal format to byte in hexadecimal format 44 --> byte 0x44
*
* @param hexString
* @return
*/
public static byte[] HexStringToByteArray(String hexString) {//
if (hexString == null || hexString.equals("")) {
return new byte[]{};
}
if (hexString.length() == 1 || hexString.length() % 2 != 0) {
hexString = "0" + hexString;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
private static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
/**
* convert Chinese string into hexadecimal array
*
* @param str
* @return
*/
public static byte[] CNToHex(String str) {
// String string = "";
// for (int i = 0; i < str.length(); i++) {
// String s = String.valueOf(str.charAt(i));
// byte[] bytes = null;
// try {
// bytes = s.getBytes("gbk");
// } catch (UnsupportedEncodingException e) {
// e.printStackTrace();
// }
// for (int j = 0; j < bytes.length; j++) {
// string += Integer.toHexString(bytes[j] & 0xff);
// }
// }
byte[] b = null;
try {
b = str.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return b;
}
//Low position ahead
public static byte[] intToBytes( int value )
{
byte[] src = new byte[2];
src[1] = (byte) ((value>>8) & 0xFF);
src[0] = (byte) (value & 0xFF);
return src;
}
public static String intToHex2(int i) {
String string = null;
if (i >= 0 && i < 10) {
string = "0" + i;
} else {
string = Integer.toHexString(i);
}
if(string.length() == 2){
string = "00" + string;
}else if (string.length() == 1) {
string = "000" + string;
}else if(string.length() == 3){
string = "0" + string;
}
return string;
}
/**
* convert byte to hexadecimal string
*
* @param b
* @return
*/
public static String getHexString(byte[] b) {
StringBuffer result = new StringBuffer("");
for (int i = 0; i < b.length; i++) {
result.append("0x" + Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1) + ",");
}
return result.substring(0, result.length() - 1);
}
/**
* convert int to hexadecimal byte
*
* @param i
* @return
*/
public static byte[] IntToHex(int i) {
String string = null;
if (i >= 0 && i < 10) {
string = "0" + i;
} else {
string = Integer.toHexString(i);
}
return HexStringToByteArray(string);
}
/**
* convert the specified byte array to hexadecimal and print
*
* @param b
*/
public static void printHexString(byte[] b) {
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
System.out.print(hex.toUpperCase());
}
}
/**
* convert hexadecimal byte to int
*
* @param b
* @return
*/
public static int byteArrayToInt(byte[] b) {
int result = 0;
for (int i = 0; i < b.length; i++) {
result <<= 8;
result |= (b[i] & 0xff); //
}
return result;
}
/**
* XOR input byte stream
*
* @param b
* @param startPos
* @param Len
* @return
*/
public static byte XorByteStream(byte[] b, int startPos, int Len) {
byte bRet = 0x00;
for (int i = 0; i < Len; i++) {
bRet ^= b[startPos + i];
}
return bRet;
}
/**
* Gets the subarray from <tt>array</tt> that starts at <tt>offset</tt>.
*/
public static byte[] get(byte[] array, int offset) {
return get(array, offset, array.length - offset);
}
/**
* Gets the subarray of length <tt>length</tt> from <tt>array</tt> that
* starts at <tt>offset</tt>.
*/
public static byte[] get(byte[] array, int offset, int length) {
byte[] result = new byte[length];
System.arraycopy(array, offset, result, 0, length);
return result;
}
public static void turnUpVolume(Context context, int factor) {
int sv;
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
sv = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, sv * factor / 10, AudioManager.FLAG_PLAY_SOUND);
}
public static byte[] bcd2asc(byte[] src) {
byte[] results = new byte[src.length * 2];
for (int i = 0; i < src.length; i++) {
// high Nibble conversion
if (((src[i] & 0xF0) >> 4) <= 9) {
results[2 * i] = (byte) (((src[i] & 0xF0) >> 4) + 0x30);
} else {
results[2 * i] = (byte) (((src[i] & 0xF0) >> 4) + 0x37); // 大写A~F
}
// low Nibble conversion
if ((src[i] & 0x0F) <= 9) {
results[2 * i + 1] = (byte) ((src[i] & 0x0F) + 0x30);
} else {
results[2 * i + 1] = (byte) ((src[i] & 0x0F) + 0x37); // 大写A~F
}
}
return results;
}
public static byte[] ecb(byte[] in) {
byte[] a1 = new byte[8];
for (int i = 0; i < (in.length / 8); i++) {
byte[] temp = new byte[8];
System.arraycopy(in, i * 8, temp, 0, temp.length);
a1 = xor8(a1, temp);
}
if ((in.length % 8) != 0) {
byte[] temp = new byte[8];
System.arraycopy(in, (in.length / 8) * 8, temp, 0, in.length - (in.length / 8) * 8);
a1 = xor8(a1, temp);
}
return bcd2asc(a1);
}
public static byte[] xor8(byte[] src1, byte[] src2) {
byte[] results = new byte[8];
for (int i = 0; i < results.length; i++) {
results[i] = (byte) (src1[i] ^ src2[i]);
}
return results;
}
public static boolean checkStringAllZero(String str) {
if (str.startsWith("0"))
return true;
boolean result = true;
// Integer.MAX_VALUE 4 bytes
// long MAX_VALUE = 0x7fffffffffffffffL;
int byteCou = str.length() / 2;
int count;
if (byteCou % 4 == 0) {
count = byteCou / 4;
} else {
count = byteCou / 4 + 1;
}
String sub = null;
for (int i = 0; i < count; i++) {
if (i == count - 1) {
sub = str.substring(i * 8, sub.length());
} else {
sub = str.substring(i * 8, (i + 1) * 8);
}
long l = Long.parseLong(sub, 16);
if (l > 0) {
result = false;
break;
}
}
return result;
}
public static String readRSANStream(InputStream in) throws Exception {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(in,"UTF-8"));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append('\r');
}
return sb.toString();
} catch (IOException var5) {
throw new Exception("\ue104挜鏁版嵁娴佽\ue1f0鍙栭敊锟<EFBFBD>?");
} catch (NullPointerException var6) {
throw new Exception("\ue104挜杈撳叆娴佷负锟<EFBFBD>?");
}
}
public static String buildCvmPinBlock(Hashtable<String, String> value, String pin) {
String randomData = value.get("RandomData") == null ? "" : value.get("RandomData");
String pan = value.get("PAN") == null ? "" : value.get("PAN");
String AESKey = value.get("AESKey") == null ? "" : value.get("AESKey");
String isOnline = value.get("isOnlinePin") == null ? "" : value.get("isOnlinePin");
String pinTryLimit = value.get("pinTryLimit") == null ? "" : value.get("pinTryLimit");
//iso-format4 pinblock
int pinLen = pin.length();
pin = "4" + Integer.toHexString(pinLen) + pin;
for (int i = 0; i < 14 - pinLen; i++) {
pin = pin + "A";
}
pin += randomData.substring(0, 16);
String panBlock = "";
int panLen = pan.length();
int m = 0;
if (panLen < 12) {
panBlock = "0";
for (int i = 0; i < 12 - panLen; i++) {
panBlock += "0";
}
panBlock = panBlock + pan + "0000000000000000000";
} else {
m = pan.length() - 12;
panBlock = m + pan;
for (int i = 0; i < 31 - panLen; i++) {
panBlock += "0";
}
}
String pinBlock1 = AESUtil.encrypt(AESKey, pin);
pin = Util.xor16(HexStringToByteArray(pinBlock1), HexStringToByteArray(panBlock));
String pinBlock2 = AESUtil.encrypt(AESKey, pin);
return pinBlock2;
}
}

View File

@ -0,0 +1,23 @@
package com.test.cardreadtest.utils;
import java.util.List;
public class TLV {
public String tag;
public String length;
public String value;
public boolean isNested;
public List<TLV> tlvList;
@Override
public String toString() {
return "TLV{" +
"tag='" + tag + '\'' +
", length='" + length + '\'' +
", value='" + value + '\'' +
", isNested=" + isNested +
'}';
}
}

View File

@ -0,0 +1,284 @@
package com.test.cardreadtest.utils;
import java.util.ArrayList;
import java.util.List;
/*
*
* tlv:5F200D202020202020202020202020204F08A0000003330101015F24032412319F160F4243544553543132333435363738009F21031142259A031808039F02060000000011119F03060000000000009F34034203009F120A50424F432044454249549F0607A00000033301015F300202209F4E0F616263640000000000000000000000C40A621067FFFFFFFFF0474FC10A00000332100300E0008BC708B04EFFA147D84FB4C00A00000332100300E00074C28201983ABB68B0A87865BFCAC1FCD6D2794C9C293A667EA2E0DF8FE08658105DF18EE870CDE7714573245EF4F1509F4F7DD2D8AA3A0700570556BB30C5BB3AA0D95C26B9A7A1A0FE45CCCF939D7587D3DBDF3D1D96722F7F9F8C1E0077C89BA4D4D267F74A60CF65E1D66F62685B6E41C25BDAEA4F353EBF9021195824842693CB76733CDEBFC61C6E75F9A87DBB33181C301074FDD300028A1037B8372CE0943EFBA84C82D2448DD142941895136A46CF65F84DFC6A792F502D556DA84106584AFDE8A0838B45E8E1BDAE9747FDF91C10E9D7BC9C5EE15CF0A1746ADDB8F7CB96EA672B127B19FF06A733509B5A04F5BF31D1678C2E5951CABE67E34E97AD946B4DACF3CA500188625890BCA60D7D29A63ED9F6CAEE3369C4E5DC9C2F890200FF24986DD6931BB13FC145D46B1961888B9317263C22351F98796A4FF75CF2262797535D54FD7B58F24535286C3A0EFA9524EE642EB6818EED427F8A447244A883E73FB36AAFB72B2C8EF0829E086CC87E6005E3CBE4C7E3A79CBF339320342B547C4E6D256BB98F78FE9E9A5434EF4CAB734093CD0329667FF2FA
*
* */
public class TLVParser {
private static ArrayList<TLV> tlvList = new ArrayList<TLV>();
public static List<TLV> parse(String tlv) {
try {
tlvList.clear();
return getTLVList(hexToByteArray(tlv));
} catch (Exception e) {
if (tlvList.size() > 0)
return tlvList;
return null;
}
}
private static List<TLV> getTLVList(byte[] data) {
int index = 0;
byte[] tag;
byte[] length;
byte[] value;
boolean isNested;
TLV tlv = null;
while (index < data.length) {
isNested = false;
if ((data[index] & (byte) 0x20) == (byte) (0x20)) {
isNested = true;
//Composite structure
} else {
isNested = false;
}
if ((data[index] & (byte) 0x1F) == (byte) (0x1F)) {
int lastByte = index + 1;
while ((data[lastByte] & (byte) 0x80) == (byte) 0x80) {
++lastByte;
}
tag = new byte[lastByte - index + 1];
System.arraycopy(data, index, tag, 0, tag.length);
index += tag.length;
} else {
tag = new byte[1];
tag[0] = data[index];
++index;
if (tag[0] == 0x00) {
break;
}
}
if ((data[index] & (byte) 0x80) == (byte) (0x80)) {
int n = (data[index] & (byte) 0x7F) + 1;
length = new byte[n];
System.arraycopy(data, index, length, 0, length.length);
index += length.length;
} else {
length = new byte[1];
length[0] = data[index];
++index;
}
int n = getLengthInt(length);
value = new byte[n];
System.arraycopy(data, index, value, 0, value.length);
index += value.length;
if (isNested) {
getTLVList(value);
}else {
tlv = new TLV();
tlv.tag = toHexString(tag);
tlv.length = toHexString(length);
tlv.value = toHexString(value);
tlv.isNested = isNested;
tlvList.add(tlv);
}
}
return tlvList;
}
public static List<TLV> parseWithoutValue(String tlv) {
try {
return getTLVListWithoutValue(hexToByteArray(tlv));
} catch (Exception e) {
return null;
}
}
private static List<TLV> getTLVListWithoutValue(byte[] data) {
int index = 0;
ArrayList<TLV> tlvList = new ArrayList<TLV>();
while (index < data.length) {
byte[] tag;
byte[] length;
boolean isNested;
if ((data[index] & (byte) 0x20) == (byte) (0x20)) {
isNested = true;
} else {
isNested = false;
}
if ((data[index] & (byte) 0x1F) == (byte) (0x1F)) {
int lastByte = index + 1;
while ((data[lastByte] & (byte) 0x80) == (byte) 0x80) {
++lastByte;
}
tag = new byte[lastByte - index + 1];
System.arraycopy(data, index, tag, 0, tag.length);
index += tag.length;
} else {
tag = new byte[1];
tag[0] = data[index];
++index;
if (tag[0] == 0x00) {
break;
}
}
if ((data[index] & (byte) 0x80) == (byte) (0x80)) {
int n = (data[index] & (byte) 0x7F) + 1;
length = new byte[n];
System.arraycopy(data, index, length, 0, length.length);
index += length.length;
} else {
length = new byte[1];
length[0] = data[index];
++index;
}
TLV tlv = new TLV();
tlv.tag = toHexString(tag);
tlv.length = toHexString(length);
tlv.isNested = isNested;
tlvList.add(tlv);
}
return tlvList;
}
private static int getLengthInt(byte[] data) {
if ((data[0] & (byte) 0x80) == (byte) (0x80)) {
int n = data[0] & (byte) 0x7F;
int length = 0;
for (int i = 1; i < n + 1; ++i) {
length <<= 8;
length |= (data[i] & 0xFF);
}
return length;
} else {
return data[0] & 0xFF;
}
}
// Hexadecimal string to byte array conversion
public static byte[] hexToByteArray(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2),
16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
protected static String toHexString(byte[] b) {
String result = "";
for (int i = 0; i < b.length; i++) {
result += Integer.toString((b[i] & 0xFF) + 0x100, 16).substring(1);
}
return result;
}
public static TLV searchTLV(List<TLV> tlvList, String targetTag) {
for (int i = 0; i < tlvList.size(); ++i) {
TLV tlv = tlvList.get(i);
if (tlv.tag.equalsIgnoreCase(targetTag)) {
return tlv;
} else if (tlv.isNested) {
TLV searchChild = searchTLV(tlv.tlvList, targetTag);
if (searchChild != null) {
return searchChild;
}
}
}
return null;
}
public static void main(String[] args) {
// String tlv = "5F201A5052415645454E204B554D41522042204E20202020202020202F4F07A00000000310105F24032311309F160F4243544553543132333435363738009F21031244089A031907109F02060000000000059F03060000000000009F34034203009F120A564953412044454249549F0607A00000000310105F300202269F4E0F616263640000000000000000000000C408414367FFFFFF0912C10A10218083100492E0000CC70836D3E567845F788FC00A10218083100492E0000CC2820198BBA22DE72324CD77FBFE7BCA8343BC2F26719BBC1F4633FB0E10329E35018CB35077D634CD3A84F998F52DFAC4F0442E2CD03A85D89BFF630D8A85727132E12C88664FBE5A664BB8AA21FF0D10A2D79E324D87B4225A5B9AAC68BD1FFCF5DD334B38D128B02E983DBBD32EC35DBE26CFFA01C11C272F99D8095107DE981818534873828880F1091B8BC62FD39C8394B19E7A410CF9C870CF27986D0CB251E0B6B2D364DE7F3EF1453B397B9FD2D181668510BA16DE250BEC7C1C6A3C12F7006B6B7660D7B331D326D2EA4990F899B4D11AC17D3C0FF63AEF482A349CD8849D906F60B320832E41D8349316E55DE764F8C0AF6ACE3AACA43B3994536A231BE2E790471EB559F4B9FAA5370067B7A0EA3FE59421B7AC17FA5383C6BB3159EBDE3718FEC72CC20EC1AE178386B4F7B3948C97A439AB0F70A386B392276B9B30D8398BAFE3D01AEAB03079368EEF05248E5FAE7BAB070E527981BB25F441A9224AC66DAE623BECDD9B0D1BB05A6EBCAE1E9151FB7AE3E5034B57BD6C3D609276B7743176179A801AD1B378B4629D08263148859ADDE1687CB5E9D0104D84851E5733F4C95D71E880EF20607C";
String tlv = "9F0610000000000000000000000000000000005F2A0204805F3601009F01061234567890129F150212349F160F3131323233333434353536363737389F1A0204809F1C0831323334353637389F1E0831313232333334349F3303E0D8C89F3501219F3901079F40057000B0A0019F4E0F3131323233333434353536363737389F5301529F811701019F811801009F814301019F814501019F81470100D5020001";
List<TLV> parse = parse(tlv);
for (TLV tlvcon:parse) {
System.out.println(tlvcon.toString());
if(tlvcon.tag.equals("d5")){
System.out.println("9a is "+tlvcon.value);
}
}
System.out.println(searchTLV(parse,"d5"));
}
/*
*
* Verify TLV format
* Only take the first tlv for judgment. Once 0 is encountered, it means the end
* tlv is true
* tv is false
* */
public static boolean VerifyTLV(String emvCfg) {
if (emvCfg.startsWith("9F06"))
return true;
if (emvCfg.startsWith("00"))
return false;
byte[] data = hexToByteArray(emvCfg);
int index = 0;
byte[] length;
if ((data[index] & (byte) 0x20) == (byte) (0x20)) {
return false;
}
if ((data[index] & (byte) 0x1F) == (byte) (0x1F)) {
int lastByte = index + 1;
while ((data[lastByte] & (byte) 0x80) == (byte) 0x80) {
++lastByte;
}
index += lastByte - index + 1;
if (index >= data.length)
return false;
} else {
if (data[index] == 0x00) {
return false;
}
++index;
}
if ((data[index] & (byte) 0x80) == (byte) (0x80)) {
int n = (data[index] & (byte) 0x7F) + 1;
length = new byte[n];
index += length.length;
} else {
length = new byte[1];
length[0] = data[index];
++index;
}
int n = getLengthInt(length);
if ((n + index) > data.length)
return false;
return true;
}
}

View File

@ -0,0 +1,59 @@
package com.test.cardreadtest.utils;
import android.content.Context;
import android.util.Log;
public class TRACE {
public static String NEW_LINE = System.getProperty("line.separator");
private static String AppName = "POS_LOG";
private static Boolean isTesting = true;
private static Context mContext;
public static void setContext(Context context){
mContext = context;
}
public static void v(String string) {
if (isTesting) {
Log.v(AppName, string);
// Sentry.captureMessage(string);
}
}
public static void i(String string) {
if (isTesting) {
Log.i(AppName, string);
// Sentry.captureMessage(string);
}
}
public static void w(String string) {
if (isTesting) {
Log.w(AppName, string);
// Sentry.captureMessage(string);
}
}
public static void e(Exception exception) {
if (isTesting) {
Log.e(AppName, exception.toString());
}
}
public static void e(String exception) {
if (isTesting) {
Log.e(AppName, exception);
}
}
public static void d(String string) {
if (isTesting) {
Log.d(AppName, string);
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/btnReadCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click to Read POS Card"
android:textSize="18sp"
android:padding="22dp" />
</LinearLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.test.cardreadtest.payment.PaymentViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="20dp">
<TextView
android:id="@+id/tvAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount: Testing..."
android:textSize="24sp"
android:layout_marginBottom="30dp"
tools:text="Amount: $1.00" />
<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status: Ready"
android:textSize="18sp"
android:layout_marginBottom="20dp" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel Transaction"
android:layout_marginTop="30dp" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.CardReadTest" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">CardReadTest</string>
</resources>

View File

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.CardReadTest" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.CardReadTest" parent="Base.Theme.CardReadTest" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package com.test.cardreadtest;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

4
build.gradle Normal file
View File

@ -0,0 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
}

21
gradle.properties Normal file
View File

@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

22
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,22 @@
[versions]
agp = "8.10.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.13.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Sep 24 09:50:53 MSK 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

23
settings.gradle Normal file
View File

@ -0,0 +1,23 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "CardReadTest"
include ':app'