BluetoothSocket.java revision 34b2feb041e263e11bbcec1f2a5094e21be9dc13
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 17package android.bluetooth; 18 19import android.bluetooth.IBluetoothCallback; 20import android.os.ParcelUuid; 21import android.os.RemoteException; 22import android.util.Log; 23 24import java.io.Closeable; 25import java.io.IOException; 26import java.io.InputStream; 27import java.io.OutputStream; 28import 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 */ 78public 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 if (mSocketState != SocketState.CLOSED) { 246 mSocketState = SocketState.CLOSED; 247 destroyNative(); 248 } 249 } finally { 250 mLock.writeLock().unlock(); 251 } 252 } 253 254 /** 255 * Get the remote device this socket is connecting, or connected, to. 256 * @return remote device 257 */ 258 public BluetoothDevice getRemoteDevice() { 259 return mDevice; 260 } 261 262 /** 263 * Get the input stream associated with this socket. 264 * <p>The input stream will be returned even if the socket is not yet 265 * connected, but operations on that stream will throw IOException until 266 * the associated socket is connected. 267 * @return InputStream 268 */ 269 public InputStream getInputStream() throws IOException { 270 return mInputStream; 271 } 272 273 /** 274 * Get the output stream associated with this socket. 275 * <p>The output stream will be returned even if the socket is not yet 276 * connected, but operations on that stream will throw IOException until 277 * the associated socket is connected. 278 * @return OutputStream 279 */ 280 public OutputStream getOutputStream() throws IOException { 281 return mOutputStream; 282 } 283 284 /** 285 * Get the connection status of this socket, ie, whether there is an active connection with 286 * remote device. 287 * @return true if connected 288 * false if not connected 289 */ 290 public boolean isConnected() { 291 return (mSocketState == SocketState.CONNECTED); 292 } 293 294 /** 295 * Currently returns unix errno instead of throwing IOException, 296 * so that BluetoothAdapter can check the error code for EADDRINUSE 297 */ 298 /*package*/ int bindListen() { 299 mLock.readLock().lock(); 300 try { 301 if (mSocketState == SocketState.CLOSED) return EBADFD; 302 return bindListenNative(); 303 } finally { 304 mLock.readLock().unlock(); 305 } 306 } 307 308 /*package*/ BluetoothSocket accept(int timeout) throws IOException { 309 mLock.readLock().lock(); 310 try { 311 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 312 313 BluetoothSocket acceptedSocket = acceptNative(timeout); 314 mSocketState = SocketState.CONNECTED; 315 return acceptedSocket; 316 } finally { 317 mLock.readLock().unlock(); 318 } 319 } 320 321 /*package*/ int available() throws IOException { 322 mLock.readLock().lock(); 323 try { 324 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 325 return availableNative(); 326 } finally { 327 mLock.readLock().unlock(); 328 } 329 } 330 331 /*package*/ int read(byte[] b, int offset, int length) throws IOException { 332 mLock.readLock().lock(); 333 try { 334 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 335 return readNative(b, offset, length); 336 } finally { 337 mLock.readLock().unlock(); 338 } 339 } 340 341 /*package*/ int write(byte[] b, int offset, int length) throws IOException { 342 mLock.readLock().lock(); 343 try { 344 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 345 return writeNative(b, offset, length); 346 } finally { 347 mLock.readLock().unlock(); 348 } 349 } 350 351 private native void initSocketNative() throws IOException; 352 private native void initSocketFromFdNative(int fd) throws IOException; 353 private native void connectNative() throws IOException; 354 private native int bindListenNative(); 355 private native BluetoothSocket acceptNative(int timeout) throws IOException; 356 private native int availableNative() throws IOException; 357 private native int readNative(byte[] b, int offset, int length) throws IOException; 358 private native int writeNative(byte[] b, int offset, int length) throws IOException; 359 private native void abortNative() throws IOException; 360 private native void destroyNative() throws IOException; 361 /** 362 * Throws an IOException for given posix errno. Done natively so we can 363 * use strerr to convert to string error. 364 */ 365 /*package*/ native void throwErrnoNative(int errno) throws IOException; 366 367 /** 368 * Helper to perform blocking SDP lookup. 369 */ 370 private static class SdpHelper extends IBluetoothCallback.Stub { 371 private final IBluetooth service; 372 private final ParcelUuid uuid; 373 private final BluetoothDevice device; 374 private int channel; 375 private boolean canceled; 376 public SdpHelper(BluetoothDevice device, ParcelUuid uuid) { 377 service = BluetoothDevice.getService(); 378 this.device = device; 379 this.uuid = uuid; 380 canceled = false; 381 } 382 /** 383 * Returns the RFCOMM channel for the UUID, or throws IOException 384 * on failure. 385 */ 386 public synchronized int doSdp() throws IOException { 387 if (canceled) throw new IOException("Service discovery canceled"); 388 channel = -1; 389 390 boolean inProgress = false; 391 try { 392 inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this); 393 } catch (RemoteException e) {Log.e(TAG, "", e);} 394 395 if (!inProgress) throw new IOException("Unable to start Service Discovery"); 396 397 try { 398 /* 12 second timeout as a precaution - onRfcommChannelFound 399 * should always occur before the timeout */ 400 wait(12000); // block 401 402 } catch (InterruptedException e) {} 403 404 if (canceled) throw new IOException("Service discovery canceled"); 405 if (channel < 1) throw new IOException("Service discovery failed"); 406 407 return channel; 408 } 409 /** Object cannot be re-used after calling cancel() */ 410 public synchronized void cancel() { 411 if (!canceled) { 412 canceled = true; 413 channel = -1; 414 notifyAll(); // unblock 415 } 416 } 417 public synchronized void onRfcommChannelFound(int channel) { 418 if (!canceled) { 419 this.channel = channel; 420 notifyAll(); // unblock 421 } 422 } 423 } 424} 425