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