Bluetooth

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:

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.

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():

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:

  1. Register a BluetoothPairingCallback with the BluetoothConnectionManager
  2. Call initiatePairing() with the discovered peer device
  3. Handle the pairing request in onPairingInitiated()
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 mBluetoothConnectionManager;

    @Override
    protected void onCreate(Bundled savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBluetoothConnectionManager = BluetoothConnectionManager.getInstance();
        mBluetoothConnectionManager.registerPairingCallback(mBluetoothPairingCallback);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothConnectionManager.unregisterPairingCallback(mBluetoothPairingCallback);
    }

    private void startPairing(BluetoothDevice remoteDevice) {
        mBluetoothConnectionManager.initiatePairing(remoteDevice);
    }

    private BluetoothPairingCallback mBluetoothPairingCallback = 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. Your app is responsible for taking the appropriate action based on the variant, such as accepting user input or showing a confirmation. For pairing variants that require user interaction, complete the process by calling finishPairing().

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
            mBluetoothConnectionManager.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
            mBluetoothConnectionManager.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:

  1. Register a BluetoothConnectionCallback with the BluetoothConnectionManager
  2. Initiate the connection with the connect() method, providing the device and the target BluetoothProfile
  3. Handle the connection request in onConnectionRequested()
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 mBluetoothConnectionManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBluetoothConnectionManager = BluetoothConnectionManager.getInstance();
        mBluetoothConnectionManager.registerConnectionCallback(mBluetoothConnectionCallback);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothConnectionManager.unregisterConnectionCallback(mBluetoothConnectionCallback);
    }

    private void connectToA2dp(BluetoothDevice bluetoothDevice) {
        mBluetoothConnectionManager.connect(bluetoothDevice, BluetoothProfile.A2DP_SINK);
    }

    // Set up callbacks for the profile connection process.
    private final BluetoothConnectionCallback mBluetoothConnectionCallback = new BluetoothConnectionCallback() {
        @Override
        public void onConnectionRequested(BluetoothDevice bluetoothDevice, ConnectionParams connectionParams) {
            // Handle incoming connection request
            handleConnectionRequest();
        }

        @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():

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
    mBluetoothConnectionManager.confirmOrDenyConnection(bluetoothDevice, connectionParams, accept);
}