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