The Android platform includes support for the Bluetooth network stack. The Bluetooth network stack allows a device to wirelessly exchange data with other Bluetooth devices. The application framework provides access to Bluetooth functionality through the Android Bluetooth APIs.
Android Things extends these APIs to enable apps to control the Bluetooth system settings, device pairing, and connection process.
Adding the required permissions
In addition to the BLUETOOTH
and BLUETOOTH_ADMIN
permissions, add the following to
your app's manifest file to use the Bluetooth connection and pairing APIs:
<uses-permission android:name="com.google.android.things.permission.MANAGE_BLUETOOTH" />
Configuring device attributes
The Android Things Bluetooth APIs enable you to control the device class and
supported profiles exposed by the local Bluetooth adapter. To configure the
Bluetooth Class of Device
(CoD),
create a BluetoothClass
instance via the
BluetoothClassFactory
and set it using the
BluetoothConfigManager:
Kotlin
import android.bluetooth.BluetoothClass import com.google.android.things.bluetooth.BluetoothClassFactory import com.google.android.things.bluetooth.BluetoothConfigManager ... val manager = BluetoothConfigManager.getInstance() // Report the local Bluetooth device class as a speaker manager.bluetoothClass = BluetoothClassFactory.build( BluetoothClass.Service.AUDIO, BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER )
Java
import android.bluetooth.BluetoothClass; import com.google.android.things.bluetooth.BluetoothClassFactory; import com.google.android.things.bluetooth.BluetoothConfigManager; ... BluetoothConfigManager manager = BluetoothConfigManager.getInstance(); // Report the local Bluetooth device class as a speaker BluetoothClass deviceClass = BluetoothClassFactory.build( BluetoothClass.Service.AUDIO, BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER); manager.setBluetoothClass(deviceClass);
I/O capabilities
Use BluetoothConfigManager to report the Input/Output capabilities of your device to the Bluetooth service. Set the I/O capabilities for Bluetooth with setIoCapability() and Bluetooth Low Energy (LE) with setLeIoCapability() These methods accept one of the following values:
- IO_CAPABILITY_NONE: Device has no input or output capabilities. This is the default value.
- IO_CAPABILITY_OUT: Device has a display only.
- IO_CAPABILITY_IN: Device can accept keyboard user input only.
- IO_CAPABILITY_IO: Device has a display and can accept basic (yes/no) input.
- IO_CAPABILITY_KBDISP: Device has a display and can accept keyboard user input.
The I/O capabilities of the device are shared with remote devices during the Pairing Feature Exchange phase of device pairing. The Bluetooth service determines which pairing variants your device can support based on its I/O capabilities.
Kotlin
import com.google.android.things.bluetooth.BluetoothConfigManager ... val manager = BluetoothConfigManager.getInstance() // Report full input/output capability for this device manager.ioCapability = BluetoothConfigManager.IO_CAPABILITY_IO
Java
import com.google.android.things.bluetooth.BluetoothConfigManager; ... BluetoothConfigManager manager = BluetoothConfigManager.getInstance(); // Report full input/output capability for this device manager.setIoCapability(BluetoothConfigManager.IO_CAPABILITY_IO);
Enabled profiles
To configure the enabled Bluetooth profiles on the local device, use the BluetoothProfileManager. Each BluetoothProfile must be enabled before a remote device can connect to it. Query the current set of enabled profiles with getEnabledProfiles() and update them using one of the following methods: enableProfiles() or disableProfiles():
Kotlin
import com.google.android.things.bluetooth.BluetoothProfileManager; import com.google.android.things.bluetooth.BluetoothProfile; ... val manager = BluetoothProfileManager.getInstance() val enabledProfiles = manager.enabledProfiles if (!enabledProfiles.contains(BluetoothProfile.A2DP_SINK)) { Log.d(TAG, "Enabling A2DP sink mode.") val toDisable = listOf(BluetoothProfile.A2DP) val toEnable = listOf(BluetoothProfile.A2DP_SINK, BluetoothProfile.AVRCP_CONTROLLER) manager.enableAndDisableProfiles(toEnable, toDisable) }
Java
import com.google.android.things.bluetooth.BluetoothProfileManager; import com.google.android.things.bluetooth.BluetoothProfile; ... BluetoothProfileManager manager = BluetoothProfileManager.getInstance(); List<Integer> enabledProfiles = manager.getEnabledProfiles(); if (!enabledProfiles.contains(BluetoothProfile.A2DP_SINK)) { Log.d(TAG, "Enabling A2DP sink mode."); List<Integer> toDisable = Arrays.asList(BluetoothProfile.A2DP); List<Integer> toEnable = Arrays.asList( BluetoothProfile.A2DP_SINK, BluetoothProfile.AVRCP_CONTROLLER); manager.enableAndDisableProfiles(toEnable, toDisable); }
Pairing with a remote device
See Finding Devices in the Android Bluetooth Guide for more details on discovering remote devices and determining if they are already bonded with the local device. If the remote device is already bonded, you can jump directly to connecting to a remote device.
To begin the pairing process with a remote device:
- Register a BluetoothPairingCallback with the BluetoothConnectionManager
- Call
initiatePairing()
with the discovered peer device. For peer devices which report their
Input/Output Capability as
IO_CAPABILITY_NONE
, you will receive a callback in onPaired(), once pairing is complete. - If the peer device requires user input to pair, handle the pairing request in onPairingInitiated().
Kotlin
import android.bluetooth.BluetoothDevice import com.google.android.things.bluetooth.BluetoothConnectionManager import com.google.android.things.bluetooth.BluetoothPairingCallback import com.google.android.things.bluetooth.PairingParams ... class PairingActivity : Activity() { private lateinit var bluetoothConnectionManager: BluetoothConnectionManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bluetoothConnectionManager = BluetoothConnectionManager.getInstance().apply { registerPairingCallback(bluetoothPairingCallback) } } override fun onDestroy() { super.onDestroy() bluetoothConnectionManager.unregisterPairingCallback(bluetoothPairingCallback) } private fun startPairing(remoteDevice: BluetoothDevice) { bluetoothConnectionManager.initiatePairing(remoteDevice) } private val bluetoothPairingCallback = object : BluetoothPairingCallback { override fun onPairingInitiated( bluetoothDevice: BluetoothDevice, pairingParams: PairingParams ) { // Handle incoming pairing request or confirmation of outgoing pairing request handlePairingRequest(bluetoothDevice, pairingParams) } override fun onPaired(bluetoothDevice: BluetoothDevice) { // Device pairing complete } override fun onUnpaired(bluetoothDevice: BluetoothDevice) { // Device unpaired } override fun onPairingError( bluetoothDevice: BluetoothDevice, pairingError: BluetoothPairingCallback.PairingError ) { // Something went wrong! } } }
Java
import android.bluetooth.BluetoothDevice; import com.google.android.things.bluetooth.BluetoothConnectionManager; import com.google.android.things.bluetooth.BluetoothPairingCallback; import com.google.android.things.bluetooth.PairingParams; ... public class PairingActivity extends Activity { BluetoothConnectionManager bluetoothConnectionManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); bluetoothConnectionManager = BluetoothConnectionManager.getInstance(); bluetoothConnectionManager.registerPairingCallback(bluetoothPairingCallback); } @Override protected void onDestroy() { super.onDestroy(); bluetoothConnectionManager.unregisterPairingCallback(bluetoothPairingCallback); } private void startPairing(BluetoothDevice remoteDevice) { bluetoothConnectionManager.initiatePairing(remoteDevice); } private BluetoothPairingCallback bluetoothPairingCallback = new BluetoothPairingCallback() { @Override public void onPairingInitiated(BluetoothDevice bluetoothDevice, PairingParams pairingParams) { // Handle incoming pairing request or confirmation of outgoing pairing request handlePairingRequest(bluetoothDevice, pairingParams); } @Override public void onPaired(BluetoothDevice bluetoothDevice) { // Device pairing complete } @Override public void onUnpaired(BluetoothDevice bluetoothDevice) { // Device unpaired } @Override public void onPairingError(BluetoothDevice bluetoothDevice, BluetoothPairingCallback.PairingError pairingError) { // Something went wrong! } }; }
The PairingParams provided to the callback defines the Bluetooth pairing variant required by the remote device. These represent different pairing security schemes, and your app is responsible for taking the appropriate action as documented for each variant. For pairing variants that require user input, complete the process by calling finishPairing().
Kotlin
private fun handlePairingRequest(bluetoothDevice: BluetoothDevice, pairingParams: PairingParams) { when (pairingParams.pairingType) { PairingParams.PAIRING_VARIANT_DISPLAY_PIN, PairingParams.PAIRING_VARIANT_DISPLAY_PASSKEY -> { // Display the required PIN to the user Log.d(TAG, "Display Passkey - ${pairingParams.pairingPin}") } PairingParams.PAIRING_VARIANT_PIN, PairingParams.PAIRING_VARIANT_PIN_16_DIGITS -> { // Obtain PIN from the user val pin = ... // Pass the result to complete pairing bluetoothConnectionManager.finishPairing(bluetoothDevice, pin) } PairingParams.PAIRING_VARIANT_CONSENT, PairingParams.PAIRING_VARIANT_PASSKEY_CONFIRMATION -> { // Show confirmation of pairing to the user ... // Complete the pairing process bluetoothConnectionManager.finishPairing(bluetoothDevice) } } }
Java
private void handlePairingRequest(BluetoothDevice bluetoothDevice, PairingParams pairingParams) { switch (pairingParams.getPairingType()) { case PairingParams.PAIRING_VARIANT_DISPLAY_PIN: case PairingParams.PAIRING_VARIANT_DISPLAY_PASSKEY: // Display the required PIN to the user Log.d(TAG, "Display Passkey - " + pairingParams.getPairingPin()); break; case PairingParams.PAIRING_VARIANT_PIN: case PairingParams.PAIRING_VARIANT_PIN_16_DIGITS: // Obtain PIN from the user String pin = ...; // Pass the result to complete pairing bluetoothConnectionManager.finishPairing(bluetoothDevice, pin); break; case PairingParams.PAIRING_VARIANT_CONSENT: case PairingParams.PAIRING_VARIANT_PASSKEY_CONFIRMATION: // Show confirmation of pairing to the user ... // Complete the pairing process bluetoothConnectionManager.finishPairing(bluetoothDevice); break; } }
Connecting to a remote device
Once you have successfully paired over Bluetooth, your app can connect to profiles and services on the remote device. The Android Bluetooth API exposes connection features for a restricted set of device profiles. See Connecting Devices in the Android Bluetooth Guide and the Bluetooth Low Energy Guide for more details on connecting to devices using the RFCOMM and GATT profiles.
The Android Things BluetoothConnectionManager enables apps to connect to additional profiles and services on remote devices. Use the getConnectableProfiles() method to report the available profiles on the remote device.
To connect with a specific profile on a given BluetoothDevice
:
- Register a BluetoothConnectionCallback with the BluetoothConnectionManager
- Initiate the connection with the connect() method, providing the device and the target BluetoothProfile
- Handle the connection request in onConnectionRequested()
Kotlin
import android.bluetooth.BluetoothDevice import com.google.android.things.bluetooth.BluetoothConnectionManager import com.google.android.things.bluetooth.BluetoothConnectionCallback import com.google.android.things.bluetooth.BluetoothProfile import com.google.android.things.bluetooth.ConnectionParams ... class ConnectActivity : Activity() { private lateinit var bluetoothConnectionManager: BluetoothConnectionManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bluetoothConnectionManager = BluetoothConnectionManager.getInstance().apply { registerConnectionCallback(bluetoothConnectionCallback) } } override fun onDestroy() { super.onDestroy() bluetoothConnectionManager.unregisterConnectionCallback(bluetoothConnectionCallback) } private fun connectToA2dp(bluetoothDevice: BluetoothDevice) { bluetoothConnectionManager.connect(bluetoothDevice, BluetoothProfile.A2DP_SINK) } // Set up callbacks for the profile connection process. private val bluetoothConnectionCallback = object : BluetoothConnectionCallback { override fun onConnectionRequested( bluetoothDevice: BluetoothDevice, connectionParams: ConnectionParams ) { // Handle incoming connection request handleConnectionRequest(bluetoothDevice, connectionParams) } override fun onConnectionRequestCancelled( bluetoothDevice: BluetoothDevice, requestType: Int ) { // Request cancelled } override fun onConnected(bluetoothDevice: BluetoothDevice, profile: Int) { // Connection completed successfully } override fun onDisconnected(bluetoothDevice: BluetoothDevice, profile: Int) { // Remote device disconnected } } }
Java
import android.bluetooth.BluetoothDevice; import com.google.android.things.bluetooth.BluetoothConnectionManager; import com.google.android.things.bluetooth.BluetoothConnectionCallback; import com.google.android.things.bluetooth.BluetoothProfile; import com.google.android.things.bluetooth.ConnectionParams; ... public class ConnectActivity extends Activity { BluetoothConnectionManager bluetoothConnectionManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); bluetoothConnectionManager = BluetoothConnectionManager.getInstance(); bluetoothConnectionManager.registerConnectionCallback(bluetoothConnectionCallback); } @Override protected void onDestroy() { super.onDestroy(); bluetoothConnectionManager.unregisterConnectionCallback(bluetoothConnectionCallback); } private void connectToA2dp(BluetoothDevice bluetoothDevice) { bluetoothConnectionManager.connect(bluetoothDevice, BluetoothProfile.A2DP_SINK); } // Set up callbacks for the profile connection process. private final BluetoothConnectionCallback bluetoothConnectionCallback = new BluetoothConnectionCallback() { @Override public void onConnectionRequested(BluetoothDevice bluetoothDevice, ConnectionParams connectionParams) { // Handle incoming connection request handleConnectionRequest(bluetoothDevice, connectionParams); } @Override public void onConnectionRequestCancelled(BluetoothDevice bluetoothDevice, int requestType) { // Request cancelled } @Override public void onConnected(BluetoothDevice bluetoothDevice, int profile) { // Connection completed successfully } @Override public void onDisconnected(BluetoothDevice bluetoothDevice, int profile) { // Remote device disconnected } }; }
The ConnectionParams provided to the callback include additional details about the type of connection requested. Inspect those parameters in your code, and then determine whether to accept or reject the request with confirmOrDenyConnection():
Kotlin
private fun handleConnectionRequest( bluetoothDevice: BluetoothDevice, connectionParams: ConnectionParams ) { // Determine whether to accept the connection request val accept = connectionParams.requestType == ConnectionParams.REQUEST_TYPE_PROFILE_CONNECTION // Pass that result on to the BluetoothConnectionManager bluetoothConnectionManager.confirmOrDenyConnection(bluetoothDevice, connectionParams, accept) }
Java
private void handleConnectionRequest(BluetoothDevice bluetoothDevice, ConnectionParams connectionParams) { // Determine whether to accept the connection request boolean accept = false; if (connectionParams.getRequestType() == ConnectionParams.REQUEST_TYPE_PROFILE_CONNECTION) { accept = true; } // Pass that result on to the BluetoothConnectionManager bluetoothConnectionManager.confirmOrDenyConnection(bluetoothDevice, connectionParams, accept); }