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