* {@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>
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
*
* @hide
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
* @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
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;
+ }
}
}
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);
+ }
}
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;
* 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();
}
/**
+ * 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.
*/
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;
/** 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;
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) {
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)
/*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
// 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 {
}
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]);