BluetoothSocket.java revision 6d95fc0a2ca910212a43c4547c0ef000659b72dc
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
17package android.bluetooth;
18
19import android.bluetooth.IBluetoothCallback;
20import android.os.ParcelUuid;
21import android.os.RemoteException;
22import android.util.Log;
23
24import java.io.Closeable;
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28import 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 */
72public 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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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
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
323    private native void initSocketNative() throws IOException;
324    private native void initSocketFromFdNative(int fd) throws IOException;
325    private native void connectNative() throws IOException;
326    private native int bindListenNative();
327    private native BluetoothSocket acceptNative(int timeout) throws IOException;
328    private native int availableNative() throws IOException;
329    private native int readNative(byte[] b, int offset, int length) throws IOException;
330    private native int writeNative(byte[] b, int offset, int length) throws IOException;
331    private native void abortNative() throws IOException;
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     */
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;
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         */
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() */
382        public synchronized void cancel() {
383            if (!canceled) {
384                canceled = true;
385                channel = -1;
386                notifyAll();  // unblock
387            }
388        }
389        public synchronized void onRfcommChannelFound(int channel) {
390            if (!canceled) {
391                this.channel = channel;
392                notifyAll();  // unblock
393            }
394        }
395    }
396}
397