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