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 com.android.nfc.DeviceHost.LlcpServerSocket;
19 import com.android.nfc.DeviceHost.LlcpSocket;
20 import com.android.nfc.LlcpException;
21 import com.android.nfc.NfcService;
22 import com.android.nfc.beam.BeamManager;
23 import com.android.nfc.beam.BeamReceiveService;
24 import com.android.nfc.beam.BeamTransferRecord;
25 
26 import android.bluetooth.BluetoothDevice;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.nfc.FormatException;
30 import android.nfc.NdefMessage;
31 import android.os.UserHandle;
32 import android.util.Log;
33 
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.util.Arrays;
37 
38 public 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 {
onHandoverRequestReceived()54         void onHandoverRequestReceived();
onHandoverBusy()55         void onHandoverBusy();
56     }
57 
HandoverServer(Context context, int sap, HandoverDataParser manager, Callback callback)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 
start()65     public synchronized void start() {
66         if (mServerThread == null) {
67             mServerThread = new ServerThread();
68             mServerThread.start();
69             mServerRunning = true;
70         }
71     }
72 
stop()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
run()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 
shutdown()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 
ConnectionThread(LlcpSocket socket)171         ConnectionThread(LlcpSocket socket) {
172             super(TAG);
173             mSock = socket;
174         }
175 
176         @Override
run()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