Merge "Added APIs for Connection-oriented channels"

am: e1992384e0

Change-Id: If7cb99a59aee8640f6dddd96f2388e2a5284df45
This commit is contained in:
Stanley Tng
2018-01-18 17:59:19 +00:00
committed by android-build-merger
4 changed files with 227 additions and 9 deletions

View File

@@ -79,8 +79,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* {@link BluetoothDevice} objects representing all paired devices with
* {@link #getBondedDevices()}; start device discovery with
* {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to
* listen for incoming connection requests with
* {@link #listenUsingRfcommWithServiceRecord(String, UUID)}; or start a scan for
* listen for incoming RFComm connection requests with {@link
* #listenUsingRfcommWithServiceRecord(String, UUID)}; listen for incoming L2CAP Connection-oriented
* Channels (CoC) connection requests with listenUsingL2capCoc(int)}; or start a scan for
* Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
* </p>
* <p>This class is thread safe.</p>
@@ -209,6 +210,14 @@ public final class BluetoothAdapter {
*/
public static final int STATE_BLE_TURNING_OFF = 16;
/**
* UUID of the GATT Read Characteristics for LE_PSM value.
*
* @hide
*/
public static final UUID LE_PSM_CHARACTERISTIC_UUID =
UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a");
/**
* Human-readable string helper for AdapterState
*
@@ -2156,7 +2165,9 @@ public final class BluetoothAdapter {
min16DigitPin);
int errno = socket.mSocket.bindListen();
if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
int assignedChannel = socket.mSocket.getPort();
if (DBG) Log.d(TAG, "listenUsingL2capOn: set assigned channel to " + assignedChannel);
socket.setChannel(assignedChannel);
}
if (errno != 0) {
//TODO(BT): Throw the same exception error code
@@ -2197,12 +2208,18 @@ public final class BluetoothAdapter {
* @hide
*/
public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException {
Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port);
BluetoothServerSocket socket =
new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, false, false, port, false,
false);
false);
int errno = socket.mSocket.bindListen();
if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
int assignedChannel = socket.mSocket.getPort();
if (DBG) {
Log.d(TAG, "listenUsingInsecureL2capOn: set assigned channel to "
+ assignedChannel);
}
socket.setChannel(assignedChannel);
}
if (errno != 0) {
//TODO(BT): Throw the same exception error code
@@ -2761,4 +2778,103 @@ public final class BluetoothAdapter {
scanner.stopScan(scanCallback);
}
}
/**
* Create a secure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and
* assign a dynamic protocol/service multiplexer (PSM) value. This socket can be used to listen
* for incoming connections.
* <p>A remote device connecting to this socket will be authenticated and communication on this
* socket will be encrypted.
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening
* {@link BluetoothServerSocket}.
* <p>The system will assign a dynamic PSM value. This PSM value can be read from the {#link
* BluetoothServerSocket#getPsm()} and this value will be released when this server socket is
* closed, Bluetooth is turned off, or the application exits unexpectedly.
* <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is
* defined and performed by the application.
* <p>Use {@link BluetoothDevice#createL2capCocSocket(int, int)} to connect to this server
* socket from another Android device that is given the PSM value.
*
* @param transport Bluetooth transport to use, must be {@link BluetoothDevice#TRANSPORT_LE}
* @return an L2CAP CoC BluetoothServerSocket
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions, or unable to start this CoC
* @hide
*/
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothServerSocket listenUsingL2capCoc(int transport)
throws IOException {
if (transport != BluetoothDevice.TRANSPORT_LE) {
throw new IllegalArgumentException("Unsupported transport: " + transport);
}
BluetoothServerSocket socket =
new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, true, true,
SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false);
int errno = socket.mSocket.bindListen();
if (errno != 0) {
throw new IOException("Error: " + errno);
}
int assignedPsm = socket.mSocket.getPort();
if (assignedPsm == 0) {
throw new IOException("Error: Unable to assign PSM value");
}
if (DBG) {
Log.d(TAG, "listenUsingL2capCoc: set assigned PSM to "
+ assignedPsm);
}
socket.setChannel(assignedPsm);
return socket;
}
/**
* Create an insecure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and
* assign a dynamic PSM value. This socket can be used to listen for incoming connections.
* <p>The link key is not required to be authenticated, i.e the communication may be vulnerable
* to man-in-the-middle attacks. Use {@link #listenUsingL2capCoc}, if an encrypted and
* authenticated communication channel is desired.
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening
* {@link BluetoothServerSocket}.
* <p>The system will assign a dynamic protocol/service multiplexer (PSM) value. This PSM value
* can be read from the {#link BluetoothServerSocket#getPsm()} and this value will be released
* when this server socket is closed, Bluetooth is turned off, or the application exits
* unexpectedly.
* <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is
* defined and performed by the application.
* <p>Use {@link BluetoothDevice#createInsecureL2capCocSocket(int, int)} to connect to this
* server socket from another Android device that is given the PSM value.
*
* @param transport Bluetooth transport to use, must be {@link BluetoothDevice#TRANSPORT_LE}
* @return an L2CAP CoC BluetoothServerSocket
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions, or unable to start this CoC
* @hide
*/
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothServerSocket listenUsingInsecureL2capCoc(int transport)
throws IOException {
if (transport != BluetoothDevice.TRANSPORT_LE) {
throw new IllegalArgumentException("Unsupported transport: " + transport);
}
BluetoothServerSocket socket =
new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, false, false,
SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false);
int errno = socket.mSocket.bindListen();
if (errno != 0) {
throw new IOException("Error: " + errno);
}
int assignedPsm = socket.mSocket.getPort();
if (assignedPsm == 0) {
throw new IOException("Error: Unable to assign PSM value");
}
if (DBG) {
Log.d(TAG, "listenUsingInsecureL2capOn: set assigned PSM to "
+ assignedPsm);
}
socket.setChannel(assignedPsm);
return socket;
}
}

