• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.bluetooth;
18 
19 import android.os.IBinder;
20 import android.os.ParcelUuid;
21 import android.os.ParcelFileDescriptor;
22 import android.os.RemoteException;
23 import android.os.ServiceManager;
24 import android.util.Log;
25 
26 import java.io.Closeable;
27 import java.io.FileDescriptor;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.util.List;
34 import java.util.UUID;
35 import android.net.LocalSocket;
36 import java.nio.ByteOrder;
37 import 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  */
86 public 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      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, BluetoothDevice device, int port, ParcelUuid uuid)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     }
BluetoothSocket(BluetoothSocket s)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     }
acceptSocket(String RemoteAddr)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      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, int port)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
finalize()222     protected void finalize() throws Throwable {
223         try {
224             close();
225         } finally {
226             super.finalize();
227         }
228     }
getSecurityFlags()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      */
getRemoteDevice()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      */
getInputStream()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      */
getOutputStream()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      */
isConnected()274     public boolean isConnected() {
275         return mSocketState == SocketState.CONNECTED;
276     }
277 
setServiceName(String name)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      */
connect()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      */
bindListen()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 
accept(int timeout)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 
available()406     /*package*/ int available() throws IOException {
407         if (VDBG) Log.d(TAG, "available: " + mSocketIS);
408         return mSocketIS.available();
409     }
410 
read(byte[] b, int offset, int length)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 
write(byte[] b, int offset, int length)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
close()431     public void close() throws IOException {
432         if (VDBG) 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 
removeChannel()457     /*package */ void removeChannel() {
458     }
459 
getPort()460     /*package */ int getPort() {
461         return mPort;
462     }
convertAddr(final byte[] addr)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     }
waitSocketSignal(InputStream is)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     }
readAll(InputStream is, byte[] b)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 
readInt(InputStream is)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