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