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            // TODO(BT) derive flag from auth and encrypt
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            // TODO(BT) right error code?
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        // TODO(BT) wait on an incoming connection
392        String RemoteAddr = waitSocketSignal(mSocketIS);
393        synchronized(this)
394        {
395            if (mSocketState != SocketState.LISTENING)
396                throw new IOException("bt socket is not in listen state");
397            acceptedSocket = acceptSocket(RemoteAddr);
398            //quick drop the reference of the file handle
399        }
400        // TODO(BT) rfcomm socket only supports one connection, return this?
401        // return this;
402        return acceptedSocket;
403    }
404
405    /*package*/ int available() throws IOException {
406        if (VDBG) Log.d(TAG, "available: " + mSocketIS);
407        return mSocketIS.available();
408    }
409
410    /*package*/ int read(byte[] b, int offset, int length) throws IOException {
411
412            if (VDBG) Log.d(TAG, "read in:  " + mSocketIS + " len: " + length);
413            int ret = mSocketIS.read(b, offset, length);
414            if(ret < 0)
415                throw new IOException("bt socket closed, read return: " + ret);
416            if (VDBG) Log.d(TAG, "read out:  " + mSocketIS + " ret: " + ret);
417            return ret;
418    }
419
420    /*package*/ int write(byte[] b, int offset, int length) throws IOException {
421
422            if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
423            mSocketOS.write(b, offset, length);
424            // There is no good way to confirm since the entire process is asynchronous anyway
425            if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
426            return length;
427    }
428
429    @Override
430    public void close() throws IOException {
431        Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
432        if(mSocketState == SocketState.CLOSED)
433            return;
434        else
435        {
436            synchronized(this)
437            {
438                 if(mSocketState == SocketState.CLOSED)
439                    return;
440                 mSocketState = SocketState.CLOSED;
441                 if (VDBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
442                        ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
443                 if(mSocket != null) {
444                    if (VDBG) Log.d(TAG, "Closing mSocket: " + mSocket);
445                    mSocket.shutdownInput();
446                    mSocket.shutdownOutput();
447                    mSocket.close();
448                    mSocket = null;
449                }
450                if(mPfd != null)
451                    mPfd.detachFd();
452           }
453        }
454        // TODO(BT) unbind proxy,
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        byte [] addr = new byte[6];
475        bb.get(addr);
476        int channel = bb.getInt();
477        int status = bb.getInt();
478        String RemoteAddr = convertAddr(addr);
479        if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
480                + RemoteAddr + ", channel: " + channel + ", status: " + status);
481        if(status != 0)
482            throw new IOException("Connection failure, status: " + status);
483        return RemoteAddr;
484    }
485    private int readAll(InputStream is, byte[] b) throws IOException {
486        int left = b.length;
487        while(left > 0) {
488            int ret = is.read(b, b.length - left, left);
489            if(ret <= 0)
490                 throw new IOException("read failed, socket might closed, read ret: " + ret);
491            left -= ret;
492            if(left != 0)
493                Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
494                            ", expect size: " + b.length);
495        }
496        return b.length;
497    }
498
499    private int readInt(InputStream is) throws IOException {
500        byte[] ibytes = new byte[4];
501        int ret = readAll(is, ibytes);
502        if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret);
503        ByteBuffer bb = ByteBuffer.wrap(ibytes);
504        bb.order(ByteOrder.nativeOrder());
505        return bb.getInt();
506    }
507}
508