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