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