View File

@@ -1921,4 +1921,75 @@ public final class BluetoothDevice implements Parcelable {
}
return null;
}
/**
* Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can
* be used to start a secure outgoing connection to the remote device with the same dynamic
* protocol/service multiplexer (PSM) value.
* <p>This is designed to be used with {@link BluetoothAdapter#listenUsingL2capCoc(int)} for
* peer-peer Bluetooth applications.
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection.
* <p>Application using this API is responsible for obtaining PSM value from remote device.
* <p>The remote device will be authenticated and communication on this socket will be
* encrypted.
* <p> Use this socket if an authenticated socket link is possible. Authentication refers
* to the authentication of the link key to prevent man-in-the-middle type of attacks. When a
* secure socket connection is not possible, use {#link createInsecureLeL2capCocSocket(int,
* int)}.
*
* @param transport Bluetooth transport to use, must be {@link #TRANSPORT_LE}
* @param psm dynamic PSM value from remote device
* @return a CoC #BluetoothSocket ready for an outgoing connection
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions
* @hide
*/
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothSocket createL2capCocSocket(int transport, int psm) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "createL2capCocSocket: Bluetooth is not enabled");
throw new IOException();
}
if (transport != BluetoothDevice.TRANSPORT_LE) {
throw new IllegalArgumentException("Unsupported transport: " + transport);
}
if (DBG) Log.d(TAG, "createL2capCocSocket: transport=" + transport + ", psm=" + psm);
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, true, true, this, psm,
null);
}
/**
* Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can
* be used to start a secure outgoing connection to the remote device with the same dynamic
* protocol/service multiplexer (PSM) value.
* <p>This is designed to be used with {@link BluetoothAdapter#listenUsingInsecureL2capCoc(int)}
* for peer-peer Bluetooth applications.
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection.
* <p>Application using this API is responsible for obtaining PSM value from remote device.
* <p> The communication channel may not have an authenticated link key, i.e. it may be subject
* to man-in-the-middle attacks. Use {@link #createL2capCocSocket(int, int)} if an encrypted and
* authenticated communication channel is possible.
*
* @param transport Bluetooth transport to use, must be {@link #TRANSPORT_LE}
* @param psm dynamic PSM value from remote device
* @return a CoC #BluetoothSocket ready for an outgoing connection
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions
* @hide
*/
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothSocket createInsecureL2capCocSocket(int transport, int psm) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "createInsecureL2capCocSocket: Bluetooth is not enabled");
throw new IOException();
}
if (transport != BluetoothDevice.TRANSPORT_LE) {
throw new IllegalArgumentException("Unsupported transport: " + transport);
}
if (DBG) {
Log.d(TAG, "createInsecureL2capCocSocket: transport=" + transport + ", psm=" + psm);
}
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, false, false, this, psm,
null);
}
}

