Companion device pairing

On devices running Android 8.0 (API level 26) and higher, companion device pairing performs a Bluetooth or Wi-Fi scan of nearby devices on behalf of your app without requiring the ACCESS_FINE_LOCATION or BLUETOOTH_ADMIN permission. This helps minimize the number of permissions needed by your app and is more privacy-friendly for users. After the device is paired, the device can leverage the REQUEST_COMPANION_RUN_IN_BACKGROUND and REQUEST_COMPANION_USE_DATA_IN_BACKGROUND permissions to start the app from the background. You might use this method of pairing devices for initial configuration of a BLE-capable smart watch, for example.

Companion device pairing doesn't create the connection to chosen devices. You use Bluetooth or Wi-Fi connectivity APIs to establish connections. Companion device pairing also isn't intended for continuous scanning. It is a means for implementing custom protocols over Bluetooth, BLE, and Wi-Fi that are required for the companion device to function.

The requested permissions are granted when the user associates your app by selecting a device from the list. These permissions are revoked only if you call disassociate() or if the app is uninstalled. An app is responsible for clearing its own associations if the user no longer needs them, such as when they log out or remove bonded devices.

Implement companion device pairing

You can customize the pairing request dialog when trying to pair with companion devices over Bluetooth, BLE, and Wi-Fi.

In your app, you can specify whether users see a list of possible companion devices or only one suggestion for a companion device. You can also filter the items that appear in the pairing request dialog, such as by type (Bluetooth, BLE, and Wi-Fi) or by device name.

In your implementation, you must do the following:

The following code snippet demonstrates how to display a dialog asking whether the user wants to pair a handheld device with a single, Bluetooth-connected companion device named "My device."

Kotlin

class MyDeviceSelectionActivity : Activity() {

    private val deviceManager: CompanionDeviceManager by lazy(LazyThreadSafetyMode.NONE) {
        getSystemService(CompanionDeviceManager::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...

        // To skip filtering based on name and supported feature flags (UUIDs),
        // don't include calls to setNamePattern() and addServiceUuid(),
        // respectively. This example uses Bluetooth.
        val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
                .setNamePattern(Pattern.compile("My device"))
                .addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null)
                .build()

        // The argument provided in setSingleDevice() determines whether a single
        // device name or a list of device names is presented to the user as
        // pairing options.
        val pairingRequest: AssociationRequest = AssociationRequest.Builder()
                .addDeviceFilter(deviceFilter)
                .setSingleDevice(true)
                .build()

        // When the app tries to pair with the Bluetooth device, show the
        // appropriate pairing request dialog to the user.
        deviceManager.associate(pairingRequest,
                object : CompanionDeviceManager.Callback() {

                    override fun onDeviceFound(chooserLauncher: IntentSender) {
                        startIntentSenderForResult(chooserLauncher,
                                SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
                    }

                    override fun onFailure(error: CharSequence?) {
                        // Handle failure
                    }
                }, null)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        when (requestCode) {
            SELECT_DEVICE_REQUEST_CODE -> when(resultCode) {
                Activity.RESULT_OK -> {
                    // User has chosen to pair with the Bluetooth device.
                    val deviceToPair: BluetoothDevice = 
                            data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
                    deviceToPair.createBond()
                    // ... Continue interacting with the paired device.
                }
            }
        }
    }
}

Java

public class MyDeviceSelectionActivity extends Activity {
    private CompanionDeviceManager deviceManager;
    private AssociationRequest pairingRequest;
    private BluetoothDeviceFilter deviceFilter;

    private static final int SELECT_DEVICE_REQUEST_CODE = 42;

    @override
    public void onCreate() {
        // ...
        deviceManager = getSystemService(CompanionDeviceManager.class);

        // To skip filtering based on name and supported feature flags (UUIDs),
        // don't include calls to setNamePattern() and addServiceUuid(),
        // respectively. This example uses Bluetooth.
        deviceFilter = new BluetoothDeviceFilter.Builder()
                .setNamePattern(Pattern.compile("My device"), null)
                .addServiceUuid(new ParcelUuid(new UUID(0x123abcL, -1L)))
                .build();

        // The argument provided in setSingleDevice() determines whether a single
        // device name or a list of device names is presented to the user as
        // pairing options.
        pairingRequest = new AssociationRequest.Builder()
                .addDeviceFilter(deviceFilter)
                .setSingleDevice(true)
                .build();

        // When the app tries to pair with the Bluetooth device, show the
        // appropriate pairing request dialog to the user.
        deviceManager.associate(pairingRequest,
                new CompanionDeviceManager.Callback() {
                    @Override
                    public void onDeviceFound(IntentSender chooserLauncher) {
                        startIntentSenderForResult(chooserLauncher,
                                SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0);
                    }
                },
                null);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == SELECT_DEVICE_REQUEST_CODE &&
                resultCode == Activity.RESULT_OK) {
            // User has chosen to pair with the Bluetooth device.
            BluetoothDevice deviceToPair =
                    data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
            deviceToPair.createBond();

            // ... Continue interacting with the paired device.
        }
    }
}

The following image shows the dialog box that appears.

Figure 1. The Companion Device Pairing screen