Testing an app by faking NFC tag scan
Asked Answered
C

1

2

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?

Corri answered 19/1, 2016 at 16:38 Comment(0)
O
2

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.

Outrageous answered 19/1, 2016 at 22:59 Comment(5)
@Michael-- Thanks a lot! I will go through the points mentioned by you and let you know the outcome. Yes, I assumed that there will be several in my code as I am new to this, that's why I want to do proper unit testing. :)Corri
@Michael-- If possible, can you please tell me roughly which all the issues I have in my code? Or my approach in this case was wrong? I am trying to do some Unit Testing meanwhile.Corri
I was able to fix the issue by keeping MIME type only as Text. I was able to capture in the intent. But since I was not able to get the NFC Adapter and Tag information, I was not able to test the entire code. Meanwhile, if it is possible please highlight what all the issues I have in the code?Corri
@SaumikBhattacharya One problem is, for instance, that you set detected_tag = params[0]; in your AsyncTask but that you never pass the tag handle as parameter to the task (instead you directly update detected_tag with it which you later overwrite with params[0] again). Another one is that you use getIntent() to obtain the tag handle in your HandleIntent method (instead of using the parameter intent).Outrageous
How does this fake an NFC scan - like OP requested?Kamala

© 2022 - 2024 — McMap. All rights reserved.