diff options
author | Jeff Hamilton <jham@android.com> | 2010-12-10 16:11:35 -0800 |
---|---|---|
committer | Jeff Hamilton <jham@android.com> | 2011-01-18 09:02:14 -0800 |
commit | ca1a86ecb8edce740a232c3439355e8d5b706e7a (patch) | |
tree | c240a32ac92119a738c7747fbe4c01eebce786a7 | |
parent | eb488e0b323ec2d4fe03528e2b9f3a73e66ae17d (diff) | |
download | packages_apps_nfc-ca1a86ecb8edce740a232c3439355e8d5b706e7a.zip packages_apps_nfc-ca1a86ecb8edce740a232c3439355e8d5b706e7a.tar.gz packages_apps_nfc-ca1a86ecb8edce740a232c3439355e8d5b706e7a.tar.bz2 |
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
-rwxr-xr-x | src/com/android/nfc/NfcService.java | 43 | ||||
-rwxr-xr-x | src/com/android/nfc/ndefpush/NdefPushClient.java (renamed from src/com/android/nfc/mytag/MyTagClient.java) | 83 | ||||
-rw-r--r-- | src/com/android/nfc/ndefpush/NdefPushProtocol.java | 167 | ||||
-rwxr-xr-x | src/com/android/nfc/ndefpush/NdefPushServer.java (renamed from src/com/android/nfc/mytag/MyTagServer.java) | 21 |
4 files changed, 258 insertions, 56 deletions
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 @@ package com.android.nfc; 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 @@ public class NfcService extends Application { 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 @@ public class NfcService extends Application { 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 @@ public class NfcService extends Application { 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 class NfcService extends Application { 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 @@ public class NfcService extends Application { } @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 @@ public class NfcService extends Application { 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/ndefpush/NdefPushClient.java index 613fad4..8d7c9a8 100755 --- a/src/com/android/nfc/mytag/MyTagClient.java +++ b/src/com/android/nfc/ndefpush/NdefPushClient.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.LlcpSocket; @@ -30,20 +30,32 @@ 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 MyTagClient extends BroadcastReceiver { - private static final String TAG = "MyTagClient"; +public class NdefPushClient extends BroadcastReceiver { + private static final String TAG = "NdefPushClient"; private static final int MIU = 128; private static final boolean DBG = true; - public MyTagClient(Context context) { + /** 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, @@ -54,63 +66,68 @@ public class MyTagClient extends BroadcastReceiver { } if (DBG) Log.d(TAG, "LLCP connection up and running"); + + NdefMessage foregroundMsg; + synchronized (this) { + foregroundMsg = mForegroundMsg; + } + 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 + 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; } - - 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]; + + // 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 { - trace("about to create socket"); + 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); - trace("about to connect"); -// sock.connect(MyTagServer.SERVICE_NAME); - sock.connect(0x20); + if (DBG) Log.d(TAG, "about to connect to service " + NdefPushServer.SERVICE_NAME); + sock.connect(NdefPushServer.SERVICE_NAME); remoteMiu = sock.getRemoteSocketMiu(); - trace("about to send a " + buffer.length + "-bytes message"); + if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte 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"); + 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) { - error("couldn't send tag", 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 - error("couldn't send tag", e); + Log.e(TAG, "couldn't send tag"); + if (DBG) Log.d(TAG, "exception:", e); } finally { if (sock != null) { try { - trace("about to close"); + if (DBG) Log.d(TAG, "about to close"); sock.close(); } catch (IOException e) { // Ignore 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 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.internal.nfc.LlcpSocket; 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 @@ import java.io.IOException; * 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 @@ public class MyTagServer { private LlcpSocket mSock; ConnectionThread(LlcpSocket sock) { - super("MyTagServer"); + super(TAG); mSock = sock; } @@ -83,12 +82,12 @@ public class MyTagServer { } } - // 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 class MyTagServer { 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"); |