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