BluetoothSocket.java revision e4caddbb7a3b39fd6a1ccf107c7dbf09bc8978e8
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 154 //TODO(BT) 155 /* 156 if (fd == -1) { 157 initSocketNative(); 158 } else { 159 initSocketFromFdNative(fd); 160 }*/ 161 mInputStream = new BluetoothInputStream(this); 162 mOutputStream = new BluetoothOutputStream(this); 163 mSocketState = SocketState.INIT; 164 mLock = new ReentrantReadWriteLock(); 165 } 166 167 /** 168 * Construct a BluetoothSocket from address. Used by native code. 169 * @param type type of socket 170 * @param fd fd to use for connected socket, or -1 for a new socket 171 * @param auth require the remote device to be authenticated 172 * @param encrypt require the connection to be encrypted 173 * @param address remote device that this socket can connect to 174 * @param port remote port 175 * @throws IOException On error, for example Bluetooth not available, or 176 * insufficient privileges 177 */ 178 private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, 179 int port) throws IOException { 180 this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null); 181 } 182 183 /** @hide */ 184 @Override 185 protected void finalize() throws Throwable { 186 try { 187 close(); 188 } finally { 189 super.finalize(); 190 } 191 } 192 193 /** 194 * Attempt to connect to a remote device. 195 * <p>This method will block until a connection is made or the connection 196 * fails. If this method returns without an exception then this socket 197 * is now connected. 198 * <p>Creating new connections to 199 * remote Bluetooth devices should not be attempted while device discovery 200 * is in progress. Device discovery is a heavyweight procedure on the 201 * Bluetooth adapter and will significantly slow a device connection. 202 * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing 203 * discovery. Discovery is not managed by the Activity, 204 * but is run as a system service, so an application should always call 205 * {@link BluetoothAdapter#cancelDiscovery()} even if it 206 * did not directly request a discovery, just to be sure. 207 * <p>{@link #close} can be used to abort this call from another thread. 208 * @throws IOException on error, for example connection failure 209 */ 210 public void connect() throws IOException { 211 mLock.readLock().lock(); 212 try { 213 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 214 215 if (mSdp != null) { 216 mPort = mSdp.doSdp(); // blocks 217 } 218 219 connectNative(); // blocks 220 mSocketState = SocketState.CONNECTED; 221 } finally { 222 mLock.readLock().unlock(); 223 } 224 } 225 226 /** 227 * Immediately close this socket, and release all associated resources. 228 * <p>Causes blocked calls on this socket in other threads to immediately 229 * throw an IOException. 230 */ 231 public void close() throws IOException { 232 // abort blocking operations on the socket 233 //TODO(BT) 234 /* 235 mLock.readLock().lock(); 236 try { 237 if (mSocketState == SocketState.CLOSED) return; 238 if (mSdp != null) { 239 mSdp.cancel(); 240 } 241 abortNative(); 242 } finally { 243 mLock.readLock().unlock(); 244 } 245 246 // all native calls are guaranteed to immediately return after 247 // abortNative(), so this lock should immediately acquire 248 mLock.writeLock().lock(); 249 try { 250 if (mSocketState != SocketState.CLOSED) { 251 mSocketState = SocketState.CLOSED; 252 destroyNative(); 253 } 254 } finally { 255 mLock.writeLock().unlock(); 256 }*/ 257 } 258 259 /** 260 * Get the remote device this socket is connecting, or connected, to. 261 * @return remote device 262 */ 263 public BluetoothDevice getRemoteDevice() { 264 return mDevice; 265 } 266 267 /** 268 * Get the input stream associated with this socket. 269 * <p>The input stream will be returned even if the socket is not yet 270 * connected, but operations on that stream will throw IOException until 271 * the associated socket is connected. 272 * @return InputStream 273 */ 274 public InputStream getInputStream() throws IOException { 275 return mInputStream; 276 } 277 278 /** 279 * Get the output stream associated with this socket. 280 * <p>The output stream will be returned even if the socket is not yet 281 * connected, but operations on that stream will throw IOException until 282 * the associated socket is connected. 283 * @return OutputStream 284 */ 285 public OutputStream getOutputStream() throws IOException { 286 return mOutputStream; 287 } 288 289 /** 290 * Get the connection status of this socket, ie, whether there is an active connection with 291 * remote device. 292 * @return true if connected 293 * false if not connected 294 */ 295 public boolean isConnected() { 296 return (mSocketState == SocketState.CONNECTED); 297 } 298 299 /** 300 * Currently returns unix errno instead of throwing IOException, 301 * so that BluetoothAdapter can check the error code for EADDRINUSE 302 */ 303 /*package*/ int bindListen() { 304 return -1; 305 //TODO(BT) 306 /* 307 mLock.readLock().lock(); 308 try { 309 if (mSocketState == SocketState.CLOSED) return EBADFD; 310 return bindListenNative(); 311 } finally { 312 mLock.readLock().unlock(); 313 }*/ 314 } 315 316 /*package*/ BluetoothSocket accept(int timeout) throws IOException { 317 mLock.readLock().lock(); 318 try { 319 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 320 321 BluetoothSocket acceptedSocket = acceptNative(timeout); 322 mSocketState = SocketState.CONNECTED; 323 return acceptedSocket; 324 } finally { 325 mLock.readLock().unlock(); 326 } 327 } 328 329 /*package*/ int available() throws IOException { 330 mLock.readLock().lock(); 331 try { 332 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 333 return availableNative(); 334 } finally { 335 mLock.readLock().unlock(); 336 } 337 } 338 339 /*package*/ int read(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 readNative(b, offset, length); 344 } finally { 345 mLock.readLock().unlock(); 346 } 347 } 348 349 /*package*/ int write(byte[] b, int offset, int length) throws IOException { 350 mLock.readLock().lock(); 351 try { 352 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 353 return writeNative(b, offset, length); 354 } finally { 355 mLock.readLock().unlock(); 356 } 357 } 358 359 private native void initSocketNative() throws IOException; 360 private native void initSocketFromFdNative(int fd) throws IOException; 361 private native void connectNative() throws IOException; 362 private native int bindListenNative(); 363 private native BluetoothSocket acceptNative(int timeout) throws IOException; 364 private native int availableNative() throws IOException; 365 private native int readNative(byte[] b, int offset, int length) throws IOException; 366 private native int writeNative(byte[] b, int offset, int length) throws IOException; 367 private native void abortNative() throws IOException; 368 private native void destroyNative() throws IOException; 369 /** 370 * Throws an IOException for given posix errno. Done natively so we can 371 * use strerr to convert to string error. 372 */ 373 /*package*/ native void throwErrnoNative(int errno) throws IOException; 374 375 /** 376 * Helper to perform blocking SDP lookup. 377 */ 378 private static class SdpHelper extends IBluetoothCallback.Stub { 379 private final IBluetooth service; 380 private final ParcelUuid uuid; 381 private final BluetoothDevice device; 382 private int channel; 383 private boolean canceled; 384 public SdpHelper(BluetoothDevice device, ParcelUuid uuid) { 385 service = BluetoothDevice.getService(); 386 this.device = device; 387 this.uuid = uuid; 388 canceled = false; 389 } 390 /** 391 * Returns the RFCOMM channel for the UUID, or throws IOException 392 * on failure. 393 */ 394 public synchronized int doSdp() throws IOException { 395 if (canceled) throw new IOException("Service discovery canceled"); 396 channel = -1; 397 398 boolean inProgress = false; 399 //TODO(BT) 400 /* 401 try { 402 inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this); 403 } catch (RemoteException e) {Log.e(TAG, "", e);}*/ 404 405 if (!inProgress) throw new IOException("Unable to start Service Discovery"); 406 407 try { 408 /* 12 second timeout as a precaution - onRfcommChannelFound 409 * should always occur before the timeout */ 410 wait(12000); // block 411 412 } catch (InterruptedException e) {} 413 414 if (canceled) throw new IOException("Service discovery canceled"); 415 if (channel < 1) throw new IOException("Service discovery failed"); 416 417 return channel; 418 } 419 /** Object cannot be re-used after calling cancel() */ 420 public synchronized void cancel() { 421 if (!canceled) { 422 canceled = true; 423 channel = -1; 424 notifyAll(); // unblock 425 } 426 } 427 public synchronized void onRfcommChannelFound(int channel) { 428 if (!canceled) { 429 this.channel = channel; 430 notifyAll(); // unblock 431 } 432 } 433 } 434} 435