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