Home | History | Annotate | Download | only in bluetooth
      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