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 java.io.IOException;
18 
19 import javax.obex.ResponseCodes;
20 import javax.obex.ServerSession;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothServerSocket;
25 import android.bluetooth.BluetoothSocket;
26 import android.util.Log;
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 TAG;
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 = null;
59     private SocketAcceptThread mL2capThread = null;
60 
61     private volatile boolean mConAccepted = false;
62 
63     private static volatile int sInstanceCounter = 0;
64 
ObexServerSockets(IObexConnectionHandler conHandler, BluetoothServerSocket rfcommSocket, BluetoothServerSocket l2capSocket)65     private ObexServerSockets(IObexConnectionHandler conHandler,
66             BluetoothServerSocket rfcommSocket,
67             BluetoothServerSocket l2capSocket) {
68         mConHandler = conHandler;
69         mRfcommSocket = rfcommSocket;
70         mL2capSocket = l2capSocket;
71         TAG = "ObexServerSockets" + sInstanceCounter++;
72     }
73 
74     /**
75      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
76      * @param validator a reference to the {@link IObexConnectionHandler} object to call
77      *                  to validate an incoming connection.
78      * @return a reference to a {@link ObexServerSockets} object instance.
79      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
80      */
create(IObexConnectionHandler validator)81     public static ObexServerSockets create(IObexConnectionHandler validator) {
82         return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
83                 BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true);
84     }
85 
86     /**
87      * Creates an Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
88          *                  {@link BluetoothServerSocket}
89      * @param validator a reference to the {@link IObexConnectionHandler} object to call
90      *                  to validate an incoming connection.
91      * @return a reference to a {@link ObexServerSockets} object instance.
92      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
93      */
createInsecure(IObexConnectionHandler validator)94     public static ObexServerSockets createInsecure(IObexConnectionHandler validator) {
95         return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
96                 BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false);
97     }
98 
99     /**
100      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
101      * with specific l2cap and RFCOMM channel numbers. It is the responsibility of the caller to
102      * ensure the numbers are free and can be used, e.g. by calling {@link #getL2capPsm()} and
103      * {@link #getRfcommChannel()} in {@link ObexServerSockets}.
104      * @param validator a reference to the {@link IObexConnectionHandler} object to call
105      *                  to validate an incoming connection.
106      * @param isSecure boolean flag to determine whther socket would be secured or inseucure.
107      * @return a reference to a {@link ObexServerSockets} object instance.
108      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
109      *
110      * TODO: Make public when it becomes possible to determine that the listen-call
111      *       failed due to channel-in-use.
112      */
create( IObexConnectionHandler validator, int rfcommChannel, int l2capPsm, boolean isSecure)113     private static ObexServerSockets create(
114             IObexConnectionHandler validator, int rfcommChannel, int l2capPsm, boolean isSecure) {
115         if(D) Log.d(STAG,"create(rfcomm = " +rfcommChannel + ", l2capPsm = " + l2capPsm +")");
116         BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
117         if(bt == null) {
118             throw new RuntimeException("No bluetooth adapter...");
119         }
120         BluetoothServerSocket rfcommSocket = null;
121         BluetoothServerSocket l2capSocket = null;
122         boolean initSocketOK = false;
123         final int CREATE_RETRY_TIME = 10;
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) &&
151                     (state != BluetoothAdapter.STATE_ON)) {
152                     Log.w(STAG, "initServerSockets failed as BT is (being) turned off");
153                     break;
154                 }
155                 try {
156                     if (D) Log.v(STAG, "waiting 300 ms...");
157                     Thread.sleep(300);
158                 } catch (InterruptedException e) {
159                     Log.e(STAG, "create() was interrupted");
160                 }
161             } else {
162                 break;
163             }
164         }
165 
166         if (initSocketOK) {
167             if (D) Log.d(STAG, "Succeed to create listening sockets ");
168             ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket);
169             sockets.startAccept();
170             return sockets;
171         } else {
172             Log.e(STAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
173             return null;
174         }
175     }
176 
177     /**
178      * Returns the channel number assigned to the RFCOMM socket. This will be a static value, that
179      * should be reused for multiple connections.
180      * @return the RFCOMM channel number
181      */
getRfcommChannel()182     public int getRfcommChannel() {
183         return mRfcommSocket.getChannel();
184     }
185 
186     /**
187      * Returns the channel number assigned to the L2CAP socket. This will be a static value, that
188      * should be reused for multiple connections.
189      * @return the L2CAP channel number
190      */
getL2capPsm()191     public int getL2capPsm() {
192         return mL2capSocket.getChannel();
193     }
194 
195     /**
196      * Initiate the accept threads.
197      * Will create a thread for each socket type. an incoming connection will be signaled to
198      * the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit.
199      */
startAccept()200     private void startAccept() {
201         if(D) Log.d(TAG,"startAccept()");
202         prepareForNewConnect();
203 
204         mRfcommThread = new SocketAcceptThread(mRfcommSocket);
205         mRfcommThread.start();
206 
207         mL2capThread = new SocketAcceptThread(mL2capSocket);
208         mL2capThread.start();
209     }
210 
211     /**
212      * Set state to accept new incoming connection. Will cause the next incoming connection to be
213      * Signaled through {@link IObexConnectionValidator#onConnect()};
214      */
prepareForNewConnect()215     synchronized public void prepareForNewConnect() {
216         if(D) Log.d(TAG, "prepareForNewConnect()");
217         mConAccepted = false;
218     }
219 
220     /**
221      * Called from the AcceptThreads to signal an incoming connection.
222      * This is the entry point that needs to synchronize between the accept
223      * threads, and ensure only a single connection is accepted.
224      * {@link mAcceptedSocket} is used a state variable.
225      * @param device the connecting device.
226      * @param conSocket the socket associated with the connection.
227      * @return true if the connection is accepted, false otherwise.
228      */
onConnect(BluetoothDevice device, BluetoothSocket conSocket)229     synchronized private boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
230         if(D) Log.d(TAG, "onConnect() socket: " + conSocket + " mConAccepted = " + mConAccepted);
231         if(mConAccepted  == false && mConHandler.onConnect(device, conSocket) == true) {
232             mConAccepted = true; // TODO: Reset this when ready to accept new connection
233             /* Signal the remaining threads to stop.
234             shutdown(false); */ // UPDATE: TODO: remove - redesigned to keep running...
235             return true;
236         }
237         return false;
238     }
239 
240     /**
241      * Signal to the {@link IObexConnectionHandler} that an error have occurred.
242      */
onAcceptFailed()243     synchronized private void onAcceptFailed() {
244         shutdown(false);
245         BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
246         if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) {
247             Log.d(TAG, "onAcceptFailed() calling shutdown...");
248             mConHandler.onAcceptFailed();
249         }
250     }
251 
252     /**
253      * Terminate any running accept threads
254      * @param block Set true to block the calling thread until the AcceptThreads
255      * has ended execution
256      */
shutdown(boolean block)257     synchronized public void shutdown(boolean block) {
258         if(D) Log.d(TAG, "shutdown(block = " + block + ")");
259         if(mRfcommThread != null) {
260             mRfcommThread.shutdown();
261         }
262         if(mL2capThread != null){
263             mL2capThread.shutdown();
264         }
265         if(block == true) {
266             while(mRfcommThread != null || mL2capThread != null) {
267                 try {
268                     if(mRfcommThread != null) {
269                         mRfcommThread.join();
270                         mRfcommThread = null;
271                     }
272                     if(mL2capThread != null) {
273                         mL2capThread.join();
274                         mL2capThread = null;
275                     }
276                 } catch (InterruptedException e) {
277                     Log.i(TAG, "shutdown() interrupted, continue waiting...", e);
278                 }
279             }
280         } else {
281             mRfcommThread = null;
282             mL2capThread = null;
283         }
284     }
285 
286     /**
287      * A thread that runs in the background waiting for remote an incoming
288      * connect. Once a remote socket connects, this thread will be
289      * shutdown. When the remote disconnect, this thread shall be restarted to
290      * accept a new connection.
291      */
292     private class SocketAcceptThread extends Thread {
293 
294         private boolean mStopped = false;
295         private final BluetoothServerSocket mServerSocket;
296 
297         /**
298          * Create a SocketAcceptThread
299          * @param serverSocket shall never be null.
300          * @param latch shall never be null.
301          * @throws IllegalArgumentException
302          */
SocketAcceptThread(BluetoothServerSocket serverSocket)303         public SocketAcceptThread(BluetoothServerSocket serverSocket) {
304             if(serverSocket == null) {
305                 throw new IllegalArgumentException("serverSocket cannot be null");
306             }
307             mServerSocket = serverSocket;
308         }
309 
310         /**
311          * Run until shutdown of BT.
312          * Accept incoming connections and reject if needed. Keep accepting incoming connections.
313          */
314         @Override
run()315         public void run() {
316             try {
317                 while (!mStopped) {
318                     BluetoothSocket connSocket;
319                     BluetoothDevice device;
320 
321                     try {
322                         if (D) Log.d(TAG, "Accepting socket connection...");
323 
324                         connSocket = mServerSocket.accept();
325                         if (D) Log.d(TAG, "Accepted socket connection from: " + mServerSocket);
326 
327                        if (connSocket == null) {
328                            // TODO: Do we need a max error count, to avoid spinning?
329                             Log.w(TAG, "connSocket is null - reattempt accept");
330                             continue;
331                         }
332                         device = connSocket.getRemoteDevice();
333 
334                         if (device == null) {
335                             Log.i(TAG, "getRemoteDevice() = null - reattempt accept");
336                             try{
337                                 connSocket.close();
338                             } catch (IOException e) {
339                                 Log.w(TAG, "Error closing the socket. ignoring...",e );
340                             }
341                             continue;
342                         }
343 
344                         /* Signal to the service that we have received an incoming connection.
345                          */
346                         boolean isValid = ObexServerSockets.this.onConnect(device, connSocket);
347 
348                         if(isValid == false) {
349                             /* Close connection if we already have a connection with another device
350                              * by responding to the OBEX connect request.
351                              */
352                             Log.i(TAG, "RemoteDevice is invalid - creating ObexRejectServer.");
353                             BluetoothObexTransport obexTrans =
354                                     new BluetoothObexTransport(connSocket);
355                             // Create and detach a selfdestructing ServerSession to respond to any
356                             // incoming OBEX signals.
357                             new ServerSession(obexTrans,
358                                     new ObexRejectServer(
359                                             ResponseCodes.OBEX_HTTP_UNAVAILABLE,
360                                             connSocket),
361                                     null);
362                             // now wait for a new connect
363                         } else {
364                             // now wait for a new connect
365                         }
366                     } catch (IOException ex) {
367                         if(mStopped == true) {
368                             // Expected exception because of shutdown.
369                         } else {
370                             Log.w(TAG, "Accept exception for " +
371                                     mServerSocket, ex);
372                             ObexServerSockets.this.onAcceptFailed();
373                         }
374                         mStopped=true;
375                     }
376                 } // End while()
377             } finally {
378                 if (D) Log.d(TAG, "AcceptThread ended for: " + mServerSocket);
379             }
380         }
381 
382         /**
383          * Shuts down the accept threads, and closes the ServerSockets, causing all related
384          * BluetoothSockets to disconnect, hence do not call until all all accepted connections
385          * are ready to be disconnected.
386          */
shutdown()387         public void shutdown() {
388             if(mStopped == false) {
389                 mStopped = true;
390                 // TODO: According to the documentation, this should not close the accepted
391                 //       sockets - and that is true, but it closes the l2cap connections, and
392                 //       therefore it implicitly also closes the accepted sockets...
393                 try {
394                      mServerSocket.close();
395                 } catch (IOException e) {
396                     if(D) Log.d(TAG, "Exception while thread shutdown:", e);
397                 }
398             }
399             // If called from another thread, interrupt the thread
400             if(!Thread.currentThread().equals(this)){
401                 // TODO: Will this interrupt the thread if it is blocked in synchronized?
402                 // Else: change to use InterruptableLock
403                 if(D) Log.d(TAG, "shutdown called from another thread - interrupt().");
404                 interrupt();
405             }
406         }
407     }
408 }
409