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