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