• 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.Locale;
35 import java.util.UUID;
36 import android.net.LocalSocket;
37 import java.nio.ByteOrder;
38 import 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  */
87 public 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      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, BluetoothDevice device, int port, ParcelUuid uuid)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     }
BluetoothSocket(BluetoothSocket s)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     }
acceptSocket(String RemoteAddr)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      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, int port)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
finalize()224     protected void finalize() throws Throwable {
225         try {
226             close();
227         } finally {
228             super.finalize();
229         }
230     }
getSecurityFlags()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      */
getRemoteDevice()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      */
getInputStream()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      */
getOutputStream()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      */
isConnected()276     public boolean isConnected() {
277         return mSocketState == SocketState.CONNECTED;
278     }
279 
setServiceName(String name)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      */
connect()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      */
bindListen()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 
accept(int timeout)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 
available()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      */
flush()419     /*package*/ void flush() throws IOException {
420         if (VDBG) Log.d(TAG, "flush: " + mSocketOS);
421         mSocketOS.flush();
422     }
423 
read(byte[] b, int offset, int length)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 
write(byte[] b, int offset, int length)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
close()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 
removeChannel()470     /*package */ void removeChannel() {
471     }
472 
getPort()473     /*package */ int getPort() {
474         return mPort;
475     }
convertAddr(final byte[] addr)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     }
waitSocketSignal(InputStream is)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     }
readAll(InputStream is, byte[] b)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 
readInt(InputStream is)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