BluetoothSocket.java revision b49a89635906e0901637a4a4c1c2f5bb263b6595
1/* 2 * Copyright (C) 2012 Google Inc. 3 */ 4 5package android.bluetooth; 6 7import android.os.IBinder; 8import android.os.ParcelUuid; 9import android.os.ParcelFileDescriptor; 10import android.os.RemoteException; 11import android.os.ServiceManager; 12import android.util.Log; 13 14import java.io.Closeable; 15import java.io.FileDescriptor; 16import java.io.FileInputStream; 17import java.io.FileOutputStream; 18import java.io.IOException; 19import java.io.InputStream; 20import java.io.OutputStream; 21import java.util.List; 22import java.util.UUID; 23import android.net.LocalSocket; 24import java.nio.ByteOrder; 25import java.nio.ByteBuffer; 26/** 27 * A connected or connecting Bluetooth socket. 28 * 29 * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: 30 * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server 31 * side, use a {@link BluetoothServerSocket} to create a listening server 32 * socket. When a connection is accepted by the {@link BluetoothServerSocket}, 33 * it will return a new {@link BluetoothSocket} to manage the connection. 34 * On the client side, use a single {@link BluetoothSocket} to both initiate 35 * an outgoing connection and to manage the connection. 36 * 37 * <p>The most common type of Bluetooth socket is RFCOMM, which is the type 38 * supported by the Android APIs. RFCOMM is a connection-oriented, streaming 39 * transport over Bluetooth. It is also known as the Serial Port Profile (SPP). 40 * 41 * <p>To create a {@link BluetoothSocket} for connecting to a known device, use 42 * {@link BluetoothDevice#createRfcommSocketToServiceRecord 43 * BluetoothDevice.createRfcommSocketToServiceRecord()}. 44 * Then call {@link #connect()} to attempt a connection to the remote device. 45 * This call will block until a connection is established or the connection 46 * fails. 47 * 48 * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the 49 * {@link BluetoothServerSocket} documentation. 50 * 51 * <p>Once the socket is connected, whether initiated as a client or accepted 52 * as a server, open the IO streams by calling {@link #getInputStream} and 53 * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream} 54 * and {@link java.io.OutputStream} objects, respectively, which are 55 * automatically connected to the socket. 56 * 57 * <p>{@link BluetoothSocket} is thread 58 * safe. In particular, {@link #close} will always immediately abort ongoing 59 * operations and close the socket. 60 * 61 * <p class="note"><strong>Note:</strong> 62 * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. 63 * 64 * <div class="special reference"> 65 * <h3>Developer Guides</h3> 66 * <p>For more information about using Bluetooth, read the 67 * <a href="{@docRoot}guide/topics/wireless/bluetooth.html">Bluetooth</a> developer guide.</p> 68 * </div> 69 * 70 * {@see BluetoothServerSocket} 71 * {@see java.io.InputStream} 72 * {@see java.io.OutputStream} 73 */ 74public final class BluetoothSocket implements Closeable { 75 private static final String TAG = "BluetoothSocket"; 76 private static final boolean DBG = true; 77 private static final boolean VDBG = false; 78 79 /** @hide */ 80 public static final int MAX_RFCOMM_CHANNEL = 30; 81 82 /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */ 83 /*package*/ static final int TYPE_RFCOMM = 1; 84 /*package*/ static final int TYPE_SCO = 2; 85 /*package*/ static final int TYPE_L2CAP = 3; 86 87 /*package*/ static final int EBADFD = 77; 88 /*package*/ static final int EADDRINUSE = 98; 89 90 /*package*/ static final int SEC_FLAG_ENCRYPT = 1; 91 /*package*/ static final int SEC_FLAG_AUTH = 1 << 1; 92 93 private final int mType; /* one of TYPE_RFCOMM etc */ 94 private BluetoothDevice mDevice; /* remote device */ 95 private String mAddress; /* remote address */ 96 private final boolean mAuth; 97 private final boolean mEncrypt; 98 private final BluetoothInputStream mInputStream; 99 private final BluetoothOutputStream mOutputStream; 100 private final ParcelUuid mUuid; 101 private ParcelFileDescriptor mPfd; 102 private LocalSocket mSocket; 103 private InputStream mSocketIS; 104 private OutputStream mSocketOS; 105 private int mPort; /* RFCOMM channel or L2CAP psm */ 106 private int mFd; 107 private String mServiceName; 108 private static int PROXY_CONNECTION_TIMEOUT = 5000; 109 110 private static int SOCK_SIGNAL_SIZE = 16; 111 112 private enum SocketState { 113 INIT, 114 CONNECTED, 115 LISTENING, 116 CLOSED, 117 } 118 119 /** prevents all native calls after destroyNative() */ 120 private volatile SocketState mSocketState; 121 122 /** protects mSocketState */ 123 //private final ReentrantReadWriteLock mLock; 124 125 /** 126 * Construct a BluetoothSocket. 127 * @param type type of socket 128 * @param fd fd to use for connected socket, or -1 for a new socket 129 * @param auth require the remote device to be authenticated 130 * @param encrypt require the connection to be encrypted 131 * @param device remote device that this socket can connect to 132 * @param port remote port 133 * @param uuid SDP uuid 134 * @throws IOException On error, for example Bluetooth not available, or 135 * insufficient privileges 136 */ 137 /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, 138 BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { 139 if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) { 140 if (port < 1 || port > MAX_RFCOMM_CHANNEL) { 141 throw new IOException("Invalid RFCOMM channel: " + port); 142 } 143 } 144 if(uuid != null) 145 mUuid = uuid; 146 else mUuid = new ParcelUuid(new UUID(0, 0)); 147 mType = type; 148 mAuth = auth; 149 mEncrypt = encrypt; 150 mDevice = device; 151 mPort = port; 152 mFd = fd; 153 154 mSocketState = SocketState.INIT; 155 156 if (device == null) { 157 // Server socket 158 mAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); 159 } else { 160 // Remote socket 161 mAddress = device.getAddress(); 162 } 163 mInputStream = new BluetoothInputStream(this); 164 mOutputStream = new BluetoothOutputStream(this); 165 } 166 private BluetoothSocket(BluetoothSocket s) { 167 mUuid = s.mUuid; 168 mType = s.mType; 169 mAuth = s.mAuth; 170 mEncrypt = s.mEncrypt; 171 mPort = s.mPort; 172 mInputStream = new BluetoothInputStream(this); 173 mOutputStream = new BluetoothOutputStream(this); 174 mServiceName = s.mServiceName; 175 } 176 private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException { 177 BluetoothSocket as = new BluetoothSocket(this); 178 as.mSocketState = SocketState.CONNECTED; 179 FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors(); 180 if (VDBG) Log.d(TAG, "socket fd passed by stack fds: " + fds); 181 if(fds == null || fds.length != 1) { 182 Log.e(TAG, "socket fd passed from stack failed, fds: " + fds); 183 throw new IOException("bt socket acept failed"); 184 } 185 as.mSocket = new LocalSocket(fds[0]); 186 as.mSocketIS = as.mSocket.getInputStream(); 187 as.mSocketOS = as.mSocket.getOutputStream(); 188 as.mAddress = RemoteAddr; 189 as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(RemoteAddr); 190 return as; 191 } 192 /** 193 * Construct a BluetoothSocket from address. Used by native code. 194 * @param type type of socket 195 * @param fd fd to use for connected socket, or -1 for a new socket 196 * @param auth require the remote device to be authenticated 197 * @param encrypt require the connection to be encrypted 198 * @param address remote device that this socket can connect to 199 * @param port remote port 200 * @throws IOException On error, for example Bluetooth not available, or 201 * insufficient privileges 202 */ 203 private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, 204 int port) throws IOException { 205 this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null); 206 } 207 208 /** @hide */ 209 @Override 210 protected void finalize() throws Throwable { 211 try { 212 close(); 213 } finally { 214 super.finalize(); 215 } 216 } 217 private int getSecurityFlags() { 218 int flags = 0; 219 if(mAuth) 220 flags |= SEC_FLAG_AUTH; 221 if(mEncrypt) 222 flags |= SEC_FLAG_ENCRYPT; 223 return flags; 224 } 225 226 /** 227 * Get the remote device this socket is connecting, or connected, to. 228 * @return remote device 229 */ 230 public BluetoothDevice getRemoteDevice() { 231 return mDevice; 232 } 233 234 /** 235 * Get the input stream associated with this socket. 236 * <p>The input stream will be returned even if the socket is not yet 237 * connected, but operations on that stream will throw IOException until 238 * the associated socket is connected. 239 * @return InputStream 240 */ 241 public InputStream getInputStream() throws IOException { 242 return mInputStream; 243 } 244 245 /** 246 * Get the output stream associated with this socket. 247 * <p>The output stream will be returned even if the socket is not yet 248 * connected, but operations on that stream will throw IOException until 249 * the associated socket is connected. 250 * @return OutputStream 251 */ 252 public OutputStream getOutputStream() throws IOException { 253 return mOutputStream; 254 } 255 256 /** 257 * Get the connection status of this socket, ie, whether there is an active connection with 258 * remote device. 259 * @return true if connected 260 * false if not connected 261 */ 262 public boolean isConnected() { 263 return mSocketState == SocketState.CONNECTED; 264 } 265 266 /*package*/ void setServiceName(String name) { 267 mServiceName = name; 268 } 269 270 /** 271 * Attempt to connect to a remote device. 272 * <p>This method will block until a connection is made or the connection 273 * fails. If this method returns without an exception then this socket 274 * is now connected. 275 * <p>Creating new connections to 276 * remote Bluetooth devices should not be attempted while device discovery 277 * is in progress. Device discovery is a heavyweight procedure on the 278 * Bluetooth adapter and will significantly slow a device connection. 279 * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing 280 * discovery. Discovery is not managed by the Activity, 281 * but is run as a system service, so an application should always call 282 * {@link BluetoothAdapter#cancelDiscovery()} even if it 283 * did not directly request a discovery, just to be sure. 284 * <p>{@link #close} can be used to abort this call from another thread. 285 * @throws IOException on error, for example connection failure 286 */ 287 public void connect() throws IOException { 288 if (mDevice == null) throw new IOException("Connect is called on null device"); 289 290 try { 291 // TODO(BT) derive flag from auth and encrypt 292 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 293 IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); 294 if (bluetoothProxy == null) throw new IOException("Bluetooth is off"); 295 mPfd = bluetoothProxy.connectSocket(mDevice, mType, 296 mUuid, mPort, getSecurityFlags()); 297 synchronized(this) 298 { 299 if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd); 300 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 301 if (mPfd == null) throw new IOException("bt socket connect failed"); 302 FileDescriptor fd = mPfd.getFileDescriptor(); 303 mSocket = new LocalSocket(fd); 304 mSocketIS = mSocket.getInputStream(); 305 mSocketOS = mSocket.getOutputStream(); 306 } 307 int channel = readInt(mSocketIS); 308 if (channel <= 0) 309 throw new IOException("bt socket connect failed"); 310 mPort = channel; 311 waitSocketSignal(mSocketIS); 312 synchronized(this) 313 { 314 if (mSocketState == SocketState.CLOSED) 315 throw new IOException("bt socket closed"); 316 mSocketState = SocketState.CONNECTED; 317 } 318 } catch (RemoteException e) { 319 Log.e(TAG, Log.getStackTraceString(new Throwable())); 320 } 321 } 322 323 /** 324 * Currently returns unix errno instead of throwing IOException, 325 * so that BluetoothAdapter can check the error code for EADDRINUSE 326 */ 327 /*package*/ int bindListen() { 328 int ret; 329 if (mSocketState == SocketState.CLOSED) return EBADFD; 330 IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); 331 if (bluetoothProxy == null) { 332 Log.e(TAG, "bindListen fail, reason: bluetooth is off"); 333 return -1; 334 } 335 try { 336 mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName, 337 mUuid, mPort, getSecurityFlags()); 338 } catch (RemoteException e) { 339 Log.e(TAG, Log.getStackTraceString(new Throwable())); 340 // TODO(BT) right error code? 341 return -1; 342 } 343 344 // read out port number 345 try { 346 synchronized(this) { 347 if (VDBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " + 348 mPfd); 349 if(mSocketState != SocketState.INIT) return EBADFD; 350 if(mPfd == null) return -1; 351 FileDescriptor fd = mPfd.getFileDescriptor(); 352 if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket "); 353 mSocket = new LocalSocket(fd); 354 if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() "); 355 mSocketIS = mSocket.getInputStream(); 356 mSocketOS = mSocket.getOutputStream(); 357 } 358 if (VDBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS); 359 int channel = readInt(mSocketIS); 360 synchronized(this) { 361 if(mSocketState == SocketState.INIT) 362 mSocketState = SocketState.LISTENING; 363 } 364 if (VDBG) Log.d(TAG, "channel: " + channel); 365 if (mPort == -1) { 366 mPort = channel; 367 } // else ASSERT(mPort == channel) 368 ret = 0; 369 } catch (IOException e) { 370 Log.e(TAG, "bindListen, fail to get port number, exception: " + e); 371 return -1; 372 } 373 return ret; 374 } 375 376 /*package*/ BluetoothSocket accept(int timeout) throws IOException { 377 BluetoothSocket acceptedSocket; 378 if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state"); 379 // TODO(BT) wait on an incoming connection 380 String RemoteAddr = waitSocketSignal(mSocketIS); 381 synchronized(this) 382 { 383 if (mSocketState != SocketState.LISTENING) 384 throw new IOException("bt socket is not in listen state"); 385 acceptedSocket = acceptSocket(RemoteAddr); 386 //quick drop the reference of the file handle 387 } 388 // TODO(BT) rfcomm socket only supports one connection, return this? 389 // return this; 390 return acceptedSocket; 391 } 392 393 /*package*/ int available() throws IOException { 394 if (VDBG) Log.d(TAG, "available: " + mSocketIS); 395 return mSocketIS.available(); 396 } 397 398 /*package*/ int read(byte[] b, int offset, int length) throws IOException { 399 400 if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length); 401 int ret = mSocketIS.read(b, offset, length); 402 if(ret < 0) 403 throw new IOException("bt socket closed, read return: " + ret); 404 if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); 405 return ret; 406 } 407 408 /*package*/ int write(byte[] b, int offset, int length) throws IOException { 409 410 if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length); 411 mSocketOS.write(b, offset, length); 412 // There is no good way to confirm since the entire process is asynchronous anyway 413 if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length); 414 return length; 415 } 416 417 @Override 418 public void close() throws IOException { 419 Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState); 420 if(mSocketState == SocketState.CLOSED) 421 return; 422 else 423 { 424 synchronized(this) 425 { 426 if(mSocketState == SocketState.CLOSED) 427 return; 428 mSocketState = SocketState.CLOSED; 429 if (VDBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS + 430 ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket); 431 if(mSocket != null) { 432 if (VDBG) Log.d(TAG, "Closing mSocket: " + mSocket); 433 mSocket.shutdownInput(); 434 mSocket.shutdownOutput(); 435 mSocket.close(); 436 mSocket = null; 437 } 438 if(mPfd != null) 439 mPfd.detachFd(); 440 } 441 } 442 // TODO(BT) unbind proxy, 443 } 444 445 /*package */ void removeChannel() { 446 } 447 448 /*package */ int getPort() { 449 return mPort; 450 } 451 private String convertAddr(final byte[] addr) { 452 return String.format("%02X:%02X:%02X:%02X:%02X:%02X", 453 addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]); 454 } 455 private String waitSocketSignal(InputStream is) throws IOException { 456 byte [] sig = new byte[SOCK_SIGNAL_SIZE]; 457 int ret = readAll(is, sig); 458 if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret); 459 ByteBuffer bb = ByteBuffer.wrap(sig); 460 bb.order(ByteOrder.nativeOrder()); 461 int size = bb.getShort(); 462 byte [] addr = new byte[6]; 463 bb.get(addr); 464 int channel = bb.getInt(); 465 int status = bb.getInt(); 466 String RemoteAddr = convertAddr(addr); 467 if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: " 468 + RemoteAddr + ", channel: " + channel + ", status: " + status); 469 if(status != 0) 470 throw new IOException("Connection failure, status: " + status); 471 return RemoteAddr; 472 } 473 private int readAll(InputStream is, byte[] b) throws IOException { 474 int left = b.length; 475 while(left > 0) { 476 int ret = is.read(b, b.length - left, left); 477 if(ret <= 0) 478 throw new IOException("read failed, socket might closed, read ret: " + ret); 479 left -= ret; 480 if(left != 0) 481 Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) + 482 ", expect size: " + b.length); 483 } 484 return b.length; 485 } 486 487 private int readInt(InputStream is) throws IOException { 488 byte[] ibytes = new byte[4]; 489 int ret = readAll(is, ibytes); 490 if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret); 491 ByteBuffer bb = ByteBuffer.wrap(ibytes); 492 bb.order(ByteOrder.nativeOrder()); 493 return bb.getInt(); 494 } 495} 496