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