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