Unified sdk (#12)
* unifying Android and Desktop SDK * implement desktop SDK adapter * updating declarations * change include syntax * following jitpack.io guide for Android libraries * add install task to all artefacts, add javadoc generation * fixing javadoc * use explicit provider "SC" instead of relying on order * move to BouncyCastle for desktop compatibility * improve documentation
This commit is contained in:
@@ -100,7 +100,7 @@ public class ApplicationInfo {
|
||||
|
||||
/**
|
||||
* The number of remaining pairing slots. If zero is returned, no further pairing is possible.
|
||||
* @return
|
||||
* @return the number of remaining pairing slots
|
||||
*/
|
||||
public byte getFreePairingSlots() {
|
||||
return freePairingSlots;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.status.keycard.applet;
|
||||
|
||||
import org.spongycastle.crypto.digests.KeccakDigest;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.crypto.digests.KeccakDigest;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
@@ -41,8 +41,8 @@ public class CardDuplicator {
|
||||
* @param pin the card PIN
|
||||
* @param deviceCount the number of devices which will be adding entropy for the key, including this one
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws APDUException
|
||||
* @throws IOException communication error
|
||||
* @throws APDUException unexpected card response
|
||||
*/
|
||||
public void startDuplication(CardChannel channel, Pairing pairing, String pin, int deviceCount) throws IOException, APDUException {
|
||||
KeycardCommandSet cmdSet = preamble(channel, pairing, pin);
|
||||
@@ -56,8 +56,8 @@ public class CardDuplicator {
|
||||
* @param pairing the pairing info
|
||||
* @param pin the card PIN
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws APDUException
|
||||
* @throws IOException communication error
|
||||
* @throws APDUException unexpected card response
|
||||
*/
|
||||
public byte[] exportKey(CardChannel channel, Pairing pairing, String pin) throws IOException, APDUException {
|
||||
KeycardCommandSet cmdSet = preamble(channel, pairing, pin);
|
||||
@@ -66,13 +66,13 @@ public class CardDuplicator {
|
||||
|
||||
/**
|
||||
* Imports key. Must be used on all cards designated as the target for the duplication.
|
||||
* @param channel
|
||||
* @param pairing
|
||||
* @param pin
|
||||
* @param key
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws APDUException
|
||||
* @param channel the card channel
|
||||
* @param pairing the pairing info
|
||||
* @param pin the user PIN
|
||||
* @param key the key to import
|
||||
* @return the key UID
|
||||
* @throws IOException communication error
|
||||
* @throws APDUException unexpected card response
|
||||
*/
|
||||
public byte[] importKey(CardChannel channel, Pairing pairing, String pin, byte[] key) throws IOException, APDUException {
|
||||
KeycardCommandSet cmdSet = preamble(channel, pairing, pin);
|
||||
@@ -84,8 +84,8 @@ public class CardDuplicator {
|
||||
* exactly once, except for the device which started the backup.
|
||||
*
|
||||
* @param channel
|
||||
* @throws IOException
|
||||
* @throws APDUException
|
||||
* @throws IOException communication error
|
||||
* @throws APDUException unexpected card response
|
||||
*/
|
||||
public void addEntropy(CardChannel channel) throws IOException, APDUException {
|
||||
KeycardCommandSet cmdSet = new KeycardCommandSet(channel);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.status.keycard.applet;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
public class Identifiers {
|
||||
public static final byte[] PACKAGE_AID = Hex.decode("53746174757357616C6C6574");
|
||||
|
||||
@@ -4,9 +4,8 @@ import im.status.keycard.io.APDUCommand;
|
||||
import im.status.keycard.io.APDUException;
|
||||
import im.status.keycard.io.APDUResponse;
|
||||
import im.status.keycard.io.CardChannel;
|
||||
import org.spongycastle.jce.interfaces.ECPrivateKey;
|
||||
import org.spongycastle.jce.interfaces.ECPublicKey;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.bouncycastle.jce.interfaces.ECPrivateKey;
|
||||
import org.bouncycastle.jce.interfaces.ECPublicKey;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
@@ -117,7 +116,6 @@ public class KeycardCommandSet {
|
||||
/**
|
||||
* Opens the secure channel. Calls the corresponding method of the SecureChannel class.
|
||||
*
|
||||
* @return the raw card response
|
||||
* @throws IOException communication error
|
||||
*/
|
||||
public void autoOpenSecureChannel() throws IOException {
|
||||
@@ -145,7 +143,7 @@ public class KeycardCommandSet {
|
||||
SecretKey key;
|
||||
|
||||
try {
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256", "BC");
|
||||
PBEKeySpec spec = new PBEKeySpec(pairingPassword.toCharArray(), "Keycard Pairing Password Salt".getBytes(), 50000, 32 * 8);
|
||||
key = skf.generateSecret(spec);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -141,7 +141,7 @@ public class Mnemonic {
|
||||
SecretKey key;
|
||||
|
||||
try {
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512", "BC");
|
||||
PBEKeySpec spec = new PBEKeySpec(mnemonicPhrase.toCharArray(), ("mnemonic" + password).getBytes(), 2048, 512);
|
||||
key = skf.generateSecret(spec);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.status.keycard.applet;
|
||||
|
||||
import org.spongycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package im.status.keycard.applet;
|
||||
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.asn1.x9.X9IntegerConverter;
|
||||
import org.spongycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.math.ec.ECAlgorithms;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.math.ec.FixedPointUtil;
|
||||
import org.spongycastle.math.ec.custom.sec.SecP256K1Curve;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.asn1.x9.X9IntegerConverter;
|
||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.math.ec.ECAlgorithms;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.math.ec.FixedPointUtil;
|
||||
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
@@ -28,7 +28,7 @@ public class RecoverableSignature {
|
||||
static final ECDomainParameters CURVE;
|
||||
|
||||
static {
|
||||
FixedPointUtil.precompute(CURVE_PARAMS.getG(), 6);
|
||||
FixedPointUtil.precompute(CURVE_PARAMS.getG());
|
||||
CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import im.status.keycard.io.APDUCommand;
|
||||
import im.status.keycard.io.APDUException;
|
||||
import im.status.keycard.io.APDUResponse;
|
||||
import im.status.keycard.io.CardChannel;
|
||||
import org.spongycastle.crypto.engines.AESEngine;
|
||||
import org.spongycastle.crypto.macs.CBCBlockCipherMac;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.jce.ECNamedCurveTable;
|
||||
import org.spongycastle.jce.interfaces.ECPublicKey;
|
||||
import org.spongycastle.jce.spec.ECParameterSpec;
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||
import org.bouncycastle.jce.interfaces.ECPublicKey;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
@@ -68,17 +68,17 @@ public class SecureChannelSession {
|
||||
public void generateSecret(byte[] keyData) {
|
||||
try {
|
||||
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
|
||||
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH");
|
||||
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH", "BC");
|
||||
g.initialize(ecSpec, random);
|
||||
|
||||
KeyPair keyPair = g.generateKeyPair();
|
||||
|
||||
publicKey = ((ECPublicKey) keyPair.getPublic()).getQ().getEncoded(false);
|
||||
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
|
||||
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
|
||||
keyAgreement.init(keyPair.getPrivate());
|
||||
|
||||
ECPublicKeySpec cardKeySpec = new ECPublicKeySpec(ecSpec.getCurve().decodePoint(keyData), ecSpec);
|
||||
ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA").generatePublic(cardKeySpec);
|
||||
ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA", "BC").generatePublic(cardKeySpec);
|
||||
|
||||
keyAgreement.doPhase(cardKey, true);
|
||||
secret = keyAgreement.generateSecret();
|
||||
@@ -116,7 +116,6 @@ public class SecureChannelSession {
|
||||
* Follows the specifications from the SECURE_CHANNEL.md document.
|
||||
*
|
||||
* @param apduChannel the apdu channel
|
||||
* @return the card response
|
||||
* @throws IOException communication error
|
||||
*/
|
||||
public void autoOpenSecureChannel(CardChannel apduChannel) throws IOException {
|
||||
@@ -155,7 +154,7 @@ public class SecureChannelSession {
|
||||
|
||||
sessionEncKey = new SecretKeySpec(Arrays.copyOf(keyData, SC_SECRET_LENGTH), "AES");
|
||||
sessionMacKey = new KeyParameter(keyData, SC_SECRET_LENGTH, SC_SECRET_LENGTH);
|
||||
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding");
|
||||
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC");
|
||||
sessionMac = new CBCBlockCipherMac(new AESEngine(), 128, null);
|
||||
open = true;
|
||||
} catch(Exception e) {
|
||||
@@ -196,7 +195,7 @@ public class SecureChannelSession {
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA256");
|
||||
md = MessageDigest.getInstance("SHA256", "BC");
|
||||
} catch(Exception e) {
|
||||
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
|
||||
}
|
||||
@@ -309,7 +308,6 @@ public class SecureChannelSession {
|
||||
* Unpair all other clients
|
||||
*
|
||||
* @param apduChannel the apdu channel
|
||||
* @return the raw card response
|
||||
* @throws IOException communication error
|
||||
*/
|
||||
public void unpairOthers(CardChannel apduChannel) throws IOException, APDUException {
|
||||
@@ -441,7 +439,7 @@ public class SecureChannelSession {
|
||||
random.nextBytes(iv);
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||
sessionEncKey = new SecretKeySpec(secret, "AES");
|
||||
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding");
|
||||
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC");
|
||||
sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec);
|
||||
initData = sessionCipher.doFinal(initData);
|
||||
byte[] encrypted = new byte[1 + publicKey.length + iv.length + initData.length];
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package im.status.keycard.globalplatform;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
@@ -23,6 +21,16 @@ public class Crypto {
|
||||
public static long PIN_BOUND = 999999L;
|
||||
public static long PUK_BOUND = 999999999999L;
|
||||
|
||||
private static boolean spongyCastleLoaded = false;
|
||||
|
||||
public static void addSpongyCastleProvider() {
|
||||
if (!spongyCastleLoaded) {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
spongyCastleLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a session key for SCP02.
|
||||
*
|
||||
@@ -44,7 +52,7 @@ public class Crypto {
|
||||
|
||||
SecretKeySpec tmpKey = new SecretKeySpec(key24, "DESede");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
|
||||
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding", "BC");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, tmpKey, new IvParameterSpec(NullBytes8));
|
||||
|
||||
return cipher.doFinal(derivationData);
|
||||
@@ -52,6 +60,8 @@ public class Crypto {
|
||||
throw new IllegalStateException("error generating session keys.", e);
|
||||
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException("error generating session keys.", e);
|
||||
} catch (NoSuchProviderException e) {
|
||||
throw new RuntimeException("SpongyCastle not installed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +110,7 @@ public class Crypto {
|
||||
public static byte[] mac3des(byte[] keyData, byte[] data, byte[] iv) {
|
||||
try {
|
||||
SecretKeySpec key = new SecretKeySpec(resizeKey24(keyData), "DESede");
|
||||
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
|
||||
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding", "BC");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
|
||||
byte[] result = cipher.doFinal(data, 0, 24);
|
||||
byte[] tail = new byte[8];
|
||||
@@ -122,11 +132,11 @@ public class Crypto {
|
||||
public static byte[] macFull3des(byte[] keyData, byte[] data, byte[] iv) {
|
||||
try {
|
||||
SecretKeySpec keyDes = new SecretKeySpec(resizeKey8(keyData), "DES");
|
||||
Cipher cipherDes = Cipher.getInstance("DES/CBC/NoPadding");
|
||||
Cipher cipherDes = Cipher.getInstance("DES/CBC/NoPadding", "BC");
|
||||
cipherDes.init(Cipher.ENCRYPT_MODE, keyDes, new IvParameterSpec(iv));
|
||||
|
||||
SecretKeySpec keyDes3 = new SecretKeySpec(resizeKey24(keyData), "DESede");
|
||||
Cipher cipherDes3 = Cipher.getInstance("DESede/CBC/NoPadding");
|
||||
Cipher cipherDes3 = Cipher.getInstance("DESede/CBC/NoPadding", "BC");
|
||||
byte[] des3Iv = iv.clone();
|
||||
|
||||
if (data.length > 8) {
|
||||
@@ -183,7 +193,7 @@ public class Crypto {
|
||||
*/
|
||||
public static byte[] encryptICV(byte[] macKeyData, byte[] mac) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
|
||||
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding", "BC");
|
||||
SecretKeySpec key = new SecretKeySpec(resizeKey8(macKeyData), "DES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
return cipher.doFinal(mac);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.status.keycard.globalplatform;
|
||||
|
||||
import im.status.keycard.applet.Identifiers;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -48,8 +48,8 @@ public class SecureChannel {
|
||||
* @param hostChallenge the host challenge
|
||||
* @param cardKeys the SCP02 keys
|
||||
* @param resp the response from the card to the INITIALIZE UPDATE oommand
|
||||
* @return
|
||||
* @throws APDUException
|
||||
* @return the Session object built on succesful verification
|
||||
* @throws APDUException communication error
|
||||
*/
|
||||
public static Session verifyChallenge(byte[] hostChallenge, SCP02Keys cardKeys, APDUResponse resp) throws APDUException {
|
||||
if (resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package im.status.keycard.io;
|
||||
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implementation of the CardChannel interface using the Android NFC API.
|
||||
*/
|
||||
public class NFCCardChannel implements CardChannel {
|
||||
private static final String TAG = "CardChannel";
|
||||
|
||||
private IsoDep isoDep;
|
||||
|
||||
public NFCCardChannel(IsoDep isoDep) {
|
||||
this.isoDep = isoDep;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APDUResponse send(APDUCommand cmd) throws IOException {
|
||||
byte[] apdu = cmd.serialize();
|
||||
Log.d(TAG, String.format("COMMAND CLA: %02X INS: %02X P1: %02X P2: %02X LC: %02X", cmd.getCla(), cmd.getIns(), cmd.getP1(), cmd.getP2(), cmd.getData().length));
|
||||
byte[] resp = this.isoDep.transceive(apdu);
|
||||
APDUResponse response = new APDUResponse(resp);
|
||||
Log.d(TAG, String.format("RESPONSE LEN: %02X, SW: %04X %n-----------------------", response.getData().length, response.getSw()));
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return this.isoDep.isConnected();
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package im.status.keycard.io;
|
||||
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* Manages connection of NFC-based cards. Extends Thread and must be started using the start() method. The thread has
|
||||
* a runloop which monitors the connection and from which CardListener callbacks are called.
|
||||
*/
|
||||
public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback {
|
||||
private static final String TAG = "NFCCardManager";
|
||||
private static final int DEFAULT_LOOP_SLEEP_MS = 50;
|
||||
|
||||
private IsoDep isoDep;
|
||||
private boolean isRunning;
|
||||
private CardListener cardListener;
|
||||
private int loopSleepMS;
|
||||
|
||||
/**
|
||||
* Constructs an NFC Card Manager with default delay between loop iterations.
|
||||
*/
|
||||
public NFCCardManager() {
|
||||
this(DEFAULT_LOOP_SLEEP_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an NFC Card Manager with the given delay between loop iterations.
|
||||
*
|
||||
* @param loopSleepMS time to sleep between loops
|
||||
*/
|
||||
public NFCCardManager(int loopSleepMS) {
|
||||
this.loopSleepMS = loopSleepMS;
|
||||
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if connected, false otherwise.
|
||||
* @return if connected, false otherwise
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return isoDep != null && isoDep.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTagDiscovered(Tag tag) {
|
||||
isoDep = IsoDep.get(tag);
|
||||
try {
|
||||
isoDep = IsoDep.get(tag);
|
||||
isoDep.connect();
|
||||
isoDep.setTimeout(120000);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "error connecting to tag");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runloop. Do NOT invoke directly. Use start() instead.
|
||||
*/
|
||||
public void run() {
|
||||
boolean connected = isConnected();
|
||||
|
||||
while (true) {
|
||||
boolean newConnected = isConnected();
|
||||
if (newConnected != connected) {
|
||||
connected = newConnected;
|
||||
Log.i(TAG, "tag " + (connected ? "connected" : "disconnected"));
|
||||
|
||||
if (connected && !isRunning) {
|
||||
onCardConnected();
|
||||
} else {
|
||||
onCardDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
SystemClock.sleep(loopSleepMS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacts on card connected by calling the callback of the registered listener.
|
||||
*/
|
||||
private void onCardConnected() {
|
||||
isRunning = true;
|
||||
|
||||
if (cardListener != null) {
|
||||
cardListener.onConnected(new NFCCardChannel(isoDep));
|
||||
}
|
||||
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacts on card disconnected by calling the callback of the registered listener.
|
||||
*/
|
||||
private void onCardDisconnected() {
|
||||
isRunning = false;
|
||||
isoDep = null;
|
||||
if (cardListener != null) {
|
||||
cardListener.onDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the card listener.
|
||||
*
|
||||
* @param listener the new listener
|
||||
*/
|
||||
public void setCardListener(CardListener listener) {
|
||||
cardListener = listener;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user