Introducing NFCAsyncTask


Please note that this class was renamed to ScanAsyncTask from version 1.0.5 and onwards.
Please note that this class was removed from version 1.0.6 onwards. Consider using the TapProcessor class instead.

We received feedback that developers like to have more flexibility on when to initiate the NFC transaction between the device and the payment card. Well, we listen and NFCAsyncTask was introduced to address this very point. Prior to it, the only way to leverage the Triangle APIs was to derive from ReaderActivity which came short in scenarios with pre-existing Activity implementations or Fragments.

Developers often find themselves balancing between control and ease of use. ReaderActivity and NFCAsyncTask are typical examples of this. NFCAsyncTask gives you full control over when and where to transmit information via NFC, but it comes with the added cost of setting up the NFC sensor yourself. ReaderActivity on the other hand takes care of all the setup for you, but it forces you to extend it thereby taking some control away from you.

While setting up the NFC adapter correctly may seem like a daunting task at first, you'll soon realize it is easier than it seems. In this post, I'll show you how to create an Activity that:

  • Uses the Android's foreground dispatch system to communicate with NFC tags only when the activity is in the foreground
  • Pass any obtained NFC tags to the Triangle APIs to extract card information on a background thread
  • Warn the user if the device's NFC sensor is turned off.

Setting Up the Permissions

Before we do anything else, let's crack open the Android manifest file and ask for NFC permissions, so that our activity can interact with the NFC sensor.

<uses-permission android:name="android.permission.NFC" />

Android's Foreground Dispatch System

The foreground dispatch system enables you to handle NFC related intents while your activity is in the foreground, even if there are other activities in the OS wanting to handle the same intent in question. This is important for NFC scenarios as you are guaranteed the chance to communicate with the tag as long as the user is in your activity.

To use the dispatch system, we first need to get a hold of the NfcAdapterof the device.

// Grab a hold of the nfc sensor
this.nfcAdapter = NfcAdapter.getDefaultAdapter(this);

Once we have the NfcAdapter, we need to notify the adapter that we'd like to receive an Intent when a new NFC tag is discovered. The following code does just that.

// We'd like to listen to all incoming NFC tags that support the IsoDep interface
nfcAdapter.enableForegroundDispatch(this,
        PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0),
        new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED) },
        new String[][] { new String[] { IsoDep.class.getName() }});

It's a nasty one, isn't it? Let's dissect it parameter by parameter.

  1. This parameter is just a reference to the Activity that wants to receive the Intent.
  2. This parameter is a PendingIntent that the NfcAdapter will use when notifying your activity that a new NFC tag is discovered. PendingIntents are a mechanism that allow another application or entity to execute some code as if it was your own activity (with its set of permissions and accesses). With the second parameter, we basically enable the NfcAdapter to send an Intent targetted to our activity when a new NFC tag is discovered.
  3. This parameter is a filter for the type of Intent your activity is interested in. We choose NfcAdapter.ACTION_TECH_DISCOVERED to receive notifications when tags with specific technologies are found. There are various types of NFC tags, but as the next parameter shows, we are only interested in a limited set.
  4. Finally, this parameter filters the types of tags we are interested in. Payment cards that adhere to the EMV specifications must be of the IsoDep type. As such we limit the notifications our activity receives by setting this parameter to IsoDep.

With this setup, the activity is ready to receive NFC tags of type IsoDep!

Handling the Intent

Now that our setup is complete, we need to handle incoming tags. The NfcAdapter will send an Intent to our activity when a new NFC tag is discovered, so we simply override the onNewIntent method to handle it. The NfcAdapter will further include a reference to the discovered tag in the Intent so that our activity can communicate with it.

@Override
protected void onNewIntent(Intent intent)
{
    super.onNewIntent(intent);

    // Is the intent for a new NFC tag discovery?
    if (intent != null && intent.getAction() == NfcAdapter.ACTION_TECH_DISCOVERED)
    {
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        IsoDep isoDep = IsoDep.get(tag);

        // Does the tag support the IsoDep interface?
        if (isoDep == null)
        {
            return;
        }
    }
}

Finally, we are getting into the interesting part. We have a candidate tag that could be a payment card and can now use the Triangle APIs to do the heavylifting for us and extract the card information. The NFCAsyncTask class will communicate with the tag in the background and provide the captured information on the UI thread once it's done extracting the information. Note that we pass in the IsoDep tag we obtained from the above to the NFCAsyncTask class.

