diff options
author | Martijn Coenen <maco@google.com> | 2012-07-11 12:24:11 -0700 |
---|---|---|
committer | Martijn Coenen <maco@google.com> | 2012-07-16 18:07:14 -0700 |
commit | f5a196b643c654c7ea98a5e2935e3bff6683399b (patch) | |
tree | be3e76827f5325ffbb51aef5b7bb6a0fe9884522 /src/com/android/nfc | |
parent | ef4a69e2fc7022ea4eadfa173e3c29ac59530473 (diff) | |
download | packages_apps_nfc-f5a196b643c654c7ea98a5e2935e3bff6683399b.zip packages_apps_nfc-f5a196b643c654c7ea98a5e2935e3bff6683399b.tar.gz packages_apps_nfc-f5a196b643c654c7ea98a5e2935e3bff6683399b.tar.bz2 |
Implement connection handover LLCP service.
The Connection Handover specification dictates that we should
exchange Hr/Hs records over a dedicated LLCP service. This adds
the service, and starts using it by default. It will fall back
to the SNEP GET method to remain compatible with Android 4.1
devices. SNEP GET on these devices will return "Not Implemented",
also per the SNEP spec.
Also fixed a bug in endianness of the OOB record.
Bug: 6759842
Change-Id: Iafe67ab119e933df5dfa0a5645a95bc59badd8ae
Diffstat (limited to 'src/com/android/nfc')
-rwxr-xr-x | src/com/android/nfc/P2pLinkManager.java | 36 | ||||
-rw-r--r-- | src/com/android/nfc/handover/BluetoothOppHandover.java | 18 | ||||
-rw-r--r-- | src/com/android/nfc/handover/ConfirmConnectActivity.java | 16 | ||||
-rw-r--r-- | src/com/android/nfc/handover/HandoverClient.java | 104 | ||||
-rw-r--r-- | src/com/android/nfc/handover/HandoverManager.java | 8 | ||||
-rw-r--r-- | src/com/android/nfc/handover/HandoverServer.java | 239 |
6 files changed, 414 insertions, 7 deletions
diff --git a/src/com/android/nfc/P2pLinkManager.java b/src/com/android/nfc/P2pLinkManager.java index 189b560..253ddaf 100755 --- a/src/com/android/nfc/P2pLinkManager.java +++ b/src/com/android/nfc/P2pLinkManager.java @@ -17,7 +17,9 @@ package com.android.nfc; import com.android.nfc.echoserver.EchoServer; +import com.android.nfc.handover.HandoverClient; import com.android.nfc.handover.HandoverManager; +import com.android.nfc.handover.HandoverServer; import com.android.nfc.ndefpush.NdefPushClient; import com.android.nfc.ndefpush.NdefPushServer; import com.android.nfc.snep.SnepClient; @@ -123,6 +125,7 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba // TODO dynamically assign SAP values static final int NDEFPUSH_SAP = 0x10; + static final int HANDOVER_SAP = 0x14; static final int LINK_DEBOUNCE_MS = 750; @@ -155,6 +158,7 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba final NdefPushServer mNdefPushServer; final SnepServer mDefaultSnepServer; + final HandoverServer mHandoverServer; final EchoServer mEchoServer; final ActivityManager mActivityManager; final PackageManager mPackageManager; @@ -178,6 +182,8 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba public P2pLinkManager(Context context, HandoverManager handoverManager) { mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback); mDefaultSnepServer = new SnepServer(mDefaultSnepCallback); + mHandoverServer = new HandoverServer(HANDOVER_SAP, handoverManager, mHandoverCallback); + if (ECHOSERVER_ENABLED) { mEchoServer = new EchoServer(); } else { @@ -206,12 +212,14 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba if (!mIsReceiveEnabled && receiveEnable) { mDefaultSnepServer.start(); mNdefPushServer.start(); + mHandoverServer.start(); if (mEchoServer != null) { mHandler.sendEmptyMessage(MSG_START_ECHOSERVER); } } else if (mIsReceiveEnabled && !receiveEnable) { mDefaultSnepServer.stop(); mNdefPushServer.stop(); + mHandoverServer.stop(); if (mEchoServer != null) { mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER); } @@ -465,11 +473,20 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba try { if (uris != null) { + HandoverClient handoverClient = new HandoverClient(); + NdefMessage response = null; NdefMessage request = handoverManager.createHandoverRequestMessage(); if (request != null) { - SnepMessage snepResponse = snepClient.get(request); - response = snepResponse.getNdefMessage(); + response = handoverClient.sendHandoverRequest(request); + + if (response == null) { + // Remote device may not support handover service, + // try the (deprecated) SNEP GET implementation + // for devices running Android 4.1 + SnepMessage snepResponse = snepClient.get(request); + response = snepResponse.getNdefMessage(); + } } // else, handover not supported if (response != null) { handoverManager.doHandoverUri(uris, response); @@ -495,6 +512,13 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba return SNEP_FAILURE; } + final HandoverServer.Callback mHandoverCallback = new HandoverServer.Callback() { + @Override + public void onHandoverRequestReceived() { + onReceiveHandover(); + } + }; + final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() { @Override public void onMessageReceived(NdefMessage msg) { @@ -511,13 +535,17 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba @Override public SnepMessage doGet(int acceptableLength, NdefMessage msg) { + // The NFC Forum Default SNEP server is not allowed to respond to + // SNEP GET requests - see SNEP 1.0 TS section 6.1. However, + // since Android 4.1 used the NFC Forum default server to + // implement connection handover, we will support this + // until we can deprecate it. NdefMessage response = mHandoverManager.tryHandoverRequest(msg); - if (response != null) { onReceiveHandover(); return SnepMessage.getSuccessResponse(response); } else { - return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_FOUND); + return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED); } } }; diff --git a/src/com/android/nfc/handover/BluetoothOppHandover.java b/src/com/android/nfc/handover/BluetoothOppHandover.java index ece6a7b..ceb3c62 100644 --- a/src/com/android/nfc/handover/BluetoothOppHandover.java +++ b/src/com/android/nfc/handover/BluetoothOppHandover.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 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.handover; import android.bluetooth.BluetoothAdapter; @@ -33,7 +49,7 @@ public class BluetoothOppHandover implements Handler.Callback { static final int MSG_START_SEND = 0; - static final int REMOTE_BT_ENABLE_DELAY_MS = 3000; + static final int REMOTE_BT_ENABLE_DELAY_MS = 5000; static final String ACTION_HANDOVER_SEND = "android.btopp.intent.action.HANDOVER_SEND"; diff --git a/src/com/android/nfc/handover/ConfirmConnectActivity.java b/src/com/android/nfc/handover/ConfirmConnectActivity.java index 22d518f..35a7da1 100644 --- a/src/com/android/nfc/handover/ConfirmConnectActivity.java +++ b/src/com/android/nfc/handover/ConfirmConnectActivity.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 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.handover; import android.app.Activity; diff --git a/src/com/android/nfc/handover/HandoverClient.java b/src/com/android/nfc/handover/HandoverClient.java new file mode 100644 index 0000000..86cd23b --- /dev/null +++ b/src/com/android/nfc/handover/HandoverClient.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 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.handover; + +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.util.Log; + +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; +import com.android.nfc.DeviceHost.LlcpSocket; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public final class HandoverClient { + private static final String TAG = "HandoverClient"; + private static final int MIU = 128; + // Max NDEF length to receive for Hr/Hs messages + private static final boolean DBG = false; + + public NdefMessage sendHandoverRequest(NdefMessage msg) { + if (msg == null) return null; + + NfcService service = NfcService.getInstance(); + + LlcpSocket sock = null; + int offset = 0; + byte[] buffer = msg.toByteArray(); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + + try { + sock = service.createLlcpSocket(0, MIU, 1, 1024); + if (sock == null) { + throw new IOException("Could not connect to socket."); + } + if (DBG) Log.d(TAG, "about to connect to service " + + HandoverServer.HANDOVER_SERVICE_NAME); + sock.connectToService(HandoverServer.HANDOVER_SERVICE_NAME); + + int remoteMiu = sock.getRemoteMiu(); + 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, offset+length); + if (DBG) Log.d(TAG, "about to send a " + length + " byte packet"); + sock.send(tmpBuffer); + offset += length; + } + + // Now, try to read back the SNEP response + byte[] partial = new byte[sock.getLocalMiu()]; + NdefMessage handoverSelectMsg = null; + while (true) { + int size = sock.receive(partial); + if (size < 0) { + break; + } + byteStream.write(partial, 0, size); + try { + handoverSelectMsg = new NdefMessage(byteStream.toByteArray()); + // If we get here, message is complete + break; + } catch (FormatException e) { + // Ignore, and try to fetch more bytes + } + } + return handoverSelectMsg; + } catch (IOException e) { + if (DBG) Log.d(TAG, "couldn't connect to handover service"); + } catch (LlcpException e) { + if (DBG) Log.d(TAG, "couldn't connect to handover service"); + } finally { + if (sock != null) { + try { + if (DBG) Log.d(TAG, "about to close"); + sock.close(); + } catch (IOException e) { + // Ignore + } + } + try { + byteStream.close(); + } catch (IOException e) { + // Ignore + } + } + return null; + } +} diff --git a/src/com/android/nfc/handover/HandoverManager.java b/src/com/android/nfc/handover/HandoverManager.java index 9836cdc..51b123c 100644 --- a/src/com/android/nfc/handover/HandoverManager.java +++ b/src/com/android/nfc/handover/HandoverManager.java @@ -657,8 +657,12 @@ public class HandoverManager implements BluetoothProfile.ServiceListener, NdefRecord createBluetoothOobDataRecord() { byte[] payload = new byte[8]; - payload[0] = 0; - payload[1] = (byte)payload.length; + // Note: this field should be little-endian per the BTSSP spec + // The Android 4.1 implementation used big-endian order here. + // No single Android implementation has ever interpreted this + // length field when parsing this record though. + payload[0] = (byte) (payload.length & 0xFF); + payload[1] = (byte) ((payload.length >> 8) & 0xFF); synchronized (HandoverManager.this) { if (mLocalBluetoothAddress == null) { diff --git a/src/com/android/nfc/handover/HandoverServer.java b/src/com/android/nfc/handover/HandoverServer.java new file mode 100644 index 0000000..e789387 --- /dev/null +++ b/src/com/android/nfc/handover/HandoverServer.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2012 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.handover; + +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.util.Log; + +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public final class HandoverServer { + public static final String HANDOVER_SERVICE_NAME = "urn:nfc:sn:handover"; + public static final String TAG = "HandoverServer"; + public static final Boolean DBG = false; + + public static final int MIU = 128; + + final HandoverManager mHandoverManager; + final int mSap; + final Callback mCallback; + + ServerThread mServerThread = null; + boolean mServerRunning = false; + + public interface Callback { + void onHandoverRequestReceived(); + } + + public HandoverServer(int sap, HandoverManager manager, Callback callback) { + mSap = sap; + mHandoverManager = manager; + mCallback = callback; + } + + public synchronized void start() { + if (mServerThread == null) { + mServerThread = new ServerThread(); + mServerThread.start(); + mServerRunning = true; + } + } + + public synchronized void stop() { + if (mServerThread != null) { + mServerThread.shutdown(); + mServerThread = null; + mServerRunning = false; + } + } + + private class ServerThread extends Thread { + private boolean mThreadRunning = true; + LlcpServerSocket mServerSocket; + + @Override + public void run() { + boolean threadRunning; + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + + while (threadRunning) { + try { + synchronized (HandoverServer.this) { + mServerSocket = NfcService.getInstance().createLlcpServerSocket(mSap, + HANDOVER_SERVICE_NAME, MIU, 1, 1024); + } + if (mServerSocket == null) { + if (DBG) Log.d(TAG, "failed to create LLCP service socket"); + return; + } + if (DBG) Log.d(TAG, "created LLCP service socket"); + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + + while (threadRunning) { + LlcpServerSocket serverSocket; + synchronized (HandoverServer.this) { + serverSocket = mServerSocket; + } + + if (serverSocket == null) { + if (DBG) Log.d(TAG, "Server socket shut down."); + return; + } + if (DBG) Log.d(TAG, "about to accept"); + LlcpSocket communicationSocket = serverSocket.accept(); + if (DBG) Log.d(TAG, "accept returned " + communicationSocket); + if (communicationSocket != null) { + new ConnectionThread(communicationSocket).start(); + } + + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + } + if (DBG) Log.d(TAG, "stop running"); + } catch (LlcpException e) { + Log.e(TAG, "llcp error", e); + } catch (IOException e) { + Log.e(TAG, "IO error", e); + } finally { + synchronized (HandoverServer.this) { + if (mServerSocket != null) { + if (DBG) Log.d(TAG, "about to close"); + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + } + } + + public void shutdown() { + synchronized (HandoverServer.this) { + mThreadRunning = false; + if (mServerSocket != null) { + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + } + + private class ConnectionThread extends Thread { + private final LlcpSocket mSock; + + ConnectionThread(LlcpSocket socket) { + super(TAG); + mSock = socket; + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "starting connection thread"); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + + try { + boolean running; + synchronized (HandoverServer.this) { + running = mServerRunning; + } + + byte[] partial = new byte[mSock.getLocalMiu()]; + + NdefMessage handoverRequestMsg = null; + while (running) { + int size = mSock.receive(partial); + if (size < 0) { + break; + } + byteStream.write(partial, 0, size); + // 1) Try to parse a handover request message from bytes received so far + try { + handoverRequestMsg = new NdefMessage(byteStream.toByteArray()); + } catch (FormatException e) { + // Ignore, and try to fetch more bytes + } + + if (handoverRequestMsg != null) { + // 2) convert to handover response + NdefMessage resp = mHandoverManager.tryHandoverRequest(handoverRequestMsg); + if (resp == null) { + Log.e(TAG, "Failed to create handover response"); + break; + } + + // 3) send handover response + int offset = 0; + byte[] buffer = resp.toByteArray(); + int remoteMiu = mSock.getRemoteMiu(); + while (offset < buffer.length) { + int length = Math.min(buffer.length - offset, remoteMiu); + byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length); + mSock.send(tmpBuffer); + offset += length; + } + // We're done + mCallback.onHandoverRequestReceived(); + break; + } + + synchronized (HandoverServer.this) { + running = mServerRunning; + } + } + + } catch (IOException e) { + if (DBG) Log.d(TAG, "IOException"); + } finally { + try { + if (DBG) Log.d(TAG, "about to close"); + mSock.close(); + } catch (IOException e) { + // ignore + } + try { + byteStream.close(); + } catch (IOException e) { + // ignore + } + } + if (DBG) Log.d(TAG, "finished connection thread"); + } + } +} |