I want to read NFC 4K card details, Its not reading the data, Here is the Code I use,
AndroidManifest.xml
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="#xml/nfc_tech_filter" />
</activity>
nfc_tech_filter.xml
<?xml version="1.0" encoding="utf-8"?>
<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>
MainActivity.java
public class MainActivity extends Activity implements NfcAdapter.ReaderCallback {
public static final String TAG = " --NFC-- " + MainActivity.class.getSimpleName();
private NfcAdapter nfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
showToast("No NFC found...");
} else {
showToast("NFC Enables Device...");
enableReaderMode();
}
}
#Override
public void onResume() {
super.onResume();
showToast("On Resume...");
enableReaderMode();
}
#Override
protected void onPause() {
super.onPause();
showToast("On Pause...");
disableReaderMode();
}
private void showToast(String message) {
Log.d(TAG, message);
try {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
showToast("Show Toast Exception : " + message);
}
}
private void enableReaderMode() {
showToast("Enabling reader mode...");
Activity activity = MainActivity.this;
nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (nfcAdapter != null) {
nfcAdapter.enableReaderMode(activity, this, Integer.MAX_VALUE, null);
}
}
private void disableReaderMode() {
showToast("Disabling reader mode...");
Activity activity = MainActivity.this;
nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (nfcAdapter != null)
nfcAdapter.disableReaderMode(activity);
}
#Override
public void onTagDiscovered(Tag tag) {
Log.d(TAG, "New tag discovered...");
}
#Override
protected void onNewIntent(Intent intent) {
Log.d(TAG, "New Intent...");
super.onNewIntent(intent);
}
}
Your are not handling your intent data in onNewIntent(). onNewIntent() will be call whenever you scan your NFC card.
Like below example :
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "New Intent...");
handleIntent(intent);
}
private void handleIntent(Intent intent) {
val bundle = intent.extras
if (bundle != null) {
Log.e(TAG,intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
}
}
Edited
You need to call this method from onResume() after adding adapter.
private fun setupForegroundDispatch(activity : Activity, adapter
: NfcAdapter) {
val intent = Intent(activity.applicationContext,
activity.javaClass)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
val pendingIntent =
PendingIntent.getActivity(activity.applicationContext, 0,
intent, 0)
// val filters = arrayOfNulls<IntentFilter>(1)
// val techList = arrayOf<Array<String>>()
val ndef = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
val filters = arrayOf(ndef)
// val techList = arrayOf<Array<String>>()
val techList = arrayOf(arrayOf(NfcV::class.java.name))
// Notice that this is the same filter as in our manifest.
filters[0] = IntentFilter()
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED)
filters[0].addCategory(Intent.CATEGORY_DEFAULT)
try {
filters[0].addDataType(MIME_TEXT_PLAIN)
}
catch (e : IntentFilter.MalformedMimeTypeException) {
throw RuntimeException("Check your mime type.")
}
adapter.enableForegroundDispatch(activity, pendingIntent,
filters, techList)
}
Like below :
private void enableReaderMode() {
showToast("Enabling reader mode...");
Activity activity = MainActivity.this;
nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (nfcAdapter != null) {
nfcAdapter.enableReaderMode(activity, this,
Integer.MAX_VALUE, null);
setupForegroundDispatch(this,nfcAdapter);
}
}
Follow this tutorial for more info
As you are using enableReaderMode you don't need the onNewIntent method.
Also the intent-filter in the manifest and nfc_tech_filter.xml might not be required (They are not need if you don't want your app to be started by an NFC Tag if not running, then you would handle the NFC Intent in onCreate)
What you have not done is setup enableReaderMode correctly.
#Override
protected void onResume() {
super.onResume();
if(nfcAdapter!= null && nfcAdapter.isEnabled) {
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
nfcAdapter.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);
}
}
Turning off NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS is optional if you want to generate your own notification of reading instead of the OS doing it.
Note that the newer NFC API of enableReaderMode gives you more control that the older enableForegroundDispatch and is much more reliable than enableForegroundDispatch when writing to NFC Tags and it does not have some of the Negative side effects of enableForegroundDispatch, so I would recommend to always use enableReaderMode unless you want to support really old API's
Also note that using Mifare 4K cards is not recommended as well as they are a proprietary format and not supported by all Android Hardware (not matter which API you use)
Related
In my Xamarin Forms android app I receive and read a nfc tag. The below code works fine when the app is opened and focused.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity,
NfcAdapter.IOnNdefPushCompleteCallback, NfcAdapter.ICreateNdefMessageCallback
{
private NfcAdapter _nfcAdapter;
const string _appModePrefix = "#AppMode";
const string _package = "com.companyname.nfc";
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
_nfcAdapter = NfcAdapter.GetDefaultAdapter(this);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
protected override void OnResume()
{
base.OnResume();
if (_nfcAdapter != null && _nfcAdapter.IsEnabled && _nfcAdapter.IsNdefPushEnabled)
{
var intent = new Intent(this, GetType()).AddFlags(ActivityFlags.SingleTop);
_nfcAdapter.EnableForegroundDispatch
(
this,
PendingIntent.GetActivity(this, 0, intent, 0),
new[] { new IntentFilter(NfcAdapter.ActionNdefDiscovered) },
new String[][] {
new string[] {
NFCTechs.Ndef
},
new string[] {
NFCTechs.MifareClassic,
},
}
);
_nfcAdapter.SetOnNdefPushCompleteCallback(this, this);
_nfcAdapter.SetNdefPushMessageCallback(this, this);
}
}
public void OnNdefPushComplete(NfcEvent e)
{
}
public NdefMessage CreateNdefMessage(NfcEvent e)
{
byte[] mimeBytes = System.Text.Encoding.ASCII.GetBytes(string.Format("application/{0}",_package));
byte[] id = new byte[] { 1, 3, 3, 7 };
string appMode = "1";
string appId = "826F3361-2E93-4378-A6B9-33D2B6087246";
byte[] payload = System.Text.Encoding.ASCII.GetBytes(string.Format("{0}:{1}|{2}", _appModePrefix, appMode, appId));
return new NdefMessage(new NdefRecord[] {
NdefRecord.CreateApplicationRecord(_package),
new NdefRecord(NdefRecord.TnfMimeMedia, mimeBytes, id, payload),
});
}
protected override void OnPause()
{
base.OnPause();
if (_nfcAdapter != null && _nfcAdapter.IsEnabled && _nfcAdapter.IsNdefPushEnabled)
_nfcAdapter.DisableForegroundDispatch(this);
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
if (intent.Action == NfcAdapter.ActionTechDiscovered || intent.Action == NfcAdapter.ActionTagDiscovered)
{
var rawMessages = intent.GetParcelableArrayExtra(NfcAdapter.ExtraNdefMessages);
if (rawMessages == null)
return;
//...
}
}
}
When the app is not run (or works in background) then the app is opened, as expected. I also get the onNewIntent event but in this case
intent.Action == "android.nfc.action.MAIN"
and
intent.GetParcelableArrayExtra(NfcAdapter.ExtraNdefMessages);
returns null. Is it possible to get the orginal intent (with Tech or Tag discovered action) at this point?
I'm note sure if that's the perfect solution for you.
What I do:
1.) Define the Activity with an Intent Filter on TECH_DISCOVERED
<activity
android:name=".activities.general.NFCActivity"
android:launchMode="singleInstance"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
</activity>
2.) Define the resource of the techs
<resources>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcBarcode</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
</resources>
3.) Work with the tag info
if (getIntent() != null) {
if (getIntent().getExtras() != null) {
if (getIntent().getExtras().containsKey(NfcAdapter.EXTRA_ID)) {
byte[] id_array = getIntent().getByteArrayExtra(NfcAdapter.EXTRA_ID);
}
}
}
Hope it helped :)
I've followed differents tutorials and read some fix related to that issue and still, nothing seems to work... Whenever I scan a NFC tag, it opens the default android app called Tag Viewer telling me "New tag collected" and with the body "Empty tag" and nothing happens within my app. I can't get any log printed...
Here is my manifest:
<activity
android:name=".activityv2.ActivityHome"
android:label="Home"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tag_filter" />
</activity>
Here is my Activity:
public class ActivityHome extends GenericActivity implements BackHandledFragment.BackHandlerInterface, OnFragmentListener {
private final String TAG = "ActivityHome";
public static final String MIME_TEXT_PLAIN = "text/plain";
#Bind(R.id.drawer_layout)
DrawerLayout mDrawerLayout;
#Bind(R.id.actionToolbar)
Toolbar toolbar;
#Bind(R.id.left_drawer_item)
LinearLayout mDrawerLinear;
#Bind(R.id.left_drawer_child)
ListView mDrawerListChild;
#Bind(R.id.profil_pic)
CircleImageView mProfilPic;
private NfcAdapter mNfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_v2_home);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(getResources().getString(R.string.app_name));
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// enable ActionBar app icon to behave as action to toggle nav drawer
handleIntent(getIntent());
}
private void handleIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
String type = intent.getType();
if (MIME_TEXT_PLAIN.equals(type)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
new NdefReaderTask().execute(tag);
} else {
Log.d(TAG, "Wrong mime type: " + type);
}
} 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);
String[] techList = tag.getTechList();
String searchedTech = Ndef.class.getName();
for (String tech : techList) {
if (searchedTech.equals(tech)) {
new NdefReaderTask().execute(tag);
break;
}
}
}
}
#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. For more information have a look
* at the documentation.
*
* In our case this method gets called, when the user attaches a Tag to the device.
*/
handleIntent(intent);
}
private class NdefReaderTask extends AsyncTask<Tag, Void, String> {
#Override
protected String doInBackground(Tag... params) {
Tag tag = params[0];
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
// NDEF is not supported by this Tag.
return null;
}
NdefMessage ndefMessage = ndef.getCachedNdefMessage();
NdefRecord[] records = ndefMessage.getRecords();
for (NdefRecord ndefRecord : records) {
if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
try {
return readText(ndefRecord);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unsupported Encoding", e);
}
}
}
return null;
}
private String readText(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
*/
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) {
Log.e(TAG, result);
}
}
}
#Override
protected void onPause() {
super.onPause();
if (mNfcAdapter != null)
stopForegroundDispatch(this, mNfcAdapter);
}
#Override
protected void onResume() {
super.onResume();
if (mNfcAdapter != null)
setupForegroundDispatch(this, mNfcAdapter);
}
#Override
protected void onDestroy() {
getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
super.onDestroy();
}
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) {
final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
IntentFilter[] filters = new IntentFilter[1];
String[][] techList = new String[][]{};
// Notice that 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 {
filters[0].addDataType(MIME_TEXT_PLAIN);
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("Check your mime type.");
}
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
}
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
adapter.disableForegroundDispatch(activity);
}
}
Here is the XML config to catch them all:
<?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>
<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>
And the message I get when I go near a tag and the default tag viewer opens:
04-20 12:45:26.825 799-2511/? I/ActivityManager: START u0 {flg=0x10008000 cmp=com.android.nfc/.NfcRootActivity (has extras)} from uid 1027 on display 0
04-20 12:45:26.874 1952-24878/? D/NativeNfcTag: Starting background presence check
04-20 12:45:26.913 1952-4642/? W/AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 4, track 16000 Hz, output 48000 Hz
04-20 12:45:26.917 799-24452/? I/ActivityManager: START u0 {act=android.nfc.action.TECH_DISCOVERED cmp=com.google.android.tag/com.android.apps.tag.TagViewer (has extras)} from uid 1027 on display 0
04-20 12:45:26.947 799-1287/? I/ActivityManager: Start proc 24893:com.google.android.tag/u0a18 for activity com.google.android.tag/com.android.apps.tag.TagViewer
I foudn the solution from #tatianag :
In first time I read a tutorial about nfc and I use some part of the code. And there was some code that I had not adapted to the advice you gave me.
This is the old part of my code
IntentFilter[] filters = new IntentFilter[1];
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
filters[0].addDataType(MIME_TEXT_PLAIN);
} catch (MalformedMimeTypeException e) {
Log.e("App","Wrong mime")
}
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
Wrong action, mime type. Many errors... I change for
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
adapter.enableForegroundDispatch(activity, pendingIntent, null, null);
And it works perfectly! I hope it can help someone like you helped me by your explanations.
Thank you again for your time!
Just would like to add that the PendingIntent needs to be mutable. As of SDK 31, this needs to be declared explicitly:
val mutabilityFlag = if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_MUTABLE else 0
val pendingIntent = PendingIntent.getActivity(context, 0, intent, mutabilityFlag)
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.
Possibility for Fake NFC(Near Field Communication) Launch
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?
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:
How to mock a Android NFC Tag object for unit testing
Is there a way to create an ACTION_NDEF_DISCOVERED intent from code
How to simulate the tag touch from other application
Finally, be aware that there are several other issues in your code.
I have this code and I always get Ndef ndef = Ndef.get(tag) == null. I tried to get it differently but I always get null right there, in the same place. Has someone some idea that what I'm doing wrong?
private ImageView imgSearch;
private NfcAdapter mAdapter;
private PendingIntent mPendingIntent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc);
}
#Override
protected void onResume() {
super.onResume();
mAdapter = NfcAdapter.getDefaultAdapter(this);
if (mAdapter == null) {
//nfc not support your device.
Toast.makeText(this, R.string.no_nfc_supported, Toast.LENGTH_SHORT).show();
} else {
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
}
mAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
#Override
protected void onPause() {
super.onPause();
if (mAdapter != null) {
mAdapter.disableForegroundDispatch(this);
}
}
#Override
protected void onNewIntent(Intent intent) {
getTagInfo(intent);
}
private void getTagInfo(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
NdefMessage ndefMesg = ndef.getCachedNdefMessage();
if (ndefMesg != null) {
Toast.makeText(this, ndefMesg.toString(), Toast.LENGTH_SHORT).show();
}
}
}
}
Inside the manifest I have:
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
and:
<activity android:name=".activities.NFCActivity"
android:theme="#style/AppTheme.NoActionBar">
<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>
You are using the foreground dispatch system to get notified about new tags
mAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
As you pass null as the intent filter and technology list arguments (last two arguments), your activity will be notified about just any tag while it is in the foreground. Consequently, in the method getTagInfo() (or actually onNewIntent()), you receive an ACTION_TAG_DISCOVERED intent.
private void getTagInfo(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// this part is executed, hence, you received an ACTION_TAG_DISCOVERED intent
}
}
This means that the code in getTagInfo() will be executed regardless of the tag technologies supported by the tag. Therefore, if the tag does not support the Ndef tag technology, Ndef.get(tag) will return null.
You can get the list of technologies supported by a tag using:
String[] techList = tag.getTechList();
If a tag does not support the Ndef tag technology, this could mean that the tag is not yet configured to carry an NDEF payload and must first be initialized ("formatted") using the NdefFormatable technology.
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
NdefMessage ndefMesg = ndef.getCachedNdefMessage();
if (ndefMesg != null) {
...
}
} else {
NdefFormatable ndefFormatable = NdefFormatable.get(tag);
if (ndefFormatable != null) {
// initialize tag with new NDEF message
try {
ndefFormatable.connect();
ndefFormatable.format(newNdefMessage);
} finally {
try {
ndefFormatable.close();
} catch (Exception e) {}
}
}
}
However, if the tag neither supports Ndef nor NdefFormatable, this means that the tag is not an NFC Forum tag and does not support carrying an NDEF payload at all.
I am newbie in developing android application. Currently I am trying to develop my own tag reader which can read MiFare Ultralight tag.
BUt I failed to read the tag as NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) always return false. Could somebody help me out?
NfcReader.java
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mText = (TextView) findViewById(R.id.text);
mText.setText("Scan a tag");
mAdapter = NfcAdapter.getDefaultAdapter();
// Create a generic PendingIntent that will be deliver to this activity. The NFC stack
// will fill in the intent with the details of the discovered tag before delivering to
// this activity.
mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// Setup an intent filter for all MIME based dispatches
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
mFilters = new IntentFilter[] {
ndef,
};
// Setup a tech list for all NfcF tags
mTechLists = new String[][] { new String[] { NfcF.class.getName() } };
Intent intent = getIntent();
getNdefMessages(intent);
}
public void getNdefMessages(Intent intent) {
// Parse the intent
NdefMessage[] msgs = null;
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
}
else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
msgs = new NdefMessage[] {msg};
}
}
else {
//Log.e(TAG, "Unknown intent " + intent);
finish();
}
}
#Override
public void onResume() {
super.onResume();
mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists);
}
#Override
public void onNewIntent(Intent intent) {
Log.i("Foreground dispatch", "Discovered tag with intent: " + intent);
getNdefMessages(intent);
}
#Override
public void onPause() {
super.onPause();
//mAdapter.disableForegroundDispatch(this);
throw new RuntimeException("onPause not implemented to fix build");
}
Manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nfc.reader"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.NFC" />
<uses-sdk android:minSdkVersion="10"/>
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name="NfcReader"
android:theme="#android:style/Theme.NoTitleBar">
<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.TECH_DISCOVERED"/>
<data android:mimeType="text/plain" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfctech" />
</activity>
</application>
</manifest>
When a tag with an NDEF message is detected, android looks for any Application with an Activity with intent-filter for action "android.nfc.action.NDEF_DISCOVERED". If two or more applications can handle this action, the one with the most precise filter receives the intent. When applications with the same filter are found, is the user who chooses one. So in your AndroidManifest.xml you have to use this action for detecting NDEF formatted tags.
If you want to read NDEF messages don't worry about which type of tag you are reading (Ultralight, Felica, Topaz,...). Android uses android.nfc.tech.Ndef for dispatch NDEF formatted tags. So for getting all NdefMessages when you activity is on foreground, you can use this snippet:
//Called in onCreate()
private void nfcConfig() {
mAdapter = NfcAdapter.getDefaultAdapter(this);
pendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
techListsArray = new String[][]{new String[]{
Ndef.class.getName()
}};
}
#Override
public void onResume() {
super.onResume();
mAdapter.enableForegroundDispatch(this, pendingIntent, null, techListsArray);
}
#Override
public void onPause() {
super.onPause();
mAdapter.disableForegroundDispatch(this);
}
#Override
public void onNewIntent(Intent intent) {
getNdefMessages(intent);
}
If you want to read an Ultralight tag ( or another technology), it should be because you need the raw data from the tag. It must not be executed from the main application thread. For example:
private void nfcConfig(){
...
//Mifare Ultralight filter
techListsArray = new String[][]{new String[]{
NfcA.class.getName(),
MifareUltralight.class.getName()
}};
...
}
#Override
public void onNewIntent(Intent intent) {
getRawData(intent);
}
private void getRawData(Intent intent){
final Tag tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareUltralight ulTag = MifareUltralight.getTag();
new ReadMFULTask(ulTag).execute();
}
private static class ReadMFULTask extends AsyncTask<Void, Void, Void> {
private Tag mTag;
public ReadMFULTask(Tag tag){
mTag = tag;
}
#Override
protected Void doInBackground(Void... arg0) {
MifareUltralight ulTag = MifareUltralight.get(mTag);
try {
ulTag.connect();
//Read all pages from an Ul tag
for(int i=0; i < 16; i=i+4){
byte[] data = ulTag.readPages(i);
String dataString = HexUtilities.getHexString(data);
Log.d("RAW DATA", dataString);
}
ulTag.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
In your code you are setting your intent filter based on ACTION_TECH_DISCOVERED, but then you try ndef.addDataType(/), which is the way you would go about setting up an intent filter for ACTION_NDEF_DISCOVERED. Instead you could simply do something like this:
IntentFilter ntech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
mFilters = new IntentFilter[]{ ntech };
make sure you have nfc_tech_list.xml set up properly:
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>