Cómo buscar dispositivos Bluetooth

Con BluetoothAdapter, puedes encontrar dispositivos Bluetooth remotos a través del descubrimiento de dispositivos o consultando la lista de dispositivos vinculados.

Asegúrate de tener los permisos de Bluetooth adecuados y de configurar tu app para Bluetooth antes de intentar encontrar dispositivos Bluetooth.

El descubrimiento de dispositivos es un procedimiento de escaneo que busca dispositivos compatibles con Bluetooth en el área local y solicita información sobre cada uno. A veces, este proceso se denomina descubrimiento, consulta o análisis. Un dispositivo Bluetooth cercano responde a una solicitud de descubrimiento solo si está en modo detectable y acepta solicitudes de información. Si un dispositivo es detectable, responde a la solicitud de descubrimiento compartiendo cierta información, como el nombre, la clase y la dirección MAC única del dispositivo. Con esta información, el dispositivo que realiza el proceso de descubrimiento puede iniciar una conexión con el dispositivo descubierto.

Debido a que los dispositivos detectables pueden revelar información sobre la ubicación del usuario, el proceso de descubrimiento de dispositivos requiere acceso a la ubicación. Si tu app se usa en un dispositivo que ejecuta Android 8.0 (nivel de API 26) o versiones posteriores, considera usar la API de Companion Device Manager en su lugar. Esta API realiza el descubrimiento de dispositivos en nombre de tu app, por lo que no es necesario que solicites permisos de ubicación.

Una vez que se establece una conexión con un dispositivo remoto por primera vez, se le presenta automáticamente al usuario una solicitud de vinculación. Cuando se vincula un dispositivo, la información básica sobre él, como el nombre, la clase y la dirección MAC, se guarda y se puede leer con las APIs de Bluetooth. Con la dirección MAC conocida de un dispositivo remoto, se puede iniciar una conexión con él en cualquier momento sin realizar el descubrimiento, siempre y cuando el dispositivo siga dentro del alcance.

Ten presente que existe una diferencia entre la vinculación y la conexión:

  • Estar vinculado significa que dos dispositivos conocen la existencia del otro, tienen una clave de vínculo compartida que se puede usar para la autenticación y son capaces de establecer una conexión encriptada entre sí.
  • Estar conectado significa que los dispositivos comparten un canal RFCOMM y pueden transmitir datos entre sí. Las APIs de Bluetooth actuales requieren que los dispositivos se vinculen antes de que se pueda establecer una conexión RFCOMM. La vinculación se realiza automáticamente cuando inicias una conexión encriptada con las APIs de Bluetooth.

En las siguientes secciones, se describe cómo encontrar dispositivos vinculados y cómo descubrir dispositivos nuevos mediante el descubrimiento de dispositivos.

Realizar consultas a dispositivos sincronizados

Antes de realizar el descubrimiento de dispositivos, vale la pena consultar el conjunto de dispositivos vinculados para ver si ya se conoce el dispositivo deseado. Para ello, llama a getBondedDevices(). Se muestra un conjunto de objetos BluetoothDevice que representan dispositivos vinculados. Por ejemplo, puedes consultar todos los dispositivos vinculados y obtener el nombre y la dirección MAC de cada uno, como se muestra en el siguiente fragmento de código:

Kotlin

val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
pairedDevices?.forEach { device ->
   val deviceName = device.name
   val deviceHardwareAddress = device.address // MAC address
}

Java

Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();

if (pairedDevices.size() > 0) {
   // There are paired devices. Get the name and address of each paired device.
   for (BluetoothDevice device : pairedDevices) {
       String deviceName = device.getName();
       String deviceHardwareAddress = device.getAddress(); // MAC address
   }
}

Para iniciar una conexión con un dispositivo Bluetooth, todo lo que se necesita del objeto BluetoothDevice asociado es la dirección MAC, que se recupera llamando a getAddress(). Puedes obtener más información para crear una conexión en Cómo conectar dispositivos Bluetooth.

Detectar dispositivos

Para comenzar a descubrir dispositivos, llama a startDiscovery(). El proceso es asíncrono y muestra un valor booleano que indica si el descubrimiento se inició correctamente. Por lo general, el proceso de descubrimiento implica un análisis de consulta de aproximadamente 12 segundos, seguido de un análisis de página de cada dispositivo encontrado para recuperar su nombre Bluetooth.

