1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.nfc.handover; 17 18import com.android.nfc.DeviceHost.LlcpServerSocket; 19import com.android.nfc.DeviceHost.LlcpSocket; 20import com.android.nfc.LlcpException; 21import com.android.nfc.NfcService; 22import com.android.nfc.beam.BeamManager; 23import com.android.nfc.beam.BeamReceiveService; 24import com.android.nfc.beam.BeamTransferRecord; 25 26import android.bluetooth.BluetoothDevice; 27import android.content.Context; 28import android.content.Intent; 29import android.nfc.FormatException; 30import android.nfc.NdefMessage; 31import android.os.UserHandle; 32import android.util.Log; 33 34import java.io.ByteArrayOutputStream; 35import java.io.IOException; 36import java.util.Arrays; 37 38public final class HandoverServer { 39 static final String HANDOVER_SERVICE_NAME = "urn:nfc:sn:handover"; 40 static final String TAG = "HandoverServer"; 41 static final Boolean DBG = false; 42 43 static final int MIU = 128; 44 45 final HandoverDataParser mHandoverDataParser; 46 final int mSap; 47 final Callback mCallback; 48 private final Context mContext; 49 50 ServerThread mServerThread = null; 51 boolean mServerRunning = false; 52 53 public interface Callback { 54 void onHandoverRequestReceived(); 55 void onHandoverBusy(); 56 } 57 58 public HandoverServer(Context context, int sap, HandoverDataParser manager, Callback callback) { 59 mContext = context; 60 mSap = sap; 61 mHandoverDataParser = manager; 62 mCallback = callback; 63 } 64 65 public synchronized void start() { 66 if (mServerThread == null) { 67 mServerThread = new ServerThread(); 68 mServerThread.start(); 69 mServerRunning = true; 70 } 71 } 72 73 public synchronized void stop() { 74 if (mServerThread != null) { 75 mServerThread.shutdown(); 76 mServerThread = null; 77 mServerRunning = false; 78 } 79 } 80 81 private class ServerThread extends Thread { 82 private boolean mThreadRunning = true; 83 LlcpServerSocket mServerSocket; 84 85 @Override 86 public void run() { 87 boolean threadRunning; 88 synchronized (HandoverServer.this) { 89 threadRunning = mThreadRunning; 90 } 91 92 while (threadRunning) { 93 try { 94 synchronized (HandoverServer.this) { 95 mServerSocket = NfcService.getInstance().createLlcpServerSocket(mSap, 96 HANDOVER_SERVICE_NAME, MIU, 1, 1024); 97 } 98 if (mServerSocket == null) { 99 if (DBG) Log.d(TAG, "failed to create LLCP service socket"); 100 return; 101 } 102 if (DBG) Log.d(TAG, "created LLCP service socket"); 103 synchronized (HandoverServer.this) { 104 threadRunning = mThreadRunning; 105 } 106 107 while (threadRunning) { 108 LlcpServerSocket serverSocket; 109 synchronized (HandoverServer.this) { 110 serverSocket = mServerSocket; 111 } 112 113 if (serverSocket == null) { 114 if (DBG) Log.d(TAG, "Server socket shut down."); 115 return; 116 } 117 if (DBG) Log.d(TAG, "about to accept"); 118 LlcpSocket communicationSocket = serverSocket.accept(); 119 if (DBG) Log.d(TAG, "accept returned " + communicationSocket); 120 if (communicationSocket != null) { 121 new ConnectionThread(communicationSocket).start(); 122 } 123 124 synchronized (HandoverServer.this) { 125 threadRunning = mThreadRunning; 126 } 127 } 128 if (DBG) Log.d(TAG, "stop running"); 129 } catch (LlcpException e) { 130 Log.e(TAG, "llcp error", e); 131 } catch (IOException e) { 132 Log.e(TAG, "IO error", e); 133 } finally { 134 synchronized (HandoverServer.this) { 135 if (mServerSocket != null) { 136 if (DBG) Log.d(TAG, "about to close"); 137 try { 138 mServerSocket.close(); 139 } catch (IOException e) { 140 // ignore 141 } 142 mServerSocket = null; 143 } 144 } 145 } 146 147 synchronized (HandoverServer.this) { 148 threadRunning = mThreadRunning; 149 } 150 } 151 } 152 153 public void shutdown() { 154 synchronized (HandoverServer.this) { 155 mThreadRunning = false; 156 if (mServerSocket != null) { 157 try { 158 mServerSocket.close(); 159 } catch (IOException e) { 160 // ignore 161 } 162 mServerSocket = null; 163 } 164 } 165 } 166 } 167 168 private class ConnectionThread extends Thread { 169 private final LlcpSocket mSock; 170 171 ConnectionThread(LlcpSocket socket) { 172 super(TAG); 173 mSock = socket; 174 } 175 176 @Override 177 public void run() { 178 if (DBG) Log.d(TAG, "starting connection thread"); 179 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 180 181 try { 182 boolean running; 183 synchronized (HandoverServer.this) { 184 running = mServerRunning; 185 } 186 187 byte[] partial = new byte[mSock.getLocalMiu()]; 188 189 NdefMessage handoverRequestMsg = null; 190 while (running) { 191 int size = mSock.receive(partial); 192 if (size < 0) { 193 break; 194 } 195 byteStream.write(partial, 0, size); 196 // 1) Try to parse a handover request message from bytes received so far 197 try { 198 handoverRequestMsg = new NdefMessage(byteStream.toByteArray()); 199 } catch (FormatException e) { 200 // Ignore, and try to fetch more bytes 201 } 202 203 if (handoverRequestMsg != null) { 204 BeamManager beamManager = BeamManager.getInstance(); 205 206 if (beamManager.isBeamInProgress()) { 207 mCallback.onHandoverBusy(); 208 break; 209 } 210 211 // 2) convert to handover response 212 HandoverDataParser.IncomingHandoverData handoverData 213 = mHandoverDataParser.getIncomingHandoverData(handoverRequestMsg); 214 if (handoverData == null) { 215 Log.e(TAG, "Failed to create handover response"); 216 break; 217 } 218 219 // 3) send handover response 220 int offset = 0; 221 byte[] buffer = handoverData.handoverSelect.toByteArray(); 222 int remoteMiu = mSock.getRemoteMiu(); 223 while (offset < buffer.length) { 224 int length = Math.min(buffer.length - offset, remoteMiu); 225 byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length); 226 mSock.send(tmpBuffer); 227 offset += length; 228 } 229 // We're done 230 mCallback.onHandoverRequestReceived(); 231 if (!beamManager.startBeamReceive(mContext, handoverData.handoverData)) { 232 mCallback.onHandoverBusy(); 233 break; 234 } 235 // We can process another handover transfer 236 byteStream = new ByteArrayOutputStream(); 237 } 238 239 synchronized (HandoverServer.this) { 240 running = mServerRunning; 241 } 242 } 243 244 } catch (IOException e) { 245 if (DBG) Log.d(TAG, "IOException"); 246 } finally { 247 try { 248 if (DBG) Log.d(TAG, "about to close"); 249 mSock.close(); 250 } catch (IOException e) { 251 // ignore 252 } 253 try { 254 byteStream.close(); 255 } catch (IOException e) { 256 // ignore 257 } 258 } 259 if (DBG) Log.d(TAG, "finished connection thread"); 260 } 261 } 262} 263 264