/* * Copyright (C) 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. * *

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. * *

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 final int mSocketType; /** Encryption requirement for the Bluetooth socket. */ private final boolean mEncryptionRequired; /** Authentication requirement for the Bluetooth socket. */ private final boolean mAuthenticationRequired; /** L2CAP Protocol/Service Multiplexer (PSM) for the Bluetooth Socket. */ private final int mL2capPsm; /** RFCOMM service name associated with the Bluetooth socket. */ private final String mRfcommServiceName; /** RFCOMM service UUID associated with the Bluetooth socket. */ private final 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: * *

*/ private final @SocketDataPath 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. * *

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 final String mSocketName; /** * When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the hub hosting the endpoint. * *

Hub represents a logical/physical representation of multiple endpoints. A pair of {@code * mHubId} and {@code mEndpointId} uniquely identifies the endpoint globally. */ private final long mHubId; /** * When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the specific endpoint within * the hub that is associated with this socket. */ private final 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 final int mMaximumPacketSize; /** * Returns the type of the Bluetooth socket. * *

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. * *

Defaults to {@code null}. */ @Nullable @RequiresNoPermission public String getRfcommServiceName() { return mRfcommServiceName; } /** * Returns the RFCOMM service UUID used for a BluetoothSocket#TYPE_RFCOMM socket. * *

Defaults to {@code null}. */ @Nullable @RequiresNoPermission public UUID getRfcommUuid() { return mRfcommUuid; } /** * Checks if encryption is enabled for the Bluetooth socket. * *

Defaults to {@code false}. */ @RequiresNoPermission public boolean isEncryptionRequired() { return mEncryptionRequired; } /** * Checks if authentication is enabled for the Bluetooth socket. * *

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. * *

Defaults to {@link #DATA_PATH_NO_OFFLOAD}. * *

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. * *

When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded * application running on the low-power processor. * *

Defaults to {@code null} if no name was explicitly set. * *

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}. * *

If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns * {@link #INVALID_HUB_ID}. * *

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. * *

If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns * {@link #INVALID_ENDPOINT_ID}. * *

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}. * *

Defaults to {@link #HARDWARE_OFFLOAD_PACKET_MAX_SIZE}. * *

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. * *

Must be one of: * *

* *

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. * *

This is only used for {@link BluetoothSocket#TYPE_LE} sockets. * *

Valid PSM values for {@link BluetoothSocket#TYPE_LE} sockets is ranging from 128 * (0x80) to 255 (0xFF). * *

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. * *

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. * *

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. * *

This name is used to identify the service when a remote device searches for it using * SDP. * *

This is only used for {@link BluetoothSocket#TYPE_RFCOMM} sockets. * *

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. * *

This UUID is used to uniquely identify the service when a remote device searches for * it using SDP. * *

This is only used for {@link BluetoothSocket#TYPE_RFCOMM} sockets. * *

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. * *

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. * *

When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the * offloaded application running on low-power processor. * *

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}. * *

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. * *

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}. * *

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. * *

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); } } }