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