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