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