diff options
Diffstat (limited to 'src')
-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"); + } + } +} |