IOException con emulación de tarjeta basada en host

He estado usando HCE y he estado enfrentando una IOException en

 isoDep.connect(); 

En un dispositivo específico de lector de Android cr100 simcent .

HCE funciona perfectamente cuando habilito el modo de lectura en NFC con las siguientes banderas.

 public static int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK; 

Pero con eso no soy capaz de leer etiquetas NDEF. Aunque el mismo código funciona perfectamente bien en Nexus 7 (2012) Tablet.

Se adjunta el código completo

CardReaderFragment

 public class CardReaderFragment extends Fragment implements LoyaltyCardReader.AccountCallback { public static final String TAG = "CardReaderFragment"; // Recommend NfcAdapter flags for reading from other Android devices. Indicates that this // activity is interested in NFC-A devices (including other Android devices), and that the // system should not check for the presence of NDEF-formatted data (eg Android Beam). public static int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK; public LoyaltyCardReader mLoyaltyCardReader; private TextView mAccountField; /** Called when sample is created. Displays generic UI with welcome text. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View v = inflater.inflate(R.layout.main_fragment, container, false); if (v != null) { mAccountField = (TextView) v.findViewById(R.id.card_account_field); mAccountField.setText("Waiting..."); mLoyaltyCardReader = new LoyaltyCardReader(this); // Disable Android Beam and register our card reader callback enableReaderMode(); } return v; } @Override public void onPause() { super.onPause(); disableReaderMode(); } @Override public void onResume() { super.onResume(); enableReaderMode(); } private void enableReaderMode() { Log.i(TAG, "Enabling reader mode"); Activity activity = getActivity(); NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity); if (nfc != null) { nfc.enableReaderMode(activity, mLoyaltyCardReader, READER_FLAGS, null); } } private void disableReaderMode() { Log.i(TAG, "Disabling reader mode"); Activity activity = getActivity(); NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity); if (nfc != null) { nfc.disableReaderMode(activity); } } @Override public void onAccountReceived(final String account) { // This callback is run on a background thread, but updates to UI elements must be performed // on the UI thread. getActivity().runOnUiThread(new Runnable() { @Override public void run() { mAccountField.setText(account); } }); } } 

LoyaltyCardReader

 public class LoyaltyCardReader implements NfcAdapter.ReaderCallback { private static final String TAG = "LoyaltyCardReader"; // AID for our loyalty card service. private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222"; // ISO-DEP command HEADER for selecting an AID. // Format: [Class | Instruction | Parameter 1 | Parameter 2] private static final String SELECT_APDU_HEADER = "00A40400"; // "OK" status word sent in response to SELECT AID command (0x9000) private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00}; // Weak reference to prevent retain loop. mAccountCallback is responsible for exiting // foreground mode before it becomes invalid (eg during onPause() or onStop()). private WeakReference<AccountCallback> mAccountCallback; public interface AccountCallback { public void onAccountReceived(String account); } public LoyaltyCardReader(AccountCallback accountCallback) { mAccountCallback = new WeakReference<AccountCallback>(accountCallback); } /** * Callback when a new tag is discovered by the system. * <p> * <p>Communication with the card should take place here. * * @param tag Discovered tag */ @Override public void onTagDiscovered(Tag tag) { Log.i(TAG, "New tag discovered"); // Android's Host-based Card Emulation (HCE) feature implements the ISO-DEP (ISO 14443-4) // protocol. // // In order to communicate with a device using HCE, the discovered tag should be processed // using the IsoDep class. IsoDep isoDep = IsoDep.get(tag); if (isoDep != null) { try { // Connect to the remote NFC device isoDep.connect(); // Build SELECT AID command for our loyalty card service. // This command tells the remote device which service we wish to communicate with. Log.i(TAG, "Requesting remote AID: " + SAMPLE_LOYALTY_CARD_AID); byte[] command = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID); // Send command to remote device Log.i(TAG, "Sending: " + ByteArrayToHexString(command)); byte[] result = isoDep.transceive(command); // If AID is successfully selected, 0x9000 is returned as the status word (last 2 // bytes of the result) by convention. Everything before the status word is // optional payload, which is used here to hold the account number. int resultLength = result.length; byte[] statusWord = {result[resultLength - 2], result[resultLength - 1]}; byte[] payload = Arrays.copyOf(result, resultLength - 2); if (Arrays.equals(SELECT_OK_SW, statusWord)) { // The remote NFC device will immediately respond with its stored account number String accountNumber = new String(payload, "UTF-8"); Log.i(TAG, "Received: " + accountNumber); // Inform CardReaderFragment of received account number mAccountCallback.get().onAccountReceived(accountNumber); } } catch (IOException e) { Log.e(TAG, "Error communicating with card: " + e.toString()); } } else { Ndef ndef = Ndef.get(tag); if (ndef == null) { // NDEF is not supported by this Tag. Log.d("NFCCardTagNDEF", "even this is null"); // return; } NdefMessage ndefMessage = ndef.getCachedNdefMessage(); if (ndefMessage == null) { Log.d("NFCCardTagNDEF", "ndef message is null"); // return; } NdefRecord[] records = ndefMessage.getRecords(); String text = ndefRecordToString(records[0]); Log.d("NFCCardTagNFC", "old" + text); mAccountCallback.get().onAccountReceived(text); } } public String ndefRecordToString(NdefRecord record) { byte[] payload = record.getPayload(); return new String(payload); } /** * Build APDU for SELECT AID command. This command indicates which service a reader is * interested in communicating with. See ISO 7816-4. * * @param aid Application ID (AID) to select * @return APDU for SELECT AID command */ public static byte[] BuildSelectApdu(String aid) { // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA] return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid); } /** * Utility class to convert a byte array to a hexadecimal string. * * @param bytes Bytes to convert * @return String, containing hexadecimal representation. */ public static String ByteArrayToHexString(byte[] bytes) { final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] hexChars = new char[bytes.length * 2]; int v; for (int j = 0; j < bytes.length; j++) { v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Utility class to convert a hexadecimal string to a byte string. * <p> * <p>Behavior with input strings containing non-hexadecimal characters is undefined. * * @param s String containing hexadecimal characters to convert * @return Byte array generated from input */ public static byte[] HexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } } 

Cualquier ayuda sería apreciada

Normalmente, no hay mucho que usted podría hacer sobre este error. Una IOException (o la IOException TagLostException más específica) en IsoDep.connect() indica que el teléfono de lectura no pudo iniciar la comunicación con el dispositivo HCE. Esto suele deberse a problemas de conectividad causados ​​por un acoplamiento incorrecto (por ejemplo, dimensiones de la antena mal ajustadas, diseño de antena pobre, antenas parcialmente cubiertas por la batería del dispositivo sin diseño adecuado de la antena, etc.) o tiempos largos de encendido No con HCE). Por lo general, este error no es causado por problemas lógicos con el protocolo de comunicación (que posiblemente puede arreglar a través de software), sino por problemas con las características físicas de los dispositivos (que normalmente requieren modificaciones de hardware).

Desafortunadamente, no hay mucho que puedas hacer al respecto. Posibles soluciones podrían ser:

  1. Trate de colocar mejor los dos teléfonos juntos (para que la antena del dispositivo lector y la antena del dispositivo HCE) se alineen entre sí y que las partes metálicas de la caja del dispositivo o la batería del dispositivo no cubran la antena del otro dispositivo. En el caso de tamaños de antena significativamente diferentes, trate de alinear los otros bordes de las antenas entre sí de modo que el más pequeño esté dentro del más grande.

  2. En algunos dispositivos (!) Puede ayudar a aumentar el tiempo de espera transceive antes de llamar a connect() :

     isoDep.setTimeout(10000); 

    Sin embargo, parece que este tiempo de espera no tiene ningún efecto antes de conectar se llamó en la mayoría de los dispositivos.

  3. Existen "impulsores" de antena para NFC que detune la frecuencia de resonancia HF para obtener una mejor coincidencia, que mitigan los problemas de acoplamiento para adaptar la forma de la antena alrededor de las baterías o que reemplazan la antena original por completo. No tengo mucha experiencia con esto y no puedo citar ninguna fuente para tales impulsores.

EDITAR

Después de volver a leer su pregunta, parece que la comunicación entre el cr100 y el dispositivo HCE funciona si se establece el indicador NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK en el dispositivo lector. Si ese es el caso, la IOException podría de hecho ser causada por un error lógico. En ese caso, podría ser que el cr100 tiene una implementación de descubrimiento NFC que se rompe si una tarjeta ISO-DEP (por ejemplo, el dispositivo HCE) no implementa la especificación de la etiqueta NDEF. Si bien consideraría esto altamente improbable, fácilmente podría probar esto implementando la aplicación de Etiqueta Tipo 4 en su aplicación HCE. Vea mi respuesta aquí y el código fuente aquí para un ejemplo de cómo hacer esto.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.