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 } catch (SecurityException e) { 147 Log.e(STAG, "Error create ServerSockets ", e); 148 initSocketOK = false; 149 break; 150 } 151 if (!initSocketOK) { 152 // Need to break out of this loop if BT is being turned off. 153 int state = bt.getState(); 154 if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state 155 != BluetoothAdapter.STATE_ON)) { 156 Log.w(STAG, "initServerSockets failed as BT is (being) turned off"); 157 break; 158 } 159 try { 160 if (D) { 161 Log.v(STAG, "waiting 300 ms..."); 162 } 163 Thread.sleep(300); 164 } catch (InterruptedException e) { 165 Log.e(STAG, "create() was interrupted"); 166 } 167 } else { 168 break; 169 } 170 } 171 172 if (initSocketOK) { 173 if (D) { 174 Log.d(STAG, "Succeed to create listening sockets "); 175 } 176 ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket); 177 sockets.startAccept(); 178 return sockets; 179 } else { 180 Log.e(STAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); 181 return null; 182 } 183 } 184 185 /** 186 * Returns the channel number assigned to the RFCOMM socket. This will be a static value, that 187 * should be reused for multiple connections. 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 * @return the L2CAP channel number 198 */ getL2capPsm()199 public int getL2capPsm() { 200 return mL2capSocket.getChannel(); 201 } 202 203 /** 204 * Initiate the accept threads. 205 * Will create a thread for each socket type. an incoming connection will be signaled to 206 * the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit. 207 */ startAccept()208 private void startAccept() { 209 if (D) { 210 Log.d(mTag, "startAccept()"); 211 } 212 213 mRfcommThread = new SocketAcceptThread(mRfcommSocket); 214 mRfcommThread.start(); 215 216 mL2capThread = new SocketAcceptThread(mL2capSocket); 217 mL2capThread.start(); 218 } 219 220 /** 221 * Called from the AcceptThreads to signal an incoming connection. 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 if (D) { 228 Log.d(mTag, "onConnect() socket: " + conSocket); 229 } 230 return mConHandler.onConnect(device, conSocket); 231 } 232 233 /** 234 * Signal to the {@link IObexConnectionHandler} that an error have occurred. 235 */ onAcceptFailed()236 private synchronized void onAcceptFailed() { 237 shutdown(false); 238 BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); 239 if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) { 240 Log.d(mTag, "onAcceptFailed() calling shutdown..."); 241 mConHandler.onAcceptFailed(); 242 } 243 } 244 245 /** 246 * Terminate any running accept threads 247 * @param block Set true to block the calling thread until the AcceptThreads 248 * has ended execution 249 */ shutdown(boolean block)250 public synchronized void shutdown(boolean block) { 251 if (D) { 252 Log.d(mTag, "shutdown(block = " + block + ")"); 253 } 254 if (mRfcommThread != null) { 255 mRfcommThread.shutdown(); 256 } 257 if (mL2capThread != null) { 258 mL2capThread.shutdown(); 259 } 260 if (block) { 261 while (mRfcommThread != null || mL2capThread != null) { 262 try { 263 if (mRfcommThread != null) { 264 mRfcommThread.join(); 265 mRfcommThread = null; 266 } 267 if (mL2capThread != null) { 268 mL2capThread.join(); 269 mL2capThread = null; 270 } 271 } catch (InterruptedException e) { 272 Log.i(mTag, "shutdown() interrupted, continue waiting...", e); 273 } 274 } 275 } else { 276 mRfcommThread = null; 277 mL2capThread = null; 278 } 279 } 280 281 /** 282 * A thread that runs in the background waiting for remote an incoming 283 * connect. Once a remote socket connects, this thread will be 284 * shutdown. When the remote disconnect, this thread shall be restarted to 285 * accept a new connection. 286 */ 287 private class SocketAcceptThread extends Thread { 288 289 private boolean mStopped = false; 290 private final BluetoothServerSocket mServerSocket; 291 292 /** 293 * Create a SocketAcceptThread 294 * @param serverSocket shall never be null. 295 * @param latch shall never be null. 296 * @throws IllegalArgumentException 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