Para recibir información sobre cada dispositivo descubierto, tu app debe registrar un BroadcastReceiver para el intent ACTION_FOUND. El sistema emite esta intent para cada dispositivo. El intent contiene los campos adicionales EXTRA_DEVICE y EXTRA_CLASS, que a su vez contienen un BluetoothDevice y un BluetoothClass, respectivamente. En el siguiente fragmento de código, se muestra cómo puedes registrarte para controlar la transmisión cuando se detectan dispositivos:

Kotlin

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

   // Register for broadcasts when a device is discovered.
   val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
   registerReceiver(receiver, filter)
}

// Create a BroadcastReceiver for ACTION_FOUND.
private val receiver = object : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       val action: String = intent.action
       when(action) {
           BluetoothDevice.ACTION_FOUND -> {
               // Discovery has found a device. Get the BluetoothDevice
               // object and its info from the Intent.
               val device: BluetoothDevice =
                       intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
               val deviceName = device.name
               val deviceHardwareAddress = device.address // MAC address
           }
       }
   }
}

override fun onDestroy() {
   super.onDestroy()
   ...

   // Don't forget to unregister the ACTION_FOUND receiver.
   unregisterReceiver(receiver)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
   ...

   // Register for broadcasts when a device is discovered.
   IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
   registerReceiver(receiver, filter);
}

// Create a BroadcastReceiver for ACTION_FOUND.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
   public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();
       if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           // Discovery has found a device. Get the BluetoothDevice
           // object and its info from the Intent.
           BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
           String deviceName = device.getName();
           String deviceHardwareAddress = device.getAddress(); // MAC address
       }
   }
};

@Override
protected void onDestroy() {
   super.onDestroy();
   ...

   // Don't forget to unregister the ACTION_FOUND receiver.
   unregisterReceiver(receiver);
}

Para iniciar una conexión con un dispositivo Bluetooth, llama a getAddress() en BluetoothDevice para recuperar la dirección MAC asociada.

Habilitar la visibilidad

Para que otros dispositivos puedan detectar el dispositivo local, llama a startActivityForResult(Intent, int) con el intent ACTION_REQUEST_DISCOVERABLE. Esto emite una solicitud para habilitar el modo detectable del sistema sin tener que navegar a la app de Configuración, lo que detendría tu propia app. De forma predeterminada, el dispositivo se vuelve detectable durante dos minutos. Para definir una duración diferente, de hasta cinco minutos, agrega el elemento EXTRA_DISCOVERABLE_DURATION adicional.

En el siguiente fragmento de código, se establece que el dispositivo sea detectable durante cinco minutos:

Kotlin

val requestCode = 1;
val discoverableIntent: Intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply {
   putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300)
}
startActivityForResult(discoverableIntent, requestCode)

Java

int requestCode = 1;
Intent discoverableIntent =
       new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(discoverableIntent, requestCode);


Figura 2: Diálogo para habilitar la visibilidad.

Aparece un diálogo en el que se solicita el permiso del usuario para que el dispositivo sea detectable, como se muestra en la Figura 2. Si el usuario responde "Permitir", el dispositivo se vuelve detectable durante el período especificado. Luego, tu actividad recibe una llamada a la devolución de llamada de onActivityResult(), con el código de resultado igual a la duración durante la que el dispositivo es detectable. Si el usuario respondió "Rechazar" o si se produjo un error, el código de resultado es RESULT_CANCELED.

El dispositivo se mantendrá silenciosamente en el modo detectable durante el tiempo asignado. Para recibir una notificación cuando cambie el modo detectable, registra un BroadcastReceiver para el intent ACTION_SCAN_MODE_CHANGED. Este intent contiene los campos adicionales EXTRA_SCAN_MODE y EXTRA_PREVIOUS_SCAN_MODE, que proporcionan el modo de escaneo nuevo y el anterior, respectivamente. Los valores posibles para cada elemento adicional son los siguientes:

SCAN_MODE_CONNECTABLE_DISCOVERABLE
El dispositivo está en modo detectable.
SCAN_MODE_CONNECTABLE
El dispositivo no está en modo detectable, pero puede recibir conexiones.
SCAN_MODE_NONE
El dispositivo no está en modo detectable y no puede recibir conexiones.

Si inicias la conexión a un dispositivo remoto, no es necesario que habilites la visibilidad del dispositivo. Habilitar la visibilidad solo es necesario cuando quieres que tu app aloje un socket de servidor que acepte conexiones entrantes, ya que los dispositivos remotos deben poder descubrir otros dispositivos antes de iniciar conexiones con ellos.