Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing sample app for this library #79

Open
AndroidCrypto opened this issue May 19, 2022 · 1 comment
Open

Missing sample app for this library #79

AndroidCrypto opened this issue May 19, 2022 · 1 comment

Comments

@AndroidCrypto
Copy link

What a pity that there is no complete example for running your very nice library on an Android device as your library makes working with EMV cards as easy as it could be - just a few lines of extra code and you get the data.

For that reason I decided to makes such a sample app - maybe it can help others with this app. The complete source code is on my GitHub repository and here with all relevant parts.

Complete sourcecode: Android-EMV-NFC-Paycard-Example

This sample program shows how to implement the library EMV-NFC-Paycard-Enrollment for reading the data on an EMC (CreditCard etc) with the NFC technology (contactless).

Source code: https://github.com/devnied/EMV-NFC-Paycard-Enrollment

The app uses the IsoDep class for communication with the NFC card and the enableReaderMode on the NfcAdapter for detecting a NFC tag; this mode is more reliable then the often used enableForegroundDispatch. see here for a more detailed explanation:
https://stackoverflow.com/questions/33633736/whats-the-difference-between-enablereadermode-and-enableforegrounddispatch

This app is getting just a small subset of all available data fields on an EMV card: typeName, aid(s), card number and expiration date of the card. The complete code is available here:

https://github.com/AndroidCrypto/Android-EMV-NFC-Paycard-Example

Don't forget to view the Logcat as the app gives a deep look to the commands and data that is exchanged between the Android device and the EMV card.

As this app does not use any intent filter the data grabbing will work only if the app is in the foreground when the EMV card is detected.

The app was tested on a real device with Android 8 (SDK 26) and Android 12 (SDK 31).

build.gradle (Module):

plugins {
    id 'com.android.application'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "de.androidcrypto.android_emv_nfc_paycard_example"
        minSdk 26
        targetSdk 32
        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_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.6.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'com.github.devnied.emvnfccard:library:3.0.1'
}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="de.androidcrypto.android_emv_nfc_paycard_example">
    <uses-permission android:name="android.permission.NFC" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <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.AndroidEMVNFCPaycardExample"
        tools:targetApi="31">
        <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>
    </application>
</manifest>

MainActivity.java:

package de.androidcrypto.android_emv_nfc_paycard_example;

import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.github.devnied.emvnfccard.enums.EmvCardScheme;
import com.github.devnied.emvnfccard.model.Application;
import com.github.devnied.emvnfccard.model.EmvCard;
import com.github.devnied.emvnfccard.parser.EmvTemplate;