new NFCAsyncTask()
{
    @Override
    protected void onPostExecute(PaymentCard paymentCard, Exception exception)
    {
        // Do something with the extracted information. Check the exception parameter
        // to check for any errors that may have occurred.
    }
}.execute(isoDep);

Is the Sensor On?

Wouldn't it be a shame if we do all the setup above just to see it go to waste because the NFC sensor is off? While the Android operating system does not allow us to turn on the sensor (at least without using reflection), it does allow us to check whether it is enabled. We can further directly point the user to the correct settings dialog where the sensor can be turned on.

// Ensure that the device's NFC sensor is on
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);

if (nfcAdapter != null && !nfcAdapter.isEnabled())
{
    // Alert the user that NFC is off
    new AlertDialog.Builder(this)
            .setTitle("NFC Sensor Turned Off")
            .setMessage("In order to use this application, the NFC sensor must be turned on. Do you wish to turn it on?")
            .setPositiveButton("Go to Settings", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialogInterface, int i)
                {
                    // Send the user to the settings page and hope they turn it on
                    if (android.os.Build.VERSION.SDK_INT >= 16)
                    {
                        startActivity(new Intent(android.provider.Settings.ACTION_NFC_SETTINGS));
                    }
                    else
                    {
                        startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS));
                    }
                }
            })
            .setNegativeButton("Do Nothing", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialogInterface, int i)
                {
                    // Do nothing
                }
            })
            .show();
}

Putting All the Pieces Together

By now, you should have an activity that extracts the card information on a background thread and instructs the user to turn on the NFC sensor in case it is off. For the sake of completeness, I'm providing the full source code of the activity class below. If you have any questions or comments, please feel free to drop us a line.

package io.triangle.reader.sample;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import io.triangle.reader.NFCAsyncTask;

/**
 * Extracts credit card information of an RFID enabled card using the NFC interface of the device. If the device
 * is not equipped with an NFC sensor, this activity will gracefully downgrade and do nothing. 
 */
public class ReaderActivity extends Activity
{
    /**
     * Adapter used to grab NFC information from the sensor.
     */
    NfcAdapter nfcAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // Grab a hold of the nfc sensor
        this.nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    }

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

        if (this.nfcAdapter != null)
        {
            this.ensureSensorIsOn();

            // We'd like to listen to all incoming NFC tags that support the IsoDep interface
            nfcAdapter.enableForegroundDispatch(this,
                    PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0),
                    new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED) },
                    new String[][] { new String[] { IsoDep.class.getName() }});
        }
    }

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

        if (this.nfcAdapter != null)
        {
            this.nfcAdapter.disableForegroundDispatch(this);
        }
    }

    @Override
    protected void onNewIntent(Intent intent)
    {
        super.onNewIntent(intent);

        // Is the intent for a new NFC tag discovery?
        if (intent != null && intent.getAction() == NfcAdapter.ACTION_TECH_DISCOVERED)
        {
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            IsoDep isoDep = IsoDep.get(tag);

            // Does the tag support the IsoDep interface?
            if (isoDep == null)
            {
                return;
            }
            
            // Use the NFCAsyncTask class to extract the card information on the background
            new NFCAsyncTask()
            {
                @Override
                protected void onPostExecute(PaymentCard paymentCard, Exception exception)
                {
                    // PaymentCard will contain the payment card's information. If an error occurred, the
                    // exception parameter will contain details about it.
                    // Note: this method will be called on the UI thread
                }
            }.execute(isoDep);
        }
    }

    private void ensureSensorIsOn()
    {
        if(!this.nfcAdapter.isEnabled())
        {
            // Alert the user that NFC is off
            new AlertDialog.Builder(this)
                .setTitle("NFC Sensor Turned Off")
                .setMessage("In order to use this application, the NFC sensor must be turned on. Do you wish to turn it on?")
                .setPositiveButton("Go to Settings", new DialogInterface.OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i)
                    {
                        // Send the user to the settings page and hope they turn it on
                        if (android.os.Build.VERSION.SDK_INT >= 16)
                        {
                            startActivity(new Intent(android.provider.Settings.ACTION_NFC_SETTINGS));
                        }
                        else
                        {
                            startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS));
                        }
                    }
                })
                .setNegativeButton("Do Nothing", new DialogInterface.OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i)
                    {
                        // Do nothing
                    }
                })
                .show();
        }
    }
}

Posts

  Share on Facebook   Tweet