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     /** 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 privileges
    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 privileges
    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 immediately 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