2
votes

I am new to Android, working on Near Field Communication for reading data from NFC tags. Neither I have NFC supported Android mobile nor NFC tags to test the application I created.

I found the below two posts which says about faking NFC tag scans by triggering an Intent.

  1. Possibility for Fake NFC(Near Field Communication) Launch

  2. Need To Fake an NFC Tag Being Scanned In Android

I changed my code according to the first post, where on click of a button I am triggering the required Intent in the 1st activity. Whereas I have created one more activity capable of handling that same intent. The reading of NFC tag and handling data is based on a button click on the 2nd activity.

The problem is: Whenever I am triggering the fake NFC tag scan intent from the 1st activity, it is throwing an error "No Activity found to handle Intent { act=android.nfc.action.NDEF_DISCOVERED (has extras) }".

The Manifest file goes like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.expensemanager.saubhattacharya">

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

<uses-feature android:name="android.hardware.nfc" android:required="false" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/icon1"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".Set_Monthly_Target"
        android:label="@string/title_activity_set__monthly__target"
        android:parentActivityName=".MainActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.expensemanager.saubhattacharya.MainActivity" />
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain"/>
            <data android:mimeType="image/*" />
        </intent-filter>
    </activity>
    <activity
        android:name=".Add_Daily_Expense"
        android:label="@string/add_daily_expense_activity"
        android:parentActivityName=".MainActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.expensemanager.saubhattacharya.MainActivity" />
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain"/>
            <data android:mimeType="image/*" />
        </intent-filter>
    </activity>
</application>

</manifest>

The intent trigger code snippet from the 1st activity is below:

public void scan_tag (View view)
{
    final Intent intent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, "Custom Messages");
    startActivity(intent);
}

The code snippet from the 2nd activity, which handles the above trigger is below:

public class Add_Daily_Expense extends AppCompatActivity {

Button read_data;
TextView show_data;
Tag detected_tag;
NfcAdapter nfcAdapter;
IntentFilter[] intentFilters;
public static final String MIME_TEXT_PLAIN = "text/plain";
public static final String MIME_IMAGE_ALL = "image/*";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add__daily__expense);
    final PackageManager pm = this.getPackageManager();
    show_data = (TextView) findViewById(R.id.show_data);
    nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
    read_data = (Button) findViewById(R.id.read_nfc);
    read_data.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC)) {
                try {
                    AlertDialog.Builder builder = new AlertDialog.Builder(Add_Daily_Expense.this);
                    builder.setMessage("NFC feature is not available on this device!")
                            .setCancelable(false)
                            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int id) {
                                    dialog.cancel();
                                }
                            });
                    AlertDialog alert = builder.create();
                    alert.show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                Toast.makeText(getApplicationContext(), "NFC feature is available on this device!", Toast.LENGTH_SHORT).show();
                HandleIntent(getIntent());
            }
        }
    });
}

public void HandleIntent(Intent intent)
{
    String action = intent.getAction();
    if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action))
    {
        detected_tag = getIntent().getParcelableExtra(nfcAdapter.EXTRA_TAG);
        NDefReaderTask NDefReader = new NDefReaderTask();
        NDefReader.execute();
    }
}

public void onResume()
{
    super.onResume();
    if(nfcAdapter != null)
    setupForeGroundDispatch(this, nfcAdapter);
}

public void onPause()
{
    super.onPause();
    if(nfcAdapter != null)
    stopForeGroundDispatch(this, nfcAdapter);
}

public void onNewIntent(Intent intent)
{
    HandleIntent(intent);
}

public void setupForeGroundDispatch (final Activity activity, NfcAdapter nfcAdapter)
{
    Intent new_intent = new Intent(getApplicationContext(),Add_Daily_Expense.class);
    new_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

    PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),0,new_intent,0);
    intentFilters = new IntentFilter[1];
    String[][] techList = new String[][]{};

    intentFilters[0] = new IntentFilter();
    intentFilters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intentFilters[0].addCategory(Intent.CATEGORY_DEFAULT);
    try {
        intentFilters[0].addDataType(MIME_TEXT_PLAIN);
        intentFilters[0].addDataType(MIME_IMAGE_ALL);
    }
    catch(IntentFilter.MalformedMimeTypeException me)
    {
        me.printStackTrace();
    }

    nfcAdapter.enableForegroundDispatch(activity, pendingIntent, intentFilters, techList);
}

public void stopForeGroundDispatch (final Activity activity, NfcAdapter nfcAdapter)
{
    nfcAdapter.disableForegroundDispatch(activity);
}

public class NDefReaderTask extends AsyncTask <Tag, Void, String>
{
    @Override
    protected String doInBackground(Tag... params)
    {
        try
        {
            detected_tag = params[0];
            Ndef ndef = Ndef.get(detected_tag);
            ndef.connect();
            if(ndef != null)
            {
                NdefMessage ndefMessage = ndef.getCachedNdefMessage();
                NdefRecord[] records = ndefMessage.getRecords();
                for(NdefRecord ndefRecord : records)
                {
                    if((ndefRecord.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) || (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN))
                    {
                        byte[] payload = ndefRecord.getPayload();
                        String encoding1 = "UTF-8";
                        String encoding2 = "UTF-16";
                        String textEncoding = ((payload[0] & 128) == 0) ? encoding1 : encoding2;
                        int languageCodeLength = payload[0] & 0063;
                        return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
                    }
                }
            }
            ndef.close();
        }
        catch (UnsupportedEncodingException UE)
        {
            UE.printStackTrace();
        }
        catch (IOException IE)
        {
            IE.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPreExecute()
    {

    }

    protected void onPostExecute(String result)
    {
        if(result != null)
        {
            show_data.setText(result);
        }
    }
}
}

My question is: What is wrong I am doing here? Is there any other way to test my app by faking the NFC tag scan?

1

1 Answers

2
votes

You specify a MIME type filter for the NDEF_DISCOVERED intent filter:

<data android:mimeType="text/plain"/>
<data android:mimeType="image/*" />

Consequently, the fake NFC intent needs to contain one of these MIME types to match the intent filter. You can add the type information to the intent using the setType() method:

public void scan_tag (View view) {
    final Intent intent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intent.setType("text/plain");
    intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, ...);
    startActivity(intent);
}

Also note that the above code won't add a tag handle to the NFC intent. Hence, you can't obtain a Tag object with

detected_tag = getIntent().getParcelableExtra(nfcAdapter.EXTRA_TAG);

Consequently, you also can't obtain an instance of the Ndef connection class using

Ndef ndef = Ndef.get(detected_tag);

You might want to look into the following questions/answers regarding mock tag objects:

Finally, be aware that there are several other issues in your code.