import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback {
    /**
     * This sample program shows how to implement the library EMV-NFC-Paycard-Enrollment
     * for reading the data on an EMC (CreditCard etc) with the NFC technology (contactless).
     * Source code: https://github.com/devnied/EMV-NFC-Paycard-Enrollment
     *
     * The app uses the IsoDep class for communication with the NFC card and
     * the enableReaderMode on the NfcAdapter for detecting a NFC tag;
     * this mode is more reliable then the often used enableForegroundDispatch.
     * see here for a more detailed explanation:
     * https://stackoverflow.com/questions/33633736/whats-the-difference-between-enablereadermode-and-enableforegrounddispatch
     *
     * This app is getting just a small subset of all available data fields on an EMV card:
     * typeName, aid(s), card number and expiration date of the card.
     * The complete code is available here:
     * https://github.com/AndroidCrypto/Android-EMV-NFC-Paycard-Example
     *
     * Don't forget to view the Logcat as the app gives a deep look to the commands and data
     * that is exchanged between the Android device and the EMV card
     *
     * As this app does not use any intent filter the data grabbing will work only if the app is
     * in the foreground when the EMV card is detected.
     *
     * The app was tested on a real device with Android 8 (SDK 26) and Android 12 (SDK 31).
     */

    TextView nfcaContent;
    private NfcAdapter mNfcAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        nfcaContent = findViewById(R.id.tvNfcaContent);
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mNfcAdapter != null) {
            Bundle options = new Bundle();
            // Work around for some broken Nfc firmware implementations that poll the card too fast
            options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);

            // Enable ReaderMode for all types of card and disable platform sounds
            // the option NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK is NOT set
            // to get the data of the tag after reading
            mNfcAdapter.enableReaderMode(this,
                    this,
                    NfcAdapter.FLAG_READER_NFC_A |
                            NfcAdapter.FLAG_READER_NFC_B |
                            NfcAdapter.FLAG_READER_NFC_F |
                            NfcAdapter.FLAG_READER_NFC_V |
                            NfcAdapter.FLAG_READER_NFC_BARCODE |
                            NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
                    options);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mNfcAdapter != null)
            mNfcAdapter.disableReaderMode(this);
    }

    // This method is run in another thread when a card is discovered
    // !!!! This method cannot cannot direct interact with the UI Thread
    // Use `runOnUiThread` method to change the UI from this method
    @Override
    public void onTagDiscovered(Tag tag) {

        IsoDep isoDep = null;

        // Whole process is put into a big try-catch trying to catch the transceive's IOException
        try {
            isoDep = IsoDep.get(tag);
            if (isoDep != null) {
                ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(VibrationEffect.createOneShot(150, 10));
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //UI related things, not important for NFC
                    nfcaContent.setText("");
                }
            });
            isoDep.connect();
            byte[] response;
            String idContentString = "Content of ISO-DEP tag";

            PcscProvider provider = new PcscProvider();
            provider.setmTagCom(isoDep);

            EmvTemplate.Config config = EmvTemplate.Config()
                    .setContactLess(true)
                    .setReadAllAids(true)
                    .setReadTransactions(true)
                    .setRemoveDefaultParsers(false)
                    .setReadAt(true);

            EmvTemplate parser = EmvTemplate.Builder()
                    .setProvider(provider)
                    .setConfig(config)
                    .build();

            EmvCard card = parser.readEmvCard();
            String cardNumber = card.getCardNumber();
            Date expireDate = card.getExpireDate();
            LocalDate date = LocalDate.of(1999, 12, 31);
            if (expireDate != null) {
                date = expireDate.toInstant()
                        .atZone(ZoneId.systemDefault())
                        .toLocalDate();
            }
            EmvCardScheme cardGetType = card.getType();
            if (cardGetType != null) {
                String typeName = card.getType().getName();
                String[] typeAids = card.getType().getAid();
                idContentString = idContentString + "\n" + "typeName: " + typeName;
                for (int i = 0; i < typeAids.length; i++) {
                    idContentString = idContentString + "\n" + "aid " + i + " : " + typeAids[i];
                }
            }

            List<Application> applications = card.getApplications();
            idContentString = idContentString + "\n" + "cardNumber: " + prettyPrintCardNumber(cardNumber);
            idContentString = idContentString + "\n" + "expireDate: " + date;

            String finalIdContentString = idContentString;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //UI related things, not important for NFC
                    nfcaContent.setText(finalIdContentString);
                }
            });
            try {
                isoDep.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            //Trying to catch any ioexception that may be thrown
            e.printStackTrace();
        } catch (Exception e) {
            //Trying to catch any exception that may be thrown
            e.printStackTrace();
        }

    }

    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {
        super.onPointerCaptureChanged(hasCapture);
    }

    public static String prettyPrintCardNumber(String cardNumber) {
        if (cardNumber == null) return null;
        char delimiter = ' ';
        return cardNumber.replaceAll(".{4}(?!$)", "$0" + delimiter);
    }

    public static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }
}

PcscProvider.java:

package de.androidcrypto.android_emv_nfc_paycard_example;

import android.nfc.tech.IsoDep;
import android.util.Log;

import com.github.devnied.emvnfccard.enums.SwEnum;
import com.github.devnied.emvnfccard.exception.CommunicationException;
import com.github.devnied.emvnfccard.parser.IProvider;
import com.github.devnied.emvnfccard.utils.TlvUtil;

import java.io.IOException;

import fr.devnied.bitlib.BytesUtils;

public class PcscProvider implements IProvider {
    private static final String TAG = "Provider";
    private IsoDep mTagCom;

    public void setmTagCom(final IsoDep mTagCom) {
        this.mTagCom = mTagCom;
    }

    @Override
    public byte[] transceive(byte[] pCommand) throws CommunicationException {
        byte[] response = null;
        try {
            // send command to emv card
            mTagCom.getTag();
            //mTagCom.connect();
            if (mTagCom.isConnected()){
                response = mTagCom.transceive(pCommand);
            }
        } catch (IOException e) {
            throw new CommunicationException(e.getMessage());
        }
        Log.d(TAG, "resp: " + BytesUtils.bytesToString(response));
        try {
            Log.d(TAG, "resp: " + TlvUtil.prettyPrintAPDUResponse(response));
            SwEnum val = SwEnum.getSW(response);
            if (val != null) {
                Log.d(TAG, "resp: " + val.getDetail());
            }
        } catch (Exception e) {
        }
        return response;
    }

    @Override
    public byte[] getAt() {
        // return new byte[0]; // from Stackoverflow
        byte[] result;
        result = mTagCom.getHistoricalBytes(); // for tags using NFC-B
        if (result == null) {
            result = mTagCom.getHiLayerResponse(); // for tags using NFC-B
        }
        return result;
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="This app shows how to implement the EMV-NFC-Paycard-Enrollment library to read an EMV card via NFC."
            android:textAlignment="center"
            android:textSize="20sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tvInformation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="LAY your smartphone on the card and don't move the phone until the card data is displayed"
            android:textAlignment="center"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tvNfcaContent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="8dp"
            android:hint="the EMV card data comes here"
            android:textSize="18sp" />

    </LinearLayout>
</ScrollView>

Have fun with this library,
Greetings Michael

@cristianonescu
Copy link

@AndroidCrypto MAN! I LOVE YOU!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants