读取 NFC 标签得到空 UID
Reading NFC Tag gets null UID
我正在开发一个使用 NfcA
、MifareUltralight
和 Ndef
技术的 NFC reader 应用程序。当我尝试使用贴纸标签时它工作正常,我的意思是它可以正确读取序列号。当使用另一种类型的标签时,根据 NFC 工具,它似乎涵盖了相同的技术,但是 getByteArrayExtra
returns null
即使标签具有有效的序列号。
另一方面,我遇到问题的最后一类标签确实包含 NDEF 消息。
简而言之,这些标签的序列号用于识别经过入口设备的用户。
我对此很困惑,欢迎任何帮助。代码如下:
主要活动:
package org.bogdan.learning.gantopendemo;
import org.bogdan.learning.gantopendemo.util.SystemUiHider;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetManager;
import android.media.MediaPlayer;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareUltralight;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* @see SystemUiHider
*/
public class MainActivity extends Activity {
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
/**
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* If set, will toggle the system UI visibility upon interaction. Otherwise,
* will show the system UI visibility upon interaction.
*/
private static final boolean TOGGLE_ON_CLICK = true;
/**
* The flags to pass to {@link SystemUiHider#getInstance}.
*/
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
/**
* The instance of the {@link SystemUiHider} for this activity.
*/
private SystemUiHider mSystemUiHider;
// NFC Related variables go here, namely the adapter and helpers
public static final String MIME_TEXT_PLAIN = "text/plain";
public static final String TAG = "EventTAG";
private NfcAdapter mNfcAdapter; // Used for accessing the NFC Hardware
// SQLite Variables go here
private EventTagDataSource dataSource;
// wait interval before screen is redrawn
private int miliseconds = 6000;
// Sound related activities
private MediaPlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gant_open);
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.fullscreen_content);
/**
* Open the database connection
*/
dataSource = new EventTagDataSource(this);
try {
dataSource.open();
} catch (SQLException exp) {
Log.d("SQLExp", "SQL Exception ocurred", exp);
}
// return the default NFC adapter
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// is NFC Enabled or not=
if (mNfcAdapter == null) {
finish();
return;
}
// is the NFC Reader enabled?
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(this, "NFC Reader is not enabled. Please enable and try again", Toast.LENGTH_LONG).show();
}
// Set up an instance of SystemUiHider to control the system UI for
// this activity.
mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
// Cached values.
int mControlsHeight;
int mShortAnimTime;
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
// If the ViewPropertyAnimator API is available
// (Honeycomb MR2 and later), use it to animate the
// in-layout UI controls at the bottom of the
// screen.
if (mControlsHeight == 0) {
mControlsHeight = controlsView.getHeight();
}
if (mShortAnimTime == 0) {
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
controlsView.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
} else {
// If the ViewPropertyAnimator APIs aren't
// available, simply show or hide the in-layout UI
// controls.
controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
if (visible && AUTO_HIDE) {
// Schedule a hide().
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
}
});
// Set up the user interaction to manually show or hide the system UI.
contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TOGGLE_ON_CLICK) {
mSystemUiHider.toggle();
} else {
mSystemUiHider.show();
}
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
findViewById(R.id.dummy_button).setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
//Toast.makeText(getApplicationContext(), "Long Click Detected", Toast.LENGTH_LONG).show();
launchRingDialog(view);
return false;
}
});
handleIntent(getIntent());
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
mSystemUiHider.hide();
}
};
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
/**
* onResume - when application returns from standby it reopens database connection
* and sets up foreground dispatch
*/
@Override
protected void onResume() {
try {
dataSource.open();
} catch (SQLException exp) {
Log.d("SQLExp", "SQL Exception ocurred", exp);
}
super.onResume();
setupForegroundDispatch(this, mNfcAdapter);
}
/**
* onPause - closes database connection, stops foreground despatch and sets application in
* standby mode.
*/
@Override
protected void onPause() {
dataSource.close();
stopForegroundDispatch(this, mNfcAdapter);
super.onPause();
}
/**
* creates a new intent and calles handleIntent(intent) to deal with it.
*
* @param intent
*/
@Override
protected void onNewIntent(Intent intent) {
handleIntent(intent);
}
/**
*
*/
public String uidByteToHexString(byte[] uid) {
int iCounter, jCounter, in;
String[] hex = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
};
String out = "";
for (jCounter = 0; jCounter < uid.length; jCounter++) {
in = uid[jCounter] & 0xff;
iCounter = (in >> 4) & 0x0f;
out += hex[iCounter];
iCounter = in & 0x0f;
out += hex[iCounter];
}
return out;
}
/**
* Handles intent within the application (such as NFC Tag detected)
*
* @param intent
*/
private void handleIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
new NdefReaderTask().execute(tag);
}
/*else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
new NdefReaderTask().execute(tag);
}*/
}
/**
* Method: loadTagsFromFile
*
* @param - none
* @return - void
* @description - Loads a set of NFC Tags from a given CSV file into the SQLite Database
*/
public void loadTagsFromFile(String fileName) {
AssetManager manager = getApplicationContext().getAssets();
InputStream inStream = null;
try {
inStream = manager.open(fileName);
} catch (IOException exp) {
exp.printStackTrace();
}
// open a buffered reader and start reading from the inputStream
BufferedReader buffer = new BufferedReader(new InputStreamReader(inStream));
String line = ""; //current line in our CSV file
// open a transcation to start mass inserting data
dataSource.database.beginTransaction();
try {
while ((line = buffer.readLine()) != null) {
String[] columns = line.split(";");
if (columns.length != 4) {
Log.d("CSVParser", "Skipping malformed CSV format");
continue;
}
dataSource.createEventTag(columns[1].trim(), columns[2].trim(), columns[3]);
}
} catch (IOException exp) {
exp.printStackTrace();
}
dataSource.database.setTransactionSuccessful();
dataSource.database.endTransaction();
}
/**
* Method: launchRingDialog
* Description: Opens the Ring Dialog and loads the CSV File into the Database
*
* @param view
*/
public void launchRingDialog(View view) {
final ProgressDialog ringProgressDialog = ProgressDialog.show(this, "Loading NFC Tags ...", "Loading records ...", true);
ringProgressDialog.setCancelable(true);
new Thread(new Runnable() {
@Override
public void run() {
try {
//Thread.sleep(10000);
loadTagsFromFile("nfccodes.csv");
} catch (Exception e) {
Log.d("CSVLoader", "CSV Loader Exception occurred", e);
}
ringProgressDialog.dismiss();
}
}).start();
}
/**
* Sets up Foreground Dispatch system for the activity and NfcAdapter required
*
* @param activity
* @param adapter
*/
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[][]{ new String[] {MifareUltralight.class.getName()},
new String[] {NfcA.class.getName()},
new String[] {Ndef.class.getName()}
};
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
filters[0].addAction(NfcAdapter.ACTION_TAG_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);
}
/**
* Stops the Foreground Dispatch system for specified activity and nfc adapter.
*
* @param activity
* @param adapter
*/
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
adapter.disableForegroundDispatch(activity);
}
/**
* Internal class used to create an NFC Reading Task, triggered when an NFC card is brought
* close or in contact to the device's NFC reading system.
*/
private class NdefReaderTask extends AsyncTask<Tag, Void, String> {
/**
* Overriden abstract method that performs the async task of reading the task.
*
* @param params
* @return - the contents of the Tag as String
*/
@Override
protected String doInBackground(Tag... params) {
Tag tag = params[0];
byte[] uid = getIntent().getByteArrayExtra(NfcAdapter.EXTRA_ID);
String serialNumber = uidByteToHexString(uid);
if (serialNumber != null && !serialNumber.isEmpty()) {
return serialNumber;
}
return null;
}
/**
* Reads a NdefRecord and returns the string calculated from the tag's payload taken over as a
* byte array
*
* @param record
* @return String - the string representation of the NFC Tag's payload
* @throws UnsupportedEncodingException
*/
private String readText(NdefRecord record) throws UnsupportedEncodingException {
byte[] payload = record.getPayload();
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
int languageCodeLength = payload[0] & 0063;
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
}
/**
* Overriden method - based on the result (Tag's code) it shows the correct screen,
* either OK or FAILURE screen, plays a sound and returns app to default screen.
* <p/>
* This method is executed after the AsyncTask is finished.
*
* @param result
*/
@Override
protected void onPostExecute(String result) {
if (result != null) {
// Do SOMETHING HERE
// find the tag which has the same code as result
EventTag currentTag = dataSource.getEventTag(result);
if (currentTag != null) {
//Toast.makeText(getApplicationContext(), "Welcome " + currentTag.getFullName() + " you are using tag: " + currentTag.getNfcCode(), Toast.LENGTH_LONG).show();
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bggreen);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.txt_tag_ok) + " " + currentTag.getFullName());
// create a media player or better just play the music
mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.gantsound_ok);
mediaPlayer.start();
} else {
//Toast.makeText(getApplicationContext(), "Tag with code: " + result + " not found in database", Toast.LENGTH_LONG).show();
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgred);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.txt_tag_notok));
// create a media player or better just play the music
mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.error);
mediaPlayer.start();
}
// use a handler here
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
public void run() {
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgblue);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.dummy_content));
handler.postDelayed(this, miliseconds);
}
};
handler.postDelayed(runnable, miliseconds);
}
else {
//Toast.makeText(getApplicationContext(), "Tag with code: " + result + " not found in database", Toast.LENGTH_LONG).show();
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgred);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.txt_tag_notok));
// create a media player or better just play the music
mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.error);
mediaPlayer.start();
// use a handler here
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
public void run() {
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgblue);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.dummy_content));
handler.postDelayed(this, miliseconds);
}
};
handler.postDelayed(runnable, miliseconds);
}
}
}
}
申请清单:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="my app and package" >
<!-- Application permissions go here, mainly NFC -->
<uses-permission android:name="android.permission.NFC" />
<users-feature android:name="android.hardware.nfc" android:required="true"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:theme="@style/FullscreenTheme" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- NFC Intent Filters -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</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>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>
</manifest>
我的技术列表:
<?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-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
您的 AsyncTask
使用
byte[] uid = getIntent().getByteArrayExtra(NfcAdapter.EXTRA_ID);
获取被扫描标签的防冲突标识符(UID)
这里有问题的部分是getIntent()
。这将使用使用 setIntent()
设置的最后一个意图(或者如果设置的意图从未像您的代码中那样使用,它将使用启动您的 activity 的意图)。因此,如果您使用其启动器图标启动 activity,getIntent()
将 return 启动器意图而不是 NFC 发现意图。因此,不会有一个名为 NfcAdapter.EXTRA_ID
.
的额外意图
所以你可以做的是将整个意图而不只是标签对象传递给 AsyncTask
:
private void handleIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
new NdefReaderTask().execute(intent);
}
}
并相应地调整您的 AsyncTask
:
private class NdefReaderTask extends AsyncTask<Intent, Void, String> {
@Override
protected String doInBackground(Intent... params) {
Intent intent = params[0];
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] uid = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
// ... //
}
一些进一步的改进
您的清单中不需要后备意图过滤器 TAG_DISCOVERED
。只有当设备上没有应用程序注册与您的标签匹配的更具体的 NFC 事件时,才会触发。由于您已经注册了更具体的事件类型(NDEF_DISCOVERED
和 TECH_DISCOVERED
),因此此 intent 过滤器不会用于您的标签。所以你可以删除
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
TECH_DISCOVERED
过滤器不使用任何类别,所以你也可以这样写:
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
所有 MIFARE Ultralight 标签都是 NFC-A 标签,因此在技术过滤器文件中使用 MifareUltralight
+ NfcA
是多余的。你可以简单地使用
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
</resources>
您的前台调度设置注册您的 activity 以接收数据类型 "text/plain" 和 全部的所有 NDEF_DISCOVERED
事件TECH_DISCOVERED
标签技术 (MifareUltralight
OR NfcA
OR Ndef
), 和 用于所有 NFC 发现事件 (TAG_DISCOVERED
)。这是多余的,您还可以简单地注册所有 NFC 发现事件:
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) {
final Intent intent = new Intent(activity, activity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
adapter.enableForegroundDispatch(activity, pendingIntent, null, null);
}
当您简化前台调度以在任何标记上触发时,Android 将向您的应用传递一个 TAG_DDISCOVERED
意图。所以你必须相应地更新 handleIntent()
方法:
private void handleIntent(Intent 意图) {
字符串操作 = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
new NdefReaderTask().execute(intent);
}
}
如果您的应用仅检测标签,而您的 activity 在前台 运行,则您的应用中不需要任何 *_DISCOVERED
过滤器清单。
我正在开发一个使用 NfcA
、MifareUltralight
和 Ndef
技术的 NFC reader 应用程序。当我尝试使用贴纸标签时它工作正常,我的意思是它可以正确读取序列号。当使用另一种类型的标签时,根据 NFC 工具,它似乎涵盖了相同的技术,但是 getByteArrayExtra
returns null
即使标签具有有效的序列号。
另一方面,我遇到问题的最后一类标签确实包含 NDEF 消息。
简而言之,这些标签的序列号用于识别经过入口设备的用户。
我对此很困惑,欢迎任何帮助。代码如下:
主要活动:
package org.bogdan.learning.gantopendemo;
import org.bogdan.learning.gantopendemo.util.SystemUiHider;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetManager;
import android.media.MediaPlayer;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareUltralight;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* @see SystemUiHider
*/
public class MainActivity extends Activity {
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
/**
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* If set, will toggle the system UI visibility upon interaction. Otherwise,
* will show the system UI visibility upon interaction.
*/
private static final boolean TOGGLE_ON_CLICK = true;
/**
* The flags to pass to {@link SystemUiHider#getInstance}.
*/
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
/**
* The instance of the {@link SystemUiHider} for this activity.
*/
private SystemUiHider mSystemUiHider;
// NFC Related variables go here, namely the adapter and helpers
public static final String MIME_TEXT_PLAIN = "text/plain";
public static final String TAG = "EventTAG";
private NfcAdapter mNfcAdapter; // Used for accessing the NFC Hardware
// SQLite Variables go here
private EventTagDataSource dataSource;
// wait interval before screen is redrawn
private int miliseconds = 6000;
// Sound related activities
private MediaPlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gant_open);
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.fullscreen_content);
/**
* Open the database connection
*/
dataSource = new EventTagDataSource(this);
try {
dataSource.open();
} catch (SQLException exp) {
Log.d("SQLExp", "SQL Exception ocurred", exp);
}
// return the default NFC adapter
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// is NFC Enabled or not=
if (mNfcAdapter == null) {
finish();
return;
}
// is the NFC Reader enabled?
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(this, "NFC Reader is not enabled. Please enable and try again", Toast.LENGTH_LONG).show();
}
// Set up an instance of SystemUiHider to control the system UI for
// this activity.
mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
// Cached values.
int mControlsHeight;
int mShortAnimTime;
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
// If the ViewPropertyAnimator API is available
// (Honeycomb MR2 and later), use it to animate the
// in-layout UI controls at the bottom of the
// screen.
if (mControlsHeight == 0) {
mControlsHeight = controlsView.getHeight();
}
if (mShortAnimTime == 0) {
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
controlsView.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
} else {
// If the ViewPropertyAnimator APIs aren't
// available, simply show or hide the in-layout UI
// controls.
controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
if (visible && AUTO_HIDE) {
// Schedule a hide().
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
}
});
// Set up the user interaction to manually show or hide the system UI.
contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TOGGLE_ON_CLICK) {
mSystemUiHider.toggle();
} else {
mSystemUiHider.show();
}
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
findViewById(R.id.dummy_button).setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
//Toast.makeText(getApplicationContext(), "Long Click Detected", Toast.LENGTH_LONG).show();
launchRingDialog(view);
return false;
}
});
handleIntent(getIntent());
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
mSystemUiHider.hide();
}
};
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
/**
* onResume - when application returns from standby it reopens database connection
* and sets up foreground dispatch
*/
@Override
protected void onResume() {
try {
dataSource.open();
} catch (SQLException exp) {
Log.d("SQLExp", "SQL Exception ocurred", exp);
}
super.onResume();
setupForegroundDispatch(this, mNfcAdapter);
}
/**
* onPause - closes database connection, stops foreground despatch and sets application in
* standby mode.
*/
@Override
protected void onPause() {
dataSource.close();
stopForegroundDispatch(this, mNfcAdapter);
super.onPause();
}
/**
* creates a new intent and calles handleIntent(intent) to deal with it.
*
* @param intent
*/
@Override
protected void onNewIntent(Intent intent) {
handleIntent(intent);
}
/**
*
*/
public String uidByteToHexString(byte[] uid) {
int iCounter, jCounter, in;
String[] hex = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
};
String out = "";
for (jCounter = 0; jCounter < uid.length; jCounter++) {
in = uid[jCounter] & 0xff;
iCounter = (in >> 4) & 0x0f;
out += hex[iCounter];
iCounter = in & 0x0f;
out += hex[iCounter];
}
return out;
}
/**
* Handles intent within the application (such as NFC Tag detected)
*
* @param intent
*/
private void handleIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
new NdefReaderTask().execute(tag);
}
/*else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
new NdefReaderTask().execute(tag);
}*/
}
/**
* Method: loadTagsFromFile
*
* @param - none
* @return - void
* @description - Loads a set of NFC Tags from a given CSV file into the SQLite Database
*/
public void loadTagsFromFile(String fileName) {
AssetManager manager = getApplicationContext().getAssets();
InputStream inStream = null;
try {
inStream = manager.open(fileName);
} catch (IOException exp) {
exp.printStackTrace();
}
// open a buffered reader and start reading from the inputStream
BufferedReader buffer = new BufferedReader(new InputStreamReader(inStream));
String line = ""; //current line in our CSV file
// open a transcation to start mass inserting data
dataSource.database.beginTransaction();
try {
while ((line = buffer.readLine()) != null) {
String[] columns = line.split(";");
if (columns.length != 4) {
Log.d("CSVParser", "Skipping malformed CSV format");
continue;
}
dataSource.createEventTag(columns[1].trim(), columns[2].trim(), columns[3]);
}
} catch (IOException exp) {
exp.printStackTrace();
}
dataSource.database.setTransactionSuccessful();
dataSource.database.endTransaction();
}
/**
* Method: launchRingDialog
* Description: Opens the Ring Dialog and loads the CSV File into the Database
*
* @param view
*/
public void launchRingDialog(View view) {
final ProgressDialog ringProgressDialog = ProgressDialog.show(this, "Loading NFC Tags ...", "Loading records ...", true);
ringProgressDialog.setCancelable(true);
new Thread(new Runnable() {
@Override
public void run() {
try {
//Thread.sleep(10000);
loadTagsFromFile("nfccodes.csv");
} catch (Exception e) {
Log.d("CSVLoader", "CSV Loader Exception occurred", e);
}
ringProgressDialog.dismiss();
}
}).start();
}
/**
* Sets up Foreground Dispatch system for the activity and NfcAdapter required
*
* @param activity
* @param adapter
*/
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[][]{ new String[] {MifareUltralight.class.getName()},
new String[] {NfcA.class.getName()},
new String[] {Ndef.class.getName()}
};
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
filters[0].addAction(NfcAdapter.ACTION_TAG_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);
}
/**
* Stops the Foreground Dispatch system for specified activity and nfc adapter.
*
* @param activity
* @param adapter
*/
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
adapter.disableForegroundDispatch(activity);
}
/**
* Internal class used to create an NFC Reading Task, triggered when an NFC card is brought
* close or in contact to the device's NFC reading system.
*/
private class NdefReaderTask extends AsyncTask<Tag, Void, String> {
/**
* Overriden abstract method that performs the async task of reading the task.
*
* @param params
* @return - the contents of the Tag as String
*/
@Override
protected String doInBackground(Tag... params) {
Tag tag = params[0];
byte[] uid = getIntent().getByteArrayExtra(NfcAdapter.EXTRA_ID);
String serialNumber = uidByteToHexString(uid);
if (serialNumber != null && !serialNumber.isEmpty()) {
return serialNumber;
}
return null;
}
/**
* Reads a NdefRecord and returns the string calculated from the tag's payload taken over as a
* byte array
*
* @param record
* @return String - the string representation of the NFC Tag's payload
* @throws UnsupportedEncodingException
*/
private String readText(NdefRecord record) throws UnsupportedEncodingException {
byte[] payload = record.getPayload();
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
int languageCodeLength = payload[0] & 0063;
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
}
/**
* Overriden method - based on the result (Tag's code) it shows the correct screen,
* either OK or FAILURE screen, plays a sound and returns app to default screen.
* <p/>
* This method is executed after the AsyncTask is finished.
*
* @param result
*/
@Override
protected void onPostExecute(String result) {
if (result != null) {
// Do SOMETHING HERE
// find the tag which has the same code as result
EventTag currentTag = dataSource.getEventTag(result);
if (currentTag != null) {
//Toast.makeText(getApplicationContext(), "Welcome " + currentTag.getFullName() + " you are using tag: " + currentTag.getNfcCode(), Toast.LENGTH_LONG).show();
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bggreen);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.txt_tag_ok) + " " + currentTag.getFullName());
// create a media player or better just play the music
mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.gantsound_ok);
mediaPlayer.start();
} else {
//Toast.makeText(getApplicationContext(), "Tag with code: " + result + " not found in database", Toast.LENGTH_LONG).show();
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgred);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.txt_tag_notok));
// create a media player or better just play the music
mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.error);
mediaPlayer.start();
}
// use a handler here
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
public void run() {
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgblue);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.dummy_content));
handler.postDelayed(this, miliseconds);
}
};
handler.postDelayed(runnable, miliseconds);
}
else {
//Toast.makeText(getApplicationContext(), "Tag with code: " + result + " not found in database", Toast.LENGTH_LONG).show();
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgred);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.txt_tag_notok));
// create a media player or better just play the music
mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.error);
mediaPlayer.start();
// use a handler here
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
public void run() {
findViewById(R.id.fullscreen_content).setBackgroundResource(R.drawable.bgblue);
((TextView) findViewById(R.id.fullscreen_content)).setText(getResources().getString(R.string.dummy_content));
handler.postDelayed(this, miliseconds);
}
};
handler.postDelayed(runnable, miliseconds);
}
}
}
}
申请清单:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="my app and package" >
<!-- Application permissions go here, mainly NFC -->
<uses-permission android:name="android.permission.NFC" />
<users-feature android:name="android.hardware.nfc" android:required="true"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:theme="@style/FullscreenTheme" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- NFC Intent Filters -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</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>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>
</manifest>
我的技术列表:
<?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-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
您的 AsyncTask
使用
byte[] uid = getIntent().getByteArrayExtra(NfcAdapter.EXTRA_ID);
获取被扫描标签的防冲突标识符(UID)
这里有问题的部分是getIntent()
。这将使用使用 setIntent()
设置的最后一个意图(或者如果设置的意图从未像您的代码中那样使用,它将使用启动您的 activity 的意图)。因此,如果您使用其启动器图标启动 activity,getIntent()
将 return 启动器意图而不是 NFC 发现意图。因此,不会有一个名为 NfcAdapter.EXTRA_ID
.
所以你可以做的是将整个意图而不只是标签对象传递给 AsyncTask
:
private void handleIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
new NdefReaderTask().execute(intent);
}
}
并相应地调整您的 AsyncTask
:
private class NdefReaderTask extends AsyncTask<Intent, Void, String> {
@Override
protected String doInBackground(Intent... params) {
Intent intent = params[0];
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] uid = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
// ... //
}
一些进一步的改进
您的清单中不需要后备意图过滤器
TAG_DISCOVERED
。只有当设备上没有应用程序注册与您的标签匹配的更具体的 NFC 事件时,才会触发。由于您已经注册了更具体的事件类型(NDEF_DISCOVERED
和TECH_DISCOVERED
),因此此 intent 过滤器不会用于您的标签。所以你可以删除<intent-filter> <action android:name="android.nfc.action.TAG_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT"/> </intent-filter>
TECH_DISCOVERED
过滤器不使用任何类别,所以你也可以这样写:<intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED" /> </intent-filter>
所有 MIFARE Ultralight 标签都是 NFC-A 标签,因此在技术过滤器文件中使用
MifareUltralight
+NfcA
是多余的。你可以简单地使用<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.Ndef</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcA</tech> </tech-list> </resources>
您的前台调度设置注册您的 activity 以接收数据类型 "text/plain" 和 全部的所有
NDEF_DISCOVERED
事件TECH_DISCOVERED
标签技术 (MifareUltralight
ORNfcA
ORNdef
), 和 用于所有 NFC 发现事件 (TAG_DISCOVERED
)。这是多余的,您还可以简单地注册所有 NFC 发现事件:public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) { final Intent intent = new Intent(activity, activity.getClass()); intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); final PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0); adapter.enableForegroundDispatch(activity, pendingIntent, null, null); }
当您简化前台调度以在任何标记上触发时,Android 将向您的应用传递一个
TAG_DDISCOVERED
意图。所以你必须相应地更新handleIntent()
方法:private void handleIntent(Intent 意图) { 字符串操作 = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { new NdefReaderTask().execute(intent); } }
如果您的应用仅检测标签,而您的 activity 在前台 运行,则您的应用中不需要任何
*_DISCOVERED
过滤器清单。