diff --git a/app/build.gradle b/app/build.gradle
index 0592a0b..e23c4db 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -105,10 +105,15 @@ dependencies {
implementation 'com.github.d-max:spots-dialog:0.7@aar'
// Coppernic
- implementation "fr.coppernic.sdk.core:CpcCore:3.8.0"
+ implementation("fr.coppernic.sdk.core:CpcCore:3.8.0") {
+ exclude group: "fr.coppernic.external", module: "D2xx"
+ }
+
implementation 'fr.coppernic.lib.splash:splash:0.4.1'
- pcscv1Implementation 'fr.coppernic.sdk.pcsc:CpcPcsc:1.6.0'
- pcscv2Implementation "fr.coppernic.sdk.pcsc2:CpcCore:3.7.0"
+ pcscv1Implementation 'fr.coppernic.sdk.pcsc:CpcPcsc:1.7.0'
+ pcscv2Implementation("fr.coppernic.sdk.pcsc2:CpcCore:3.7.0") {
+ exclude group: "fr.coppernic.external", module: "D2xx"
+ }
// RX-java
implementation 'io.reactivex.rxjava2:rxjava:2.2.10'
@@ -117,4 +122,9 @@ dependencies {
// Coroutine
implementation "androidx.lifecycle:lifecycle-common-java8:2.5.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
+
+
+ // FTDI for Fusion support
+ pcscv1Implementation(files("libs/d2xx.jar"))
+
}
diff --git a/app/libs/d2xx.jar b/app/libs/d2xx.jar
new file mode 100644
index 0000000..8673f2f
Binary files /dev/null and b/app/libs/d2xx.jar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index edc0088..9bb7baf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -37,6 +37,26 @@
android:name="activity"
android:value="fr.coppernic.samples.pcsc.ui.MainActivity"/>
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/xml/device_filter.xml b/app/src/main/res/xml/device_filter.xml
new file mode 100644
index 0000000..129e140
--- /dev/null
+++ b/app/src/main/res/xml/device_filter.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/pcscv1/java/fr/coppernic/samples/pcsc/ui/MainActivity.java b/app/src/pcscv1/java/fr/coppernic/samples/pcsc/ui/MainActivity.java
index 5fd8d09..075d5ae 100644
--- a/app/src/pcscv1/java/fr/coppernic/samples/pcsc/ui/MainActivity.java
+++ b/app/src/pcscv1/java/fr/coppernic/samples/pcsc/ui/MainActivity.java
@@ -1,11 +1,7 @@
package fr.coppernic.samples.pcsc.ui;
-import static fr.coppernic.sdk.power.OutletPowerManager.ACTION_USB_PERMISSION;
-
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -17,7 +13,10 @@
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.TextView;
+import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
@@ -33,6 +32,7 @@
import fr.coppernic.samples.pcsc.reader.PcscReader;
import fr.coppernic.sdk.pcsc.ApduResponse;
import fr.coppernic.sdk.power.OutletPowerManager;
+import fr.coppernic.sdk.usboutlet.UsbConfigurationManager;
import fr.coppernic.sdk.utils.core.CpcBytes;
import fr.coppernic.sdk.utils.core.CpcResult;
import fr.coppernic.sdk.utils.core.CpcResult.RESULT;
@@ -41,6 +41,21 @@
public class MainActivity extends AppCompatActivity {
+ private UsbConfigurationManager usbConfigurationManager;
+
+ private final ActivityResultLauncher usbConfigurationLauncher =
+ registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == RESULT_OK) {
+ startPcscReader();
+ } else {
+ handleUsbConfigurationFailure();
+ }
+ }
+ );
+
+
FloatingActionButton fab;
Toolbar toolbar;
MaterialBetterSpinner spReader;
@@ -69,6 +84,25 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+
+ initUi();
+
+ usbConfigurationManager = new UsbConfigurationManager(this);
+ }
+
+ private void initializeUsb() {
+ // If SmartCard reader is powered and permission granted, no need to launch configuration activity
+ if (usbConfigurationManager.isSmartCardReaderReady()) {
+ startPcscReader();
+ } else {
+ usbConfigurationLauncher.launch(
+ UsbConfigurationManager.Companion.createConfigurationIntent(this)
+ );
+ }
+ }
+
+ private void initUi() {
+
initTitle();
fab = findViewById(R.id.fab);
@@ -113,37 +147,33 @@ public void afterTextChanged(Editable s) {
//Init empty spinner
ArrayAdapter arrayAdapter = new ArrayAdapter<>(this,
- android.R.layout.simple_dropdown_item_1line,
- new ArrayList());
+ android.R.layout.simple_dropdown_item_1line,
+ new ArrayList());
spReader.setAdapter(arrayAdapter);
etResult.clearFocus();
etApdu.setOnEditorActionListener(editorActionListener);
+
+ showFAB(false);
+
+ swConnect.setEnabled(false);
+ }
+
+ private void handleUsbConfigurationFailure() {
+ Toast.makeText(this, "USB configuration failed", Toast.LENGTH_SHORT).show();
+ finish();
}
@Override
protected void onStart() {
Timber.d("onStart");
super.onStart();
- showFAB(false);
-
- // Register USB receiver
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_USB_PERMISSION);
- registerReceiver(usbReceiver, filter, RECEIVER_EXPORTED);
-
- powerOn();
+ initializeUsb();
}
@Override
protected void onStop() {
Timber.d("onStop");
- // Unregister USB receiver
- try {
- unregisterReceiver(usbReceiver);
- } catch (Exception e) {
- e.printStackTrace();
- }
stopPcscReader();
super.onStop();
}
@@ -199,38 +229,6 @@ private void stopPcscReader() {
}
//endregion
-
- //region USB
- private BroadcastReceiver usbReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (ACTION_USB_PERMISSION.equalsIgnoreCase(action)) {
- swConnect.setEnabled(true);
- startPcscReader();
- }
- }
- };
- //endregion
-
-
- // region Methods
- void powerOn() {
- if (!manager.isPowerOn(getApplicationContext())) {
- Coroutines coroutines = new Coroutines();
- manager.powerOn(MainActivity.this, 10000L, coroutines.getContinuation(
- (res, err) -> {
- try {
- Thread.sleep(500L);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- manager.getUsbPermissions(MainActivity.this);
- }
- ));
- }
- }
-
void connectCard() {
if (reader != null) {
if (!reader.isConnected()) {//Connect reader
@@ -325,6 +323,8 @@ private void updateSpinner() {
deviceList);
spReader.setAdapter(arrayAdapter);
}
+
+ swConnect.setEnabled(deviceList != null);
}
//endregion
}
diff --git a/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/FTdiManager.kt b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/FTdiManager.kt
new file mode 100644
index 0000000..bd6885d
--- /dev/null
+++ b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/FTdiManager.kt
@@ -0,0 +1,83 @@
+package fr.coppernic.sdk.usboutlet
+
+import android.content.Context
+import android.content.Context.USB_SERVICE
+import android.hardware.usb.UsbManager
+import android.util.Log
+import com.ftdi.j2xx.D2xxManager
+import com.ftdi.j2xx.FT_Device
+
+const val SET_GPIO_OUTPUT_ALL_HIGH = 0xF0
+
+const val SET_GPIO_OUTPUT_ALL_LOG = 0xFF
+
+const val SET_BIT_MODE = 0x20
+const val FTDI_CONFIGURATION_DELAY = 2000L
+
+
+object FtdiManager {
+
+ private var ftDevice: FT_Device? = null
+ private const val TAG = "FtdiManager"
+
+ fun powerSmartCardReader(context: Context): Boolean {
+ val usbManager = context.applicationContext.getSystemService(USB_SERVICE) as UsbManager
+
+ val ftdiUsbDevice =
+ findUsbDevice(usbManager, FTDI_VID, listOf(FTDI_PID_1, FTDI_PID_2)) ?: return false
+
+
+ try {
+ val d2xxManager = D2xxManager.getInstance(context)
+ val count = d2xxManager.createDeviceInfoList(context)
+
+ if (count == 0) {
+ Log.e(TAG, "D2xx createDeviceInfoList() count == 0")
+ return false
+ }
+
+ ftDevice = d2xxManager.openByUsbDevice(
+ context, ftdiUsbDevice
+ )
+
+ if (ftDevice == null) {
+ Log.e(TAG, "D2xx openByUsbDevice() failed")
+ return false
+ }
+
+ ftDevice?.apply {
+ resetDevice()
+ setBaudRate(9600)
+ setDataCharacteristics(
+ D2xxManager.FT_DATA_BITS_8,
+ D2xxManager.FT_STOP_BITS_1,
+ D2xxManager.FT_PARITY_NONE
+ )
+ }
+
+ val mask = computeFtdiGpioOutputMask(false, false, false, true)
+ ftDevice?.setBitMode(mask, SET_BIT_MODE.toByte())
+
+ return true
+ } catch (e: Exception) {
+ Log.e(TAG, "FTDI communication failed", e)
+ return false
+ }
+ }
+
+
+ private fun computeFtdiGpioOutputMask(
+ gpio0: Boolean, gpio1: Boolean, gpio2: Boolean, gpio3: Boolean
+ ): Byte {
+
+ val direction = 0xF shl 4
+ var values = 0
+ if (!gpio0) values = values or (1 shl 0)
+ if (!gpio1) values = values or (1 shl 1)
+ if (!gpio2) values = values or (1 shl 2)
+ if (!gpio3) values = values or (1 shl 3)
+
+ return (direction or values).toByte()
+ }
+
+}
diff --git a/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbAttachActivity.kt b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbAttachActivity.kt
new file mode 100644
index 0000000..1509db5
--- /dev/null
+++ b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbAttachActivity.kt
@@ -0,0 +1,31 @@
+package fr.coppernic.sdk.usboutlet
+
+import android.app.Activity
+import android.hardware.usb.UsbDevice
+import android.hardware.usb.UsbManager
+import android.os.Bundle
+import android.util.Log
+
+class UsbAttachActivity : Activity() {
+ private val TAG = "UsbAttachActivity"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val device =
+ intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
+
+ if (device != null) {
+ val manager =
+ getSystemService(USB_SERVICE) as UsbManager
+
+ Log.d(
+ TAG,
+ "USB attached: " + device.deviceName +
+ ", permission=" + manager.hasPermission(device)
+ )
+ }
+
+ finish()
+ }
+}
diff --git a/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbConfigurationActivity.kt b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbConfigurationActivity.kt
new file mode 100644
index 0000000..3e3c6c4
--- /dev/null
+++ b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbConfigurationActivity.kt
@@ -0,0 +1,355 @@
+package fr.coppernic.sdk.usboutlet
+
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.hardware.usb.UsbDevice
+import android.hardware.usb.UsbManager
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+import com.ftdi.j2xx.FT_Device
+import fr.coppernic.sdk.power.OutletPowerManager
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+class UsbConfigurationActivity : AppCompatActivity() {
+
+ companion object {
+ private const val TAG = "UsbConfiguration"
+
+ // Adapt these
+ private const val OUTLET_POWER_TIMEOUT_MS = 5_000L
+ private const val DEVICE_ENUM_TIMEOUT_MS = 5_000L
+
+ // Time given to the user to answer each permission dialog
+ private const val PERMISSION_TIMEOUT_MS = 20_000L
+
+ private const val POLL_DELAY_MS = 200L
+ }
+
+ private lateinit var usbManager: UsbManager
+
+ private val usbPermissionAction by lazy {
+ "$packageName.USB_PERMISSION"
+ }
+
+ private data class PendingPermission(
+ val deviceId: Int, val result: CompletableDeferred
+ )
+
+ private var pendingPermission: PendingPermission? = null
+
+ private val usbPermissionReceiver = object : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.d(TAG, "USB permission received" + intent.action)
+
+ if (intent.action != usbPermissionAction) {
+ return
+ }
+
+ val device = getUsbDevice(intent) ?: return
+
+ val pending = pendingPermission ?: return
+
+ // Ignore an old/unrelated permission answer
+ if (device.deviceId != pending.deviceId) {
+ return
+ }
+
+ val granted = intent.getBooleanExtra(
+ UsbManager.EXTRA_PERMISSION_GRANTED, false
+ )
+
+ Log.i(
+ TAG, "Permission result for " + "%04X:%04X = $granted".format(
+ device.vendorId, device.productId
+ )
+ )
+
+ pending.result.complete(granted)
+ }
+
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ Log.i(TAG, "Starting USB configuration")
+
+ usbManager = getSystemService(USB_SERVICE) as UsbManager
+
+ registerPermissionReceiver()
+
+ lifecycleScope.launch {
+ val success = configureUsb()
+
+ setResult(
+ if (success) RESULT_OK
+ else RESULT_CANCELED
+ )
+
+ finish()
+ }
+ }
+
+ override fun onDestroy() {
+ pendingPermission?.result?.cancel()
+ pendingPermission = null
+
+ unregisterReceiver(usbPermissionReceiver)
+
+ super.onDestroy()
+ }
+
+ private suspend fun configureUsb(): Boolean {
+ try {
+
+ if (!setupFtdi()) {
+ Log.e(TAG, "Unable to setup FTDI")
+ return false
+ }
+
+ if (!setupSmartCardReader()) {
+ Log.e(TAG, "Unable to setup SmartCard reader")
+ return false
+ }
+
+ /*
+ * Everything is ready.
+ */
+ Log.i(TAG, "USB configuration successful")
+ return true
+
+ } catch (e: CancellationException) {
+ throw e
+
+ } catch (e: Exception) {
+ Log.e(TAG, "USB configuration failed", e)
+ return false
+ } finally {
+ }
+ }
+
+ private suspend fun powerOutlet(): Boolean {
+ if (findUsbDevice(usbManager, FTDI_VID, listOf(FTDI_PID_1, FTDI_PID_2)) != null) {
+ Log.d(TAG, "FTDI already powered")
+ return true
+ }/*
+ * 1. Power the outlet.
+ *
+ * this call blocks until the outlet is powered
+ * or until its own timeout occurs.
+ */
+ Log.i(TAG, "Powering FTDI outlet")
+
+ withContext(Dispatchers.IO) {
+ OutletPowerManager().powerOn(
+ applicationContext, OUTLET_POWER_TIMEOUT_MS
+ )
+ }
+
+ /*
+ * 2. Wait for Android USB host to enumerate FTDI.
+ */
+ Log.i(TAG, "Waiting for FTDI")
+
+ val ftdiDevice = waitForUsbDevice(
+ FTDI_VID, listOf(FTDI_PID_1, FTDI_PID_2), DEVICE_ENUM_TIMEOUT_MS
+ )
+
+ if (ftdiDevice == null) {
+ Log.e(TAG, "FTDI did not appear")
+ return false
+ }
+
+ Log.i(TAG, "device detected: ${ftdiDevice.deviceName}")
+ Log.i(TAG, "device is Ftdi : ${ftdiDevice.isFtdi()}")
+
+ return true
+ }
+
+ private suspend fun setupFtdi(): Boolean {
+ if (!powerOutlet()) {
+ return false
+ }
+
+ val ftdiDevice = findUsbDevice(
+ usbManager, FTDI_VID, listOf(FTDI_PID_1, FTDI_PID_2)
+ ) ?: return false
+
+ /*
+ * 3. Get FTDI permission, only when necessary.
+ */
+ if (!ensureUsbPermission(ftdiDevice)) {
+ Log.e(TAG, "FTDI permission not granted")
+ return false
+ }
+ return true
+ }
+
+ private suspend fun setupSmartCardReader(): Boolean {
+ if (!powerSmartCardReader()) {
+ Log.e(TAG, "Unable to power smart card reader")
+ return false
+ }
+
+ val sec1210USbDevice =
+ findUsbDevice(usbManager, SEC1210_VID, listOf(SEC1210_PID)) ?: return false
+
+ if (!ensureUsbPermission(sec1210USbDevice)) {
+ Log.e(TAG, "SEC1210 permission not granted")
+ return false
+ }
+
+ return true
+ }
+
+ /**
+ * Waits for Android to enumerate a specific USB device.
+ *
+ * Polling here is intentional:
+ * this Activity only lives for a few seconds and it makes
+ * enumeration after power-on very simple.
+ */
+ private suspend fun waitForUsbDevice(
+ vendorId: Int, productIdList: List, timeoutMs: Long
+ ): UsbDevice? = withTimeoutOrNull(timeoutMs) {
+ var device: UsbDevice? = null
+
+ while (device == null) {
+ device = findUsbDevice(usbManager, vendorId, productIdList)
+
+ if (device == null) {
+ delay(POLL_DELAY_MS)
+ }
+ }
+ device
+ }
+
+ /**
+ * Returns true if permission exists or if the user grants it.
+ *
+ * If the user ignores the permission dialog for too long,
+ * this returns false and the Activity terminates.
+ */
+ private suspend fun ensureUsbPermission(
+ device: UsbDevice
+ ): Boolean {
+
+ if (usbManager.hasPermission(device)) {
+ Log.i(
+ TAG, "Permission already granted for ${device.deviceName}"
+ )
+ return true
+ }
+
+ Log.i(
+ TAG, "Requesting permission for ${device.deviceName}"
+ )
+
+ val deferred = CompletableDeferred()
+
+ pendingPermission = PendingPermission(
+ deviceId = device.deviceId, result = deferred
+ )
+
+ val permissionIntent = Intent(usbPermissionAction).setPackage(packageName)
+
+ val pendingIntent = PendingIntent.getBroadcast(
+ this, 0, permissionIntent, PendingIntent.FLAG_MUTABLE
+ )
+
+ usbManager.requestPermission(
+ device, pendingIntent
+ )
+
+ val granted = try {
+ withTimeoutOrNull(PERMISSION_TIMEOUT_MS) {
+ deferred.await()
+ } ?: false
+
+ } finally {
+ if (pendingPermission?.deviceId == device.deviceId) {
+ pendingPermission = null
+ }
+ }
+
+ if (!granted) {
+ Log.w(
+ TAG, "Permission denied or timed out for ${device.deviceName}"
+ )
+ }
+
+ return granted
+ }
+
+ var ftDevice: FT_Device? = null
+
+ /**
+ * FTDI CBUS command that powers the smart card reader.
+ */
+ private suspend fun powerSmartCardReader(): Boolean = withContext(Dispatchers.IO) {
+ if (findUsbDevice(usbManager, SEC1210_VID, listOf(SEC1210_PID)) != null) {
+ Log.d(TAG, "Smart Card reader already powered")
+ return@withContext true
+ }
+
+ if (!FtdiManager.powerSmartCardReader(context = this@UsbConfigurationActivity)) {
+ Log.e(TAG, "Error while powering Smartcard reader with FTDI")
+ return@withContext false
+ }
+
+ Log.i(TAG, "Waiting for SEC1210")
+
+ val sec1210 = waitForUsbDevice(
+ SEC1210_VID, listOf(SEC1210_PID), DEVICE_ENUM_TIMEOUT_MS
+ )
+
+ if (sec1210 == null) {
+ Log.e(TAG, "SEC1210 did not appear")
+ return@withContext false
+ }
+
+ Log.i(TAG, "SEC1210 detected: ${sec1210.deviceName}")
+
+ return@withContext true
+
+ }
+
+ private fun registerPermissionReceiver() {
+ ContextCompat.registerReceiver(
+ this,
+ usbPermissionReceiver,
+ IntentFilter(usbPermissionAction),
+ ContextCompat.RECEIVER_NOT_EXPORTED
+ )
+ }
+
+ private fun getUsbDevice(
+ intent: Intent
+ ): UsbDevice? {
+
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(
+ UsbManager.EXTRA_DEVICE, UsbDevice::class.java
+ )
+ } else {
+ @Suppress("DEPRECATION") intent.getParcelableExtra(
+ UsbManager.EXTRA_DEVICE
+ )
+ }
+ }
+
+}
+
diff --git a/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbConfigurationManager.kt b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbConfigurationManager.kt
new file mode 100644
index 0000000..e498948
--- /dev/null
+++ b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbConfigurationManager.kt
@@ -0,0 +1,32 @@
+package fr.coppernic.sdk.usboutlet
+
+import android.content.Context
+import android.content.Intent
+import android.hardware.usb.UsbManager
+
+class UsbConfigurationManager(
+ context: Context
+) {
+
+ companion object {
+
+ fun createConfigurationIntent(context: Context): Intent {
+ return Intent(
+ context,
+ UsbConfigurationActivity::class.java
+ )
+ }
+ }
+
+ private val usbManager =
+ context.applicationContext
+ .getSystemService(Context.USB_SERVICE) as UsbManager
+
+ fun isSmartCardReaderReady(): Boolean {
+ val reader = getSmartCardReader(usbManager)
+ ?: return false
+
+ return usbManager.hasPermission(reader)
+ }
+
+ }
diff --git a/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbExtension.kt b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbExtension.kt
new file mode 100644
index 0000000..6960053
--- /dev/null
+++ b/app/src/pcscv1/java/fr/coppernic/sdk/usboutlet/UsbExtension.kt
@@ -0,0 +1,46 @@
+package fr.coppernic.sdk.usboutlet
+
+import android.hardware.usb.UsbDevice
+import android.hardware.usb.UsbManager
+
+const val FTDI_VID = 0x0403
+const val FTDI_PID_1 = 0x6010
+const val FTDI_PID_2 = 0x6015
+
+const val SEC1210_VID = 0x0424
+const val SEC1210_PID = 0x1202
+
+fun isUsbDeviceReady(usbManager: UsbManager, vendorId: Int, productIdList: List): Boolean {
+ val device = findUsbDevice(usbManager, vendorId, productIdList) ?: return false
+ return usbManager.hasPermission(device)
+}
+
+
+fun findUsbDevice(usbManager: UsbManager,
+ vendorId: Int, productIdList: List
+): UsbDevice? {
+ return usbManager.deviceList.values.firstOrNull { device ->
+ device.vendorId == vendorId && productIdList.contains(device.productId)
+ }
+}
+
+fun getFtdiDevice(usbManager: UsbManager): UsbDevice? {
+ return findUsbDevice(
+ usbManager,
+ FTDI_VID,
+ listOf(FTDI_PID_1, FTDI_PID_2)
+ )
+}
+
+fun getSmartCardReader(usbManager: UsbManager): UsbDevice? {
+ return findUsbDevice(
+ usbManager,
+ SEC1210_VID,
+ listOf(SEC1210_PID)
+ )
+}
+
+fun UsbDevice.isFtdi(): Boolean {
+ return (this.productId == FTDI_PID_1 || this.productId == FTDI_PID_2) && this.vendorId == FTDI_VID
+}
+