1 /*
2 * Copyright (C) 2015 Samsung System LSI
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package com.android.bluetooth;
16 
17 import android.bluetooth.BluetoothAdapter;
18 import android.bluetooth.BluetoothDevice;
19 import android.bluetooth.BluetoothServerSocket;
20 import android.bluetooth.BluetoothSocket;
21 import android.util.Log;
22 
23 import java.io.IOException;
24 
25 import javax.obex.ResponseCodes;
26 import javax.obex.ServerSession;
27 
28 /**
29  * Wraps multiple BluetoothServerSocket objects to make it possible to accept connections on
30  * both a RFCOMM and L2CAP channel in parallel.<br>
31  * Create an instance using {@link #create()}, which will block until the sockets have been created
32  * and channel numbers have been assigned.<br>
33  * Use {@link #getRfcommChannel()} and {@link #getL2capPsm()} to get the channel numbers to
34  * put into the SDP record.<br>
35  * Call {@link #shutdown(boolean)} to terminate the accept threads created by the call to
36  * {@link #create(IObexConnectionHandler)}.<br>
37  * A reference to an object of this type cannot be reused, and the {@link BluetoothServerSocket}
38  * object references passed to this object will be closed by this object, hence cannot be reused
39  * either (This is needed, as the only way to interrupt an accept call is to close the socket...)
40  * <br>
41  * When a connection is accepted,
42  * {@link IObexConnectionHandler#onConnect(BluetoothDevice, BluetoothSocket)} will be called.<br>
43  * If the an error occur while waiting for an incoming connection
44  * {@link IObexConnectionHandler#onConnect(BluetoothDevice, BluetoothSocket)} will be called.<br>
45  * In both cases the {@link ObexServerSockets} object have terminated, and a new must be created.
46  */
47 public class ObexServerSockets {
48     private final String mTag;
49     private static final String STAG = "ObexServerSockets";
50     private static final boolean D = true; // TODO: set to false!
51     private static final int NUMBER_OF_SOCKET_TYPES = 2; // increment if LE will be supported
52 
53     private final IObexConnectionHandler mConHandler;
54     /* The wrapped sockets */
55     private final BluetoothServerSocket mRfcommSocket;
56     private final BluetoothServerSocket mL2capSocket;
57     /* Handles to the accept threads. Needed for shutdown. */
58     private SocketAcceptThread mRfcommThread;
59     private SocketAcceptThread mL2capThread;
60 
61     private static volatile int sInstanceCounter;
62 
ObexServerSockets(IObexConnectionHandler conHandler, BluetoothServerSocket rfcommSocket, BluetoothServerSocket l2capSocket)63     private ObexServerSockets(IObexConnectionHandler conHandler, BluetoothServerSocket rfcommSocket,
64             BluetoothServerSocket l2capSocket) {
65         mConHandler = conHandler;
66         mRfcommSocket = rfcommSocket;
67         mL2capSocket = l2capSocket;
68         mTag = "ObexServerSockets" + sInstanceCounter++;
69     }
70 
71     /**
72      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
73      * @param validator a reference to the {@link IObexConnectionHandler} object to call
74      *                  to validate an incoming connection.
75      * @return a reference to a {@link ObexServerSockets} object instance.
76      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
77      */
create(IObexConnectionHandler validator)78     public static ObexServerSockets create(IObexConnectionHandler validator) {
79         return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
80                 BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true);
81     }
82 
83     /**
84      * Creates an Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
85      *                  {@link BluetoothServerSocket}
86      * @param validator a reference to the {@link IObexConnectionHandler} object to call
87      *                  to validate an incoming connection.
88      * @return a reference to a {@link ObexServerSockets} object instance.
89      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
90      */
createInsecure(IObexConnectionHandler validator)91     public static ObexServerSockets createInsecure(IObexConnectionHandler validator) {
92         return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
93                 BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false);
94     }
95 
96     private static final int CREATE_RETRY_TIME = 10;
97 
98     /**
99      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
100      * with specific l2cap and RFCOMM channel numbers. It is the responsibility of the caller to
101      * ensure the numbers are free and can be used, e.g. by calling {@link #getL2capPsm()} and
102      * {@link #getRfcommChannel()} in {@link ObexServerSockets}.
103      * @param validator a reference to the {@link IObexConnectionHandler} object to call
104      *                  to validate an incoming connection.
105      * @param isSecure boolean flag to determine whther socket would be secured or inseucure.
106      * @return a reference to a {@link ObexServerSockets} object instance.
107      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
108      *
109      * TODO: Make public when it becomes possible to determine that the listen-call
110      *       failed due to channel-in-use.
111      */
create(IObexConnectionHandler validator, int rfcommChannel, int l2capPsm, boolean isSecure)112     private static ObexServerSockets create(IObexConnectionHandler validator, int rfcommChannel,
113             int l2capPsm, boolean isSecure) {
114         if (D) {
115             Log.d(STAG, "create(rfcomm = " + rfcommChannel + ", l2capPsm = " + l2capPsm + ")");
116         }
117         BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
118         if (bt == null) {
119             throw new RuntimeException("No bluetooth adapter...");
120         }
121         BluetoothServerSocket rfcommSocket = null;
122         BluetoothServerSocket l2capSocket = null;
123         boolean initSocketOK = false;
124 
125         // It's possible that create will fail in some cases. retry for 10 times
126         for (int i = 0; i < CREATE_RETRY_TIME; i++) {
127             initSocketOK = true;
128             try {
129                 if (rfcommSocket == null) {
130                     if (isSecure) {
131                         rfcommSocket = bt.listenUsingRfcommOn(rfcommChannel);
132                     } else {
133                         rfcommSocket = bt.listenUsingInsecureRfcommOn(rfcommChannel);
134                     }
135                 }
136                 if (l2capSocket == null) {
137                     if (isSecure) {
138                         l2capSocket = bt.listenUsingL2capOn(l2capPsm);
139                     } else {
140                         l2capSocket = bt.listenUsingInsecureL2capOn(l2capPsm);
141                     }
142                 }
143             } catch (IOException e) {
144                 Log.e(STAG, "Error create ServerSockets ", e);
145                 initSocketOK = false;
146             }
147             if (!initSocketOK) {
148                 // Need to break out of this loop if BT is being turned off.
149                 int state = bt.getState();
150                 if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
151                         != BluetoothAdapter.STATE_ON)) {
152                     Log.w(STAG, "initServerSockets failed as BT is (being) turned off");
153                     break;
154                 }
155                 try {
156                     if (D) {
157                         Log.v(STAG, "waiting 300 ms...");
158                     }
159                     Thread.sleep(300);
160                 } catch (InterruptedException e) {
161                     Log.e(STAG, "create() was interrupted");
162                 }
163             } else {
164                 break;
165             }
166         }
167 
168         if (initSocketOK) {
169             if (D) {
170                 Log.d(STAG, "Succeed to create listening sockets ");
171             }
172             ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket);
173             sockets.startAccept();
174             return sockets;
175         } else {
176             Log.e(STAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
177             return null;
178         }
179     }
180 
181     /**
182      * Returns the channel number assigned to the RFCOMM socket. This will be a static value, that
183      * should be reused for multiple connections.
184      * @return the RFCOMM channel number
185      */
getRfcommChannel()186     public int getRfcommChannel() {
187         return mRfcommSocket.getChannel();
188     }
189 
190     /**
191      * Returns the channel number assigned to the L2CAP socket. This will be a static value, that
192      * should be reused for multiple connections.
193      * @return the L2CAP channel number
194      */
getL2capPsm()195     public int getL2capPsm() {
196         return mL2capSocket.getChannel();
197     }
198 
199     /**
200      * Initiate the accept threads.
201      * Will create a thread for each socket type. an incoming connection will be signaled to
202      * the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit.
203      */
startAccept()204     private void startAccept() {
205         if (D) {
206             Log.d(mTag, "startAccept()");
207         }
208 
209         mRfcommThread = new SocketAcceptThread(mRfcommSocket);
210         mRfcommThread.start();
211 
212         mL2capThread = new SocketAcceptThread(mL2capSocket);
213         mL2capThread.start();
214     }
215 
216     /**
217      * Called from the AcceptThreads to signal an incoming connection.
218      * @param device the connecting device.
219      * @param conSocket the socket associated with the connection.
220      * @return true if the connection is accepted, false otherwise.
221      */
onConnect(BluetoothDevice device, BluetoothSocket conSocket)222     private synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
223         if (D) {
224             Log.d(mTag, "onConnect() socket: " + conSocket);
225         }
226         return mConHandler.onConnect(device, conSocket);
227     }
228 
229     /**
230      * Signal to the {@link IObexConnectionHandler} that an error have occurred.
231      */
onAcceptFailed()232     private synchronized void onAcceptFailed() {
233         shutdown(false);
234         BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
235         if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) {
236             Log.d(mTag, "onAcceptFailed() calling shutdown...");
237             mConHandler.onAcceptFailed();
238         }
239     }
240 
241     /**
242      * Terminate any running accept threads
243      * @param block Set true to block the calling thread until the AcceptThreads
244      * has ended execution
245      */
shutdown(boolean block)246     public synchronized void shutdown(boolean block) {
247         if (D) {
248             Log.d(mTag, "shutdown(block = " + block + ")");
249         }
250         if (mRfcommThread != null) {
251             mRfcommThread.shutdown();
252         }
253         if (mL2capThread != null) {
254             mL2capThread.shutdown();
255         }
256         if (block) {
257             while (mRfcommThread != null || mL2capThread != null) {
258                 try {
259                     if (mRfcommThread != null) {
260                         mRfcommThread.join();
261                         mRfcommThread = null;
262                     }
263                     if (mL2capThread != null) {
264                         mL2capThread.join();
265                         mL2capThread = null;
266                     }
267                 } catch (InterruptedException e) {
268                     Log.i(mTag, "shutdown() interrupted, continue waiting...", e);
269                 }
270             }
271         } else {
272             mRfcommThread = null;
273             mL2capThread = null;
274         }
275     }
276 
277     /**
278      * A thread that runs in the background waiting for remote an incoming
279      * connect. Once a remote socket connects, this thread will be
280      * shutdown. When the remote disconnect, this thread shall be restarted to
281      * accept a new connection.
282      */
283     private class SocketAcceptThread extends Thread {
284 
285         private boolean mStopped = false;
286         private final BluetoothServerSocket mServerSocket;
287 
288         /**
289          * Create a SocketAcceptThread
290          * @param serverSocket shall never be null.
291          * @param latch shall never be null.
292          * @throws IllegalArgumentException
293          */
SocketAcceptThread(BluetoothServerSocket serverSocket)294         SocketAcceptThread(BluetoothServerSocket serverSocket) {
295             if (serverSocket == null) {
296                 throw new IllegalArgumentException("serverSocket cannot be null");
297             }
298             mServerSocket = serverSocket;
299         }
300 
301         /**
302          * Run until shutdown of BT.
303          * Accept incoming connections and reject if needed. Keep accepting incoming connections.
304          */
305         @Override
run()306         public void run() {
307             try {
308                 while (!mStopped) {
309                     BluetoothSocket connSocket;
310                     BluetoothDevice device;
311 
312                     try {
313                         if (D) {
314                             Log.d(mTag, "Accepting socket connection...");
315                         }
316 
317                         connSocket = mServerSocket.accept();
318                         if (D) {
319                             Log.d(mTag, "Accepted socket connection from: " + mServerSocket);
320                         }
321 
322                         if (connSocket == null) {
323                             // TODO: Do we need a max error count, to avoid spinning?
324                             Log.w(mTag, "connSocket is null - reattempt accept");
325                             continue;
326                         }
327                         device = connSocket.getRemoteDevice();
328 
329                         if (device == null) {
330                             Log.i(mTag, "getRemoteDevice() = null - reattempt accept");
331                             try {
332                                 connSocket.close();
333                             } catch (IOException e) {
334                                 Log.w(mTag, "Error closing the socket. ignoring...", e);
335                             }
336                             continue;
337                         }
338 
339                         /* Signal to the service that we have received an incoming connection.
340                          */
341                         boolean isValid = ObexServerSockets.this.onConnect(device, connSocket);
342 
343                         if (!isValid) {
344                             /* Close connection if we already have a connection with another device
345                              * by responding to the OBEX connect request.
346                              */
347                             Log.i(mTag, "RemoteDevice is invalid - creating ObexRejectServer.");
348                             BluetoothObexTransport obexTrans =
349                                     new BluetoothObexTransport(connSocket);
350                             // Create and detach a selfdestructing ServerSession to respond to any
351                             // incoming OBEX signals.
352                             new ServerSession(obexTrans,
353                                     new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE,
354                                             connSocket), null);
355                             // now wait for a new connect
356                         } else {
357                             // now wait for a new connect
358                         }
359                     } catch (IOException ex) {
360                         if (mStopped) {
361                             // Expected exception because of shutdown.
362                         } else {
363                             Log.w(mTag, "Accept exception for " + mServerSocket, ex);
364                             ObexServerSockets.this.onAcceptFailed();
365                         }
366                         mStopped = true;
367                     }
368                 } // End while()
369             } finally {
370                 if (D) {
371                     Log.d(mTag, "AcceptThread ended for: " + mServerSocket);
372                 }
373             }
374         }
375 
376         /**
377          * Shuts down the accept threads, and closes the ServerSockets, causing all related
378          * BluetoothSockets to disconnect, hence do not call until all all accepted connections
379          * are ready to be disconnected.
380          */
shutdown()381         public void shutdown() {
382             if (!mStopped) {
383                 mStopped = true;
384                 // TODO: According to the documentation, this should not close the accepted
385                 //       sockets - and that is true, but it closes the l2cap connections, and
386                 //       therefore it implicitly also closes the accepted sockets...
387                 try {
388                     mServerSocket.close();
389                 } catch (IOException e) {
390                     if (D) {
391                         Log.d(mTag, "Exception while thread shutdown:", e);
392                     }
393                 }
394             }
395             // If called from another thread, interrupt the thread
396             if (!Thread.currentThread().equals(this)) {
397                 // TODO: Will this interrupt the thread if it is blocked in synchronized?
398                 // Else: change to use InterruptableLock
399                 if (D) {
400                     Log.d(mTag, "shutdown called from another thread - interrupt().");
401                 }
402                 interrupt();
403             }
404         }
405     }
406 }
407