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