View File

@@ -68,6 +68,7 @@ import java.io.IOException;
public final class BluetoothServerSocket implements Closeable {
private static final String TAG = "BluetoothServerSocket";
private static final boolean DBG = false;
/*package*/ final BluetoothSocket mSocket;
private Handler mHandler;
private int mMessage;
@@ -169,6 +170,7 @@ public final class BluetoothServerSocket implements Closeable {
* close any {@link BluetoothSocket} received from {@link #accept()}.
*/
public void close() throws IOException {
if (DBG) Log.d(TAG, "BluetoothServerSocket:close() called. mChannel=" + mChannel);
synchronized (this) {
if (mHandler != null) {
mHandler.obtainMessage(mMessage).sendToTarget();
@@ -196,6 +198,20 @@ public final class BluetoothServerSocket implements Closeable {
return mChannel;
}
/**
* Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP
* Connection-oriented Channel (CoC) server socket. This server socket must be returned by the
* {#link BluetoothAdapter.listenUsingL2capCoc(int)} or {#link
* BluetoothAdapter.listenUsingInsecureL2capCoc(int)}. The returned value is undefined if this
* method is called on non-L2CAP server sockets.
*
* @return the assigned PSM or LE_PSM value depending on transport
* @hide
*/
public int getPsm() {
return mChannel;
}
/**
* Sets the channel on which future sockets are bound.
* Currently used only when a channel is auto generated.
@@ -227,6 +243,10 @@ public final class BluetoothServerSocket implements Closeable {
sb.append("TYPE_L2CAP");
break;
}
case BluetoothSocket.TYPE_L2CAP_LE: {
sb.append("TYPE_L2CAP_LE");
break;
}
case BluetoothSocket.TYPE_SCO: {
sb.append("TYPE_SCO");
break;

View File

@@ -99,6 +99,16 @@ public final class BluetoothSocket implements Closeable {
/** L2CAP socket */
public static final int TYPE_L2CAP = 3;
/** L2CAP socket on BR/EDR transport
* @hide
*/
public static final int TYPE_L2CAP_BREDR = TYPE_L2CAP;
/** L2CAP socket on LE transport
* @hide
*/
public static final int TYPE_L2CAP_LE = 4;
/*package*/ static final int EBADFD = 77;
/*package*/ static final int EADDRINUSE = 98;
@@ -417,6 +427,7 @@ public final class BluetoothSocket implements Closeable {
return -1;
}
try {
if (DBG) Log.d(TAG, "bindListen(): mPort=" + mPort + ", mType=" + mType);
mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
mUuid, mPort, getSecurityFlags());
} catch (RemoteException e) {
@@ -451,7 +462,7 @@ public final class BluetoothSocket implements Closeable {
mSocketState = SocketState.LISTENING;
}
}
if (DBG) Log.d(TAG, "channel: " + channel);
if (DBG) Log.d(TAG, "bindListen(): channel=" + channel + ", mPort=" + mPort);
if (mPort <= -1) {
mPort = channel;
} // else ASSERT(mPort == channel)
@@ -515,7 +526,7 @@ public final class BluetoothSocket implements Closeable {
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
int ret = 0;
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
if (mType == TYPE_L2CAP) {
if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) {
int bytesToRead = length;
if (VDBG) {
Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length
@@ -558,7 +569,7 @@ public final class BluetoothSocket implements Closeable {
// Rfcomm uses dynamic allocation, and should not have any bindings
// to the actual message length.
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
if (mType == TYPE_L2CAP) {
if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) {
if (length <= mMaxTxPacketSize) {
mSocketOS.write(b, offset, length);
} else {
@@ -702,7 +713,7 @@ public final class BluetoothSocket implements Closeable {
}
private void createL2capRxBuffer() {
if (mType == TYPE_L2CAP) {
if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) {
// Allocate the buffer to use for reads.
if (VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize);
mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]);