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