1 /* 2 * Copyright (C) 2009 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.bluetooth.IBluetoothCallback; 20 import android.os.ParcelUuid; 21 import android.os.RemoteException; 22 import android.util.Log; 23 24 import java.io.Closeable; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import java.util.concurrent.locks.ReentrantReadWriteLock; 29 30 /** 31 * A connected or connecting Bluetooth socket. 32 * 33 * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: 34 * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server 35 * side, use a {@link BluetoothServerSocket} to create a listening server 36 * socket. When a connection is accepted by the {@link BluetoothServerSocket}, 37 * it will return a new {@link BluetoothSocket} to manage the connection. 38 * On the client side, use a single {@link BluetoothSocket} to both initiate 39 * an outgoing connection and to manage the connection. 40 * 41 * <p>The most common type of Bluetooth socket is RFCOMM, which is the type 42 * supported by the Android APIs. RFCOMM is a connection-oriented, streaming 43 * transport over Bluetooth. It is also known as the Serial Port Profile (SPP). 44 * 45 * <p>To create a {@link BluetoothSocket} for connecting to a known device, use 46 * {@link BluetoothDevice#createRfcommSocketToServiceRecord 47 * BluetoothDevice.createRfcommSocketToServiceRecord()}. 48 * Then call {@link #connect()} to attempt a connection to the remote device. 49 * This call will block until a connection is established or the connection 50 * fails. 51 * 52 * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the 53 * {@link BluetoothServerSocket} documentation. 54 * 55 * <p>Once the socket is connected, whether initiated as a client or accepted 56 * as a server, open the IO streams by calling {@link #getInputStream} and 57 * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream} 58 * and {@link java.io.OutputStream} objects, respectively, which are 59 * automatically connected to the socket. 60 * 61 * <p>{@link BluetoothSocket} is thread 62 * safe. In particular, {@link #close} will always immediately abort ongoing 63 * operations and close the socket. 64 * 65 * <p class="note"><strong>Note:</strong> 66 * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. 67 * 68 * {@see BluetoothServerSocket} 69 * {@see java.io.InputStream} 70 * {@see java.io.OutputStream} 71 */ 72 public final class BluetoothSocket implements Closeable { 73 private static final String TAG = "BluetoothSocket"; 74 75 /** @hide */ 76 public static final int MAX_RFCOMM_CHANNEL = 30; 77 78 /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */ 79 /*package*/ static final int TYPE_RFCOMM = 1; 80 /*package*/ static final int TYPE_SCO = 2; 81 /*package*/ static final int TYPE_L2CAP = 3; 82 83 /*package*/ static final int EBADFD = 77; 84 /*package*/ static final int EADDRINUSE = 98; 85 86 private final int mType; /* one of TYPE_RFCOMM etc */ 87 private final BluetoothDevice mDevice; /* remote device */ 88 private final String mAddress; /* remote address */ 89 private final boolean mAuth; 90 private final boolean mEncrypt; 91 private final BluetoothInputStream mInputStream; 92 private final BluetoothOutputStream mOutputStream; 93 private final SdpHelper mSdp; 94 95 private int mPort; /* RFCOMM channel or L2CAP psm */ 96 97 private enum SocketState { 98 INIT, 99 CONNECTED, 100 CLOSED 101 } 102 103 /** prevents all native calls after destroyNative() */ 104 private SocketState mSocketState; 105 106 /** protects mSocketState */ 107 private final ReentrantReadWriteLock mLock; 108 109 /** used by native code only */ 110 private int mSocketData; 111 112 /** 113 * Construct a BluetoothSocket. 114 * @param type type of socket 115 * @param fd fd to use for connected socket, or -1 for a new socket 116 * @param auth require the remote device to be authenticated 117 * @param encrypt require the connection to be encrypted 118 * @param device remote device that this socket can connect to 119 * @param port remote port 120 * @param uuid SDP uuid 121 * @throws IOException On error, for example Bluetooth not available, or 122 * insufficient privileges 123 */ 124 /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, 125 BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { 126 if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) { 127 if (port < 1 || port > MAX_RFCOMM_CHANNEL) { 128 throw new IOException("Invalid RFCOMM channel: " + port); 129 } 130 } 131 if (uuid == null) { 132 mPort = port; 133 mSdp = null; 134 } else { 135 mSdp = new SdpHelper(device, uuid); 136 mPort = -1; 137 } 138 mType = type; 139 mAuth = auth; 140 mEncrypt = encrypt; 141 mDevice = device; 142 if (device == null) { 143 mAddress = null; 144 } else { 145 mAddress = device.getAddress(); 146 } 147 if (fd == -1) { 148 initSocketNative(); 149 } else { 150 initSocketFromFdNative(fd); 151 } 152 mInputStream = new BluetoothInputStream(this); 153 mOutputStream = new BluetoothOutputStream(this); 154 mSocketState = SocketState.INIT; 155 mLock = new ReentrantReadWriteLock(); 156 } 157 158 /** 159 * Construct a BluetoothSocket from address. Used by native code. 160 * @param type type of socket 161 * @param fd fd to use for connected socket, or -1 for a new socket 162 * @param auth require the remote device to be authenticated 163 * @param encrypt require the connection to be encrypted 164 * @param address remote device that this socket can connect to 165 * @param port remote port 166 * @throws IOException On error, for example Bluetooth not available, or 167 * insufficient privileges 168 */ 169 private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, 170 int port) throws IOException { 171 this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null); 172 } 173 174 /** @hide */ 175 @Override 176 protected void finalize() throws Throwable { 177 try { 178 close(); 179 } finally { 180 super.finalize(); 181 } 182 } 183 184 /** 185 * Attempt to connect to a remote device. 186 * <p>This method will block until a connection is made or the connection 187 * fails. If this method returns without an exception then this socket 188 * is now connected. 189 * <p>Creating new connections to 190 * remote Bluetooth devices should not be attempted while device discovery 191 * is in progress. Device discovery is a heavyweight procedure on the 192 * Bluetooth adapter and will significantly slow a device connection. 193 * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing 194 * discovery. Discovery is not managed by the Activity, 195 * but is run as a system service, so an application should always call 196 * {@link BluetoothAdapter#cancelDiscovery()} even if it 197 * did not directly request a discovery, just to be sure. 198 * <p>{@link #close} can be used to abort this call from another thread. 199 * @throws IOException on error, for example connection failure 200 */ 201 public void connect() throws IOException { 202 mLock.readLock().lock(); 203 try { 204 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 205 206 if (mSdp != null) { 207 mPort = mSdp.doSdp(); // blocks 208 } 209 210 connectNative(); // blocks 211 mSocketState = SocketState.CONNECTED; 212 } finally { 213 mLock.readLock().unlock(); 214 } 215 } 216 217 /** 218 * Immediately close this socket, and release all associated resources. 219 * <p>Causes blocked calls on this socket in other threads to immediately 220 * throw an IOException. 221 */ 222 public void close() throws IOException { 223 // abort blocking operations on the socket 224 mLock.readLock().lock(); 225 try { 226 if (mSocketState == SocketState.CLOSED) return; 227 if (mSdp != null) { 228 mSdp.cancel(); 229 } 230 abortNative(); 231 } finally { 232 mLock.readLock().unlock(); 233 } 234 235 // all native calls are guaranteed to immediately return after 236 // abortNative(), so this lock should immediately acquire 237 mLock.writeLock().lock(); 238 try { 239 mSocketState = SocketState.CLOSED; 240 destroyNative(); 241 } finally { 242 mLock.writeLock().unlock(); 243 } 244 } 245 246 /** 247 * Get the remote device this socket is connecting, or connected, to. 248 * @return remote device 249 */ 250 public BluetoothDevice getRemoteDevice() { 251 return mDevice; 252 } 253 254 /** 255 * Get the input stream associated with this socket. 256 * <p>The input 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 InputStream 260 */ 261 public InputStream getInputStream() throws IOException { 262 return mInputStream; 263 } 264 265 /** 266 * Get the output stream associated with this socket. 267 * <p>The output stream will be returned even if the socket is not yet 268 * connected, but operations on that stream will throw IOException until 269 * the associated socket is connected. 270 * @return OutputStream 271 */ 272 public OutputStream getOutputStream() throws IOException { 273 return mOutputStream; 274 } 275 276 /** 277 * Get the connection status of this socket, ie, whether there is an active connection with 278 * remote device. 279 * @return true if connected 280 * false if not connected 281 */ 282 public boolean isConnected() { 283 return (mSocketState == SocketState.CONNECTED); 284 } 285 286 /** 287 * Currently returns unix errno instead of throwing IOException, 288 * so that BluetoothAdapter can check the error code for EADDRINUSE 289 */ 290 /*package*/ int bindListen() { 291 mLock.readLock().lock(); 292 try { 293 if (mSocketState == SocketState.CLOSED) return EBADFD; 294 return bindListenNative(); 295 } finally { 296 mLock.readLock().unlock(); 297 } 298 } 299 300 /*package*/ BluetoothSocket accept(int timeout) throws IOException { 301 mLock.readLock().lock(); 302 try { 303 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 304 305 BluetoothSocket acceptedSocket = acceptNative(timeout); 306 mSocketState = SocketState.CONNECTED; 307 return acceptedSocket; 308 } finally { 309 mLock.readLock().unlock(); 310 } 311 } 312 313 /*package*/ int available() throws IOException { 314 mLock.readLock().lock(); 315 try { 316 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 317 return availableNative(); 318 } finally { 319 mLock.readLock().unlock(); 320 } 321 } 322 323 /*package*/ int read(byte[] b, int offset, int length) throws IOException { 324 mLock.readLock().lock(); 325 try { 326 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 327 return readNative(b, offset, length); 328 } finally { 329 mLock.readLock().unlock(); 330 } 331 } 332 333 /*package*/ int write(byte[] b, int offset, int length) throws IOException { 334 mLock.readLock().lock(); 335 try { 336 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 337 return writeNative(b, offset, length); 338 } finally { 339 mLock.readLock().unlock(); 340 } 341 } 342 343 private native void initSocketNative() throws IOException; 344 private native void initSocketFromFdNative(int fd) throws IOException; 345 private native void connectNative() throws IOException; 346 private native int bindListenNative(); 347 private native BluetoothSocket acceptNative(int timeout) throws IOException; 348 private native int availableNative() throws IOException; 349 private native int readNative(byte[] b, int offset, int length) throws IOException; 350 private native int writeNative(byte[] b, int offset, int length) throws IOException; 351 private native void abortNative() throws IOException; 352 private native void destroyNative() throws IOException; 353 /** 354 * Throws an IOException for given posix errno. Done natively so we can 355 * use strerr to convert to string error. 356 */ 357 /*package*/ native void throwErrnoNative(int errno) throws IOException; 358 359 /** 360 * Helper to perform blocking SDP lookup. 361 */ 362 private static class SdpHelper extends IBluetoothCallback.Stub { 363 private final IBluetooth service; 364 private final ParcelUuid uuid; 365 private final BluetoothDevice device; 366 private int channel; 367 private boolean canceled; 368 public SdpHelper(BluetoothDevice device, ParcelUuid uuid) { 369 service = BluetoothDevice.getService(); 370 this.device = device; 371 this.uuid = uuid; 372 canceled = false; 373 } 374 /** 375 * Returns the RFCOMM channel for the UUID, or throws IOException 376 * on failure. 377 */ 378 public synchronized int doSdp() throws IOException { 379 if (canceled) throw new IOException("Service discovery canceled"); 380 channel = -1; 381 382 boolean inProgress = false; 383 try { 384 inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this); 385 } catch (RemoteException e) {Log.e(TAG, "", e);} 386 387 if (!inProgress) throw new IOException("Unable to start Service Discovery"); 388 389 try { 390 /* 12 second timeout as a precaution - onRfcommChannelFound 391 * should always occur before the timeout */ 392 wait(12000); // block 393 394 } catch (InterruptedException e) {} 395 396 if (canceled) throw new IOException("Service discovery canceled"); 397 if (channel < 1) throw new IOException("Service discovery failed"); 398 399 return channel; 400 } 401 /** Object cannot be re-used after calling cancel() */ 402 public synchronized void cancel() { 403 if (!canceled) { 404 canceled = true; 405 channel = -1; 406 notifyAll(); // unblock 407 } 408 } 409 public synchronized void onRfcommChannelFound(int channel) { 410 if (!canceled) { 411 this.channel = channel; 412 notifyAll(); // unblock 413 } 414 } 415 } 416 } 417