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