504 lines
18 KiB
Java
504 lines
18 KiB
Java
|
/*
|
||
|
* Copyright (C) 2009 The Android Open Source Project
|
||
|
* Copyright (c) 2011, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.bluetooth;
|
||
|
|
||
|
import android.bluetooth.IBluetoothCallback;
|
||
|
import android.os.ParcelUuid;
|
||
|
import android.os.RemoteException;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.io.Closeable;
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.OutputStream;
|
||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||
|
|
||
|
/**
|
||
|
* A connected or connecting Bluetooth socket.
|
||
|
*
|
||
|
* <p>The interface for Bluetooth Sockets is similar to that of TCP sockets:
|
||
|
* {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server
|
||
|
* side, use a {@link BluetoothServerSocket} to create a listening server
|
||
|
* socket. When a connection is accepted by the {@link BluetoothServerSocket},
|
||
|
* it will return a new {@link BluetoothSocket} to manage the connection.
|
||
|
* On the client side, use a single {@link BluetoothSocket} to both initiate
|
||
|
* an outgoing connection and to manage the connection.
|
||
|
*
|
||
|
* <p>The most common type of Bluetooth socket is RFCOMM, which is the type
|
||
|
* supported by the Android APIs. RFCOMM is a connection-oriented, streaming
|
||
|
* transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
|
||
|
*
|
||
|
* <p>To create a {@link BluetoothSocket} for connecting to a known device, use
|
||
|
* {@link BluetoothDevice#createRfcommSocketToServiceRecord
|
||
|
* BluetoothDevice.createRfcommSocketToServiceRecord()}.
|
||
|
* Then call {@link #connect()} to attempt a connection to the remote device.
|
||
|
* This call will block until a connection is established or the connection
|
||
|
* fails.
|
||
|
*
|
||
|
* <p>To create a {@link BluetoothSocket} as a server (or "host"), see the
|
||
|
* {@link BluetoothServerSocket} documentation.
|
||
|
*
|
||
|
* <p>Once the socket is connected, whether initiated as a client or accepted
|
||
|
* as a server, open the IO streams by calling {@link #getInputStream} and
|
||
|
* {@link #getOutputStream} in order to retrieve {@link java.io.InputStream}
|
||
|
* and {@link java.io.OutputStream} objects, respectively, which are
|
||
|
* automatically connected to the socket.
|
||
|
*
|
||
|
* <p>{@link BluetoothSocket} is thread
|
||
|
* safe. In particular, {@link #close} will always immediately abort ongoing
|
||
|
* operations and close the socket.
|
||
|
*
|
||
|
* <p class="note"><strong>Note:</strong>
|
||
|
* Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
|
||
|
*
|
||
|
* {@see BluetoothServerSocket}
|
||
|
* {@see java.io.InputStream}
|
||
|
* {@see java.io.OutputStream}
|
||
|
*/
|
||
|
public final class BluetoothSocket implements Closeable {
|
||
|
private static final String TAG = "BluetoothSocket";
|
||
|
|
||
|
/** @hide */
|
||
|
public static final int MAX_RFCOMM_CHANNEL = 30;
|
||
|
|
||
|
/** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
|
||
|
/*package*/ static final int TYPE_RFCOMM = 1;
|
||
|
/*package*/ static final int TYPE_SCO = 2;
|
||
|
/*package*/ static final int TYPE_L2CAP = 3;
|
||
|
/*package*/ static final int TYPE_EL2CAP = 4;
|
||
|
|
||
|
/** Keep BT_AMP_POLICY fields in sync with bluetooth/bluetooth.h */
|
||
|
/** @hide */ public static final int BT_AMP_POLICY_REQUIRE_BR_EDR = 0;
|
||
|
/** @hide */ public static final int BT_AMP_POLICY_PREFER_BR_EDR = 1;
|
||
|
/** @hide */ public static final int BT_AMP_POLICY_PREFER_AMP = 2;
|
||
|
|
||
|
/*package*/ static final int EBADFD = 77;
|
||
|
/*package*/ static final int EADDRINUSE = 98;
|
||
|
|
||
|
private final int mType; /* one of TYPE_RFCOMM etc */
|
||
|
private final BluetoothDevice mDevice; /* remote device */
|
||
|
private final String mAddress; /* remote address */
|
||
|
private final boolean mAuth;
|
||
|
private final boolean mEncrypt;
|
||
|
private final BluetoothInputStream mInputStream;
|
||
|
private final BluetoothOutputStream mOutputStream;
|
||
|
private final SdpHelper mSdp;
|
||
|
private final AmpPolicyHelper mAmpPolicy;
|
||
|
|
||
|
private int mPort; /* RFCOMM channel or L2CAP psm */
|
||
|
|
||
|
/** prevents all native calls after destroyNative() */
|
||
|
private boolean mClosed;
|
||
|
|
||
|
/** protects mClosed */
|
||
|
private final ReentrantReadWriteLock mLock;
|
||
|
|
||
|
/** used by native code only */
|
||
|
private int mSocketData;
|
||
|
|
||
|
/**
|
||
|
* Construct a BluetoothSocket.
|
||
|
* @param type type of socket
|
||
|
* @param fd fd to use for connected socket, or -1 for a new socket
|
||
|
* @param auth require the remote device to be authenticated
|
||
|
* @param encrypt require the connection to be encrypted
|
||
|
* @param device remote device that this socket can connect to
|
||
|
* @param port remote port
|
||
|
* @param uuid SDP uuid
|
||
|
* @throws IOException On error, for example Bluetooth not available, or
|
||
|
* insufficient privileges
|
||
|
*/
|
||
|
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
|
||
|
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
|
||
|
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
|
||
|
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
|
||
|
throw new IOException("Invalid RFCOMM channel: " + port);
|
||
|
}
|
||
|
}
|
||
|
if (uuid == null) {
|
||
|
mPort = port;
|
||
|
mSdp = null;
|
||
|
} else {
|
||
|
mSdp = new SdpHelper(device, uuid);
|
||
|
mPort = -1;
|
||
|
}
|
||
|
mType = type;
|
||
|
mAuth = auth;
|
||
|
mEncrypt = encrypt;
|
||
|
mDevice = device;
|
||
|
if (device == null) {
|
||
|
mAddress = null;
|
||
|
} else {
|
||
|
mAddress = device.getAddress();
|
||
|
}
|
||
|
if (fd == -1) {
|
||
|
initSocketNative();
|
||
|
} else {
|
||
|
initSocketFromFdNative(fd);
|
||
|
}
|
||
|
/* Register with the Bluetooth connection manager
|
||
|
* and set the initial socket AMP policy
|
||
|
*/
|
||
|
if (type == BluetoothSocket.TYPE_EL2CAP) {
|
||
|
mAmpPolicy = new AmpPolicyHelper(BT_AMP_POLICY_REQUIRE_BR_EDR);
|
||
|
} else {
|
||
|
mAmpPolicy = null;
|
||
|
}
|
||
|
mInputStream = new BluetoothInputStream(this);
|
||
|
mOutputStream = new BluetoothOutputStream(this);
|
||
|
mClosed = false;
|
||
|
mLock = new ReentrantReadWriteLock();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct a BluetoothSocket from address. Used by native code.
|
||
|
* @param type type of socket
|
||
|
* @param fd fd to use for connected socket, or -1 for a new socket
|
||
|
* @param auth require the remote device to be authenticated
|
||
|
* @param encrypt require the connection to be encrypted
|
||
|
* @param address remote device that this socket can connect to
|
||
|
* @param port remote port
|
||
|
* @throws IOException On error, for example Bluetooth not available, or
|
||
|
* insufficient privileges
|
||
|
*/
|
||
|
private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
|
||
|
int port) throws IOException {
|
||
|
this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@Override
|
||
|
protected void finalize() throws Throwable {
|
||
|
try {
|
||
|
close();
|
||
|
} finally {
|
||
|
super.finalize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to connect to a remote device.
|
||
|
* <p>This method will block until a connection is made or the connection
|
||
|
* fails. If this method returns without an exception then this socket
|
||
|
* is now connected.
|
||
|
* <p>Creating new connections to
|
||
|
* remote Bluetooth devices should not be attempted while device discovery
|
||
|
* is in progress. Device discovery is a heavyweight procedure on the
|
||
|
* Bluetooth adapter and will significantly slow a device connection.
|
||
|
* Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
|
||
|
* discovery. Discovery is not managed by the Activity,
|
||
|
* but is run as a system service, so an application should always call
|
||
|
* {@link BluetoothAdapter#cancelDiscovery()} even if it
|
||
|
* did not directly request a discovery, just to be sure.
|
||
|
* <p>{@link #close} can be used to abort this call from another thread.
|
||
|
* @throws IOException on error, for example connection failure
|
||
|
*/
|
||
|
public void connect() throws IOException {
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) throw new IOException("socket closed");
|
||
|
|
||
|
if (mSdp != null) {
|
||
|
mPort = mSdp.doSdp(); // blocks
|
||
|
}
|
||
|
|
||
|
connectNative(); // blocks
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Immediately close this socket, and release all associated resources.
|
||
|
* <p>Causes blocked calls on this socket in other threads to immediately
|
||
|
* throw an IOException.
|
||
|
*/
|
||
|
public void close() throws IOException {
|
||
|
// abort blocking operations on the socket
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) return;
|
||
|
if (mSdp != null) {
|
||
|
mSdp.cancel();
|
||
|
}
|
||
|
if (mAmpPolicy != null) {
|
||
|
mAmpPolicy.deregister();
|
||
|
}
|
||
|
abortNative();
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
|
||
|
// all native calls are guaranteed to immediately return after
|
||
|
// abortNative(), so this lock should immediately acquire
|
||
|
mLock.writeLock().lock();
|
||
|
try {
|
||
|
mClosed = true;
|
||
|
destroyNative();
|
||
|
} finally {
|
||
|
mLock.writeLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the desired AMP Policy for EL2CAP type sockets
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public boolean setDesiredAmpPolicy(int policy) {
|
||
|
boolean result = false;
|
||
|
if (mAmpPolicy != null) {
|
||
|
IBluetooth service = BluetoothDevice.getService();
|
||
|
try {
|
||
|
result = service.setDesiredAmpPolicy(mAmpPolicy.mHandle, policy);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the remote device this socket is connecting, or connected, to.
|
||
|
* @return remote device
|
||
|
*/
|
||
|
public BluetoothDevice getRemoteDevice() {
|
||
|
return mDevice;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the input stream associated with this socket.
|
||
|
* <p>The input stream will be returned even if the socket is not yet
|
||
|
* connected, but operations on that stream will throw IOException until
|
||
|
* the associated socket is connected.
|
||
|
* @return InputStream
|
||
|
*/
|
||
|
public InputStream getInputStream() throws IOException {
|
||
|
return mInputStream;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the output stream associated with this socket.
|
||
|
* <p>The output stream will be returned even if the socket is not yet
|
||
|
* connected, but operations on that stream will throw IOException until
|
||
|
* the associated socket is connected.
|
||
|
* @return OutputStream
|
||
|
*/
|
||
|
public OutputStream getOutputStream() throws IOException {
|
||
|
return mOutputStream;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the MTU for the socket (if RFCOMM, returns 65K)
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public int getMtu() {
|
||
|
int ret = 0;
|
||
|
try {
|
||
|
ret = getMtuNative();
|
||
|
} catch (IOException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
}
|
||
|
|
||
|
Log.d(TAG, "getMtu " + Integer.toString(ret));
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Currently returns unix errno instead of throwing IOException,
|
||
|
* so that BluetoothAdapter can check the error code for EADDRINUSE
|
||
|
*/
|
||
|
/*package*/ int bindListen() {
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) return EBADFD;
|
||
|
return bindListenNative();
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) throw new IOException("socket closed");
|
||
|
return acceptNative(timeout);
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ int available() throws IOException {
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) throw new IOException("socket closed");
|
||
|
return availableNative();
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) throw new IOException("socket closed");
|
||
|
return readNative(b, offset, length);
|
||
|
} catch (IOException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
throw e;
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
|
||
|
mLock.readLock().lock();
|
||
|
try {
|
||
|
if (mClosed) throw new IOException("socket closed");
|
||
|
return writeNative(b, offset, length);
|
||
|
} catch (IOException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
throw e;
|
||
|
} finally {
|
||
|
mLock.readLock().unlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private native void initSocketNative() throws IOException;
|
||
|
private native void initSocketFromFdNative(int fd) throws IOException;
|
||
|
private native void connectNative() throws IOException;
|
||
|
private native int bindListenNative();
|
||
|
private native BluetoothSocket acceptNative(int timeout) throws IOException;
|
||
|
private native int availableNative() throws IOException;
|
||
|
private native int readNative(byte[] b, int offset, int length) throws IOException;
|
||
|
private native int writeNative(byte[] b, int offset, int length) throws IOException;
|
||
|
private native void abortNative() throws IOException;
|
||
|
private native void destroyNative() throws IOException;
|
||
|
private native void setAmpPolicyNative(int amppol) throws IOException;
|
||
|
private native int getMtuNative() throws IOException;
|
||
|
|
||
|
/**
|
||
|
* Throws an IOException for given posix errno. Done natively so we can
|
||
|
* use strerr to convert to string error.
|
||
|
*/
|
||
|
/*package*/ native void throwErrnoNative(int errno) throws IOException;
|
||
|
|
||
|
/**
|
||
|
* Helper to perform blocking SDP lookup.
|
||
|
*/
|
||
|
private static class SdpHelper extends IBluetoothCallback.Stub {
|
||
|
private final IBluetooth service;
|
||
|
private final ParcelUuid uuid;
|
||
|
private final BluetoothDevice device;
|
||
|
private int channel;
|
||
|
private boolean canceled;
|
||
|
public SdpHelper(BluetoothDevice device, ParcelUuid uuid) {
|
||
|
service = BluetoothDevice.getService();
|
||
|
this.device = device;
|
||
|
this.uuid = uuid;
|
||
|
canceled = false;
|
||
|
}
|
||
|
/**
|
||
|
* Returns the RFCOMM channel for the UUID, or throws IOException
|
||
|
* on failure.
|
||
|
*/
|
||
|
public synchronized int doSdp() throws IOException {
|
||
|
if (canceled) throw new IOException("Service discovery canceled");
|
||
|
channel = -1;
|
||
|
|
||
|
boolean inProgress = false;
|
||
|
try {
|
||
|
inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
|
||
|
if (!inProgress) throw new IOException("Unable to start Service Discovery");
|
||
|
|
||
|
try {
|
||
|
/* 12 second timeout as a precaution - onRfcommChannelFound
|
||
|
* should always occur before the timeout */
|
||
|
wait(12000); // block
|
||
|
|
||
|
} catch (InterruptedException e) {}
|
||
|
|
||
|
if (canceled) throw new IOException("Service discovery canceled");
|
||
|
if (channel < 1) throw new IOException("Service discovery failed");
|
||
|
|
||
|
return channel;
|
||
|
}
|
||
|
/** Object cannot be re-used after calling cancel() */
|
||
|
public synchronized void cancel() {
|
||
|
if (!canceled) {
|
||
|
canceled = true;
|
||
|
channel = -1;
|
||
|
notifyAll(); // unblock
|
||
|
}
|
||
|
}
|
||
|
public synchronized void onRfcommChannelFound(int channel) {
|
||
|
if (!canceled) {
|
||
|
this.channel = channel;
|
||
|
notifyAll(); // unblock
|
||
|
}
|
||
|
}
|
||
|
/** Stub function, unused for SDP lookup */
|
||
|
public synchronized void onAmpPolicyChange(int newPolicy) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper to handle AMP policy changes from Bluetooth connection manager
|
||
|
*/
|
||
|
private class AmpPolicyHelper extends IBluetoothCallback.Stub {
|
||
|
private final IBluetooth service;
|
||
|
private int mHandle = 0;
|
||
|
|
||
|
public AmpPolicyHelper(int initialPolicy) {
|
||
|
service = BluetoothDevice.getService();
|
||
|
|
||
|
try {
|
||
|
mHandle = service.registerEl2capConnection(this, initialPolicy);
|
||
|
|
||
|
// Configure initial AMP policy. Note: this
|
||
|
// assumes the socket has been initialized.
|
||
|
setAmpPolicyNative(service.getEffectiveAmpPolicy(initialPolicy));
|
||
|
} catch (RemoteException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
} catch (IOException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized void deregister() {
|
||
|
try {
|
||
|
service.deregisterEl2capConnection(mHandle);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
}
|
||
|
|
||
|
|
||
|
public synchronized void onAmpPolicyChange(int newPolicy) {
|
||
|
try {
|
||
|
setAmpPolicyNative(newPolicy);
|
||
|
} catch (IOException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Stub function, unused for AMP policy changes */
|
||
|
public synchronized void onRfcommChannelFound(int channel) {
|
||
|
}
|
||
|
}
|
||
|
}
|