blob: b7706d50feb63f7c8a6f384cf70fe50fb00abf67 [file] [log] [blame]
/*
* Copyright 2024 The Android Open Source Project
*
* 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 static android.bluetooth.BluetoothSocket.SocketType;
import static java.util.Objects.requireNonNull;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresNoPermission;
import android.annotation.SystemApi;
import com.android.bluetooth.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* Defines parameters for creating Bluetooth server and client socket channels.
*
* <p>Used with {@link BluetoothAdapter#listenUsingSocketSettings} to create a server socket and
* {@link BluetoothDevice#createUsingSocketSettings} to create a client socket.
*
* @see BluetoothAdapter#listenUsingSocketSettings
* @see BluetoothDevice#createUsingSocketSettings
*/
@FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API)
public final class BluetoothSocketSettings {
private static final int L2CAP_PSM_UNSPECIFIED = -1;
/**
* Annotation to define the data path used for Bluetooth socket communication. This determines
* how data flows between the application and the Bluetooth controller.
*
* @hide
*/
@IntDef(
prefix = {"DATA_PATH_"},
value = {DATA_PATH_NO_OFFLOAD, DATA_PATH_HARDWARE_OFFLOAD})
@Retention(RetentionPolicy.SOURCE)
public @interface SocketDataPath {}
/**
* Non-offload data path where the application's socket data is processed by the main Bluetooth
* stack.
*
* @hide
*/
@SystemApi public static final int DATA_PATH_NO_OFFLOAD = 0;
/**
* Hardware offload data path where the application's socket data is processed by a offloaded
* application running on the low-power processor.
*
* <p>Using this data path requires the {@code BLUETOOTH_PRIVILEGED} permission, which will be
* checked when a socket connection or channel is created.
*
* @hide
*/
@SystemApi public static final int DATA_PATH_HARDWARE_OFFLOAD = 1;
/**
* Maximum size (in bytes) of a data packet that can be received from the endpoint when using
* {@link #DATA_PATH_HARDWARE_OFFLOAD}.
*/
@SystemApi private static final int HARDWARE_OFFLOAD_PACKET_MAX_SIZE = 65535;
/**
* Maximum length (in bytes) of a socket name when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
*/
@SystemApi private static final int HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH = 127;
/**
* Constant representing an invalid hub ID. This value indicates that a hub ID has not been
* assigned or is not valid.
*
* @hide
*/
private static final long INVALID_HUB_ID = 0;
/**
* Constant representing an invalid hub endpoint ID. This value indicates that an endpoint ID
* has not been assigned or is not valid.
*
* @hide
*/
private static final long INVALID_ENDPOINT_ID = 0;
/** Type of the Bluetooth socket */
@SocketType private int mSocketType;
/** Encryption requirement for the Bluetooth socket. */
private boolean mEncryptionRequired;
/** Authentication requirement for the Bluetooth socket. */
private boolean mAuthenticationRequired;
/** L2CAP Protocol/Service Multiplexer (PSM) for the Bluetooth Socket. */
private int mL2capPsm;
/** RFCOMM service name associated with the Bluetooth socket. */
private String mRfcommServiceName;
/** RFCOMM service UUID associated with the Bluetooth socket. */
private UUID mRfcommUuid;
/**
* Specifies the data path used for this socket, influencing how data is transmitted and
* processed. Select the appropriate data path based on performance and power consumption
* requirements:
*
* <ul>
* <li>{@link #DATA_PATH_NO_OFFLOAD}: Suitable for applications that require the full
* processing capabilities of the main Bluetooth stack.
* <li>{@link #DATA_PATH_HARDWARE_OFFLOAD}: Optimized for lower power consumption by utilizing
* an offloaded application running on a dedicated low-power processor.
* </ul>
*/
@SocketDataPath private int mDataPath;
/**
* A user-friendly name for this socket, primarily for debugging and logging. This name should
* be descriptive and can help identify the socket during development and troubleshooting.
*
* <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded
* application running on the low-power processor. This allows the offloaded application to
* identify and manage the socket.
*/
private String mSocketName;
/**
* When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the hub hosting the endpoint.
*
* <p>Hub represents a logical/physical representation of multiple endpoints. A pair of {@code
* mHubId} and {@code mEndpointId} uniquely identifies the endpoint globally.
*/
private long mHubId;
/**
* When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the specific endpoint within
* the hub that is associated with this socket.
*/
private long mEndpointId;
/**
* The maximum size (in bytes) of a single data packet that can be received from the endpoint
* when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
*/
private int mMaximumPacketSize;
/**
* Returns the type of the Bluetooth socket.
*
* <p>Defaults to {@code BluetoothSocket#TYPE_RFCOMM}.
*/
@RequiresNoPermission
@SocketType
public int getSocketType() {
return mSocketType;
}
/** Returns the L2CAP PSM value used for a BluetoothSocket#TYPE_LE socket. */
@RequiresNoPermission
public @IntRange(from = 128, to = 255) int getL2capPsm() {
return mL2capPsm;
}
/**
* Returns the RFCOMM service name used for a BluetoothSocket#TYPE_RFCOMM socket.
*
* <p>Defaults to {@code null}.
*/
@Nullable
@RequiresNoPermission
public String getRfcommServiceName() {
return mRfcommServiceName;
}
/**
* Returns the RFCOMM service UUID used for a BluetoothSocket#TYPE_RFCOMM socket.
*
* <p>Defaults to {@code null}.
*/
@Nullable
@RequiresNoPermission
public UUID getRfcommUuid() {
return mRfcommUuid;
}
/**
* Checks if encryption is enabled for the Bluetooth socket.
*
* <p>Defaults to {@code false}.
*/
@RequiresNoPermission
public boolean isEncryptionRequired() {
return mEncryptionRequired;
}
/**
* Checks if authentication is enabled for the Bluetooth socket.
*
* <p>Defaults to {@code false}.
*/
@RequiresNoPermission
public boolean isAuthenticationRequired() {
return mAuthenticationRequired;
}
/**
* Returns the data path used for this socket. The data path determines how data is routed and
* processed for the socket connection.
*
* <p>Defaults to {@link #DATA_PATH_NO_OFFLOAD}.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @hide
*/
@SystemApi
@RequiresNoPermission
public @SocketDataPath int getDataPath() {
return mDataPath;
}
/**
* Returns the user-friendly name assigned to this socket. This name is primarily used for
* debugging and logging purposes.
*
* <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded
* application running on the low-power processor.
*
* <p>Defaults to {@code null} if no name was explicitly set.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @hide
*/
@SystemApi
@NonNull
@RequiresNoPermission
public String getSocketName() {
return mSocketName;
}
/**
* Returns the ID of the hub associated with this socket when using {@link
* #DATA_PATH_HARDWARE_OFFLOAD}.
*
* <p>If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns
* {@link #INVALID_HUB_ID}.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @hide
*/
@SystemApi
@RequiresNoPermission
public long getHubId() {
if (mDataPath != DATA_PATH_HARDWARE_OFFLOAD) {
return INVALID_HUB_ID;
}
return mHubId;
}
/**
* Returns the ID of the endpoint within the hub associated with this socket when using {@link
* #DATA_PATH_HARDWARE_OFFLOAD}. An endpoint represents a specific point of communication within
* the hub.
*
* <p>If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns
* {@link #INVALID_ENDPOINT_ID}.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @hide
*/
@SystemApi
@RequiresNoPermission
public long getEndpointId() {
if (mDataPath != DATA_PATH_HARDWARE_OFFLOAD) {
return INVALID_ENDPOINT_ID;
}
return mEndpointId;
}
/**
* Returns the requested maximum size (in bytes) of a data packet that can be received from the
* endpoint associated with this socket when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
*
* <p>Defaults to {@link #HARDWARE_OFFLOAD_PACKET_MAX_SIZE}.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @hide
*/
@SystemApi
@RequiresNoPermission
public int getRequestedMaximumPacketSize() {
return mMaximumPacketSize;
}
/**
* Returns a {@link String} that describes each BluetoothSocketSettings parameter current value.
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder("BluetoothSocketSettings{");
builder.append("mSocketType=")
.append(mSocketType)
.append(", mEncryptionRequired=")
.append(mEncryptionRequired)
.append(", mAuthenticationRequired=")
.append(mAuthenticationRequired);
if (mSocketType == BluetoothSocket.TYPE_RFCOMM) {
builder.append(", mRfcommServiceName=")
.append(mRfcommServiceName)
.append(", mRfcommUuid=")
.append(mRfcommUuid);
} else {
builder.append(", mL2capPsm=").append(mL2capPsm);
}
if (mDataPath == DATA_PATH_HARDWARE_OFFLOAD) {
builder.append(", mDataPath=")
.append(mDataPath)
.append(", mSocketName=")
.append(mSocketName)
.append(", mHubId=")
.append(mHubId)
.append(", mEndpointId=")
.append(mEndpointId)
.append(", mMaximumPacketSize=")
.append(mMaximumPacketSize);
}
builder.append("}");
return builder.toString();
}
private BluetoothSocketSettings(
int socketType,
int l2capPsm,
boolean encryptionRequired,
boolean authenticationRequired,
String rfcommServiceName,
UUID rfcommUuid,
int dataPath,
String socketName,
long hubId,
long endpointId,
int maximumPacketSize) {
mSocketType = socketType;
mL2capPsm = l2capPsm;
mEncryptionRequired = encryptionRequired;
mAuthenticationRequired = authenticationRequired;
mRfcommUuid = rfcommUuid;
mRfcommServiceName = rfcommServiceName;
mDataPath = dataPath;
mSocketName = socketName;
mHubId = hubId;
mEndpointId = endpointId;
mMaximumPacketSize = maximumPacketSize;
}
/** Builder for {@link BluetoothSocketSettings}. */
@FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API)
public static final class Builder {
private int mSocketType = BluetoothSocket.TYPE_RFCOMM;
private int mL2capPsm = L2CAP_PSM_UNSPECIFIED;
private boolean mEncryptionRequired = false;
private boolean mAuthenticationRequired = false;
private String mRfcommServiceName = null;
private UUID mRfcommUuid = null;
private int mDataPath = DATA_PATH_NO_OFFLOAD;
private String mSocketName = BluetoothSocket.DEFAULT_SOCKET_NAME;
private long mHubId = INVALID_HUB_ID;
private long mEndpointId = INVALID_ENDPOINT_ID;
private int mMaximumPacketSize = HARDWARE_OFFLOAD_PACKET_MAX_SIZE;
public Builder() {}
/**
* Sets the socket type.
*
* <p>Must be one of:
*
* <ul>
* <li>{@link BluetoothSocket#TYPE_RFCOMM}
* <li>{@link BluetoothSocket#TYPE_LE}
* </ul>
*
* <p>Defaults to {@code BluetoothSocket#TYPE_RFCOMM}.
*
* @param socketType The type of socket.
* @return This builder.
* @throws IllegalArgumentException If {@code socketType} is invalid.
*/
@NonNull
@RequiresNoPermission
public Builder setSocketType(@SocketType int socketType) {
if (socketType != BluetoothSocket.TYPE_RFCOMM
&& socketType != BluetoothSocket.TYPE_LE) {
throw new IllegalArgumentException("invalid socketType - " + socketType);
}
mSocketType = socketType;
return this;
}
/**
* Sets the L2CAP PSM (Protocol/Service Multiplexer) for the Bluetooth socket.
*
* <p>This is only used for {@link BluetoothSocket#TYPE_LE} sockets.
*
* <p>Valid PSM values for {@link BluetoothSocket#TYPE_LE} sockets is ranging from 128
* (0x80) to 255 (0xFF).
*
* <p>Application using this API is responsible for obtaining protocol/service multiplexer
* (PSM) value from remote device.
*
* @param l2capPsm The L2CAP PSM value.
* @return This builder.
* @throws IllegalArgumentException If l2cap PSM is not in given range.
*/
@NonNull
@RequiresNoPermission
public Builder setL2capPsm(@IntRange(from = 128, to = 255) int l2capPsm) {
if (l2capPsm < 128 || l2capPsm > 255) {
throw new IllegalArgumentException("invalid L2cap PSM - " + l2capPsm);
}
mL2capPsm = l2capPsm;
return this;
}
/**
* Sets the encryption requirement for the Bluetooth socket.
*
* <p>Defaults to {@code false}.
*
* @param encryptionRequired {@code true} if encryption is required for this socket, {@code
* false} otherwise.
* @return This builder.
*/
@NonNull
@RequiresNoPermission
public Builder setEncryptionRequired(boolean encryptionRequired) {
mEncryptionRequired = encryptionRequired;
return this;
}
/**
* Sets the authentication requirement for the Bluetooth socket.
*
* <p>Defaults to {@code false}.
*
* @param authenticationRequired {@code true} if authentication is required for this socket,
* {@code false} otherwise.
* @return This builder.
*/
@NonNull
@RequiresNoPermission
public Builder setAuthenticationRequired(boolean authenticationRequired) {
mAuthenticationRequired = authenticationRequired;
return this;
}
/**
* Sets the RFCOMM service name associated with the Bluetooth socket.
*
* <p>This name is used to identify the service when a remote device searches for it using
* SDP.
*
* <p>This is only used for {@link BluetoothSocket#TYPE_RFCOMM} sockets.
*
* <p>Defaults to {@code null}.
*
* @param rfcommServiceName The RFCOMM service name.
* @return This builder.
*/
@NonNull
@RequiresNoPermission
public Builder setRfcommServiceName(@NonNull String rfcommServiceName) {
mRfcommServiceName = rfcommServiceName;
return this;
}
/**
* Sets the RFCOMM service UUID associated with the Bluetooth socket.
*
* <p>This UUID is used to uniquely identify the service when a remote device searches for
* it using SDP.
*
* <p>This is only used for {@link BluetoothSocket#TYPE_RFCOMM} sockets.
*
* <p>Defaults to {@code null}.
*
* @param rfcommUuid The RFCOMM service UUID.
* @return This builder.
*/
@NonNull
@RequiresNoPermission
public Builder setRfcommUuid(@NonNull UUID rfcommUuid) {
mRfcommUuid = rfcommUuid;
return this;
}
/**
* Sets the data path for this socket. The data path determines how data is routed and
* processed for the socket connection.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @param dataPath The desired data path for the socket.
* @return This Builder object to allow for method chaining.
* @throws IllegalArgumentException If {@code dataPath} is an invalid value.
* @hide
*/
@SystemApi
@NonNull
@RequiresNoPermission
public Builder setDataPath(@SocketDataPath int dataPath) {
if (dataPath < DATA_PATH_NO_OFFLOAD || dataPath > DATA_PATH_HARDWARE_OFFLOAD) {
throw new IllegalArgumentException("Invalid dataPath - " + dataPath);
}
mDataPath = dataPath;
return this;
}
/**
* Sets a user-friendly name for this socket. This name is primarily used for debugging and
* logging purposes.
*
* <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the
* offloaded application running on low-power processor.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @param socketName The desired name for the socket. This should be a descriptive name that
* helps identify the socket during development and troubleshooting. The socket name
* cannot exceed {@link #HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH} bytes in length when
* encoded in UTF-8.
* @return This Builder object to allow for method chaining.
* @throws IllegalArgumentException if the provided `socketName` exceeds {@link
* #HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH} bytes when encoded in UTF-8.
* @hide
*/
@SystemApi
@NonNull
@RequiresNoPermission
public Builder setSocketName(@NonNull String socketName) {
byte[] socketNameBytes = requireNonNull(socketName).getBytes(StandardCharsets.UTF_8);
if (socketNameBytes.length > HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH) {
throw new IllegalArgumentException(
"Socket name cannot exceed "
+ HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH
+ " bytes in length when encoded in UTF-8.");
}
mSocketName = requireNonNull(socketName);
return this;
}
/**
* Sets the ID of the hub to be associated with this socket when using {@link
* #DATA_PATH_HARDWARE_OFFLOAD}.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @param hubId The ID of the hub.
* @return This Builder object to allow for method chaining.
* @hide
*/
@SystemApi
@NonNull
@RequiresNoPermission
public Builder setHubId(long hubId) {
mHubId = hubId;
return this;
}
/**
* Sets the ID of the endpoint within the hub to be associated with this socket when using
* {@link #DATA_PATH_HARDWARE_OFFLOAD}. An endpoint represents a specific point of
* communication within the hub.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @param endpointId The ID of the endpoint within the hub.
* @return This Builder object to allow for method chaining.
* @hide
*/
@SystemApi
@NonNull
@RequiresNoPermission
public Builder setEndpointId(long endpointId) {
mEndpointId = endpointId;
return this;
}
/**
* Sets the requested maximum size (in bytes) of a data packet that can be received from the
* endpoint associated with this socket when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
*
* <p>The main Bluetooth stack may adjust this value based on the actual capabilities
* negotiated with the peer device during connection establishment. To get the final
* negotiated value, use {@link BluetoothSocket#getMaxReceivePacketSize()} after the socket
* is connected.
*
* <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
* available through the System API.
*
* @param maximumPacketSize The maximum packet size in bytes.
* @return This Builder object to allow for method chaining.
* @hide
*/
@SystemApi
@NonNull
@RequiresNoPermission
public Builder setRequestedMaximumPacketSize(int maximumPacketSize) {
mMaximumPacketSize = maximumPacketSize;
return this;
}
/**
* Builds a {@link BluetoothSocketSettings} object.
*
* @return A new {@link BluetoothSocketSettings} object with the configured parameters.
* @throws IllegalArgumentException on invalid parameters
*/
@NonNull
@RequiresNoPermission
public BluetoothSocketSettings build() {
if (mSocketType == BluetoothSocket.TYPE_RFCOMM) {
if (mRfcommUuid == null) {
throw new IllegalArgumentException("RFCOMM socket with missing uuid");
}
if (mL2capPsm != L2CAP_PSM_UNSPECIFIED) {
throw new IllegalArgumentException(
"Invalid Socket config: "
+ " Socket type: "
+ mSocketType
+ " L2cap PSM: "
+ mL2capPsm);
}
}
if (mSocketType == BluetoothSocket.TYPE_LE) {
if (mRfcommUuid != null) {
throw new IllegalArgumentException(
"Invalid Socket config: "
+ "Socket type: "
+ mSocketType
+ " Rfcomm Service Name: "
+ mRfcommServiceName
+ " Rfcomm Uuid: "
+ mRfcommUuid);
}
}
if (mDataPath == DATA_PATH_HARDWARE_OFFLOAD) {
if (mHubId == INVALID_HUB_ID || mEndpointId == INVALID_ENDPOINT_ID) {
throw new IllegalArgumentException(
"Hub ID and endpoint ID must be set for hardware data path");
}
if (mMaximumPacketSize < 0) {
throw new IllegalArgumentException("invalid packet size " + mMaximumPacketSize);
}
} else {
if (mHubId != INVALID_HUB_ID || mEndpointId != INVALID_ENDPOINT_ID) {
throw new IllegalArgumentException(
"Hub ID and endpoint ID may not be set for software data path");
}
}
return new BluetoothSocketSettings(
mSocketType,
mL2capPsm,
mEncryptionRequired,
mAuthenticationRequired,
mRfcommServiceName,
mRfcommUuid,
mDataPath,
mSocketName,
mHubId,
mEndpointId,
mMaximumPacketSize);
}
}
}