Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2012 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.os.ParcelUuid;
     20 import android.os.ParcelFileDescriptor;
     21 import android.os.RemoteException;
     22 import android.util.Log;
     23 
     24 import java.io.Closeable;
     25 import java.io.FileDescriptor;
     26 import java.io.IOException;
     27 import java.io.InputStream;
     28 import java.io.OutputStream;
     29 import java.util.Locale;
     30 import java.util.UUID;
     31 import android.net.LocalSocket;
     32 import java.nio.ByteOrder;
     33 import java.nio.ByteBuffer;
     34 /**
     35  * A connected or connecting Bluetooth socket.
     36  *
     37  * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets:
     38  * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server
     39  * side, use a {@link BluetoothServerSocket} to create a listening server
     40  * socket. When a connection is accepted by the {@link BluetoothServerSocket},
     41  * it will return a new {@link BluetoothSocket} to manage the connection.
     42  * On the client side, use a single {@link BluetoothSocket} to both initiate
     43  * an outgoing connection and to manage the connection.
     44  *
     45  * <p>The most common type of Bluetooth socket is RFCOMM, which is the type
     46  * supported by the Android APIs. RFCOMM is a connection-oriented, streaming
     47  * transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
     48  *
     49  * <p>To create a {@link BluetoothSocket} for connecting to a known device, use
     50  * {@link BluetoothDevice#createRfcommSocketToServiceRecord
     51  * BluetoothDevice.createRfcommSocketToServiceRecord()}.
     52  * Then call {@link #connect()} to attempt a connection to the remote device.
     53  * This call will block until a connection is established or the connection
     54  * fails.
     55  *
     56  * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the
     57  * {@link BluetoothServerSocket} documentation.
     58  *
     59  * <p>Once the socket is connected, whether initiated as a client or accepted
     60  * as a server, open the IO streams by calling {@link #getInputStream} and
     61  * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream}
     62  * and {@link java.io.OutputStream} objects, respectively, which are
     63  * automatically connected to the socket.
     64  *
     65  * <p>{@link BluetoothSocket} is thread
     66  * safe. In particular, {@link #close} will always immediately abort ongoing
     67  * operations and close the socket.
     68  *
     69  * <p class="note"><strong>Note:</strong>
     70  * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
     71  *
     72  * <div class="special reference">
     73  * <h3>Developer Guides</h3>
     74  * <p>For more information about using Bluetooth, read the
     75  * <a href="{@docRoot}guide/topics/wireless/bluetooth.html">Bluetooth</a> developer guide.</p>
     76  * </div>
     77  *
     78  * {@see BluetoothServerSocket}
     79  * {@see java.io.InputStream}
     80  * {@see java.io.OutputStream}
     81  */
     82 public final class BluetoothSocket implements Closeable {
     83     private static final String TAG = "BluetoothSocket";
     84     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     85     private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
     86 
     87     /** @hide */
     88     public static final int MAX_RFCOMM_CHANNEL = 30;
     89 
     90     /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
     91     /*package*/ static final int TYPE_RFCOMM = 1;
     92     /*package*/ static final int TYPE_SCO = 2;
     93     /*package*/ static final int TYPE_L2CAP = 3;
     94 
     95     /*package*/ static final int EBADFD = 77;
     96     /*package*/ static final int EADDRINUSE = 98;
     97 
     98     /*package*/ static final int SEC_FLAG_ENCRYPT = 1;
     99     /*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
    100 
    101     private final int mType;  /* one of TYPE_RFCOMM etc */
    102     private BluetoothDevice mDevice;    /* remote device */
    103     private String mAddress;    /* remote address */
    104     private final boolean mAuth;
    105     private final boolean mEncrypt;
    106     private final BluetoothInputStream mInputStream;
    107     private final BluetoothOutputStream mOutputStream;
    108     private final ParcelUuid mUuid;
    109     private ParcelFileDescriptor mPfd;
    110     private LocalSocket mSocket;
    111     private InputStream mSocketIS;
    112     private OutputStream mSocketOS;
    113     private int mPort;  /* RFCOMM channel or L2CAP psm */
    114     private int mFd;
    115     private String mServiceName;
    116     private static int PROXY_CONNECTION_TIMEOUT = 5000;
    117 
    118     private static int SOCK_SIGNAL_SIZE = 16;
    119 
    120     private enum SocketState {
    121         INIT,
    122         CONNECTED,
    123         LISTENING,
    124         CLOSED,
    125     }
    126 
    127     /** prevents all native calls after destroyNative() */
    128     private volatile SocketState mSocketState;
    129 
    130     /** protects mSocketState */
    131     //private final ReentrantReadWriteLock mLock;
    132 
    133     /**
    134      * Construct a BluetoothSocket.
    135      * @param type    type of socket
    136      * @param fd      fd to use for connected socket, or -1 for a new socket
    137      * @param auth    require the remote device to be authenticated
    138      * @param encrypt require the connection to be encrypted
    139      * @param device  remote device that this socket can connect to
    140      * @param port    remote port
    141      * @param uuid    SDP uuid
    142      * @throws IOException On error, for example Bluetooth not available, or
    143      *                     insufficient privileges
    144      */
    145     /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
    146             BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
    147         if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
    148             if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
    149                 throw new IOException("Invalid RFCOMM channel: " + port);
    150             }
    151         }
    152         if(uuid != null)
    153             mUuid = uuid;
    154         else mUuid = new ParcelUuid(new UUID(0, 0));
    155         mType = type;
    156         mAuth = auth;
    157         mEncrypt = encrypt;
    158         mDevice = device;
    159         mPort = port;
    160         mFd = fd;
    161 
    162         mSocketState = SocketState.INIT;
    163 
    164         if (device == null) {
    165             // Server socket
    166             mAddress = BluetoothAdapter.getDefaultAdapter().getAddress();
    167         } else {
    168             // Remote socket
    169             mAddress = device.getAddress();
    170         }
    171         mInputStream = new BluetoothInputStream(this);
    172         mOutputStream = new BluetoothOutputStream(this);
    173     }
    174     private BluetoothSocket(BluetoothSocket s) {
    175         mUuid = s.mUuid;
    176         mType = s.mType;
    177         mAuth = s.mAuth;
    178         mEncrypt = s.mEncrypt;
    179         mPort = s.mPort;
    180         mInputStream = new BluetoothInputStream(this);
    181         mOutputStream = new BluetoothOutputStream(this);
    182         mServiceName = s.mServiceName;
    183     }
    184     private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
    185         BluetoothSocket as = new BluetoothSocket(this);
    186         as.mSocketState = SocketState.CONNECTED;
    187         FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
    188         if (DBG) Log.d(TAG, "socket fd passed by stack  fds: " + fds);
    189         if(fds == null || fds.length != 1) {
    190             Log.e(TAG, "socket fd passed from stack failed, fds: " + fds);
    191             as.close();
    192             throw new IOException("bt socket acept failed");
    193         }
    194         as.mSocket = new LocalSocket(fds[0]);
    195         as.mSocketIS = as.mSocket.getInputStream();
    196         as.mSocketOS = as.mSocket.getOutputStream();
    197         as.mAddress = RemoteAddr;
    198         as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(RemoteAddr);
    199         return as;
    200     }
    201     /**
    202      * Construct a BluetoothSocket from address. Used by native code.
    203      * @param type    type of socket
    204      * @param fd      fd to use for connected socket, or -1 for a new socket
    205      * @param auth    require the remote device to be authenticated
    206      * @param encrypt require the connection to be encrypted
    207      * @param address remote device that this socket can connect to
    208      * @param port    remote port
    209      * @throws IOException On error, for example Bluetooth not available, or
    210      *                     insufficient privileges
    211      */
    212     private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
    213             int port) throws IOException {
    214         this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
    215     }
    216 
    217     /** @hide */
    218     @Override
    219     protected void finalize() throws Throwable {
    220         try {
    221             close();
    222         } finally {
    223             super.finalize();
    224         }
    225     }
    226     private int getSecurityFlags() {
    227         int flags = 0;
    228         if(mAuth)
    229             flags |= SEC_FLAG_AUTH;
    230         if(mEncrypt)
    231             flags |= SEC_FLAG_ENCRYPT;
    232         return flags;
    233     }
    234 
    235     /**
    236      * Get the remote device this socket is connecting, or connected, to.
    237      * @return remote device
    238      */
    239     public BluetoothDevice getRemoteDevice() {
    240         return mDevice;
    241     }
    242 
    243     /**
    244      * Get the input stream associated with this socket.
    245      * <p>The input stream will be returned even if the socket is not yet
    246      * connected, but operations on that stream will throw IOException until
    247      * the associated socket is connected.
    248      * @return InputStream
    249      */
    250     public InputStream getInputStream() throws IOException {
    251         return mInputStream;
    252     }
    253 
    254     /**
    255      * Get the output stream associated with this socket.
    256      * <p>The output 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 OutputStream
    260      */
    261     public OutputStream getOutputStream() throws IOException {
    262         return mOutputStream;
    263     }
    264 
    265     /**
    266      * Get the connection status of this socket, ie, whether there is an active connection with
    267      * remote device.
    268      * @return true if connected
    269      *         false if not connected
    270      */
    271     public boolean isConnected() {
    272         return mSocketState == SocketState.CONNECTED;
    273     }
    274 
    275     /*package*/ void setServiceName(String name) {
    276         mServiceName = name;
    277     }
    278 
    279     /**
    280      * Attempt to connect to a remote device.
    281      * <p>This method will block until a connection is made or the connection
    282      * fails. If this method returns without an exception then this socket
    283      * is now connected.
    284      * <p>Creating new connections to
    285      * remote Bluetooth devices should not be attempted while device discovery
    286      * is in progress. Device discovery is a heavyweight procedure on the
    287      * Bluetooth adapter and will significantly slow a device connection.
    288      * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
    289      * discovery. Discovery is not managed by the Activity,
    290      * but is run as a system service, so an application should always call
    291      * {@link BluetoothAdapter#cancelDiscovery()} even if it
    292      * did not directly request a discovery, just to be sure.
    293      * <p>{@link #close} can be used to abort this call from another thread.
    294      * @throws IOException on error, for example connection failure
    295      */
    296     public void connect() throws IOException {
    297         if (mDevice == null) throw new IOException("Connect is called on null device");
    298 
    299         try {
    300             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
    301             IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
    302             if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
    303             mPfd = bluetoothProxy.connectSocket(mDevice, mType,
    304                     mUuid, mPort, getSecurityFlags());
    305             synchronized(this)
    306             {
    307                 if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
    308                 if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
    309                 if (mPfd == null) throw new IOException("bt socket connect failed");
    310                 FileDescriptor fd = mPfd.getFileDescriptor();
    311                 mSocket = new LocalSocket(fd);
    312                 mSocketIS = mSocket.getInputStream();
    313                 mSocketOS = mSocket.getOutputStream();
    314             }
    315             int channel = readInt(mSocketIS);
    316             if (channel <= 0)
    317                 throw new IOException("bt socket connect failed");
    318             mPort = channel;
    319             waitSocketSignal(mSocketIS);
    320             synchronized(this)
    321             {
    322                 if (mSocketState == SocketState.CLOSED)
    323                     throw new IOException("bt socket closed");
    324                 mSocketState = SocketState.CONNECTED;
    325             }
    326         } catch (RemoteException e) {
    327             Log.e(TAG, Log.getStackTraceString(new Throwable()));
    328             throw new IOException("unable to send RPC: " + e.getMessage());
    329         }
    330     }
    331 
    332     /**
    333      * Currently returns unix errno instead of throwing IOException,
    334      * so that BluetoothAdapter can check the error code for EADDRINUSE
    335      */
    336     /*package*/ int bindListen() {
    337         int ret;
    338         if (mSocketState == SocketState.CLOSED) return EBADFD;
    339         IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
    340         if (bluetoothProxy == null) {
    341             Log.e(TAG, "bindListen fail, reason: bluetooth is off");
    342             return -1;
    343         }
    344         try {
    345             mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
    346                     mUuid, mPort, getSecurityFlags());
    347         } catch (RemoteException e) {
    348             Log.e(TAG, Log.getStackTraceString(new Throwable()));
    349             return -1;
    350         }
    351 
    352         // read out port number
    353         try {
    354             synchronized(this) {
    355                 if (DBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " +
    356                                 mPfd);
    357                 if(mSocketState != SocketState.INIT) return EBADFD;
    358                 if(mPfd == null) return -1;
    359                 FileDescriptor fd = mPfd.getFileDescriptor();
    360                 if (DBG) Log.d(TAG, "bindListen(), new LocalSocket ");
    361                 mSocket = new LocalSocket(fd);
    362                 if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
    363                 mSocketIS = mSocket.getInputStream();
    364                 mSocketOS = mSocket.getOutputStream();
    365             }
    366             if (DBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
    367             int channel = readInt(mSocketIS);
    368             synchronized(this) {
    369                 if(mSocketState == SocketState.INIT)
    370                     mSocketState = SocketState.LISTENING;
    371             }
    372             if (DBG) Log.d(TAG, "channel: " + channel);
    373             if (mPort == -1) {
    374                 mPort = channel;
    375             } // else ASSERT(mPort == channel)
    376             ret = 0;
    377         } catch (IOException e) {
    378             if (mPfd != null) {
    379                 try {
    380                     mPfd.close();
    381                 } catch (IOException e1) {
    382                     Log.e(TAG, "bindListen, close mPfd: " + e1);
    383                 }
    384                 mPfd = null;
    385             }
    386             Log.e(TAG, "bindListen, fail to get port number, exception: " + e);
    387             return -1;
    388         }
    389         return ret;
    390     }
    391 
    392     /*package*/ BluetoothSocket accept(int timeout) throws IOException {
    393         BluetoothSocket acceptedSocket;
    394         if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
    395         if(timeout > 0) {
    396             Log.d(TAG, "accept() set timeout (ms):" + timeout);
    397            mSocket.setSoTimeout(timeout);
    398         }
    399         String RemoteAddr = waitSocketSignal(mSocketIS);
    400         if(timeout > 0)
    401             mSocket.setSoTimeout(0);
    402         synchronized(this)
    403         {
    404             if (mSocketState != SocketState.LISTENING)
    405                 throw new IOException("bt socket is not in listen state");
    406             acceptedSocket = acceptSocket(RemoteAddr);
    407             //quick drop the reference of the file handle
    408         }
    409         return acceptedSocket;
    410     }
    411 
    412     /*package*/ int available() throws IOException {
    413         if (VDBG) Log.d(TAG, "available: " + mSocketIS);
    414         return mSocketIS.available();
    415     }
    416     /**
    417      * Wait until the data in sending queue is emptied. A polling version
    418      * for flush implementation. Used to ensure the writing data afterwards will
    419      * be packed in new RFCOMM frame.
    420      * @throws IOException
    421      *             if an i/o error occurs.
    422      */
    423     /*package*/ void flush() throws IOException {
    424         if (mSocketOS == null) throw new IOException("flush is called on null OutputStream");
    425         if (VDBG) Log.d(TAG, "flush: " + mSocketOS);
    426         mSocketOS.flush();
    427     }
    428 
    429     /*package*/ int read(byte[] b, int offset, int length) throws IOException {
    430         if (mSocketIS == null) throw new IOException("read is called on null InputStream");
    431         if (VDBG) Log.d(TAG, "read in:  " + mSocketIS + " len: " + length);
    432         int ret = mSocketIS.read(b, offset, length);
    433         if(ret < 0)
    434             throw new IOException("bt socket closed, read return: " + ret);
    435         if (VDBG) Log.d(TAG, "read out:  " + mSocketIS + " ret: " + ret);
    436         return ret;
    437     }
    438 
    439     /*package*/ int write(byte[] b, int offset, int length) throws IOException {
    440         if (mSocketOS == null) throw new IOException("write is called on null OutputStream");
    441         if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
    442         mSocketOS.write(b, offset, length);
    443         // There is no good way to confirm since the entire process is asynchronous anyway
    444         if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
    445         return length;
    446     }
    447 
    448     @Override
    449     public void close() throws IOException {
    450         if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
    451         if(mSocketState == SocketState.CLOSED)
    452             return;
    453         else
    454         {
    455             synchronized(this)
    456             {
    457                  if(mSocketState == SocketState.CLOSED)
    458                     return;
    459                  mSocketState = SocketState.CLOSED;
    460                  if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
    461                         ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
    462                  if(mSocket != null) {
    463                     if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
    464                     mSocket.shutdownInput();
    465                     mSocket.shutdownOutput();
    466                     mSocket.close();
    467                     mSocket = null;
    468                 }
    469                 if (mPfd != null) {
    470                     mPfd.close();
    471                     mPfd = null;
    472                 }
    473            }
    474         }
    475     }
    476 
    477     /*package */ void removeChannel() {
    478     }
    479 
    480     /*package */ int getPort() {
    481         return mPort;
    482     }
    483     private String convertAddr(final byte[] addr)  {
    484         return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
    485                 addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
    486     }
    487     private String waitSocketSignal(InputStream is) throws IOException {
    488         byte [] sig = new byte[SOCK_SIGNAL_SIZE];
    489         int ret = readAll(is, sig);
    490         if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
    491         ByteBuffer bb = ByteBuffer.wrap(sig);
    492         bb.order(ByteOrder.nativeOrder());
    493         int size = bb.getShort();
    494         if(size != SOCK_SIGNAL_SIZE)
    495             throw new IOException("Connection failure, wrong signal size: " + size);
    496         byte [] addr = new byte[6];
    497         bb.get(addr);
    498         int channel = bb.getInt();
    499         int status = bb.getInt();
    500         String RemoteAddr = convertAddr(addr);
    501         if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
    502                 + RemoteAddr + ", channel: " + channel + ", status: " + status);
    503         if(status != 0)
    504             throw new IOException("Connection failure, status: " + status);
    505         return RemoteAddr;
    506     }
    507     private int readAll(InputStream is, byte[] b) throws IOException {
    508         int left = b.length;
    509         while(left > 0) {
    510             int ret = is.read(b, b.length - left, left);
    511             if(ret <= 0)
    512                  throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
    513             left -= ret;
    514             if(left != 0)
    515                 Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
    516                             ", expect size: " + b.length);
    517         }
    518         return b.length;
    519     }
    520 
    521     private int readInt(InputStream is) throws IOException {
    522         byte[] ibytes = new byte[4];
    523         int ret = readAll(is, ibytes);
    524         if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret);
    525         ByteBuffer bb = ByteBuffer.wrap(ibytes);
    526         bb.order(ByteOrder.nativeOrder());
    527         return bb.getInt();
    528     }
    529 }
    530