• 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 intiate
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  * {@see BluetoothServerSocket}
69  * {@see java.io.InputStream}
70  * {@see java.io.OutputStream}
71  */
72 public final class BluetoothSocket implements Closeable {
73     private static final String TAG = "BluetoothSocket";
74 
75     /** @hide */
76     public static final int MAX_RFCOMM_CHANNEL = 30;
77 
78     /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
79     /*package*/ static final int TYPE_RFCOMM = 1;
80     /*package*/ static final int TYPE_SCO = 2;
81     /*package*/ static final int TYPE_L2CAP = 3;
82 
83     /*package*/ static final int EBADFD = 77;
84     /*package*/ static final int EADDRINUSE = 98;
85 
86     private final int mType;  /* one of TYPE_RFCOMM etc */
87     private final BluetoothDevice mDevice;    /* remote device */
88     private final String mAddress;    /* remote address */
89     private final boolean mAuth;
90     private final boolean mEncrypt;
91     private final BluetoothInputStream mInputStream;
92     private final BluetoothOutputStream mOutputStream;
93     private final SdpHelper mSdp;
94 
95     private int mPort;  /* RFCOMM channel or L2CAP psm */
96 
97     /** prevents all native calls after destroyNative() */
98     private boolean mClosed;
99 
100     /** protects mClosed */
101     private final ReentrantReadWriteLock mLock;
102 
103     /** used by native code only */
104     private int mSocketData;
105 
106     /**
107      * Construct a BluetoothSocket.
108      * @param type    type of socket
109      * @param fd      fd to use for connected socket, or -1 for a new socket
110      * @param auth    require the remote device to be authenticated
111      * @param encrypt require the connection to be encrypted
112      * @param device  remote device that this socket can connect to
113      * @param port    remote port
114      * @param uuid    SDP uuid
115      * @throws IOException On error, for example Bluetooth not available, or
116      *                     insufficient priveleges
117      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, BluetoothDevice device, int port, ParcelUuid uuid)118     /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
119             BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
120         if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
121             if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
122                 throw new IOException("Invalid RFCOMM channel: " + port);
123             }
124         }
125         if (uuid == null) {
126             mPort = port;
127             mSdp = null;
128         } else {
129             mSdp = new SdpHelper(device, uuid);
130             mPort = -1;
131         }
132         mType = type;
133         mAuth = auth;
134         mEncrypt = encrypt;
135         mDevice = device;
136         if (device == null) {
137             mAddress = null;
138         } else {
139             mAddress = device.getAddress();
140         }
141         if (fd == -1) {
142             initSocketNative();
143         } else {
144             initSocketFromFdNative(fd);
145         }
146         mInputStream = new BluetoothInputStream(this);
147         mOutputStream = new BluetoothOutputStream(this);
148         mClosed = false;
149         mLock = new ReentrantReadWriteLock();
150     }
151 
152     /**
153      * Construct a BluetoothSocket from address. Used by native code.
154      * @param type    type of socket
155      * @param fd      fd to use for connected socket, or -1 for a new socket
156      * @param auth    require the remote device to be authenticated
157      * @param encrypt require the connection to be encrypted
158      * @param address remote device that this socket can connect to
159      * @param port    remote port
160      * @throws IOException On error, for example Bluetooth not available, or
161      *                     insufficient priveleges
162      */
BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, int port)163     private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
164             int port) throws IOException {
165         this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
166     }
167 
168     /** @hide */
169     @Override
finalize()170     protected void finalize() throws Throwable {
171         try {
172             close();
173         } finally {
174             super.finalize();
175         }
176     }
177 
178     /**
179      * Attempt to connect to a remote device.
180      * <p>This method will block until a connection is made or the connection
181      * fails. If this method returns without an exception then this socket
182      * is now connected.
183      * <p>Creating new connections to
184      * remote Bluetooth devices should not be attempted while device discovery
185      * is in progress. Device discovery is a heavyweight procedure on the
186      * Bluetooth adapter and will significantly slow a device connection.
187      * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
188      * discovery. Discovery is not managed by the Activity,
189      * but is run as a system service, so an application should always call
190      * {@link BluetoothAdapter#cancelDiscovery()} even if it
191      * did not directly request a discovery, just to be sure.
192      * <p>{@link #close} can be used to abort this call from another thread.
193      * @throws IOException on error, for example connection failure
194      */
connect()195     public void connect() throws IOException {
196         mLock.readLock().lock();
197         try {
198             if (mClosed) throw new IOException("socket closed");
199 
200             if (mSdp != null) {
201                 mPort = mSdp.doSdp();  // blocks
202             }
203 
204             connectNative();  // blocks
205         } finally {
206             mLock.readLock().unlock();
207         }
208     }
209 
210     /**
211      * Immediately close this socket, and release all associated resources.
212      * <p>Causes blocked calls on this socket in other threads to immediately
213      * throw an IOException.
214      */
close()215     public void close() throws IOException {
216         // abort blocking operations on the socket
217         mLock.readLock().lock();
218         try {
219             if (mClosed) return;
220             if (mSdp != null) {
221                 mSdp.cancel();
222             }
223             abortNative();
224         } finally {
225             mLock.readLock().unlock();
226         }
227 
228         // all native calls are guaranteed to immediately return after
229         // abortNative(), so this lock should immediatley acquire
230         mLock.writeLock().lock();
231         try {
232             mClosed = true;
233             destroyNative();
234         } finally {
235             mLock.writeLock().unlock();
236         }
237     }
238 
239     /**
240      * Get the remote device this socket is connecting, or connected, to.
241      * @return remote device
242      */
getRemoteDevice()243     public BluetoothDevice getRemoteDevice() {
244         return mDevice;
245     }
246 
247     /**
248      * Get the input stream associated with this socket.
249      * <p>The input stream will be returned even if the socket is not yet
250      * connected, but operations on that stream will throw IOException until
251      * the associated socket is connected.
252      * @return InputStream
253      */
getInputStream()254     public InputStream getInputStream() throws IOException {
255         return mInputStream;
256     }
257 
258     /**
259      * Get the output stream associated with this socket.
260      * <p>The output stream will be returned even if the socket is not yet
261      * connected, but operations on that stream will throw IOException until
262      * the associated socket is connected.
263      * @return OutputStream
264      */
getOutputStream()265     public OutputStream getOutputStream() throws IOException {
266         return mOutputStream;
267     }
268 
269     /**
270      * Currently returns unix errno instead of throwing IOException,
271      * so that BluetoothAdapter can check the error code for EADDRINUSE
272      */
bindListen()273     /*package*/ int bindListen() {
274         mLock.readLock().lock();
275         try {
276             if (mClosed) return EBADFD;
277             return bindListenNative();
278         } finally {
279             mLock.readLock().unlock();
280         }
281     }
282 
accept(int timeout)283     /*package*/ BluetoothSocket accept(int timeout) throws IOException {
284         mLock.readLock().lock();
285         try {
286             if (mClosed) throw new IOException("socket closed");
287             return acceptNative(timeout);
288         } finally {
289             mLock.readLock().unlock();
290         }
291     }
292 
available()293     /*package*/ int available() throws IOException {
294         mLock.readLock().lock();
295         try {
296             if (mClosed) throw new IOException("socket closed");
297             return availableNative();
298         } finally {
299             mLock.readLock().unlock();
300         }
301     }
302 
read(byte[] b, int offset, int length)303     /*package*/ int read(byte[] b, int offset, int length) throws IOException {
304         mLock.readLock().lock();
305         try {
306             if (mClosed) throw new IOException("socket closed");
307             return readNative(b, offset, length);
308         } finally {
309             mLock.readLock().unlock();
310         }
311     }
312 
write(byte[] b, int offset, int length)313     /*package*/ int write(byte[] b, int offset, int length) throws IOException {
314         mLock.readLock().lock();
315         try {
316             if (mClosed) throw new IOException("socket closed");
317             return writeNative(b, offset, length);
318         } finally {
319             mLock.readLock().unlock();
320         }
321     }
322 
initSocketNative()323     private native void initSocketNative() throws IOException;
initSocketFromFdNative(int fd)324     private native void initSocketFromFdNative(int fd) throws IOException;
connectNative()325     private native void connectNative() throws IOException;
bindListenNative()326     private native int bindListenNative();
acceptNative(int timeout)327     private native BluetoothSocket acceptNative(int timeout) throws IOException;
availableNative()328     private native int availableNative() throws IOException;
readNative(byte[] b, int offset, int length)329     private native int readNative(byte[] b, int offset, int length) throws IOException;
writeNative(byte[] b, int offset, int length)330     private native int writeNative(byte[] b, int offset, int length) throws IOException;
abortNative()331     private native void abortNative() throws IOException;
destroyNative()332     private native void destroyNative() throws IOException;
333     /**
334      * Throws an IOException for given posix errno. Done natively so we can
335      * use strerr to convert to string error.
336      */
throwErrnoNative(int errno)337     /*package*/ native void throwErrnoNative(int errno) throws IOException;
338 
339     /**
340      * Helper to perform blocking SDP lookup.
341      */
342     private static class SdpHelper extends IBluetoothCallback.Stub {
343         private final IBluetooth service;
344         private final ParcelUuid uuid;
345         private final BluetoothDevice device;
346         private int channel;
347         private boolean canceled;
SdpHelper(BluetoothDevice device, ParcelUuid uuid)348         public SdpHelper(BluetoothDevice device, ParcelUuid uuid) {
349             service = BluetoothDevice.getService();
350             this.device = device;
351             this.uuid = uuid;
352             canceled = false;
353         }
354         /**
355          * Returns the RFCOMM channel for the UUID, or throws IOException
356          * on failure.
357          */
doSdp()358         public synchronized int doSdp() throws IOException {
359             if (canceled) throw new IOException("Service discovery canceled");
360             channel = -1;
361 
362             boolean inProgress = false;
363             try {
364                 inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this);
365             } catch (RemoteException e) {Log.e(TAG, "", e);}
366 
367             if (!inProgress) throw new IOException("Unable to start Service Discovery");
368 
369             try {
370                 /* 12 second timeout as a precaution - onRfcommChannelFound
371                  * should always occur before the timeout */
372                 wait(12000);   // block
373 
374             } catch (InterruptedException e) {}
375 
376             if (canceled) throw new IOException("Service discovery canceled");
377             if (channel < 1) throw new IOException("Service discovery failed");
378 
379             return channel;
380         }
381         /** Object cannot be re-used after calling cancel() */
cancel()382         public synchronized void cancel() {
383             if (!canceled) {
384                 canceled = true;
385                 channel = -1;
386                 notifyAll();  // unblock
387             }
388         }
onRfcommChannelFound(int channel)389         public synchronized void onRfcommChannelFound(int channel) {
390             if (!canceled) {
391                 this.channel = channel;
392                 notifyAll();  // unblock
393             }
394         }
395     }
396 }
397