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 } 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) && (state 151 != BluetoothAdapter.STATE_ON)) { 152 Log.w(STAG, "initServerSockets failed as BT is (being) turned off"); 153 break; 154 } 155 try { 156 if (D) { 157 Log.v(STAG, "waiting 300 ms..."); 158 } 159 Thread.sleep(300); 160 } catch (InterruptedException e) { 161 Log.e(STAG, "create() was interrupted"); 162 } 163 } else { 164 break; 165 } 166 } 167 168 if (initSocketOK) { 169 if (D) { 170 Log.d(STAG, "Succeed to create listening sockets "); 171 } 172 ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket); 173 sockets.startAccept(); 174 return sockets; 175 } else { 176 Log.e(STAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); 177 return null; 178 } 179 } 180 181 /** 182 * Returns the channel number assigned to the RFCOMM socket. This will be a static value, that 183 * should be reused for multiple connections. 184 * @return the RFCOMM channel number 185 */ getRfcommChannel()186 public int getRfcommChannel() { 187 return mRfcommSocket.getChannel(); 188 } 189 190 /** 191 * Returns the channel number assigned to the L2CAP socket. This will be a static value, that 192 * should be reused for multiple connections. 193 * @return the L2CAP channel number 194 */ getL2capPsm()195 public int getL2capPsm() { 196 return mL2capSocket.getChannel(); 197 } 198 199 /** 200 * Initiate the accept threads. 201 * Will create a thread for each socket type. an incoming connection will be signaled to 202 * the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit. 203 */ startAccept()204 private void startAccept() { 205 if (D) { 206 Log.d(mTag, "startAccept()"); 207 } 208 209 mRfcommThread = new SocketAcceptThread(mRfcommSocket); 210 mRfcommThread.start(); 211 212 mL2capThread = new SocketAcceptThread(mL2capSocket); 213 mL2capThread.start(); 214 } 215 216 /** 217 * Called from the AcceptThreads to signal an incoming connection. 218 * @param device the connecting device. 219 * @param conSocket the socket associated with the connection. 220 * @return true if the connection is accepted, false otherwise. 221 */ onConnect(BluetoothDevice device, BluetoothSocket conSocket)222 private synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) { 223 if (D) { 224 Log.d(mTag, "onConnect() socket: " + conSocket); 225 } 226 return mConHandler.onConnect(device, conSocket); 227 } 228 229 /** 230 * Signal to the {@link IObexConnectionHandler} that an error have occurred. 231 */ 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(mTag, "onAcceptFailed() calling shutdown..."); 237 mConHandler.onAcceptFailed(); 238 } 239 } 240 241 /** 242 * Terminate any running accept threads 243 * @param block Set true to block the calling thread until the AcceptThreads 244 * has ended execution 245 */ shutdown(boolean block)246 public synchronized void shutdown(boolean block) { 247 if (D) { 248 Log.d(mTag, "shutdown(block = " + block + ")"); 249 } 250 if (mRfcommThread != null) { 251 mRfcommThread.shutdown(); 252 } 253 if (mL2capThread != null) { 254 mL2capThread.shutdown(); 255 } 256 if (block) { 257 while (mRfcommThread != null || mL2capThread != null) { 258 try { 259 if (mRfcommThread != null) { 260 mRfcommThread.join(); 261 mRfcommThread = null; 262 } 263 if (mL2capThread != null) { 264 mL2capThread.join(); 265 mL2capThread = null; 266 } 267 } catch (InterruptedException e) { 268 Log.i(mTag, "shutdown() interrupted, continue waiting...", e); 269 } 270 } 271 } else { 272 mRfcommThread = null; 273 mL2capThread = null; 274 } 275 } 276 277 /** 278 * A thread that runs in the background waiting for remote an incoming 279 * connect. Once a remote socket connects, this thread will be 280 * shutdown. When the remote disconnect, this thread shall be restarted to 281 * accept a new connection. 282 */ 283 private class SocketAcceptThread extends Thread { 284 285 private boolean mStopped = false; 286 private final BluetoothServerSocket mServerSocket; 287 288 /** 289 * Create a SocketAcceptThread 290 * @param serverSocket shall never be null. 291 * @param latch shall never be null. 292 * @throws IllegalArgumentException 293 */ SocketAcceptThread(BluetoothServerSocket serverSocket)294 SocketAcceptThread(BluetoothServerSocket serverSocket) { 295 if (serverSocket == null) { 296 throw new IllegalArgumentException("serverSocket cannot be null"); 297 } 298 mServerSocket = serverSocket; 299 } 300 301 /** 302 * Run until shutdown of BT. 303 * Accept incoming connections and reject if needed. Keep accepting incoming connections. 304 */ 305 @Override run()306 public void run() { 307 try { 308 while (!mStopped) { 309 BluetoothSocket connSocket; 310 BluetoothDevice device; 311 312 try { 313 if (D) { 314 Log.d(mTag, "Accepting socket connection..."); 315 } 316 317 connSocket = mServerSocket.accept(); 318 if (D) { 319 Log.d(mTag, "Accepted socket connection from: " + mServerSocket); 320 } 321 322 if (connSocket == null) { 323 // TODO: Do we need a max error count, to avoid spinning? 324 Log.w(mTag, "connSocket is null - reattempt accept"); 325 continue; 326 } 327 device = connSocket.getRemoteDevice(); 328 329 if (device == null) { 330 Log.i(mTag, "getRemoteDevice() = null - reattempt accept"); 331 try { 332 connSocket.close(); 333 } catch (IOException e) { 334 Log.w(mTag, "Error closing the socket. ignoring...", e); 335 } 336 continue; 337 } 338 339 /* Signal to the service that we have received an incoming connection. 340 */ 341 boolean isValid = ObexServerSockets.this.onConnect(device, connSocket); 342 343 if (!isValid) { 344 /* Close connection if we already have a connection with another device 345 * by responding to the OBEX connect request. 346 */ 347 Log.i(mTag, "RemoteDevice is invalid - creating ObexRejectServer."); 348 BluetoothObexTransport obexTrans = 349 new BluetoothObexTransport(connSocket); 350 // Create and detach a selfdestructing ServerSession to respond to any 351 // incoming OBEX signals. 352 new ServerSession(obexTrans, 353 new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE, 354 connSocket), null); 355 // now wait for a new connect 356 } else { 357 // now wait for a new connect 358 } 359 } catch (IOException ex) { 360 if (mStopped) { 361 // Expected exception because of shutdown. 362 } else { 363 Log.w(mTag, "Accept exception for " + mServerSocket, ex); 364 ObexServerSockets.this.onAcceptFailed(); 365 } 366 mStopped = true; 367 } 368 } // End while() 369 } finally { 370 if (D) { 371 Log.d(mTag, "AcceptThread ended for: " + mServerSocket); 372 } 373 } 374 } 375 376 /** 377 * Shuts down the accept threads, and closes the ServerSockets, causing all related 378 * BluetoothSockets to disconnect, hence do not call until all all accepted connections 379 * are ready to be disconnected. 380 */ shutdown()381 public void shutdown() { 382 if (!mStopped) { 383 mStopped = true; 384 // TODO: According to the documentation, this should not close the accepted 385 // sockets - and that is true, but it closes the l2cap connections, and 386 // therefore it implicitly also closes the accepted sockets... 387 try { 388 mServerSocket.close(); 389 } catch (IOException e) { 390 if (D) { 391 Log.d(mTag, "Exception while thread shutdown:", e); 392 } 393 } 394 } 395 // If called from another thread, interrupt the thread 396 if (!Thread.currentThread().equals(this)) { 397 // TODO: Will this interrupt the thread if it is blocked in synchronized? 398 // Else: change to use InterruptableLock 399 if (D) { 400 Log.d(mTag, "shutdown called from another thread - interrupt()."); 401 } 402 interrupt(); 403 } 404 } 405 } 406 } 407