• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.bluetooth.IBluetoothCallback;
20 import android.os.ParcelUuid;
21 import android.os.RemoteException;
22 import android.util.Log;
23 
24 import java.io.Closeable;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.util.concurrent.locks.ReentrantReadWriteLock;
29 
30 /**
31  * A connected or connecting Bluetooth socket.
32  *
33  * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets:
34  * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server
35  * side, use a {@link BluetoothServerSocket} to create a listening server
36  * socket. When a connection is accepted by the {@link BluetoothServerSocket},
37  * it will return a new {@link BluetoothSocket} to manage the connection.
38  * On the client side, use a single {@link BluetoothSocket} to both initiate
39  * an outgoing connection and to manage the connection.
40  *
41  * <p>The most common type of Bluetooth socket is RFCOMM, which is the type
42  * supported by the Android APIs. RFCOMM is a connection-oriented, streaming
43  * transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
44  *
45  * <p>To create a {@link BluetoothSocket} for connecting to a known device, use
46  * {@link BluetoothDevice#createRfcommSocketToServiceRecord
47  * BluetoothDevice.createRfcommSocketToServiceRecord()}.
48  * Then call {@link #connect()} to attempt a connection to the remote device.
49  * This call will block until a connection is established or the connection
50  * fails.
51  *
52  * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the
53  * {@link BluetoothServerSocket} documentation.
54  *
55  * <p>Once the socket is connected, whether initiated as a client or accepted
56  * as a server, open the IO streams by calling {@link #getInputStream} and
57  * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream}
58  * and {@link java.io.OutputStream} objects, respectively, which are
59  * automatically connected to the socket.
60  *
61  * <p>{@link BluetoothSocket} is thread
62  * safe. In particular, {@link #close} will always immediately abort ongoing
63  * operations and close the socket.
64  *
65  * <p class="note"><strong>Note:</strong>
66  * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
67  *
68  * <div class="special reference">
69  * <h3>Developer Guides</h3>
70  * <p>For more information about using Bluetooth, read the
71  * <a href="{@docRoot}guide/topics/wireless/bluetooth.html">Bluetooth</a> developer guide.</p>
72  * </div>
73  *
74  * {@see BluetoothServerSocket}
75  * {@see java.io.InputStream}
76  * {@see java.io.OutputStream}
77  */
78 public final class BluetoothSocket implements Closeable {
79     private static final String TAG = "BluetoothSocket";
80 
81     /** @hide */
82     public static final int MAX_RFCOMM_CHANNEL = 30;
83 
84     /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
85     /*package*/ static final int TYPE_RFCOMM = 1;
86     /*package*/ static final int TYPE_SCO = 2;
87     /*package*/ static final int TYPE_L2CAP = 3;
88 
89     /*package*/ static final int EBADFD = 77;
90     /*package*/ static final int EADDRINUSE = 98;
91 
92     private final int mType;  /* one of TYPE_RFCOMM etc */
93     private final BluetoothDevice mDevice;    /* remote device */
94     private final String mAddress;    /* remote address */
95     private final boolean mAuth;
96     private final boolean mEncrypt;
97     private final BluetoothInputStream mInputStream;
98     private final BluetoothOutputStream mOutputStream;
99     private final SdpHelper mSdp;
100 
101     private int mPort;  /* RFCOMM channel or L2CAP psm */
102 
103     private enum SocketState {
104         INIT,
105         CONNECTED,
106         CLOSED
107     }
108 
109     /** prevents all native calls after destroyNative() */
110     private SocketState mSocketState;
111 
112     /** protects mSocketState */
113     private final ReentrantReadWriteLock mLock;
114 
115     /** used by native code only */
116     private int mSocketData;
117 
118     /**
119      * Construct a BluetoothSocket.
120      * @param type    type of socket
121      * @param fd      fd to use for connected socket, or -1 for a new socket
122      * @param auth    require the remote device to be authenticated
123      * @param encrypt require the connection to be encrypted
124      * @param device  remote device that this socket can connect to
125      * @param port    remote port
126      * @param uuid    SDP uuid
127      * @throws IOException On error, for example Bluetooth not available, or
128      *                     insufficient privileges
129      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, BluetoothDevice device, int port, ParcelUuid uuid)130     /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
131             BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
132         if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
133             if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
134                 throw new IOException("Invalid RFCOMM channel: " + port);
135             }
136         }
137         if (uuid == null) {
138             mPort = port;
139             mSdp = null;
140         } else {
141             mSdp = new SdpHelper(device, uuid);
142             mPort = -1;
143         }
144         mType = type;
145         mAuth = auth;
146         mEncrypt = encrypt;
147         mDevice = device;
148         if (device == null) {
149             mAddress = null;
150         } else {
151             mAddress = device.getAddress();
152         }
153         if (fd == -1) {
154             initSocketNative();
155         } else {
156             initSocketFromFdNative(fd);
157         }
158         mInputStream = new BluetoothInputStream(this);
159         mOutputStream = new BluetoothOutputStream(this);
160         mSocketState = SocketState.INIT;
161         mLock = new ReentrantReadWriteLock();
162     }
163 
164     /**
165      * Construct a BluetoothSocket from address. Used by native code.
166      * @param type    type of socket
167      * @param fd      fd to use for connected socket, or -1 for a new socket
168      * @param auth    require the remote device to be authenticated
169      * @param encrypt require the connection to be encrypted
170      * @param address remote device that this socket can connect to
171      * @param port    remote port
172      * @throws IOException On error, for example Bluetooth not available, or
173      *                     insufficient privileges
174      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, int port)175     private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
176             int port) throws IOException {
177         this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
178     }
179 
180     /** @hide */
181     @Override
finalize()182     protected void finalize() throws Throwable {
183         try {
184             close();
185         } finally {
186             super.finalize();
187         }
188     }
189 
190     /**
191      * Attempt to connect to a remote device.
192      * <p>This method will block until a connection is made or the connection
193      * fails. If this method returns without an exception then this socket
194      * is now connected.
195      * <p>Creating new connections to
196      * remote Bluetooth devices should not be attempted while device discovery
197      * is in progress. Device discovery is a heavyweight procedure on the
198      * Bluetooth adapter and will significantly slow a device connection.
199      * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
200      * discovery. Discovery is not managed by the Activity,
201      * but is run as a system service, so an application should always call
202      * {@link BluetoothAdapter#cancelDiscovery()} even if it
203      * did not directly request a discovery, just to be sure.
204      * <p>{@link #close} can be used to abort this call from another thread.
205      * @throws IOException on error, for example connection failure
206      */
connect()207     public void connect() throws IOException {
208         mLock.readLock().lock();
209         try {
210             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
211 
212             if (mSdp != null) {
213                 mPort = mSdp.doSdp();  // blocks
214             }
215 
216             connectNative();  // blocks
217             mSocketState = SocketState.CONNECTED;
218         } finally {
219             mLock.readLock().unlock();
220         }
221     }
222 
223     /**
224      * Immediately close this socket, and release all associated resources.
225      * <p>Causes blocked calls on this socket in other threads to immediately
226      * throw an IOException.
227      */
close()228     public void close() throws IOException {
229         // abort blocking operations on the socket
230         mLock.readLock().lock();
231         try {
232             if (mSocketState == SocketState.CLOSED) return;
233             if (mSdp != null) {
234                 mSdp.cancel();
235             }
236             abortNative();
237         } finally {
238             mLock.readLock().unlock();
239         }
240 
241         // all native calls are guaranteed to immediately return after
242         // abortNative(), so this lock should immediately acquire
243         mLock.writeLock().lock();
244         try {
245             mSocketState = SocketState.CLOSED;
246             destroyNative();
247         } finally {
248             mLock.writeLock().unlock();
249         }
250     }
251 
252     /**
253      * Get the remote device this socket is connecting, or connected, to.
254      * @return remote device
255      */
getRemoteDevice()256     public BluetoothDevice getRemoteDevice() {
257         return mDevice;
258     }
259 
260     /**
261      * Get the input stream associated with this socket.
262      * <p>The input stream will be returned even if the socket is not yet
263      * connected, but operations on that stream will throw IOException until
264      * the associated socket is connected.
265      * @return InputStream
266      */
getInputStream()267     public InputStream getInputStream() throws IOException {
268         return mInputStream;
269     }
270 
271     /**
272      * Get the output stream associated with this socket.
273      * <p>The output stream will be returned even if the socket is not yet
274      * connected, but operations on that stream will throw IOException until
275      * the associated socket is connected.
276      * @return OutputStream
277      */
getOutputStream()278     public OutputStream getOutputStream() throws IOException {
279         return mOutputStream;
280     }
281 
282     /**
283      * Get the connection status of this socket, ie, whether there is an active connection with
284      * remote device.
285      * @return true if connected
286      *         false if not connected
287      */
isConnected()288     public boolean isConnected() {
289         return (mSocketState == SocketState.CONNECTED);
290     }
291 
292     /**
293      * Currently returns unix errno instead of throwing IOException,
294      * so that BluetoothAdapter can check the error code for EADDRINUSE
295      */
bindListen()296     /*package*/ int bindListen() {
297         mLock.readLock().lock();
298         try {
299             if (mSocketState == SocketState.CLOSED) return EBADFD;
300             return bindListenNative();
301         } finally {
302             mLock.readLock().unlock();
303         }
304     }
305 
accept(int timeout)306     /*package*/ BluetoothSocket accept(int timeout) throws IOException {
307         mLock.readLock().lock();
308         try {
309             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
310 
311             BluetoothSocket acceptedSocket = acceptNative(timeout);
312             mSocketState = SocketState.CONNECTED;
313             return acceptedSocket;
314         } finally {
315             mLock.readLock().unlock();
316         }
317     }
318 
available()319     /*package*/ int available() throws IOException {
320         mLock.readLock().lock();
321         try {
322             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
323             return availableNative();
324         } finally {
325             mLock.readLock().unlock();
326         }
327     }
328 
read(byte[] b, int offset, int length)329     /*package*/ int read(byte[] b, int offset, int length) throws IOException {
330         mLock.readLock().lock();
331         try {
332             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
333             return readNative(b, offset, length);
334         } finally {
335             mLock.readLock().unlock();
336         }
337     }
338 
write(byte[] b, int offset, int length)339     /*package*/ int write(byte[] b, int offset, int length) throws IOException {
340         mLock.readLock().lock();
341         try {
342             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
343             return writeNative(b, offset, length);
344         } finally {
345             mLock.readLock().unlock();
346         }
347     }
348 
initSocketNative()349     private native void initSocketNative() throws IOException;
initSocketFromFdNative(int fd)350     private native void initSocketFromFdNative(int fd) throws IOException;
connectNative()351     private native void connectNative() throws IOException;
bindListenNative()352     private native int bindListenNative();
acceptNative(int timeout)353     private native BluetoothSocket acceptNative(int timeout) throws IOException;
availableNative()354     private native int availableNative() throws IOException;
readNative(byte[] b, int offset, int length)355     private native int readNative(byte[] b, int offset, int length) throws IOException;
writeNative(byte[] b, int offset, int length)356     private native int writeNative(byte[] b, int offset, int length) throws IOException;
abortNative()357     private native void abortNative() throws IOException;
destroyNative()358     private native void destroyNative() throws IOException;
359     /**
360      * Throws an IOException for given posix errno. Done natively so we can
361      * use strerr to convert to string error.
362      */
throwErrnoNative(int errno)363     /*package*/ native void throwErrnoNative(int errno) throws IOException;
364 
365     /**
366      * Helper to perform blocking SDP lookup.
367      */
368     private static class SdpHelper extends IBluetoothCallback.Stub {
369         private final IBluetooth service;
370         private final ParcelUuid uuid;
371         private final BluetoothDevice device;
372         private int channel;
373         private boolean canceled;
SdpHelper(BluetoothDevice device, ParcelUuid uuid)374         public SdpHelper(BluetoothDevice device, ParcelUuid uuid) {
375             service = BluetoothDevice.getService();
376             this.device = device;
377             this.uuid = uuid;
378             canceled = false;
379         }
380         /**
381          * Returns the RFCOMM channel for the UUID, or throws IOException
382          * on failure.
383          */
doSdp()384         public synchronized int doSdp() throws IOException {
385             if (canceled) throw new IOException("Service discovery canceled");
386             channel = -1;
387 
388             boolean inProgress = false;
389             try {
390                 inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this);
391             } catch (RemoteException e) {Log.e(TAG, "", e);}
392 
393             if (!inProgress) throw new IOException("Unable to start Service Discovery");
394 
395             try {
396                 /* 12 second timeout as a precaution - onRfcommChannelFound
397                  * should always occur before the timeout */
398                 wait(12000);   // block
399 
400             } catch (InterruptedException e) {}
401 
402             if (canceled) throw new IOException("Service discovery canceled");
403             if (channel < 1) throw new IOException("Service discovery failed");
404 
405             return channel;
406         }
407         /** Object cannot be re-used after calling cancel() */
cancel()408         public synchronized void cancel() {
409             if (!canceled) {
410                 canceled = true;
411                 channel = -1;
412                 notifyAll();  // unblock
413             }
414         }
onRfcommChannelFound(int channel)415         public synchronized void onRfcommChannelFound(int channel) {
416             if (!canceled) {
417                 this.channel = channel;
418                 notifyAll();  // unblock
419             }
420         }
421     }
422 }
423