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