Wednesday, 9 October 2013


Filtering for NFC Intents:

To start your application when an NFC tag that you want to handle is scanned, your application can filter for one, two, or all three of the NFC intents in the Android manifest.
Usually you want to filter for the ACTION_NDEF_DISCOVERED intent for the most control of when your application starts.But ACTION_TECH_DISCOVERED intent is a fallback for ACTION_NDEF_DISCOVERED when no applications filter for ACTION_NDEF_DISCOVERED or for when the payload is not NDEF. Filtering for ACTION_TAG_DISCOVERED is usually too general of a category to filter on. Many applications will filter for ACTION_NDEF_DISCOVERED or ACTION_TECH_DISCOVERED before ACTION_TAG_DISCOVERED, so your application has a low probability of starting. ACTION_TAG_DISCOVERED is only available as a last resort for applications to filter for in the cases where no other applications are installed to handle the  ACTION_NDEF_DISCOVERED or ACTION_TECH_DISCOVERED intent.
Because NFC tag deployments vary and are many times not under your control, this is not always possible, which is why you can fallback to the other two intents when necessary. When you have control over the types of tags and data written, it is recommended that you use NDEF to format your tags. The following sections describe how to filter for each type of intent.

ACTION_NDEF_DISCOVERED:

To filter for ACTION_NDEF_DISCOVERED intents, declare the intent filter along with the type of data that you want to filter for. The following example filters for ACTION_NDEF_DISCOVERED intents with a MIME type of text/plain:
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

The following example filters for a URI in the form of  http://shaikhhamadali.blogspot.com/p/home.html.


<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
   <data android:scheme="http"
              android:host="shaikhhamadali.blogspot.com"
              android:pathPrefix="/p/home.html" />
</intent-filter>

ACTION_TECH_DISCOVERED:

If your activity filters for the ACTION_TECH_DISCOVERED intent, you must create an XML resource file that specifies the technologies that your activity supports within a tech-list set. Your activity is considered a match if a tech-list set is a subset of the technologies that are supported by the tag, which you can obtain by calling getTechList().
For example, if the tag that is scanned supports MifareClassic, NdefFormatable, and NfcA, your tech-list set must specify all three, two, or one of the technologies (and nothing else) in order for your activity to be matched.
so We must specify the technology we are interested in. For this purpose, we create a subfolder called xml in the res folder. In this folder we create the file nfc_filter.xml, in which we specify the technologies.

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
        <!-- class name -->
    </tech-list>
</resources>

<!-- other tags for NFC
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.NfcF</tech>
        <tech>android.nfc.tech.NfcV</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>
-->


Basic Application on This NDEF:

OnLaunch
Near Field Communication-NFC


On Tag Read
Near Field Communication-NFC


Create new Android Project
Project Name: NFC
//tested from 2.3.3 to current android sdk 
Build Target: Android 2.3.3   //or greater than that
Application Name: NFC
Package Name: com.shaikhhamadali.blogspot.nfc
Create layout file: activity_nfcdetails

1.create layout: 


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NFCDetails" >

   <TextView
       android:id="@+id/tV_ReadNFC"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_centerInParent="true"
       android:text="@string/attachNFCToRead"
       android:textSize="30sp" />

</RelativeLayout>

2.code of activity:

package com.shaikhhamadali.blogspot.nfc;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

/**
 * Activity for reading data from an NDEF Tag.
 * 
 * @author Hamad Shaikh 
 *
 */
public class NFCDetails extends Activity {

 public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
 public static final String TAG = "NfcTut";

