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