BluetoothSocket.java revision 652678af4d18b9a6c18ce6a860de01d958bc82fe
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
17package android.bluetooth;
18
19import android.os.IBinder;
20import android.os.ParcelUuid;
21import android.os.ParcelFileDescriptor;
22import android.os.RemoteException;
23import android.os.ServiceManager;
24import android.util.Log;
25
26import java.io.Closeable;
27import java.io.FileDescriptor;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.util.List;
34import java.util.UUID;
35import android.net.LocalSocket;
36import java.nio.ByteOrder;
37import 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 */
86public 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            throw new IOException("bt socket acept failed");
196        }
197        as.mSocket = new LocalSocket(fds[0]);
198        as.mSocketIS = as.mSocket.getInputStream();
199        as.mSocketOS = as.mSocket.getOutputStream();
200        as.mAddress = RemoteAddr;
201        as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(RemoteAddr);
202        return as;
203    }
204    /**
205     * Construct a BluetoothSocket from address. Used by native code.
206     * @param type    type of socket
207     * @param fd      fd to use for connected socket, or -1 for a new socket
208     * @param auth    require the remote device to be authenticated
209     * @param encrypt require the connection to be encrypted
210     * @param address remote device that this socket can connect to
211     * @param port    remote port
212     * @throws IOException On error, for example Bluetooth not available, or
213     *                     insufficient privileges
214     */
215    private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
216            int port) throws IOException {
217        this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
218    }
219
220    /** @hide */
221    @Override
222    protected void finalize() throws Throwable {
223        try {
224            close();
225        } finally {
226            super.finalize();
227        }
228    }
229    private int getSecurityFlags() {
230        int flags = 0;
231        if(mAuth)
232            flags |= SEC_FLAG_AUTH;
233        if(mEncrypt)
234            flags |= SEC_FLAG_ENCRYPT;
235        return flags;
236    }
237
238    /**
239     * Get the remote device this socket is connecting, or connected, to.
240     * @return remote device
241     */
242    public BluetoothDevice getRemoteDevice() {
243        return mDevice;
244    }
245
246    /**
247     * Get the input stream associated with this socket.
248     * <p>The input stream will be returned even if the socket is not yet
249     * connected, but operations on that stream will throw IOException until
250     * the associated socket is connected.
251     * @return InputStream
252     */
253    public InputStream getInputStream() throws IOException {
254        return mInputStream;
255    }
256
257    /**
258     * Get the output stream associated with this socket.
259     * <p>The output stream will be returned even if the socket is not yet
260     * connected, but operations on that stream will throw IOException until
261     * the associated socket is connected.
262     * @return OutputStream
263     */
264    public OutputStream getOutputStream() throws IOException {
265        return mOutputStream;
266    }
267
268    /**
269     * Get the connection status of this socket, ie, whether there is an active connection with
270     * remote device.
271     * @return true if connected
272     *         false if not connected
273     */
274    public boolean isConnected() {
275        return mSocketState == SocketState.CONNECTED;
276    }
277
278    /*package*/ void setServiceName(String name) {
279        mServiceName = name;
280    }
281
282    /**
283     * Attempt to connect to a remote device.
284     * <p>This method will block until a connection is made or the connection
285     * fails. If this method returns without an exception then this socket
286     * is now connected.
287     * <p>Creating new connections to
288     * remote Bluetooth devices should not be attempted while device discovery
289     * is in progress. Device discovery is a heavyweight procedure on the
290     * Bluetooth adapter and will significantly slow a device connection.
291     * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
292     * discovery. Discovery is not managed by the Activity,
293     * but is run as a system service, so an application should always call
294     * {@link BluetoothAdapter#cancelDiscovery()} even if it
295     * did not directly request a discovery, just to be sure.
296     * <p>{@link #close} can be used to abort this call from another thread.
297     * @throws IOException on error, for example connection failure
298     */
299    public void connect() throws IOException {
300        if (mDevice == null) throw new IOException("Connect is called on null device");
301
302        try {
303            if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
304            IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
305            if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
306            mPfd = bluetoothProxy.connectSocket(mDevice, mType,
307                    mUuid, mPort, getSecurityFlags());
308            synchronized(this)
309            {
310                if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
311                if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
312                if (mPfd == null) throw new IOException("bt socket connect failed");
313                FileDescriptor fd = mPfd.getFileDescriptor();
314                mSocket = new LocalSocket(fd);
315                mSocketIS = mSocket.getInputStream();
316                mSocketOS = mSocket.getOutputStream();
317            }
318            int channel = readInt(mSocketIS);
319            if (channel <= 0)
320                throw new IOException("bt socket connect failed");
321            mPort = channel;
322            waitSocketSignal(mSocketIS);
323            synchronized(this)
324            {
325                if (mSocketState == SocketState.CLOSED)
326                    throw new IOException("bt socket closed");
327                mSocketState = SocketState.CONNECTED;
328            }
329        } catch (RemoteException e) {
330            Log.e(TAG, Log.getStackTraceString(new Throwable()));
331        }
332    }
333
334    /**
335     * Currently returns unix errno instead of throwing IOException,
336     * so that BluetoothAdapter can check the error code for EADDRINUSE
337     */
338    /*package*/ int bindListen() {
339        int ret;
340        if (mSocketState == SocketState.CLOSED) return EBADFD;
341        IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
342        if (bluetoothProxy == null) {
343            Log.e(TAG, "bindListen fail, reason: bluetooth is off");
344            return -1;
345        }
346        try {
347            mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
348                    mUuid, mPort, getSecurityFlags());
349        } catch (RemoteException e) {
350            Log.e(TAG, Log.getStackTraceString(new Throwable()));
351            return -1;
352        }
353
354        // read out port number
355        try {
356            synchronized(this) {
357                if (VDBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " +
358                                mPfd);
359                if(mSocketState != SocketState.INIT) return EBADFD;
360                if(mPfd == null) return -1;
361                FileDescriptor fd = mPfd.getFileDescriptor();
362                if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket ");
363                mSocket = new LocalSocket(fd);
364                if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
365                mSocketIS = mSocket.getInputStream();
366                mSocketOS = mSocket.getOutputStream();
367            }
368            if (VDBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
369            int channel = readInt(mSocketIS);
370            synchronized(this) {
371                if(mSocketState == SocketState.INIT)
372                    mSocketState = SocketState.LISTENING;
373            }
374            if (VDBG) Log.d(TAG, "channel: " + channel);
375            if (mPort == -1) {
376                mPort = channel;
377            } // else ASSERT(mPort == channel)
378            ret = 0;
379        } catch (IOException e) {
380            Log.e(TAG, "bindListen, fail to get port number, exception: " + e);
381            return -1;
382        }
383        return ret;
384    }
385
386    /*package*/ BluetoothSocket accept(int timeout) throws IOException {
387        BluetoothSocket acceptedSocket;
388        if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
389        if(timeout > 0) {
390            Log.d(TAG, "accept() set timeout (ms):" + timeout);
391           mSocket.setSoTimeout(timeout);
392        }
393        String RemoteAddr = waitSocketSignal(mSocketIS);
394        if(timeout > 0)
395            mSocket.setSoTimeout(0);
396        synchronized(this)
397        {
398            if (mSocketState != SocketState.LISTENING)
399                throw new IOException("bt socket is not in listen state");
400            acceptedSocket = acceptSocket(RemoteAddr);
401            //quick drop the reference of the file handle
402        }
403        return acceptedSocket;
404    }
405
406    /*package*/ int available() throws IOException {
407        if (VDBG) Log.d(TAG, "available: " + mSocketIS);
408        return mSocketIS.available();
409    }
410
411    /*package*/ int read(byte[] b, int offset, int length) throws IOException {
412
413            if (VDBG) Log.d(TAG, "read in:  " + mSocketIS + " len: " + length);
414            int ret = mSocketIS.read(b, offset, length);
415            if(ret < 0)
416                throw new IOException("bt socket closed, read return: " + ret);
417            if (VDBG) Log.d(TAG, "read out:  " + mSocketIS + " ret: " + ret);
418            return ret;
419    }
420
421    /*package*/ int write(byte[] b, int offset, int length) throws IOException {
422
423            if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
424            mSocketOS.write(b, offset, length);
425            // There is no good way to confirm since the entire process is asynchronous anyway
426            if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
427            return length;
428    }
429
430    @Override
431    public void close() throws IOException {
432        Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
433        if(mSocketState == SocketState.CLOSED)
434            return;
435        else
436        {
437            synchronized(this)
438            {
439                 if(mSocketState == SocketState.CLOSED)
440                    return;
441                 mSocketState = SocketState.CLOSED;
442                 if (VDBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
443                        ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
444                 if(mSocket != null) {
445                    if (VDBG) Log.d(TAG, "Closing mSocket: " + mSocket);
446                    mSocket.shutdownInput();
447                    mSocket.shutdownOutput();
448                    mSocket.close();
449                    mSocket = null;
450                }
451                if(mPfd != null)
452                    mPfd.detachFd();
453           }
454        }
455    }
456
457    /*package */ void removeChannel() {
458    }
459
460    /*package */ int getPort() {
461        return mPort;
462    }
463    private String convertAddr(final byte[] addr)  {
464        return String.format("%02X:%02X:%02X:%02X:%02X:%02X",
465                addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
466    }
467    private String waitSocketSignal(InputStream is) throws IOException {
468        byte [] sig = new byte[SOCK_SIGNAL_SIZE];
469        int ret = readAll(is, sig);
470        if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
471        ByteBuffer bb = ByteBuffer.wrap(sig);
472        bb.order(ByteOrder.nativeOrder());
473        int size = bb.getShort();
474        if(size != SOCK_SIGNAL_SIZE)
475            throw new IOException("Connection failure, wrong signal size: " + size);
476        byte [] addr = new byte[6];
477        bb.get(addr);
478        int channel = bb.getInt();
479        int status = bb.getInt();
480        String RemoteAddr = convertAddr(addr);
481        if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
482                + RemoteAddr + ", channel: " + channel + ", status: " + status);
483        if(status != 0)
484            throw new IOException("Connection failure, status: " + status);
485        return RemoteAddr;
486    }
487    private int readAll(InputStream is, byte[] b) throws IOException {
488        int left = b.length;
489        while(left > 0) {
490            int ret = is.read(b, b.length - left, left);
491            if(ret <= 0)
492                 throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
493            left -= ret;
494            if(left != 0)
495                Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
496                            ", expect size: " + b.length);
497        }
498        return b.length;
499    }
500
501    private int readInt(InputStream is) throws IOException {
502        byte[] ibytes = new byte[4];
503        int ret = readAll(is, ibytes);
504        if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret);
505        ByteBuffer bb = ByteBuffer.wrap(ibytes);
506        bb.order(ByteOrder.nativeOrder());
507        return bb.getInt();
508    }
509}
510