 private TextView tV_ReadNFC;
 private NfcAdapter nfcAdapt;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_nfcdetails);
  //initialize control
  tV_ReadNFC = (TextView) findViewById(R.id.tV_ReadNFC);
  //initialise nfc adapter 
  nfcAdapt = NfcAdapter.getDefaultAdapter(this);
  //check is NFC adapter initialized null when device doesn't support NFC
  if (nfcAdapt == null) {
   // device deosn't support nfc
   Toast.makeText(this, "your device doesn't support NFC.", Toast.LENGTH_SHORT).show();
   finish();
   return;
  }
  //check is NFC adapter feature enabled
  if (!nfcAdapt.isEnabled()) {
   tV_ReadNFC.setText("NFC is disabled.");
  } else {
   tV_ReadNFC.setText(R.string.attachNFCToRead);
  }
  handleIntent(getIntent());
 }
 
 @Override
 protected void onResume() {
  super.onResume();
  /*
   * It's important, that the activity is in the foreground (resumed). Otherwise
   * an IllegalStateException is thrown.
   */
  requestForegroundDispatch(this, nfcAdapt);
 }
 
 @Override
 protected void onPause() {
  
  //Call this before onPause, to avoid an IllegalArgumentException.
  stopForegroundDispatch(this, nfcAdapt);
  super.onPause();
 }
 
 @Override
 protected void onNewIntent(Intent intent) {
  /*
   * This method gets called, when a new Intent gets associated with the current activity instance.
   * Instead of creating a new activity, onNewIntent will be called.
   * In our case this method gets called, when the user attaches a Tag to the device.
   */
  handleIntent(intent);
 }
 
 private void handleIntent(Intent intent) {
  //get action from intent
  String action = intent.getAction();
  //is action matches the NDEF_DISCOVERED
  if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
   //what is the mime type
   String type = intent.getType();
   //is text plain or not
   if (MIMETYPE_TEXT_PLAIN.equals(type)) {
    //create tag instance and retrieve extended data from intent
    Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    //execute background task 
    new NdefReaderBgTask().execute(tag);
    
   } else {
    Log.d(TAG, "mime type is not text/plain: " + type);
   }
  }
  //is action matches the ACTION_TECH_DISCOVERED
  else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
   
   // In case we would still use the Tech Discovered Intent
   Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
   //get the available technologies 
   String[] techList = tag.getTechList();
   //get class name
   String searchedTech = Ndef.class.getName();
   
   for (String tech : techList) {
    //tag matched then execute background task
    if (searchedTech.equals(tech)) {
     new NdefReaderBgTask().execute(tag);
     break;
    }
   }
  }
 }
 
 /**
  * @param act The corresponding {@link Activity} requesting the foreground dispatch.
  * @param adp The {@link NfcAdapter} used for the foreground dispatch.
  */
 public static void requestForegroundDispatch(final Activity act, NfcAdapter adp) {
  //create instance of intent
  final Intent intent = new Intent(act.getApplicationContext(), act.getClass());
  //set flags on top
  intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
  //crate instance of pending intent
  final PendingIntent pendingIntent = PendingIntent.getActivity(act.getApplicationContext(), 0, intent, 0);
  //create intent filters array
  IntentFilter[] filters = new IntentFilter[1];
  //create 2D array of techlist String
  String[][] techList = new String[][]{};

  // Note: This is the same filter as in our manifest.
  filters[0] = new IntentFilter();
  filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
  filters[0].addCategory(Intent.CATEGORY_DEFAULT);
  try {
   //add data type
   filters[0].addDataType(MIMETYPE_TEXT_PLAIN);
  } catch (MalformedMimeTypeException e) {
   //throw exception on different mime type
   throw new RuntimeException("Check your mime type.");
  }
  //enable foreground dispatch to current activity
  adp.enableForegroundDispatch(act, pendingIntent, filters, techList);
 }

 /**
  * @param act The corresponding {@link BaseActivity} requesting to stop the foreground dispatch.
  * @param adp The {@link NfcAdapter} used for the foreground dispatch.
  */
 public static void stopForegroundDispatch(final Activity act, NfcAdapter adp) {
  adp.disableForegroundDispatch(act);
 }
 
 /**
  * Background task for reading the data. Do not block the UI thread while reading.
  * @author Hamad Shaikh
  *
  */
 private class NdefReaderBgTask extends AsyncTask<Tag, Void, String> {

  @Override
  protected String doInBackground(Tag... params) {
   Tag tag = params[0];
   
   Ndef ndef = Ndef.get(tag);
   if (ndef == null) {
    // when NDEF is not supported by this Tag. 
    return null;
   }
   //Get the NdefMessage that was read from the tag at discovery time. 
   NdefMessage ndefMessage = ndef.getCachedNdefMessage();
   //Get the NDEF Records inside this NDEF Message.
   NdefRecord[] records = ndefMessage.getRecords();
   for (NdefRecord ndefRecord : records) {
    if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
     try {
      return readNDEFRecordText(ndefRecord);
     } catch (UnsupportedEncodingException e) {
      Log.e(TAG, "Unsupported Encoding", e);
     }
    }
   }

   return null;
  }
  
  private String readNDEFRecordText(NdefRecord record) throws UnsupportedEncodingException {
   /*
    * See NFC forum specification for "Text Record Type Definition" at 3.2.1 
    *
    * http://www.nfc-forum.org/specs/
    * 
    * bit_7 defines encoding
    * bit_6 reserved for future use, must be 0
    * bit_5..0 length of IANA language code
    */
   // get record pay load variable length
   byte[] payload = record.getPayload();

   // Get the Text Encoding
   String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";

   // Get the Language Code
   int languageCodeLength = payload[0] & 0063;
   
   // String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
   // e.g. "en"
   
   // Get the Text
   return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
  }
  
  @Override
  protected void onPostExecute(String result) {
   if (result != null) {
    tV_ReadNFC.setText("Content in your TAG: " + result);
   }
  }
 }
}

3. Manifest File:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.shaikhhamadali.blogspot.nfc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="17" />

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

    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.shaikhhamadali.blogspot.nfc.NFCDetails"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />

                <category android:name="android.intent.category.DEFAULT" />

                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>
    </application>

</manifest>

4. note that:

  • you can use this in any way you want.
  • you can read other mime types of nfc too.
  • about NFC.

5. conclusion:

  • Some information about how to Read NFC Tags
  • Know how to basic of NFC in my previous post.
  • know how to use AsyncTask to avoid UI blocking.
  • know the usage of intent filters,permissions and uses-feature and tech-list.
  • know what are NFC Intents.

6. About the post:

  • The code seems to explain itself due to comments, but if you have any questions you can freely ask too!
  •  Don’t mind to write a comment whatever you like to ask, to know,to suggest or recommend.
  •  Hope you enjoy it!
7. Source Code:
        you can download the source code here

Cheers,
Hamad Ali Shaikh