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