NDEF Push Protocol implementation.
The protocol allows pushing NDEF messages
over LLCP in order to simulate a card read
wihtout having to use card emulation. This
also allows for 2 way transfer of meesages.
The protocol allows for a single immediate
message, to be dispatched upon arrival as if
it were read from a tag, and any number of
deferred dispatch messages. The handling of
deferred dispatch messages is up to the
receiver and not implemented in this patch.
Change-Id: Ib99e4fc01532cc741debab370a417f94669b62ac
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index ff888ea..098a0aa 100755
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -18,14 +18,14 @@
import com.android.internal.nfc.LlcpServiceSocket;
import com.android.internal.nfc.LlcpSocket;
-import com.android.nfc.mytag.MyTagClient;
-import com.android.nfc.mytag.MyTagServer;
+import com.android.nfc.ndefpush.NdefPushClient;
+import com.android.nfc.ndefpush.NdefPushServer;
import android.app.Activity;
import android.app.Application;
import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
import android.app.StatusBarManager;
+import android.app.PendingIntent.CanceledException;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -252,8 +252,8 @@
private SharedPreferences mPrefs;
private SharedPreferences.Editor mPrefsEditor;
private PowerManager.WakeLock mWakeLock;
- private MyTagServer mMyTagServer;
- private MyTagClient mMyTagClient;
+ NdefPushClient mNdefPushClient;
+ NdefPushServer mNdefPushServer;
private static NfcService sService;
@@ -273,8 +273,8 @@
mManager = new NativeNfcManager(mContext, this);
mManager.initializeNativeStructure();
- mMyTagServer = new MyTagServer();
- mMyTagClient = new MyTagClient(this);
+ mNdefPushClient = new NdefPushClient(this);
+ mNdefPushServer = new NdefPushServer();
mSecureElement = new NativeNfcSecureElement();
@@ -339,7 +339,7 @@
if (previouslyEnabled) {
/* tear down the my tag server */
- mMyTagServer.stop();
+ mNdefPushServer.stop();
isSuccess = mManager.deinitialize();
if (DBG) Log.d(TAG, "NFC success of deinitialize = " + isSuccess);
if (isSuccess) {
@@ -361,10 +361,10 @@
public void enableForegroundDispatch(ComponentName activity, PendingIntent intent,
IntentFilter[] filters) {
mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
+ if (activity == null || filters == null || filters.length == 0 || intent == null) {
+ throw new IllegalArgumentException();
+ }
synchronized (this) {
- if (activity == null || filters == null || filters.length == 0 || intent == null) {
- throw new IllegalArgumentException();
- }
if (mDispatchOverrideFilters != null) {
Log.e(TAG, "Replacing active dispatch overrides");
}
@@ -386,6 +386,25 @@
}
@Override
+ public void enableForegroundNdefPush(ComponentName activity, NdefMessage msg) {
+ mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
+ if (activity == null || msg == null) {
+ throw new IllegalArgumentException();
+ }
+ if (mNdefPushClient.setForegroundMessage(msg)) {
+ Log.e(TAG, "Replacing active NDEF push message");
+ }
+ }
+
+ @Override
+ public void disableForegroundNdefPush(ComponentName activity) {
+ mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
+ if (!mNdefPushClient.setForegroundMessage(null)) {
+ Log.e(TAG, "No active foreground NDEF push message");
+ }
+ }
+
+ @Override
public int createLlcpConnectionlessSocket(int sap) throws RemoteException {
mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
@@ -2142,7 +2161,7 @@
maybeEnableDiscovery();
/* bring up the my tag server */
- mMyTagServer.start();
+ mNdefPushServer.start();
} else {
mIsNfcEnabled = false;
diff --git a/src/com/android/nfc/mytag/MyTagClient.java b/src/com/android/nfc/mytag/MyTagClient.java
deleted file mode 100755
index 613fad4..0000000
--- a/src/com/android/nfc/mytag/MyTagClient.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.nfc.mytag;
-
-import com.android.internal.nfc.LlcpException;
-import com.android.internal.nfc.LlcpSocket;
-import com.android.nfc.NfcService;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.nfc.NdefMessage;
-import android.nfc.NfcAdapter;
-import android.os.AsyncTask;
-import android.util.Log;
-
-import java.io.IOException;
-
-/**
- * Simple client to push the local NDEF message to a server on the remote side of an
- * LLCP connection. The message is set via {@link NfcAdapter#setLocalNdefMessage}.
- */
-public class MyTagClient extends BroadcastReceiver {
- private static final String TAG = "MyTagClient";
- private static final int MIU = 128;
- private static final boolean DBG = true;
-
- public MyTagClient(Context context) {
- context.registerReceiver(this, new IntentFilter(NfcAdapter.ACTION_LLCP_LINK_STATE_CHANGED));
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- int linkState = intent.getIntExtra(NfcAdapter.EXTRA_LLCP_LINK_STATE_CHANGED,
- NfcAdapter.LLCP_LINK_STATE_DEACTIVATED);
- if (linkState != NfcAdapter.LLCP_LINK_STATE_ACTIVATED) {
- // The link was torn down, ignore
- return;
- }
-
- if (DBG) Log.d(TAG, "LLCP connection up and running");
- NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
- NdefMessage msg = adapter.getLocalNdefMessage();
- if (msg == null) {
- if (DBG) Log.d(TAG, "No MyTag set, exiting");
- // Nothing to send to the server
- return;
- }
-
- new SendAsync().execute(msg);
- }
-
- final class SendAsync extends AsyncTask<NdefMessage, Void, Void> {
- private void trace(String msg) {
- if (DBG) Log.d(TAG, Thread.currentThread().getId() + ": " + msg);
- }
- private void error(String msg, Throwable e) {
- if (DBG) Log.e(TAG, Thread.currentThread().getId() + ": " + msg, e);
- }
-
- @Override
- public Void doInBackground(NdefMessage... msgs) {
- NfcService service = NfcService.getInstance();
- NdefMessage msg = msgs[0];
- byte[] buffer = msg.toByteArray();
- int offset = 0;
- int remoteMiu;
- LlcpSocket sock = null;
- try {
- trace("about to create socket");
- // Connect to the my tag server on the remote side
- sock = service.createLlcpSocket(0, MIU, 1, 1024);
- trace("about to connect");
-// sock.connect(MyTagServer.SERVICE_NAME);
- sock.connect(0x20);
-
- remoteMiu = sock.getRemoteSocketMiu();
- trace("about to send a " + buffer.length + "-bytes message");
- while (offset < buffer.length) {
- int length = buffer.length - offset;
- if (length > remoteMiu) {
- length = remoteMiu;
- }
- byte[] tmpBuffer = new byte[length];
- System.arraycopy(buffer, offset, tmpBuffer, 0, length);
- trace("about to send a " + length + "-bytes packet");
- sock.send(tmpBuffer);
- offset += length;
- }
- } catch (IOException e) {
- error("couldn't send tag", e);
- } catch (LlcpException e) {
- // Most likely the other side doesn't support the my tag protocol
- error("couldn't send tag", e);
- } finally {
- if (sock != null) {
- try {
- trace("about to close");
- sock.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- return null;
- }
- }
-}
diff --git a/src/com/android/nfc/ndefpush/NdefPushClient.java b/src/com/android/nfc/ndefpush/NdefPushClient.java
new file mode 100755
index 0000000..8d7c9a8
--- /dev/null
+++ b/src/com/android/nfc/ndefpush/NdefPushClient.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.nfc.ndefpush;
+
+import com.android.internal.nfc.LlcpException;
+import com.android.internal.nfc.LlcpSocket;
+import com.android.nfc.NfcService;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Simple client to push the local NDEF message to a server on the remote side of an
+ * LLCP connection. The message is set via {@link NfcAdapter#setLocalNdefMessage}.
+ */
+public class NdefPushClient extends BroadcastReceiver {
+ private static final String TAG = "NdefPushClient";
+ private static final int MIU = 128;
+ private static final boolean DBG = true;
+
+ /** Locked on MyTagClient.class */
+ NdefMessage mForegroundMsg;
+
+ public NdefPushClient(Context context) {
+ context.registerReceiver(this, new IntentFilter(NfcAdapter.ACTION_LLCP_LINK_STATE_CHANGED));
+ }
+
+ public boolean setForegroundMessage(NdefMessage msg) {
+ synchronized (this) {
+ boolean set = mForegroundMsg != null;
+ mForegroundMsg = msg;
+ return set;
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int linkState = intent.getIntExtra(NfcAdapter.EXTRA_LLCP_LINK_STATE_CHANGED,
+ NfcAdapter.LLCP_LINK_STATE_DEACTIVATED);
+ if (linkState != NfcAdapter.LLCP_LINK_STATE_ACTIVATED) {
+ // The link was torn down, ignore
+ return;
+ }
+
+ if (DBG) Log.d(TAG, "LLCP connection up and running");
+
+ NdefMessage foregroundMsg;
+ synchronized (this) {
+ foregroundMsg = mForegroundMsg;
+ }
+
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
+ NdefMessage myTag = adapter.getLocalNdefMessage();
+
+ if (foregroundMsg != null && myTag != null) {
+ if (DBG) Log.d(TAG, "sending foreground and my tag");
+ new SendAsync().execute(foregroundMsg, myTag);
+ } else if (myTag != null) {
+ if (DBG) Log.d(TAG, "sending my tag");
+ new SendAsync().execute(myTag);
+ } else if (foregroundMsg != null) {
+ if (DBG) Log.d(TAG, "sending foreground");
+ new SendAsync().execute(foregroundMsg);
+ } else {
+ if (DBG) Log.d(TAG, "no tags set, bailing");
+ return;
+ }
+ }
+
+ final class SendAsync extends AsyncTask<NdefMessage, Void, Void> {
+ @Override
+ public Void doInBackground(NdefMessage... msgs) {
+ NfcService service = NfcService.getInstance();
+
+ // We only handle a single immediate action for now
+ NdefPushProtocol msg = new NdefPushProtocol(msgs[0], NdefPushProtocol.ACTION_IMMEDIATE);
+ byte[] buffer = msg.toByteArray();
+ int offset = 0;
+ int remoteMiu;
+ LlcpSocket sock = null;
+ try {
+ if (DBG) Log.d(TAG, "about to create socket");
+ // Connect to the my tag server on the remote side
+ sock = service.createLlcpSocket(0, MIU, 1, 1024);
+ if (DBG) Log.d(TAG, "about to connect to service " + NdefPushServer.SERVICE_NAME);
+ sock.connect(NdefPushServer.SERVICE_NAME);
+
+ remoteMiu = sock.getRemoteSocketMiu();
+ if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
+ while (offset < buffer.length) {
+ int length = Math.min(buffer.length - offset, remoteMiu);
+ byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, length);
+ if (DBG) Log.d(TAG, "about to send a " + length + " byte packet");
+ sock.send(tmpBuffer);
+ offset += length;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "couldn't send tag");
+ if (DBG) Log.d(TAG, "exception:", e);
+ } catch (LlcpException e) {
+ // Most likely the other side doesn't support the my tag protocol
+ Log.e(TAG, "couldn't send tag");
+ if (DBG) Log.d(TAG, "exception:", e);
+ } finally {
+ if (sock != null) {
+ try {
+ if (DBG) Log.d(TAG, "about to close");
+ sock.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/nfc/ndefpush/NdefPushProtocol.java b/src/com/android/nfc/ndefpush/NdefPushProtocol.java
new file mode 100644
index 0000000..5520615
--- /dev/null
+++ b/src/com/android/nfc/ndefpush/NdefPushProtocol.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.nfc.ndefpush;
+
+import android.util.Log;
+
+import android.nfc.NdefMessage;
+import android.nfc.FormatException;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+
+/**
+ * Implementation of the NDEF Push Protocol.
+ */
+public class NdefPushProtocol {
+ public static final byte ACTION_IMMEDIATE = (byte) 0x01;
+ public static final byte ACTION_BACKGROUND = (byte) 0x02;
+
+ private static final String TAG = "NdefMessageSet";
+ private static final byte VERSION = 1;
+
+ private int mNumMessages;
+ private byte[] mActions;
+ private NdefMessage[] mMessages;
+
+ public NdefPushProtocol(NdefMessage msg, byte action) {
+ mNumMessages = 1;
+ mActions = new byte[1];
+ mActions[0] = action;
+ mMessages = new NdefMessage[1];
+ mMessages[0] = msg;
+ }
+
+ public NdefPushProtocol(byte[] actions, NdefMessage[] messages) {
+ if (actions.length != messages.length || actions.length == 0) {
+ throw new IllegalArgumentException(
+ "actions and messages must be the same size and non-empty");
+ }
+
+ // Keep a copy of these arrays
+ int numMessages = actions.length;
+ mActions = new byte[numMessages];
+ System.arraycopy(actions, 0, mActions, 0, numMessages);
+ mMessages = new NdefMessage[numMessages];
+ System.arraycopy(messages, 0, mMessages, 0, numMessages);
+ mNumMessages = numMessages;
+ }
+
+ public NdefPushProtocol(byte[] data) throws FormatException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(data);
+ DataInputStream input = new DataInputStream(buffer);
+
+ // Check version of protocol
+ byte version;
+ try {
+ version = input.readByte();
+ } catch (java.io.IOException e) {
+ Log.w(TAG, "Unable to read version");
+ throw new FormatException("Unable to read version");
+ }
+
+ if(version != VERSION) {
+ Log.w(TAG, "Got version " + version + ", expected " + VERSION);
+ throw new FormatException("Got version " + version + ", expected " + VERSION);
+ }
+
+ // Read numMessages
+ try {
+ mNumMessages = input.readInt();
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read numMessages");
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ if(mNumMessages == 0) {
+ Log.w(TAG, "No NdefMessage inside NdefMessageSet packet");
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+
+ // Read actions and messages
+ mActions = new byte[mNumMessages];
+ mMessages = new NdefMessage[mNumMessages];
+ for(int i = 0; i < mNumMessages; i++) {
+ // Read action
+ try {
+ mActions[i] = input.readByte();
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read action for message " + i);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ // Read message length
+ int length;
+ try {
+ length = input.readInt();
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read length for message " + i);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ byte[] bytes = new byte[length];
+ // Read message
+ int lengthRead;
+ try {
+ lengthRead = input.read(bytes);
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read bytes for message " + i);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ if(length != lengthRead) {
+ Log.w(TAG, "Read " + lengthRead + " bytes but expected " +
+ length);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ // Create and store NdefMessage
+ try {
+ mMessages[i] = new NdefMessage(bytes);
+ } catch(FormatException e) {
+ throw e;
+ }
+ }
+ }
+
+ public NdefMessage getImmediate() {
+ // Find and return the first immediate message
+ for(int i = 0; i < mNumMessages; i++) {
+ if(mActions[i] == ACTION_IMMEDIATE) {
+ return mMessages[i];
+ }
+ }
+ return null;
+ }
+
+ public byte[] toByteArray() {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
+ DataOutputStream output = new DataOutputStream(buffer);
+
+ try {
+ output.writeByte(VERSION);
+ output.writeInt(mNumMessages);
+ for(int i = 0; i < mNumMessages; i++) {
+ output.writeByte(mActions[i]);
+ byte[] bytes = mMessages[i].toByteArray();
+ output.writeInt(bytes.length);
+ output.write(bytes);
+ }
+ } catch(java.io.IOException e) {
+ return null;
+ }
+
+ return buffer.toByteArray();
+ }
+}
diff --git a/src/com/android/nfc/mytag/MyTagServer.java b/src/com/android/nfc/ndefpush/NdefPushServer.java
similarity index 91%
rename from src/com/android/nfc/mytag/MyTagServer.java
rename to src/com/android/nfc/ndefpush/NdefPushServer.java
index 0b7c426..4073567 100755
--- a/src/com/android/nfc/mytag/MyTagServer.java
+++ b/src/com/android/nfc/ndefpush/NdefPushServer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.nfc.mytag;
+package com.android.nfc.ndefpush;
import com.android.internal.nfc.LlcpException;
import com.android.internal.nfc.LlcpServiceSocket;
@@ -22,7 +22,6 @@
import com.android.nfc.NfcService;
import android.nfc.FormatException;
-import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.util.Log;
@@ -33,14 +32,14 @@
* A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages
* are typically set on the client side by using {@link NfcAdapter#setLocalNdefMessage}.
*/
-public class MyTagServer {
- private static final String TAG = "MyTagServer";
+public class NdefPushServer {
+ private static final String TAG = "NdefPushServer";
private static final boolean DBG = true;
- private static final int SERVICE_SAP = 0x20;
+ private static final int SERVICE_SAP = 0x10;
private static final int MIU = 256;
- static final String SERVICE_NAME = "com.android.mytag";
+ static final String SERVICE_NAME = "com.android.npp";
NfcService mService = NfcService.getInstance();
@@ -52,7 +51,7 @@
private LlcpSocket mSock;
ConnectionThread(LlcpSocket sock) {
- super("MyTagServer");
+ super(TAG);
mSock = sock;
}
@@ -83,12 +82,12 @@
}
}
- // Build NDEF message from the stream
- NdefMessage msg = new NdefMessage(buffer.toByteArray());
+ // Build NDEF message set from the stream
+ NdefPushProtocol msg = new NdefPushProtocol(buffer.toByteArray());
if (DBG) Log.d(TAG, "got message " + msg.toString());
// Send the intent for the fake tag
- mService.sendMockNdefTag(msg);
+ mService.sendMockNdefTag(msg.getImmediate());
} catch (FormatException e) {
Log.e(TAG, "badly formatted NDEF message, ignoring", e);
} finally {
@@ -112,7 +111,7 @@
public void run() {
while (mRunning) {
if (DBG) Log.d(TAG, "about create LLCP service socket");
- mServerSocket = mService.createLlcpServiceSocket(SERVICE_SAP, null,
+ mServerSocket = mService.createLlcpServiceSocket(SERVICE_SAP, SERVICE_NAME,
MIU, 1, 1024);
if (mServerSocket == null) {
if (DBG) Log.d(TAG, "failed to create LLCP service socket");