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