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