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