Programação Bluetooth no Android

Uma das coisas mais excitantes na programação de computadores e dispositivos móveis é a possibilidade de comunicação sem fio. É quase mágico quando um clique em um botão no seu celular gera uma ação em outro dispositivo. Dá uma sensação de controle, poder criar uma aplicação que envia comandos à distância para outro celular, notebook ou componentes eletrônicos feitos por você mesmo.

Neste post, vamos aprender a utilizar o controlador Bluetooth de um dispositivo Android. Ao final, seremos capazes de compartilhar dados entre dispositivos Android. No processo, vamos escrever um pequeno aplicativo capaz de buscar dispositivos Bluetooth, conectar-se e trocar mensagens.

  1. Ativando o adaptador Bluetooth;
  2. Buscando por dispositivos Bluetooth pareados;
  3. Descobrindo dispositivos Bluetooth nas proximidades;
  4. Conectando dois dispositivos;
  5. Transferência de dados bidirecional.

Alguns conceitos sobre Bluetooth

É sempre interessante deixar claro o que é quê antes de aprender algo. Então reunimos alguns conceitos que você deve entender antes de começar a brincar com seu dispositivo Bluetooth. Claro, não vamos demorar muito nesses conceitos, você tem todo o tempo do mundo e a Internet, caso queira entender tudo sobre Bluetooth. Rapidamente falando, Bluetooth é um tecnologia de comunicação baseada em ondas eletromagnéticas na faixa de frequência em torno de 2.4 GHz. Agora, alguns conceitos que nos serão úteis:

BluetoothAdapter: No Android, é classe que representa o dispositivo Bluetooth local. Esta classe contem métodos que realizam ações fundamentais como descoberta de dispositivos, criar sockets Bluetooth.

Pareamento: No protocolo Bluetooth, o pareamento é a maneira pela qual dois dispositivos se tornam conhecidos um para o outro. Os dispositivos só poderão se comunicar se estiverem pareados.

Descoberta: Uma maneira de saber quais dispositivos Bluetooth existem nas proximidades. Se um dispositivo não está pareado ao dispositivo com o qual deseja se comunicar, é necessário realizar a descoberta antes de fazer o pareamento.

Visibilidade: Seu dispositivo só será visível a outros dispositivos se você habilitar essa opção. Então, durante a descoberta, apenas os dispositivos vísiveis serão encontrados.

Conexão: Quando dois dispositivos já estão pareados, a realização de uma conexão cria um canal de comunicação bidirecional entre os aparelhos.

Sockets: Estrutura que representa a conexão realizada entre os dois dispositivos. Através do socket adquirido na conexão, é possível obter as streams de transmissão e recepção de dados.

1. Ativando o adaptador Bluetooth

1.1 Obtendo permissão

Para utilizar o Bluetooth no Android, é necessário declarar que o aplicativo utiliza essa funcionalidade. Isso é feito ao configurar as permissões do aplicativo no arquivo AndroidManifest.xml, que pode ser encontrado na pasta manifests. Abaixo você pode ver como ficará o manifesto com a adição das permissões Bluetooth. Na linha 5, a permissão BLUETOOTH declara que queremos utilizar o adaptador para iniciar conexões e transferir dados. A segunda permissão, BLUETOOTH_ADMIN, na linha 6, declara que o aplicativo poderá ter acesso às configurações do adaptador Bluetooth e também poderá realizar buscas por dispositivos próximos ainda não pareados. A permissão ACCESS_COARSE_LOCATION é necessária a partir do Android 6.0 para realização de busca de dispositivos. Veja o motivo dessa atualização aqui.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.project.dragaosemchama.superbluetooth" >

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainBluetoothActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

1.2 Garantindo que o adaptador Bluetooth funciona

Agora que nosso aplicativo possui permissão, podemos iniciar as experiências.

Vamos criar um objeto TextView chamado statusMessage, que nos permitirá imprimir mensagens na tela do aplicativo Android. Para isso, basta adicionar este widget ao arquivo activity_main_bluetooth.xml:

<TextView
    android:id="@+id/statusMessage"
    android:text="@string/hello_world"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Já no arquivo MainBluetoothActivity.java, você pode definir o objeto TextView dentro da definição de classe da atividade principal. Aproveite e adicione essas três linhas que definem as variáveis estáticas ENABLE_BLUETOOTH, SELECT_PAIRED_DEVICE e SELECT_DISCOVERED_DEVICE. Esses valores serão utilizados durante o processo de habilitação do Bluetooth, seleção de um dispositivo pareado ou descoberto. Confie…

    ...

public class MainBluetoothActivity extends ActionBarActivity {

    public static int ENABLE_BLUETOOTH = 1;
    public static int SELECT_PAIRED_DEVICE = 2;
    public static int SELECT_DISCOVERED_DEVICE = 3;

    static TextView statusMessage;

    ...

E vincule statusMessage ao widget previamente definido, dentro do método onCreate():

    ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_bluetooth);

    statusMessage = (TextView) findViewById(R.id.statusMessage);

    ...

Com isso, teremos um TextView pronto para exibir mensagens de status e poderemos saber o que está acontecendo nas entranhas do código.

Antes de tentar buscar ou conectar a outros dispositivos, é importante garantir que o aplicativo está sendo executado em um aparelho que suporta a funcionalidade Bluetooth e cujo hardware está em pleno funcionamento. Isso pode ser feito verificando se existe um BluetoothAdapter padrão para o dispositivo.

BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
if (btAdapter == null) {
    statusMessage.setText("Que pena! Hardware Bluetooth não está funcionando :(");
    } else {
    statusMessage.setText("Ótimo! Hardware Bluetooth está funcionando :)");
}

1.3 Ativando Bluetooth com permissão do usuário

Existem duas maneiras de ativar o adaptador Bluetooth. A documentação online do Android recomenda solicitar a ativação ao sistema, que automaticamente solicitará ao usuário uma permissão. O código a seguir inicialmente verifica se o adaptador Bluetooth está ativado. Se não, envia uma solicitação ao sistema na forma de um Intent. Caso o adaptador já esteja ativado, você pode ficar tranquilo.

if(!btAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH);
    statusMessage.setText("Solicitando ativação do Bluetooth...");
} else {
    statusMessage.setText("Bluetooth já ativado :)");
}

Vejamos o que está acontecendo. A classe Intent é uma abstração que representa uma operação a ser realizada. Aqui podemos imaginar que o objeto Intent que criamos, enableBtIntent, representa a operação de ativar o adaptador Bluetooth. Este Intent é utilizado para iniciar uma nova Activity, esperando uma resposta, como fazemos na linha 3 acima. A nova Activity apresenta ao usuário a escolha de ativar ou não a funcionalidade Bluetooth. Se isso tudo ainda ficou meio obscuro e você tiver interesse, essas páginas provavelmente vão fornecer uma explicação bem melhor:

Legal. Mas com isso, apenas poderemos solicitar a ativação do Bluetooth e continuar a execução do código. O problema é que, se fizermos apenas isso, as linhas seguintes do código serão executadas sem sabermos se o usuário permitiu ou não a ativação. Lembre-se que a execução do código se dá muitas vezes mais rápido do que o usuário será capaz de responder à solicitação. Então teremos que esperar que o usuário responda à solicitação, para então decidir o que fazer. Para isso, vamos adicionar o método onActivityResult() à classe MainBluetoothActivity.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if(requestCode == ENABLE_BLUETOOTH) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Bluetooth ativado :D");
        }
        else {
            statusMessage.setText("Bluetooth não ativado :(");
        }
    }
}

Este método sempre será executado quando o algoritmo retornar à MainBluetoothActivity, após a execução do método startActivityForResult() chamado internamente a MainBluetoothActivity. Por isso podemos capturar o momento em que o usuário fez sua escolha e tomar as devidas providências. É importante entender o significado de algumas das variáveis, para esse caso específico:

requestCode: funciona como um identificador sobre qual Activity está retornando um resultado. Esse valor é definido na chamada do método startActivityForResult(). Veja o valor ENABLE_BLUETOOTH. É um inteiro. Com isso, podemos saber qual Activity está retornando o resultado.

resultCode: traz a informação sobre a decisão do usuário. RESULT_OK, nesse caso, significa que o adaptador Bluetooth foi ativado, qualquer outro valor significa que o usuário negou a permissão ou que um erro ocorreu.

 

Enable Bluetooth Intent
Seja gentil, pergunte ao usuário se é isso o que ele quer, se é isso o que ele deseja.

1.4 Ativando o adaptador Bluetooth automaticamente

Certo, mas agora vamos supor que você não está feliz em pedir permissão ao sistema e ao usuário para ativar o adaptador Bluetooth. Você quer que isso aconteça automaticamente. Existe uma maneira muito simples de fazer isso.

btAdapter.enable();

Pronto, com isso, se não houver erros misteriosos, o adaptador Bluetooth estará ativado. Mas preste atenção, a documentação recomenda fortemente, ou exige, que o Bluetooth não seja ativado sem o consentimento do usuário. O método enable() é utilizado quando o aplicativo fornece uma interface gráfica em que o usuário pode escolher ativar ou não a funcionalidade. De qualquer forma, é possível ativar o Bluetooth dentro de seu código mesmo sem a permissão explícita do usuário com o código acima. Use quando estiver se sentindo rebelde ou quando sua aplicação exigir isso. A mesma coisa vale para o método disable(), que desativa o adaptador.

btAdapter.disable();

2. Buscando por dispositivos Bluetooth pareados

Para iniciar uma conexão com outro dispositivo, tudo o que precisamos é o seu endereço MAC. A não ser que sua aplicação seja bem específica e você já saiba o endereço ao qual o app deve se conectar, o app terá de obter o endereço MAC de alguma maneira. Que tal obter uma lista com todos os dispositivos conhecidos? O gerenciador Bluetooth do Android guarda na memória uma lista com vários dispositivos Bluetooth já pareados. Assim, é possível que o endereço do dispositivo com o qual o usuário deseja conectar o app já seja conhecido. Veremos como obter os dispositivos pareados, selecionar um deles e obter seu endereço MAC.

Vamos criar um botão que, quando pressionado, exibe uma lista contendo dispositivos pareados. Adicione o seguinte widget ao arquivo activity_main_bluetooth.xml:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Dispositivos\n pareados"
    android:id="@+id/button_PairedDevices"
    android:layout_below="@+id/statusMessage"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_marginTop="20dp"
    android:onClick="searchPairedDevices"/>
Adicionando botão para exibir dispositivos pareados.
Um botão ajuda a organizar as coisas. Não deixe tudo tão automático ou o usuário ficará triste.

Note que, como estamos usando um RelativeLayout, a posição do botão é configurada usando a posição do TextView statusMessage, que criamos anteriormente. Adicionalmente, o botão já está configurado para chamar o método searchPairedDevices(), quando pressionado. Bem, esse método ainda não existe. Então vamos defini-lo. Adicione o método abaixo à classe MainBluetoothActivity:

public void searchPairedDevices(View view) {

    Intent searchPairedDevicesIntent = new Intent(this, PairedDevices.class);
    startActivityForResult(searchPairedDevicesIntent, SELECT_PAIRED_DEVICE);
}

O código acima define um Intent para iniciar uma nova Activity, o que significa que faremos uma transição de uma tela do aplicativo para outra. Da mesma forma, que a atividade inicial, MainBluetoothActivity, devemos ter uma classe, subclasse de Activity, para especificar o que a nova tela vai exibir. Vamos chamar essa nova classe de PairedDevices. Agora veja que na chamada ao método startActivityForResult(), usamos o SELECT_PAIRED_DEVICE como requestCode, para diferenciar da chamada que usamos para solicitar a ativação do adaptador Bluetooth.

Faça uma pausa, descanse… Quando voltar, vá até a pasta do seu app -> src -> main -> res -> layout. Clique com o botão direito na pasta layout e selecione New -> Android resource file. Isso adiciona um novo arquivo de layout ao projeto. Escolhemos text_header.xml. Apenas misteriosamente copie e cole o seguinte código. Não se preocupe, não é vírus. Isso nos servirá no futuro, para melhorar o visual do app:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="..."
        android:id="@+id/textView"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

Seguindo…

Normalmente, também é necessário um arquivo xml para o layout da nova Activity, mas, nesse caso, faremos de uma maneira em que isso não é uma necessidade. Vamos criar uma subclasse de ListActivity, que já fornece um layout padrão de lista. Para isso, crie uma nova classe Java chamada PairedDevices, no mesmo diretório da classe MainBluetoothActivity. Use o seguinte código para essa nova classe:

package com.project.dragaosemchama.superbluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.app.ListActivity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.util.Set;

public class PairedDevices extends ListActivity {

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

        /*  Esse trecho não é essencial, mas dá um melhor visual à lista.
            Adiciona um título à lista de dispositivos pareados utilizando
        o layout text_header.xml.
        */
        ListView lv = getListView();
        LayoutInflater inflater = getLayoutInflater();
        View header = inflater.inflate(R.layout.text_header, lv, false);
        ((TextView) header.findViewById(R.id.textView)).setText("\nDispositivos pareados\n");
        lv.addHeaderView(header, null, false);

        /*  Usa o adaptador Bluetooth para obter uma lista de dispositivos pareados.
         */
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();

        /*  Cria um modelo para a lista e o adiciona à tela.
            Se houver dispositivos pareados, adiciona cada um à lista.
         */
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
        setListAdapter(adapter);
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                adapter.add(device.getName() + "\n" + device.getAddress());
            }
        }
    }

    /*  Este método é executado quando o usuário seleciona um elemento da lista.
     */
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {

        /*  Extrai nome e endereço a partir do conteúdo do elemento selecionado.
            Nota: position-1 é utilizado pois adicionamos um título à lista e o
        valor de position recebido pelo método é deslocado em uma unidade.
         */
        String item = (String) getListAdapter().getItem(position-1);
        String devName = item.substring(0, item.indexOf("\n"));
        String devAddress = item.substring(item.indexOf("\n")+1, item.length());

        /*  Utiliza um Intent para encapsular as informações de nome e endereço.
            Informa à Activity principal que tudo foi um sucesso!
            Finaliza e retorna à Activity principal.
         */
        Intent returnIntent = new Intent();
        returnIntent.putExtra("btDevName", devName);
        returnIntent.putExtra("btDevAddress", devAddress);
        setResult(RESULT_OK, returnIntent);
        finish();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_paired_devices, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Sempre que adicionar uma nova Activity ao app, você deve declará-la no manifesto. Então vá ao arquivo AndroidManifest.xml e adicione o seguinte código dentro da tag <application>:

<activity
    android:name=".PairedDevices"
    android:label="Paired Devices"
    android:parentActivityName=".MainBluetoothActivity" >
</activity>

Muito bem, temos uma Activity para visualizar os dispositivos pareados. Acredito que o código está bem comentado, caso haja seções importantes obscuras, não fique com vergonha de perguntar.

Vejamos uma breve descrição sobre o que acontece nesta Activity.

Usando um BluetoothAdapter, chamamos o método getBondedDevices() para obter um conjunto de objetos BluetoothDevice. Cada BluetoothDevice representa um dispositivo pareado.

Extraímos informação de nome e endereço de cada BluetoothDevice e populamos uma lista selecionável.

Definimos instruções a serem realizadas quando o usuário selecionar um elemento da lista. Quando isso acontecer, o método onListItemClick() será invocado.

No método onListItemClick(), extraímos o nome e endereço do dispositivo selecionado e os transferimos de volta a MainBluetoothActivity, empacotados em um Intent com o método putExtra(). Adicionamos um pequeno relatório sobre o sucesso dessas ações com o método setResult().

Com isso, teremos na tela inicial um botão que quando clicado exibe a lista de dispositivos pareados. Quando um item da lista é selecionado, obtemos o nome e endereço do dispositivo Bluetooth. Opa, ainda falta uma coisa… Precisamos preparar a classe MainBluetoothActivity para lidar com a chegada dessas informações. Sabemos que, quando iniciamos a Activity PairedDevices usando startActivityForResult() e então retornamos a MainBluetoothActivity, o método onActivityResult() é executado. Então vamos adicionar alguma cláusulas a esse método…

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if(requestCode == ENABLE_BLUETOOTH) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Bluetooth ativado :D");
        }
        else {
            statusMessage.setText("Bluetooth não ativado :(");
        }
    }
    else if(requestCode == SELECT_PAIRED_DEVICE) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Você selecionou " + data.getStringExtra("btDevName") + "\n"
                                    + data.getStringExtra("btDevAddress"));
        }
        else {
            statusMessage.setText("Nenhum dispositivo selecionado :(");
        }
    }
}

As novas linhas são muito semelhantes ao que já havíamos feito para ativar o Bluetooth, então acho que podemos pular maiores explicações. Observe nas linhas 14 e 15 como obter a String que anteriormente empacotamos no Intent com o método putExtra(). Basta chamar getStringExtra() e utilizar como parâmetro a mesma key usada ao empacotar.

Ótimo, com isso podemos obter o endereço MAC de dispositivos Bluetooth já pareados. Mas o que acontece se o que queremos é encontrar um novo dispositivo? Teremos que descobri-lo!

3. Descobrindo dispositivos Bluetooth nas proximidades

3.1 Descobrindo dispositivos Bluetooth

Iniciar uma descoberta de dispositivos Bluetooth é muito simples. Basta chamar startDiscovery(). O problema é ver os dispositivos encontrados. Isso ocorre porque o processo de descoberta ocorre de forma assíncrona, em uma thread separada da principal. Para saber que um dispositivo foi descoberto, precisamos esperar um aviso do sistema operacional, que vem na forma de um broadcast. Para capturar esse aviso, vamos criar um objeto do tipo BroadcastReceiver, que será responsável pela captura. Então vamos sobrescrever seu método onReceive() para que sejam executadas as instruções que queremos.

Antes de mergulhar nos detalhes do descobrimento de dispositivos Bluetooth, vamos criar um botão para podermos clicar quando desejarmos executar essa ação. O código abaixo define um botão à direita do que já criamos e que chama o método discoverDevices() quando pressionado.

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Iniciar descoberta\n de dispositivos"
    android:id="@+id/button_DiscoveredDevices"
    android:layout_alignTop="@+id/button_PairedDevices"
    android:layout_toRightOf="@+id/button_PairedDevices"
    android:layout_toEndOf="@+id/button_PairedDevices"
    android:layout_marginLeft="5dp"
    android:onClick="discoverDevices"/>
Iniciar descoberta de dispositivos
É uma boa ideia utilizar o editor de design visual para escolher a posição de um widget e o editor de texto para ajeitar os detalhes.

discoverDevices() ainda não existe, então adicione-o à classe MainBluetoothActivity. É a terceira vez que fazemos algo parecido, iniciar uma Activity para obter um resultado. Lembre-se de usar um requestCode diferente para cada tipo de chamada. Aqui, usamos o valor SELECT_DISCOVERED_DEVICE.

public void discoverDevices(View view) {

    Intent searchPairedDevicesIntent = new Intent(this, DiscoveredDevices.class);
    startActivityForResult(searchPairedDevicesIntent, SELECT_DISCOVERED_DEVICE);
}

Façamos o seguinte: que tal utilizarmos a mesma estrutura de lista que usamos para ver os dispositivos pareados? Vai facilitar bastante a minha vida. Espero que também a sua. Então, go! Crie uma classe, da mesma maneira que já fizemos na seção anterior. Aqui, usei o nome DiscoveredDevices para a nova classe. Agora use sua ferramenta de busca favorita e faça o código para descoberta, visualização e seleção de um novo dispositivo sozinho. Brincadeira! Ok, apenas use o seguinte código:

package com.project.dragaosemchama.superbluetooth;

import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class DiscoveredDevices extends ListActivity {

    /*  Um adaptador para conter os elementos da lista de dispositivos descobertos.
     */
    ArrayAdapter<String> arrayAdapter;

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

        /*  Esse trecho não é essencial, mas dá um melhor visual à lista.
            Adiciona um título à lista de dispositivos pareados utilizando
        o layout text_header.xml.
        */
        ListView lv = getListView();
        LayoutInflater inflater = getLayoutInflater();
        View header = inflater.inflate(R.layout.text_header, lv, false);
        ((TextView) header.findViewById(R.id.textView)).setText("\nDispositivos próximos\n");
        lv.addHeaderView(header, null, false);

        /*  Cria um modelo para a lista e o adiciona à tela.
            Para adicionar um elemento à lista, usa-se arrayAdapter.add().
         */
        arrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
        setListAdapter(arrayAdapter);

        /*  Pede permissao de localizaçao ao usuario.
        *   Necessario para API > 22 */
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1001);
        }

        /*  Usa o adaptador Bluetooth padrão para iniciar o processo de descoberta.
         */
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        btAdapter.startDiscovery();

        /*  Cria um filtro que captura o momento em que um dispositivo é descoberto.
            Registra o filtro e define um receptor para o evento de descoberta.
         */
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(receiver, filter);
    }

    /*  Este método é executado quando o usuário seleciona um elemento da lista.
     */
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {

        /*  Extrai nome e endereço a partir do conteúdo do elemento selecionado.
            Nota: position-1 é utilizado pois adicionamos um título à lista e o
        valor de position recebido pelo método é deslocado em uma unidade.
         */
        String item = (String) getListAdapter().getItem(position-1);
        String devName = item.substring(0, item.indexOf("\n"));
        String devAddress = item.substring(item.indexOf("\n")+1, item.length());

        /*  Utiliza um Intent para encapsular as informações de nome e endereço.
            Informa à Activity principal que tudo foi um sucesso!
            Finaliza e retorna à Activity principal.
         */
        Intent returnIntent = new Intent();
        returnIntent.putExtra("btDevName", devName);
        returnIntent.putExtra("btDevAddress", devAddress);
        setResult(RESULT_OK, returnIntent);
        finish();
    }

    /*  Define um receptor para o evento de descoberta de dispositivo.
     */
    private final BroadcastReceiver receiver = new BroadcastReceiver() {

        /*  Este método é executado sempre que um novo dispositivo for descoberto.
         */
        public void onReceive(Context context, Intent intent) {

            /*  Obtem o Intent que gerou a ação.
                Verifica se a ação corresponde à descoberta de um novo dispositivo.
                Obtem um objeto que representa o dispositivo Bluetooth descoberto.
                Exibe seu nome e endereço na lista.
             */
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                arrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        }
    };

    /*  Executado quando a Activity é finalizada.
     */
    @Override
    protected void onDestroy() {

        super.onDestroy();

        /*  Remove o filtro de descoberta de dispositivos do registro.
         */
        unregisterReceiver(receiver);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_paired_devices, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Não esqueça de declarar esta nova Activity ao manifesto, novamente dentro da tag <application>.

<activity
    android:name=".DiscoveredDevices"
    android:label="Discovered Devices"
    android:parentActivityName=".MainBluetoothActivity" >
</activity>

Vamos a um resumo sobre o que está acontecendo na Activity DiscoveredDevices.

Criamos uma lista e a exibimos na tela. Conforme os dispositivos forem sendo descobertos, serão adicionados à lista.

Solicitamos ao adaptador Bluetooth que inicie o processo de descoberta com startDiscovery(), que dura aproximadamente 12 segundos. Apenas os dispositivos descobertos nesse período serão exibidos.

Criamos um filtro para capturar o momento em que um dispositivo é descoberto. Depois disso, registramos o filtro para o recebimento de broadcasts.

O truque! Definimos um receptor de broadcasts. A classe BroadcastReceiver fornece um método chamado onReceive(), que é executado quando o sistema operacional Android transmite um broadcast. Entre seus argumentos está um Intent, que vem com algumas informações, por exemplo, sobre o que causou o broadcast. Extraimos essa informação e testamos se a causa é a descoberta de um dispositivo Bluetooth. Caso positivo, extraimos os dados do dispositivo e exibimos na lista. Voila.

Definimos instruções a serem realizadas quando o usuário selecionar um elemento da lista. Quando isso acontecer, o método onListItemClick() será invocado.

No método onListItemClick(), extraímos o nome e endereço do dispositivo selecionado e os transferimos de volta a MainBluetoothActivity, empacotados em um Intent com o método putExtra(). Adicionamos um pequeno relatório sobre o sucesso dessas ações com o método setResult().

Sobrescrevemos o método onDestroy() de nossa Activity. onDestroy() é sempre executado quando a Activity é finalizada e foi implementado para liberar recursos alocados pela Activity. Usamos unregisterReceiver() para remover o filtro de broadcasts. É boa prática nunca esquecer de finalizar corretamente um BroadcastReceiver, caso contrário, seu aplicativo poderá apresentar vazamentos de dados.

Precisamos de apenas mais um ajuste. Quando obtivermos os dados do dispositivo selecionado pelo usuário, retornaremos à MainBluetoothActivity e ao método onActivityResult(). Mas teremos que decidir o que fazer com os dados. Vamos aproveitar o que já foi feito com os dispositivos pareados. Apenas mostrar o nome e o endereço, por enquanto. Vá até o código de onActivityResult() na classe MainBluetoothActivity e altere a linha destacada a seguir:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if(requestCode == ENABLE_BLUETOOTH) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Bluetooth ativado :D");
        }
        else {
            statusMessage.setText("Bluetooth não ativado :(");
        }
     }
    else if(requestCode == SELECT_PAIRED_DEVICE || requestCode == SELECT_DISCOVERED_DEVICE) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Você selecionou " + data.getStringExtra("btDevName") + "\n"
                                        + data.getStringExtra("btDevAddress"));

        }
        else {
            statusMessage.setText("Nenhum dispositivo selecionado :(");
        }
    }
}

Isso faz com que o mesmo código seja executado quando o usuário selecionar um dispositivo já pareado ou recém-descoberto.

3.2 Habilitando visibilidade do dispositivo Bluetooth

Só será possível descobrir um dispositivo Bluetooth se ele estiver visível. Então vamos adicionar à interface gráfica um botão. Faremos com que o smartphone torne-se visível quando esse botão for pressionado. Adicione o seguinte widget ao arquivo activity_main_bluetooth.xml:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Habilitar\nVisibilidade"
    android:id="@+id/button_Visibility"
    android:layout_below="@+id/button_PairedDevices"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:onClick="enableVisibility" />
Habilitar visibilidade
Não precisa se preocupar agora com a beleza do app.

 Legal. Agora basta adicionar o método enableVisibility() à classe MainBluetoothActivity:

public void enableVisibility(View view) {

    Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 30);
    startActivity(discoverableIntent);
}

O código acima solicita permissão ao usuário para que o dispositivo se torne visível por 30 segundos.  A duração da visibilidade pode ser alterada. Qualquer valor entre 1 e 3600 segundos é possível. O valor 0 significa que o dispositivo estará visível sempre.

Ufa… então somos capazes de obter o endereço MAC de dispositivos já pareados e de dispositivos visíveis nas proximidades. Lembre-se que para solicitar o início de uma conexão Bluetooth a outro dispositivo, precisamos apenas de seu endereço MAC. Agora estamos prontos. Sigam-me os bons!

4. Conectando dois dispositivos

Quando escrevemos um aplicativo que conecta-se via Bluetooth, há alguns detalhes a considerar.

Na conexão, temos um servidor e um cliente. Chamamos de servidor o dispositivo que cria um socket e espera pela conexão, ou seja, ele torna-se disponível para aceitar uma conexão. E chamamos de cliente aquele que solicita a conexão com o servidor. Quando programamos aplicações que atuam em rede, o código para iniciar uma conexão é diferente para o servidor e o cliente. Aqui, dois dispositivos são considerados conectados quando compartilham um objeto BluetoothSocket. Vamos aprender a obter um BluetoothSocket tanto para o servidor quanto para o cliente.

Outro detalhe é o fato de que vários métodos utilizados para realizar a conexão ou para receber mensagens são capazes de bloquear o fluxo do código. Por exemplo, quando o servidor está esperando por uma conexão, nenhuma outra instrução poderá ser executada na sua thread. O algoritmo ficará empacado naquela linha até que alguém solicite o início de uma conexão. Isso significa que as ações de conexão e transmissão de mensagens deverão ocorrer em uma thread diferente da principal. Caso contrário, muito provavelmente o app dará belas e maravilhosas travadas, lindos crashes. Prepare-se para trabalhar com threads.

Em Java, para definirmos uma thread, criamos uma nova classe, subclasse de Thread, e escrevemos as instruções que devem ser executadas paralelamente dentro de seu método run(). Veja só, a seguir temos o esqueleto dessa classe, que chamo de ConnectionThread. Ela contém código que permite que o app haja tanto como servidor quanto como cliente. Digo esqueleto porque contem apenas o código relacionado ao início da conexão. O gerenciamento da conexão, transmissão de mensagens é assunto da próxima seção. Então, você já sabe! Adicione essa classe ao seu projeto.

Nota: como esta classe não é uma Activity, não é necessário modificar o manifesto.

package com.project.dragaosemchama.superbluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Message;

import java.io.IOException;
import java.util.UUID;

public class ConnectionThread extends Thread{

    BluetoothSocket btSocket = null;
    BluetoothServerSocket btServerSocket = null;
    String btDevAddress = null;
    String myUUID = "00001101-0000-1000-8000-00805F9B34FB";
    boolean server;
    boolean running = false;

    /*  Este construtor prepara o dispositivo para atuar como servidor.
     */
    public ConnectionThread() {

        this.server = true;
    }

    /*  Este construtor prepara o dispositivo para atuar como cliente.
        Tem como argumento uma string contendo o endereço MAC do dispositivo
    Bluetooth para o qual deve ser solicitada uma conexão.
     */
    public ConnectionThread(String btDevAddress) {

        this.server = false;
        this.btDevAddress = btDevAddress;
    }

    /*  O método run() contem as instruções que serão efetivamente realizadas
    em uma nova thread.
     */
    public void run() {

        /*  Anuncia que a thread está sendo executada.
            Pega uma referência para o adaptador Bluetooth padrão.
         */
        this.running = true;
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();

        /*  Determina que ações executar dependendo se a thread está configurada
        para atuar como servidor ou cliente.
         */
        if(this.server) {

            /*  Servidor.
             */
            try {

                /*  Cria um socket de servidor Bluetooth.
                    O socket servidor será usado apenas para iniciar a conexão.
                    Permanece em estado de espera até que algum cliente
                estabeleça uma conexão.
                 */
                btServerSocket = btAdapter.listenUsingRfcommWithServiceRecord("Super Bluetooth", UUID.fromString(myUUID));
                btSocket = btServerSocket.accept();

                /*  Se a conexão foi estabelecida corretamente, o socket
                servidor pode ser liberado.
                 */
                if(btSocket != null) {

                    btServerSocket.close();
                }

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }


        } else {

            /*  Cliente.
             */
            try {

                /*  Obtem uma representação do dispositivo Bluetooth com
                endereço btDevAddress.
                    Cria um socket Bluetooth.
                 */
                BluetoothDevice btDevice = btAdapter.getRemoteDevice(btDevAddress);
                btSocket = btDevice.createRfcommSocketToServiceRecord(UUID.fromString(myUUID));

                /*  Envia ao sistema um comando para cancelar qualquer processo
                de descoberta em execução.
                 */
                btAdapter.cancelDiscovery();

                /*  Solicita uma conexão ao dispositivo cujo endereço é
                btDevAddress.
                    Permanece em estado de espera até que a conexão seja
                estabelecida.
                 */
                if (btSocket != null)
                    btSocket.connect();

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }

        }

        /*  Pronto, estamos conectados! Agora, só precisamos gerenciar a conexão.
            ...
        */

    }

    /*  Utiliza um handler para enviar um byte array à Activity principal.
        O byte array é encapsulado em um Bundle e posteriormente em uma Message
    antes de ser enviado.
     */
    private void toMainActivity(byte[] data) {

        Message message = new Message();
        Bundle bundle = new Bundle();
        bundle.putByteArray("data", data);
        message.setData(bundle);
        MainBluetoothActivity.handler.sendMessage(message);
    }

    /*  Método utilizado pela Activity principal para encerrar a conexão
     */
    public void cancel() {

        try {

            running = false;
            btServerSocket.close();
            btSocket.close();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        running = false;
    }
}

Isso certamente vai conectar duas instâncias do seu app.

public static Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {

        Bundle bundle = msg.getData();
        byte[] data = bundle.getByteArray("data");
        String dataString= new String(data);

        if(dataString.equals("---N"))
            statusMessage.setText("Ocorreu um erro durante a conexão D:");
        else if(dataString.equals("---S"))
            statusMessage.setText("Conectado :D");
            
    }
};

O código acima define um Handler. Esse objeto será responsável por receber mensagens vindas de outra thread, nesse caso ConnectionThread, e tomar as decisões apropriadas. Aqui, fazemos com que ela mostre na tela o status da conexão, dependendo do código enviado pela thread de conexão.

Para testar você vai precisar de dois dispositivos Android com o app Super Bluetooth instalado e mais alguns botões! Mas façamos o seguinte antes: vamos ao resumo do que vai acontecer nessa thread. Depois disso veremos como iniciá-la e criaremos um botão para isso.

A thread é representada por uma classe que possui dois construtores, um para iniciar um servidor e outra para iniciar um cliente. Se você é um cliente, ou seja, aquele que busca a conexão, precisa do endereço do servidor. Então, se você criar um objeto ConnectionThread utilizando como argumento uma string contendo um endereço MAC, nossa thread define automaticamente que atuará como cliente. Caso não haja um argumento, ela atuará como servidor. A variável booleana server armazena essa informação. Ela é verdadeira se a thread atua como servidor e falsa caso atue como cliente.

ConnectionThread connect = new ConnectionThread("12-34-56-78-9A-BC");
connect.start();
ConnectionThread connect = new ConnectionThread();
connect.start();

O método run() contem as intruções que serão executadas paralelamente à Activity principal. Isso inclui operações de conexão e leitura de dados, que deixariam a thread principal travada caso fossem executadas nela.

Quando a thread inicia, verificamos sua configuração de servidor ou cliente para que as ações corretas sejam realizadas.

O servidor

listenUsingRfcommWithServiceRecord() é um método com nome muito grande. É usado para obter um ServerSocket, uma representação de servidor de um pontos de comunicação dentro da rede. Seus argumentos são uma String que identifica o app e um UUID, que identifica o serviço disponibilizado pelo servidor. O UUID tem um formato bem específico, como você pode ver na String myUUID e a ideia é que cada serviço disponibilizado via Bluetooth possua um UUID diferente, apesar de existirem alguns padrões. Mas não se preocupe, myUUID pode ser trocado por qualquer UUID válido. Para que a conexão seja realizada, o cliente deverá informar a mesma UUID do servidor no ato da conexão. Então é importante que o cliente e o servidor compartilhem essa informação previamente.

O ServerSocket é utilizado apenas para aguardar uma solicitação de conexão. O método accept() deixa o socket de servidor na escuta por solicitações de clientes. Quando uma ocorre, retorna um BluetoothSocket e este será efetivamente usado para gerenciar a conexão. Se a conexão foi estabelecida, não precisamos mais do ServerSocket, que pode ser finalizado com close().

No caso em que uma excessão ocorre durante esse processo na thread, utilizamos o método toMainActivity() para enviar um código de erro informando que a conexão não foi bem sucedida. toMainActivity() utiliza um handler da Activity principal para transmitir esse código. Handlers fornecem a funcionalidade de transmissão de dados entre threads.

O cliente

Se a thread age como cliente, ela possui um endereço ao qual deve solicitar conexão. O endereço é usado para obter a representação de um dispositivo Bluetooth em um objeto BluetoothDevice. Isso é feito para que possamos chamar createRfcommSocketToServiceRecord() e obter um BluetoothSocket. Aqui, precisamos utilizar o mesmo UUID do servidor, caso contrário, a conexão não ocorrerá. Isso é uma forma de assegurar que o serviço que estamos buscando é realmente aquele que o servidor está fornecendo.

O processo de descoberta consome muitos recursos do adaptador Bluetooth. Se uma tentativa de conexão é feita enquanto o processo ocorre, é muito provável que falhas de conexão ocorram, portanto cancelamos a descoberta antes de solicitar uma conexão com o servidor.

A solicitação de conexão com o servidor é feita com o método connect().

Assim como no servidor, caso haja uma exceção, enviamos uma mensagem de erro para a Activity principal.

Com isso descrevemos o início da conexão para os dois lados, servidor e cliente. Os dois métodos restantes na classe são toMainActivity() e cancel(). O primeiro é responsável por enviar mensagens à Activity principal do aplicativo, nesse caso, a thread que está executando os componentes da interface gráfica. O segundo método fecha os sockets e finaliza a conexão. Poderíamos criar um botão e fornecer ao usuário a capacidade de encerrar a conexão Bluetooth através desse método. Essa tarefa fica para você. Boa sorte!

Mas peraí! O que acontece se você simplesmente iniciar o aplicativo e esperar que os dispositivos se conectem? Absolutamente nada! É por isso que precisamos configurar para que o servidor espere pela conexão e o cliente solicite a conexão.

4.1 Conectando como servidor

Para o servidor, é bem simples. Você pode criar um objeto ConnectionThread em MainBluetoothActivity:

ConnectionThread connect;

Depois criamos um botão e fazemos com que ela execute o seguinte método:

public void waitConnection(View view) {

    connect = new ConnectionThread();
    connect.start();
}

Como você já entende o que acontece ao chamarmos esses métodos, não preciso explicar nada. Mas, já explicando, criamos uma thread de conexão e a executamos. Como não passamos nenhum argumento ao construtor da thread, ela assume que deve atuar como servidor e já inicia a espera. Mágico. Só adicione o método à MainBluetoothActivity e o seguinte botão à activity_main_bluetooth.xml.

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Esperar\nconexão"
    android:id="@+id/button_WaitConnection"
    android:layout_marginLeft="5dp"
    android:layout_alignBottom="@+id/button_Visibility"
    android:layout_toRightOf="@+id/button_Visibility"
    android:layout_toEndOf="@+id/button_Visibility"
    android:onClick="waitConnection" />

4.2 Conectando como cliente

Ainda mais simples! Lembra de como fizemos para selecionar um dispositivo pareado ou recém-pareado? Já tínhamos até seu endereço MAC, não é? Então faremos isso. Vamos modificar de leve o método onActivityResult() da Activity principal.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if(requestCode == ENABLE_BLUETOOTH) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Bluetooth ativado :D");
        }
        else {
            statusMessage.setText("Bluetooth não ativado :(");
        }
    }
    else if(requestCode == SELECT_PAIRED_DEVICE || requestCode == SELECT_DISCOVERED_DEVICE) {
        if(resultCode == RESULT_OK) {
            statusMessage.setText("Você selecionou " + data.getStringExtra("btDevName") + "\n"
                                    + data.getStringExtra("btDevAddress"));

            connect = new ConnectionThread(data.getStringExtra("btDevAddress"));
            connect.start();
        }
        else {
            statusMessage.setText("Nenhum dispositivo selecionado :(");
        }
    }
}

Veja as linhas 17 e 18, que adicionamos. Isso significa que iniciamos a thread de conexão com o endereço MAC de um dispositivo Bluetooth. A thread automaticamente configura-se para atuar como cliente, solicitando conexão ao dispositivo selecionado. Estamos conectados! A vida é bela. Mas apenas se o dispositivo selecionado estiver esperando como servidor e os UUIDs forem iguais. Caso contrário, a vida é bug.

5. Transferência de dados bidirecional

Uma conexão bem sucedida significa que os dois aparelhos estão prontos para trocar mensagens entre si. As duas operações, de leitura e escrita, agora podem ser realizadas a partir dos fluxos de entrada e saída dos sockets. Mas vamos pensar um pouco…

A operação de escrita é simples. Basta capturar o fluxo de saída do socket da conexão e usar o método write(). Este método tem como entrada um vetor de bytes que representa o dado a ser transmitido. Então, se você quer enviar uma string, primeiro transforme-a em bytes e depois use write(). Todo tipo de dado que pode ser transmitido pode ser representado como um vetor de bytes. Então basta encontrar uma função na extensa lista de pacotes do Java para representar a informação desejada em um vetor de bytes antes de transmiti-la. Do outro lado da conexão, usa-se algum outro método que obtenha a informação original a partir do vetor de bytes recebido. Calma, logo teremos código.

Já a operação de leitura é um pouco mais complicada. Isso porque ela bloqueia a execução do algoritmo. Quando se usa o método read(), a thread sofre uma pausa enquanto o método espera por algum dado vindo do outro lado da conexão. Então como fazer com que a mesma thread possa receber e transmitir dados ao mesmo tempo? A ideia é a seguinte.

Façamos com que, depois de realizada a conexão, a thread entre em um loop infinito na qual ela apenas espera para receber dados. Quando um dado é recebido, enviamos o dado para outra thread para processamento. No entanto, a thread de conexão volta ao estado de espera por novos dados. Isso garante que estejamos sempre na escuta de novas transmissões. Para transmitir um dado, criamos um método, dentro da thread, que tem como entrada um vetor de bytes e apenas o transmite ao outro lado da conexão. Deixaremos esse método público de form que possa ser invocado a partir de outras threads. E pronto! Com isso, basta chamar o método de transmissão, que será executado na thread em que foi invocado e não haverá conflito com a operação de leitura. Assim o aplicativo pode ficar sempre na escuta e sempre capaz de realizar uma transmissão.

Finalmente, código… Veja a seguir a versão final da classe ConnectionThread. Agora completa.

package com.project.dragaosemchama.superbluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Message;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.UUID;

public class ConnectionThread extends Thread{

    BluetoothSocket btSocket = null;
    BluetoothServerSocket btServerSocket = null;
    InputStream input = null;
    OutputStream output = null;
    String btDevAddress = null;
    String myUUID = "00001101-0000-1000-8000-00805F9B34FB";
    boolean server;
    boolean running = false;

    /*  Este construtor prepara o dispositivo para atuar como servidor.
     */
    public ConnectionThread() {

        this.server = true;
    }

    /*  Este construtor prepara o dispositivo para atuar como cliente.
        Tem como argumento uma string contendo o endereço MAC do dispositivo
    Bluetooth para o qual deve ser solicitada uma conexão.
     */
    public ConnectionThread(String btDevAddress) {

        this.server = false;
        this.btDevAddress = btDevAddress;
    }

    /*  O método run() contem as instruções que serão efetivamente realizadas
    em uma nova thread.
     */
    public void run() {

        /*  Anuncia que a thread está sendo executada.
            Pega uma referência para o adaptador Bluetooth padrão.
         */
        this.running = true;
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();

        /*  Determina que ações executar dependendo se a thread está configurada
        para atuar como servidor ou cliente.
         */
        if(this.server) {

            /*  Servidor.
             */
            try {

                /*  Cria um socket de servidor Bluetooth.
                    O socket servidor será usado apenas para iniciar a conexão.
                    Permanece em estado de espera até que algum cliente
                estabeleça uma conexão.
                 */
                btServerSocket = btAdapter.listenUsingRfcommWithServiceRecord("Super Bluetooth", UUID.fromString(myUUID));
                btSocket = btServerSocket.accept();

                /*  Se a conexão foi estabelecida corretamente, o socket
                servidor pode ser liberado.
                 */
                if(btSocket != null) {

                    btServerSocket.close();
                }

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }


        } else {

            /*  Cliente.
             */
            try {

                /*  Obtem uma representação do dispositivo Bluetooth com
                endereço btDevAddress.
                    Cria um socket Bluetooth.
                 */
                BluetoothDevice btDevice = btAdapter.getRemoteDevice(btDevAddress);
                btSocket = btDevice.createRfcommSocketToServiceRecord(UUID.fromString(myUUID));

                /*  Envia ao sistema um comando para cancelar qualquer processo
                de descoberta em execução.
                 */
                btAdapter.cancelDiscovery();

                /*  Solicita uma conexão ao dispositivo cujo endereço é
                btDevAddress.
                    Permanece em estado de espera até que a conexão seja
                estabelecida.
                 */
                if (btSocket != null)
                    btSocket.connect();

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }

        }

        /*  Pronto, estamos conectados! Agora, só precisamos gerenciar a conexão.
            ...
         */

        if(btSocket != null) {

            /*  Envia um código para a Activity principal informando que a
            a conexão ocorreu com sucesso.
             */
            toMainActivity("---S".getBytes());

            try {

                /*  Obtem referências para os fluxos de entrada e saída do
                socket Bluetooth.
                 */
                input = btSocket.getInputStream();
                output = btSocket.getOutputStream();

                /*  Cria um byte array para armazenar temporariamente uma
                mensagem recebida.
                    O inteiro bytes representará o número de bytes lidos na
                última mensagem recebida.
                 */
                byte[] buffer = new byte[1024];
                int bytes;

                /*  Permanece em estado de espera até que uma mensagem seja
                recebida.
                    Armazena a mensagem recebida no buffer.
                    Envia a mensagem recebida para a Activity principal, do
                primeiro ao último byte lido.
                    Esta thread permanecerá em estado de escuta até que
                a variável running assuma o valor false.
                 */
                while(running) {

                    bytes = input.read(buffer);
                    toMainActivity(Arrays.copyOfRange(buffer, 0, bytes));

                }

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }
        }

    }

    /*  Utiliza um handler para enviar um byte array à Activity principal.
        O byte array é encapsulado em um Bundle e posteriormente em uma Message
    antes de ser enviado.
     */
    private void toMainActivity(byte[] data) {

        Message message = new Message();
        Bundle bundle = new Bundle();
        bundle.putByteArray("data", data);
        message.setData(bundle);
        MainBluetoothActivity.handler.sendMessage(message);
    }

    /*  Método utilizado pela Activity principal para transmitir uma mensagem ao
     outro lado da conexão.
        A mensagem deve ser representada por um byte array.
     */
    public void write(byte[] data) {

        if(output != null) {
            try {

                /*  Transmite a mensagem.
                 */
                output.write(data);

            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {

            /*  Envia à Activity principal um código de erro durante a conexão.
             */
            toMainActivity("---N".getBytes());
        }
    }

    /*  Método utilizado pela Activity principal para encerrar a conexão
     */
    public void cancel() {

        try {

            running = false;
            btServerSocket.close();
            btSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        running = false;
    }
}

Show. Com essas adições implementamos a capacidade de transmissão de dados via Bluetooth. Agora para completar nosso app de demonstração, vamos adicionar três elementos à interface gráfica:

  • Um EditText, para que possamos digitar uma mensagem;
  • Um botão, para transmitir a mensagem;
  • Um TextView, para visualizarmos as mensagens recebidas.
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/editText_MessageBox"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true" />

<Button
    style="?android:attr/buttonStyleSmall"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Send"
    android:id="@+id/button_Send"
    android:layout_above="@+id/editText_MessageBox"
    android:layout_alignRight="@+id/editText_MessageBox"
    android:layout_alignEnd="@+id/editText_MessageBox"
    android:onClick="sendMessage" />

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text=""
    android:id="@+id/textSpace"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true" />

Adicione os widgets acima ao arquivo activity_main_bluetooth.xml e veja o resultado.

Screenshot (Mar 9, 2015 10-23-55 AM)

 

Interface gráfica pronta. Precisamos apenas terminar de programar suas funcionalidades.

Primeiramente o botão. Quando pressionado, o app deve ler o conteúdo da caixa de texto e fazer a transmissão. Veja o método a seguir. É só adicioná-lo à MainBluetoothActivity.

public void sendMessage(View view) {

    EditText messageBox = (EditText) findViewById(R.id.editText_MessageBox);
    String messageBoxString = messageBox.getText().toString();
    byte[] data =  messageBoxString.getBytes();
    connect.write(data);
}

Agora, quando uma mensagem for recebida, o app deve exibi-la na tela. Para isso, declare um TextView no topo da classe MainBluetoothActivity.

static TextView textSpace;

Faça a ligação desse objeto com o widget da interface gráfica. O código abaixo vai para dentro do método onCreate().

textSpace = (TextView) findViewById(R.id.textSpace);

E, finalmente, modifique o código do Handler. Adicione as linhas grifadas a seguir.

public static Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {

        Bundle bundle = msg.getData();
        byte[] data = bundle.getByteArray("data");
        String dataString= new String(data);

        if(dataString.equals("---N"))
            statusMessage.setText("Ocorreu um erro durante a conexão D:");
        else if(dataString.equals("---S"))
            statusMessage.setText("Conectado :D");
        else {

            textSpace.setText(new String(data));
        }
    }
};

 

Ótimo! Vamos testar. Consiga dois dispositivos Android e instale o app Super Bluetooth em ambos. Escolha qual iniciará como servidor e qual será agirá como cliente.

No dispositivo servidor, se os dispositivos não estiverem pareados, pressione o botão “Habilitar visibilidade” e, em seguida, “Esperar conexão”. Se os dispositivos estiverem pareado, basta clicar “Esperar conexão”.

Agora, no dispositivo cliente, se o pareamento ainda não ocorreu, pressione “Iniciar descoberta de dispositivos” e torça para o servidor aparecer na lista. Se já estiverem pareados, pressione “Dispositivos pareados”. Lembre-se que o servidor deve estar esperando a conexão para que haja sucesso.

Supondo que tudo deu certo, agora você pode digitar uma mensagem na caixa de texto, clicar “Send” e vê-la do outro lado da conexão. Parabéns!

Problems? Você pode baixar o código completo no github do Dragão, copiar tudo e melhorar para fazer seus projetos sem se preocupar com direitos autorais, tudo free. Ainda muitos bugs? Fale com a gente nos comentários!

Ufa! Acho que terminanos. Foi um post longo, então teste o app e vá imediatamente descansar ou assistir um bom filme.

David Borges

Um dia... Boom! Dragão sem Chama.

120 thoughts to “Programação Bluetooth no Android”

      1. Olá, tenho algumas duvidas. Estou implementando o codigo tal qual é mostrado, porem persiste um erro em
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main_bluetooth, menu);
        return true;
        }
        o R. “menu” nao reconhece e nao sei como proceder.
        Outra dúvida é com relação a um codigo que ja uso de comunicação, porem nesse codigo ocorre um erro ja apresentado por terceiros com relaçao a descontinuidade. Quando é para mostrar 1023, motra 1023, 023, 23 e assim em diante 840, 40, 0. O que pode ser?

        De ante mão,grato pelo post e pela ajuda.

  1. Muito bom o seu post – até eu que sou super iniciante consegui entender (algumas partes).
    Tenho algumas perguntas:

    1 – Onde eu coloco o método Handler?
    2 – Não entendi o que define se o dispositivo será servidor ou cliente. Eu tenho que especificar durante a programação?
    3 – Como/onde incluo os métodos de Thread para cliente e servidor?
    4 – O Android Studio oferece duas formas de importar o handler (os.Handler e util.logging.handler). Qual dos dois devo importar?

    Parabéns pelo post

    1. Olá, Ramon. Desculpa por demorar mais de um mês pra responder.

      1. O handler é o objeto que vai permitir você enviar mensagens de uma thread secundária para a thread principal (que contem a interface gráfica). Nesse caso, então, o handler fica na classe MainBluetoothActivity. Você pode ver o nosso código aqui (linhas 129 a 146): https://github.com/dragaosemchama/SuperBluetooth/blob/master/app/src/main/java/com/project/dragaosemchama/superbluetooth/MainBluetoothActivity.java

      2. A classe ConnectionThread possui dois construtores. O padrão, que não leva nenhum argumento. E o construtor sobrecarregado, que tem como argumento uma string. A nossa ideia é a seguinte: se criamos um objeto ConnectionThread com o construtor padrão (sem argumento), vai ser servidor; se criamos com o construtor sobrecarregado (com argumento), vai ser cliente. Veja as linhas 27 a 42 nesse link: https://github.com/dragaosemchama/SuperBluetooth/blob/master/app/src/main/java/com/project/dragaosemchama/superbluetooth/ConnectionThread.java

      3. Você vai ter a classe ConnectionThread no seu projeto. Para iniciar a conexão como servidor, veja as linhas 115 a 119 (método waitConnection()). Para iniciar como cliente, veja as linhas 87 e 88. Agora, quando você quiser enviar dados, utilizará o método write() do objeto ConnectionThread que você criou (por exemplo, linha 126). Esse método pode ser chamado a partir de qualquer parte do código, desde que você tenha o objeto ConnectionThread e esteja conectado. Não sei se respondi à pergunta, hehe.

      4. android.os.Handler.

      Muito obrigado!

        1. Neste caso, o app Android estabelece conexão como cliente. Depois de ligar o dispositivo Arduino + HC-06, você pode procurar o endereço do HC-06 na lista de dispositivos descobertos pelo SuperBluetooth. Depois que você identificá-lo, basta selecioná-lo e a conexão vai ser estabelecida.

          1. Prezado David

            Muito obrigado pela paciência…
            Coloquei o UUID de meu dispositivo, coloquei o MAC, etc. Está dando o seguinte erro de conexão:
            /System.err﹕ at android.bluetooth.BluetoothSocket.connectNative(Native Method)
            /System.err﹕ at android.bluetooth.BluetoothSocket.connect(BluetoothSocket.java:236)
            /System.err﹕ at .ConnectionThread.run(ConnectionThread.java:116)
            /BluetoothSocket.cpp﹕ readNative
            /com.example.ramon.dragaocliente W/System.err﹕ java.io.IOException: Software caused connection abort

            erro na linha btSocket.connect(); poderia me ajudar na causa deste erro?

        2. Ramon,

          Desculpe pela demora!

          Assim de primeira, também não entendo a causa do problema. Mas pra começar a investigação, qual UUID você está usando? Porque o UUID não é específico para o dispositivo (como o endereço MAC) que está sendo usado. Quando fiz meus testes, não foi necessário trocar o UUID.

          1. Prezado David

            Comprei um HC-05 para fazer alguns testes. Vou utilizar exatamente o código que está no link que informou e efetuar mais testes. O erro acontece na linha do btSocket.connect().

  2. Achei muito interessante o seu post e gostaria de tirar uma dúvida, estou desenvolvendo um app na faculdade onde controlamos a lista de chamada via bluetooth mas não temos a intenção de parear os dispositivos apenas reconhece-los e encaminhar um relatório em txt, seria viável seguir somente as instruções 1 e 3? Já que nós só precisamos que o app reconheça que ele precisa utilizar o sistema de scam por bluetooth e nos mostre os dispositivos no local?

    1. Oi, Alessandra. Gostei da sua ideia! Sim, considero viável. Minha ideia de protótipo é a seguinte:

      Você vai ter um dispositivo realizando descoberta (talvez o do professor). Quando um dispositivo for descoberto, o método onReceive() vai ser executado (linhas 87 a 99). A partir da linha 96, você vai ter o endereço MAC (device.getAddress()) do dispositivo descoberto (um estudante). Então compara com uma lista ou banco de dados contendo entradas do tipo (nome do estudante, endereço MAC) e salva os nomes dos estudantes em um banco de dados ou similar.

      Muito bem. Mas prepare-se para os desafios: alguém pode estar fazendo mac address spoofing usando aplicativos como Bluetooth Mac Address Changer. Como impedir que os estudantes enganem seu sistema?

  3. Excelente, achei muito bom o seu codigo e a sua paciencia para ensinar, muito obrigado :D
    Eu tenho uma duvida, talvez você possa me ajudar.
    Tem como eu pegar a potencia do sinal da conexão dos dois dispositivos?
    Tem como eu saber se a conexão foi perdida?

    Obrigado pela atenção.

    Abraços :D

    1. João,

      Não conheço uma forma de usar as APIs Bluetooth do Android pra pegar a potência do sinal da conexão entre dois dispositivos.

      Já em relação a saber se a conexão foi perdida, você pode usar Intent Filters para ficar escutando todos os eventos relacionados ao dispositivo Bluetooth. Um desses eventos é o ACTION_ACL_DISCONNECTED, que surge quando o dispositivo é desconectado. Encontrei um código exemplo nesse link (foi testado em um Android 2.2, então é bom você tentar executar e ver se ainda há compatibilidade, acredito que sim): http://stackoverflow.com/questions/4715865/how-to-programmatically-tell-if-a-bluetooth-device-is-connected-android-2-2

      Espero ter ajudado o/

  4. E ai cara bom dia, excelente tutorial. Mas eu Não estou conseguindo localizar (Android resource file). Eu estou usando o Eclipse Mars com o plugin e a SDK android é meu primeiro programa e estou com bastante dificuldade.

    1. Olá, Giba,

      Entendo que no Eclipse os menus possam ser diferentes. Mas, nesse caso, tudo o que essa opção “New Android Resource File” faz é criar um arquivo XML dentro da pasta layout. Você pode fazer isso manualmente mesmo e copiar o conteúdo misterioso citado lá em cima. Compreendido?

      Sucesso, tchau.

  5. Primeiramente Parabens!!

    Usei esse código para integrar com o arduino e deu certo.
    Estou mandando a leitura de um sensor de gas. Que manda um “numero” de até 3 caracteres, ele acaba tirando
    o primeiro carácter as vezes. Exemplo: (123,23,123,23,23,123).

    O acha que pode ser?

    Obrigado.

    1. Olá, Tiago,

      Primeiramente, Obrigado! Hahah

      À primeira vista, penso que pode ser um problema de sincronização. Mas não tenho certeza. Que baud rate você está usando no código da Serial no Arduino? Acredito que o valor ideal seja 115200 bps. Mas encontrei casos na Internet em que 9600 bps funcionava e 115200 não. Se puder, veja também se há alguma diferença entre usar os pinos RX TX dedicados (UART) e usar a biblioteca SoftwareSerial para simular a Serial com os pinos digitais. Teste isso e nos conte o resultado. Se não funcionar, a gente estuda melhor o caso.

      Desculpa o atraso na resposta.

    2. Estou fazendo praticamente a mesma coisa. Estou enviando um numero de 3 digitos e em boa parte das vezes que recebo, ele esta comendo os digitos, EX: (47 aparece apenas 4), (90, as vezes aparece 0) e por ai vai. Como resolver isso?

  6. Olá. Muito bom esse post. Me ajudou bastante. No meu caso, Criei um aplicativo Android e estou tendo algumas dificuldades. Caso alguém possa me ajudar, serei grato.

    Estou tendo dificuldades para enviar um determinado dado do Arduino para o Android. Se alguém souber quais comandos utilizo no Arduino para enviar essas informações, eu agradeço, pois já procurei em alguns sites mas nada funcionou.

    A outra questão é sobre como fazer para me conectar com apenas um único dispositivo Bluetooth. Ou seja, não desejo procurar dispositivos, mas quero me conectar automaticamente com apenas um em específico. Nesse caso, com o módulo Bluetooth conectado ao Arduino.

    Mais uma vez, nota 1000 para esse post. Obrigado!

    1. Olá, Jayme,

      Por parte do Arduino, a comunicação Bluetooth é feita por um módulo Bluetooth como o HC-06. Esse módulo é conectado aos pinos seriais RX TX do Arduino. Dessa maneira, para transmitir os dados, basta que, no código do Arduino, você escreva através da Serial. Então, os comandos seriam Serial.write(), Serial.print() ou Serial.println(), dependendo da informação que você precisa transmitir.

      Sobre conectar com um dispositivo específico, a única necessidade é conhecer o endereço MAC dele. Veja o código da seção 4.2, linha 19:

      connect = new ConnectionThread(data.getStringExtra(“btDevAddress”));

      Essa parte do código usa o endereço MAC do dispositivo selecionado para iniciar uma conexão. Então basta substituir data.getStringExtra(“btDevAddress”) por uma String contendo o endereço MAC específico do seu dispositivo. Então o app conectará apenas com aquele dispositivo. É claro, isso ia tirar a necessidade das telas de seleção de dispositivos.

      Bons projetos o/

      1. Olá. Este módulo que você se refere é o mesmo que possuo. A única coisa que consigo é enviar uma informação do Android para o Arduino, porém, o inverso eu não estou conseguindo. E que é o mais importante para meu projeto. Estou tentando enviar uma informação para o App cada vez que o sensor ultrassônico detectar a presença de um objeto. mas nada é exibido no textView.

        Sobre conectar automaticamente a um único dispositivo, no caso, o módulo Bluetooth, não foi bem sucedido. Sempre tenho que busca, e clicar no dispositivo encontrado para conectar. Mas isso é o de menos. O problema mesmo é o recebimento de uma informação qualquer no App para que eu possa fazer as condições necessárias.

        Mais uma vez, excelente post. Me ajudou muito.
        Obrigado!

        1. Estou tentando fazer a comunicação do Arduino com o App Android usando o Módulo Bluetooth HC-06, porém, só consigo enviar uma informação qualquer do Android para o Arduino. mas não consigo fazer o inverso. ou seja, manda uma mensagem do Arduino para o Android. Já tentei de tudo e nada. Porém, lendo alguns sites na internet, li que o módulo HC-06 opera apenas em modo slave (escravo) ou seja, apenas recebe conexões de outros dispositivos Bluetooth. Seria esse o real problema para não conseguir enviar uma mensagem para o Android?

          Se alguém puder comentar sobre isso me ajudaria bastante. Obrigado.

          1. Olá, Jayme,

            O fato de o módulo HC-06 operar apenas em modo slave não tem relação com a dificuldade em enviar mensagens do Arduino para o Android. O dispositivo funcionar como slave indica que ele não tem capacidade de iniciar a conexão, ou seja, algum outro dispositivo (master) precisa convidá-lo para a conexão. Isso significa que o master, nesse caso o Android, deve iniciar a conexão com o slave, o módulo HC-06. Mas após a conexão ser estabelecida, as mensagens devem fluir naturalmente entre os dois.

          2. Jayme!

            Refizemos os testes. Um dos problemas que enfrentei foi utilizar as portas padrão RX e TX do Arduino para realizar a comunicação. Esse problema pode ser contornado se utilizarmos a bilioteca SoftwareSerial para simular os pinos de comunicação serial nos pinos digitais. Foi isso que fizemos. E simplificamos o código no Android de forma que contenha apenas o essencial para conexão. Veja tudo aqui: http://dragaosemchama.com.br/2016/04/comunicacao-bluetooth-entre-arduino-e-android/

            Obrigado pela paciência, hahahah.

          1. Poxa! muito obrigado. O código no Android está perfeito, até porque, fiz um teste com um outro aparelho Android e consegui receber e enviar mensagens. Mas esse bendito módulo que uso não envia nada para o Android. Detalhe: (utilizo as portas RX0 e TX0). no código Arduido declaro apenas a Serial.begin(9600).

            Mais uma vez obrigado pelos esclarecimento. Cheguei até a comprar um módulo HC-05, mas irei cancelar.

          2. Olá novamente. Venho aqui para informar que finalmente conseguir receber os dados no Android.
            O problema era que eu estava usando resistores para conectar o pino Vcc do módulo Bluetooth, na porta 5v do Arduino. Depois de infinitas possibilidades e exemplo que eu testei, finalmente resolvi o problema conectando diretamente sem uso de resistor algum. Não sei se é adequado, mas assim funcionou. (Obs: nunca fiz nada em Arduino, essa é a primeira vez. Por isso o motivo de apanhar tanto =) rsrs.).

            Obrigado pela ajuda!

          3. Jayme,

            Então o problema era que o módulo Bluetooth não estava recebendo a alimentação adequada. Não tem problema alimentar o módulo diretamente com 5V, porque o HC-06 suporta como alimentação entre 3.3V e 6V. Que bom que deu certo \o/

    2. Olá JAYME C S. Tenho um projeto com função semelhante ao seu, no meu caso, preciso que o Arduino envie um numero sequencial( Ex 45.45.78.78) para identificação de mercadorias para o Android via Bluetooth.

      Porém tb estou com dificuldades na programação, msm com o outro post criado sobre Arduino e Bluetooth, qr enteder a lógica do protocolo, de como é realizada a comunicação entre 2 dispositivos.

      Vc tem algum material de estudo que possa indicar ?

      Agradeço qq ajuda.

  7. Ola, C David,

    Gostaria de agradecer pelo excelente tutorial, me ajudou bastante.
    Eu só tenho uma dúvida e espero que possa me ajudar. Eu testei a app com um celular s4 e outro aparelho LG novo que eu não sei o modelo (emprestei de uma amiga), e a comunicação funcionou perfeitamente. Porem quando uso o s4 com um s3 mini diz que ocorreu um erro na conexão. Como cliente o s3 mini não funciona, quando coloco ele como servidor ele funciona apenas recebendo dados, mas quando tenta enviar diz q deu erro de conexão.

    A minha dúvida é se essa aplicação funciona em apis level mais baixos ou se o aparelho s3 mini pode estar com problemas. Mas fora da app o bluetooth dele funciona normalmente.

    Aguardo resposta,

    Ramires Marques

    1. Saudações, Ramires,

      Não entendo o que possa estar causando esse erro. Sim, essa aplicação deveria ser compatível entre apis de níveis diferentes. Existe a possibilidade de você usar o debugger do Android Studio ou Eclipse e anotar pra gente a mensagem de erro que dá quando o problema acontece?

  8. Olá, bem completo o tutorial, gostaria de perguntar, como usar para aquelas chaves kess, eu gostaria de me aproximar do portão de casa, com o meu celular, e abrir o portão automaticamente, tipo ao se aproximar 1 mt de distancia. Um abraço

        1. Eduardo,

          Não sei em relação à chave Kess. Mas encontrei alguns tutoriais sobre como controlar travas de portas usando Android e Arduino. Foi o mais próximo que encontrei e eles estão em inglês.

          http://www.instructables.com/id/Easy-Bluetooth-Enabled-Door-Lock-With-Arduino-An/
          http://www.instructables.com/id/Arduino-Android-Based-Bluetooth-Controll-Password-/
          http://makezine.com/projects/controlling-a-lock-with-an-arudino-and-bluetooth-le/
          http://makezine.com/projects/ble-controlled-door-lock/

          Se encontrar algo em português, te aviso.

  9. Olá. Sou inciante neste tipo de programação e estou tendo dificuldade com um erro que está sendo apresentado pelo R.id, (o simbolo não pode ser resolvido. Se alguém puder dar uma luz agradeço.

    1. Olá, Anderson,

      Tente Limpar o projeto. Isso provavelmente vai reconstuir o arquivo R e o símbolo R.id poderá ser referenciado. Faça, se estiver no Android Studio, Build -> Clean.

      Caso não seja muito trabalho, tente criar um novo projeto e copiar as classes e arquivos XML que você já escreveu nele. Se não funcionar, me avisa.

      1. Olá C. Davi..
        Desculpe a demora..
        Reescrevi o código, e todos os erros java sumiram, mas ficou apenas um erro no arquivos menu_main_bluetooth.xml e no menu_paired_devices.xml.
        Nos dois arquivos aparece erro no comando (android:title=”@string/action_settings”), dá como não resolvido, porém quando vou até o MainBluetoothActivity.java está td certo.
        O que poderia ser?

      2. Consegui resolver o problema do (android:title=”@string/action_settings”), faltava “declarar”, não sei se é assim que se fala, a string “action_settings. Agora o programa está sem erros.
        Porém não estou conseguindo executar o APP no meu dispositivo, ele está configurado como programador normalmente.
        O Android Studio não o reconhece pelo USB, pesquisei e encontrei algo sobre (android:debuggable=”true”).
        Sabe algo que possa me ajudar?

        1. Anderson,

          Que bom que o código já está compilando corretamente. Agora vamos ver o que pode estar acontecendo para que o Android Studio não reconheça seu celular.

          (1) Você liberou o celular para debug via USB? Se não, é necessário fazer isso para que o Android Studio reconheça o dispositivo. Aqui está um link (em inglês) que ensina a habilitar o modo de debug para várias versões do Android. https://www.kingoapp.com/root-tutorials/how-to-enable-usb-debugging-mode-on-android.htm

          (2) Os drivers do seu celular estão instalados no computador ou atualizados? Se o seu computador reconhece o celular, provavelmente estão instalados. Mas tente atualizar os drivers.

          Essas são as fontes de problemas mais comuns quando o Android Studio não reconhece o dispositivo. Espero que uma dessas opções resolva.

          1. Olá C. Davi,

            Realmente estava com problema no driver, e a versão do JDK estava errada também.
            Após realizar esses acertos consegui executar o APP normalmente.
            Agradeço pela ajuda.

  10. Copiei o código igualzinho da classe discoveredDevices e pairedDevices e nas duas o action_settings e menu aparecem como “cannot find symbol” . O que eu faço?

    1. Saudações, Luana,

      Esses erros são sempre muito incômodos.

      Tente Build -> Clean.

      Se não funcionar: como você criou o arquivo para as classes discoveredDevices e pairedDevices? Se já não tiver feito assim, tente File -> New -> Java Class. Isso normalmente garante que os símbolos para a nova classe serão gerados no arquivo R.

      Na pior hipótese, nesse caso, você pode apagar a linha que faz referência a menu e o código if que faz referência a action_settings. Apenas essas linhas. Isso fará com que o menu de opções do aplicativo não seja ativado quando clicado. Mas como não temos nenhuma funcionalidade para esse menu nessas Activities, é aceitável.

      Espero que dê certo.

      1. Olá, estou fazendo um codigo p/ conectar o celular numa caixa de som bluetooth. Então meu celular atua como cliente, certo? No entanto, não consigo conectar, cai no erro “Ocorreu um erro durante a conexão D:”. O que eu faço?

        1. Luana,

          Esse código provavelmente não funcionará para enviar áudio a uma caixa de som Bluetooth. Eu, particularmente, nunca tentei escrever um aplicativo para isso e por isso não sei como te aconselhar.

          Por enquanto, só posso te dizer pra procurar saber sobre Perfis Bluetooth, que determinam vários protocolos para comunicação Bluetooth em diferentes serviços. O perfil utilizado para conectar e enviar dados de áudio a fones e caixas de som Bluetooth é chamado A2DP. Encontrei uma página sobre A2DP na página de desenvolvedores do Google [1] e um post (em inglês) que, aparentemente ensina a realizar a conexão [2]:

          [1] https://developer.android.com/reference/android/bluetooth/BluetoothA2dp.html
          [2] https://derivedcode.wordpress.com/2013/10/09/connecting-to-a-bluetooth-a2dp-device-from-android/

          Espero que ajude

  11. Na classe ConnectionThread, na linha MainActivity.handler.sendMessage(message); o handler dá como cannot resolve symbol ‘handler’, o código está igual ao seu, por que esse erro acontece???

    1. Rafael,

      “MainActivity.handler” é uma maneira de fazer referência ao objeto handler da classe MainActivity. Então o símbolo handler, neste caso, deve ser definido dentro da classe MainActivity. Provavelmente o que está acontecendo é que ele ainda não foi definido lá. Veja o código da nossa Activity principal aqui: https://goo.gl/UluVsD. O handler é definido entre as linhas 129 e 146.

      Espero que isso resolva o/

  12. Rafael.

    A parte de descobrir nos dispositivos não apresenta erros, mas não aparece nenhum dispositivo na lista, porém quando eu uso a descoberta nativa do aparelho, ele me mostra os que estão perto. Sabe me dizer oque pode ser?

    Grato.

    1. William,

      Desculpe pela demora. Adicione a seguinte permissão ao manifesto:

      <uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION”/>

      Imagino que o dispositivo Android que você está usando é 6.0 ou superior. A partir dessa versão, as funções de descoberta Bluetooth necessitam de mais essa permissão para funcionarem corretamente. Vou atualizar no post.

  13. CARACA! Funcionou! Criei a interface com o Design do Android Studio e como foi o meu primeiro projeto em android e com a linguagem Java um amigo aqui tb me tirou algumas dúvidas… mas parabéns pelo tutorial. Estou usando aqui com o HC05 e vou portar um protocolo serial para a aplicação,

    Grato!

  14. Primeiramente gostaria de estar agradecendo o pessoal do Dragão sem chama, por compartilhar e tirar as duvidas de muitas pessoas como eu que sou iniciante em programação.
    mais gostaria de perguntar a respeito do código PairedDevices, na parte do onCreateOptionsMenu(Menu menu), quando crio o método na parte getMenuInflater().inflate(R.menu.menu_paired_devices,menu); ele me pede para criar um menu.xml mas quando crio eu volto no método ele continua pedindo para criar o menu.xlm. Gostaria muito de resolver esse probleminha que está me deixando bastante maluco.

    Obrigado.

  15. Cara.. eu consegui fazer quase tudo.. mas tive que fazer umas modificações.. O problema é que ele exige um PIN de segurança.. sendo que o dispositivo embarcado que eu uso não exige algo do tipo.. quero fazer a conexão direta sem o PIN.. como faço?

    1. Olá, Henrique,

      Normalmente o pin de segurança é exigido só na primeira conexão, durante o pareamento. Pelo menos foi assim nas minhas experiências. No seu dispositivo, a exigência de pin ocorre toda vez, mesmo se os dispositivos estiverem pareados?

      Não sei como desativar a exigência do pin durante o pareamento. Mas fiz uma pesquisa rápida e encontrei um link interessante. Funcionou para algumas pessoas, mas não para outras:

      http://stackoverflow.com/questions/7337032/how-can-i-avoid-or-dismiss-androids-bluetooth-pairing-notification-when-i-am-do

      Espero que funcione,
      Good luck

  16. Rapaz… vou te contar,estou usando seu artigo para estudar a comunicação, segui o artigo passou a passo, alias muito bom o artigo, porem quando eu testo o meu código no samsung j7(android 6.0.1) a lista de dispositivos descobertos não funciona mas no Samsung s3(android 4.3) funciona perfeitamente. Fica estranho quando o seu programa funciona nos dois dispositivos sem problema algum, sendo que o meu programa é apenas uma adaptação do seu, sem nenhuma implementação.
    Alguma ideia do que seja e como consertar?

  17. Meu nome é Antonio fiz uma conexão bem sucedida entre meu celular Sansung e o modulo bluetooth ma com o celular sansung as vezes conecto com dificuldades.

    1. ERREI
      Meu nome é Antonio fiz uma conexão bem sucedida entre meu celular Sansung e o modulo bluetooth ma com o celular LG as vezes conecto com dificuldades.

  18. Boa noite! Quase tudo funciona pra mim,e aliás, obrigado pela ajuda no outro post. A única parte que não está funcionando é quando vou iniciar a descoberta de dispositivos, que não aparece nenhum e na descoberta nativa aparece. o está no manifesto. Tenho android 7.0 instalado no celular. Valeu!

    1. A príncipio, não sei porque a descoberta de dispositivos está com problemas no seu Android 7.0. Você falou sobre o manifesto no comentário, mas não apareceu o que exatamente você disse que já tinha incluído no manifesto. Alguma mensagem referente a Bluetooth aparece nos logs do Android Studio (logcat) quando você tenta fazer a descoberta?

  19. Boa noite. Sou CarloSilva e antes de tudo quero lhe parabenizar e agradecer por compartilhar este código, verdadeira aula tanto para iniciantes quanto para experientes programadores JAVA/ANDROID.
    Uma dúvida, como altero o código para, depois de conectar com um dispositivo, o server continue esperando novas conexões?

    1. Olá, Carlo. Valeu pelo apoio!

      Sobre a ideia de múltiplos dispositivos Bluetooth conectados ao mesmo tempo, tenho que dizer que não sei uma maneira simples e rápida de fazer essa alteração usando o código deste post. Precisaria voltar, analisar e reestruturar algumas partes do código. No entanto, fiz uma pesquisa e descobri uma biblioteca que tem suporte a múltiplas conexões. Se realmente precisar dessa funcionalidade, espero que dê certo usando essa biblioteca: http://arissa34.github.io/Android-Multi-Bluetooth-Library/

  20. Maravilha de artigo!!!! Dei altas risadas aqui…. kkkkkk

    VLW MESTRE!

    Queria adicionar uma atualização aqui:
    Para aqueles que querem utilizar o API 23(corrija se estiver errado) ou mais (lollipop para frente), deve-se pedir permissão de localização, que só será necessária na primeira abertura do app.
    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    int permissionCheck = this.checkSelfPermission(“Manifest.permission.ACCESS_FINE_LOCATION”);
    permissionCheck += this.checkSelfPermission(“Manifest.permission.ACCESS_COARSE_LOCATION”);
    if (permissionCheck != 0) {
    this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, 1001); //Any number
    }
    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

  21. Bom dia, como poderia definir a distância entre o arduino e meu dispositivo? tentei medir esta distância de acordo com a força do sinal, porém é muito impreciso. Teria alguma dica?

  22. Parabens pelo Posto. vou virar fregues…
    Gostaria de Imprimir um relatorio de pedido na impressora m58 . apartir deste codigo. alguma ideia de como proceder.

    1. Olá, Antonio. Obrigado pelo apoio :)

      Sobre imprimir na M58, isso vai depender do nível de acesso e suporte que a fabricante oferece para desenvolvedores. Provavelmente ela tem seu próprio protocolo de envio de dados para impressão, etc. Não sei como ela funciona, mas a dica é procurar saber se existe algum manual do fabricante para desenvolvedores. Caso exista, ele deve conter os passo necessários para construir uma aplicação que conecte à impressora via bluetooth. Assim esperamos. Boa sorte!

  23. consegui conectar mas na hora de enviar a mensagem o app para… nao tenho como rastrear o erro pq utilizp o AIDE da app four para celular. alguma ideia ai?

  24. Olá,
    O post é excelente. Acontece que eu tenho duas activities para usar o bluetooth.
    Faço a conexão na mainActivity.
    Quando eu vou para a primeira activity ou para a segunda, funciona tudo bem. Quando volto para a mainActivity a conexão é derrubada. Portanto não consigo trabalhar com as duas activities ora uma ora outra.
    Alguém me ajuda ?

    1. Olá, Nilmar

      Não sei como garantir que a conexão Bluetooth seja mantida durante as trocas de Activities. Mas caso eu enfrentasse esse problema, tentaria escrever um código que fizesse tentativas de reconectar quando a Activity fosse trocada e executaria esse código no background sempre que trocasse de Activity. Apenas uma ideia, mas não testei.

  25. Ola boa noite, estou tendo um problema com a conexão com o bluetooth.
    Que está gerado a seguinte exception “read failed, socket might closed or timeout, read ret: -1”.
    Por acaso já passou por isso ?

  26. Boa noite, excelente seu artigo
    Tenho em casa um Box TV que não tem a função do bluetooth, dai comprei um dongle usb, e mesmo assim não aparece a função. Fiz a instalação do seu código pelo Android Studio e foi de boa, mas na hora de ativar o bluetooth aparece a mensagem de “Ativando Bluetooth” e não sai mais dessa tela. Estou começando a ver programação agora então estou meio cru, mas tem q mudar alguma linha de comando para direcionar para o dispositivo USB?
    O duro que o android do Box TV é modificado, veio no firmware 6.0.1
    Desde já obrigado

    1. Oi Rodrigo, perdão pela demora.

      Acho que os códigos do post não vão te ajudar muito nesse caso. O post serve para quando você tem acesso à programação dos dois dispositivos que vão estar conectados. O seu Box TV provavelmente é um sistema fechado e você não tem acesso à programação ou ao código fonte dele. Ou tem?

  27. Parabéns pelo post.
    No caso, quero me conectar, como cliente, a um dispositivo bluetooth que me fornece um paring code a cada conexão… Sabe alguma forma de passar esse code na hora da conexão?

  28. Excelente trabalho!! Já a alguns dias estou procurando algum material que me oriente para parear o módulo bluetooth HC-05 ou HC-06 diretamente pelo App, sem a necessidade do usuário digitar senha ao parear. Alterei a senha(PIN) do meu módulo HC-06 e gostaria que apenas o meu App(que sabe a senha) possa se comunicar com o módulo Bluetooth. Você tem algum material que explica como proceder nesta configuração?

  29. Boa tarde, consegui aplicar seu app usando dois dispositivos. Porém eu não consegui parear um dispositivo em especifico, no caso ele é uma placa só com bluetooth.
    Pelo gerenciamento padrão do android, eu consigo esse pareamento, até mesmo com o app na play store, o BLE Scanner eu consigo efetuar o pareamento. Fiquei nessa dúvida.
    Ainda assim, ótimo tutorial, parabéns!

  30. Olá!
    Excelente publicação!
    Você conhece alguma forma de uma aplicação Android se transformar num “teclado bluetooth”?
    A ideia seria, por exemplo, meu app rodando num smartphone se conectar via bluetooth a um dispositivo qualquer (um outro smartphone, um notebook, etc) e as teclas que eu pressionasse no teclado do meu smartphone fossem enviadas e recebidas no outro aparelho como a entrada de um teclado.
    Você sabe se isso é possível?

    1. Olá, Alex. Perdão pela demora. Acredito que é possível. Mas você precisaria compreender como funciona a simulação de cliques e teclas no outro dispositivo e desenvolver um programa (que ficará sendo executado nesse outro dispositivo) para receber os comandos do seu app e simular devidamente o pressionamento das teclas. Também precisaria compreender como se dá a programação bluetooth neste outro dispositivo, claro. Mas é possível, acredite.

  31. Bom dia!! parabéns e obrigado pelo post!!
    Gostaria de saber se tem como mudar o pin padrão do modulo para um pin personalizado e se tem como exigir o pin sempre que for conectar, codificando pelo Android Studio.

  32. Boa tarde!
    Estou começando com programação Androide e direto num projeto com Bluetooth.
    Aprendi muito nesses dois dias em que estudei este artigo.

    Parabéns pela atitude altruísta de compartilhar conhecimento.

    “Vida Longa e Próspera” (Mr. Spock)

  33. Queria inserir partes desse código num projeto maior meu, mas ele é para android kit kat, na API 19. É possível utilizar mesmo assim? ou necessita ser uma versão mais atualizada? Meu medo é sair testando depois acabar bagunçando demais o que já fiz e dar tudo errado, to começando agora a programar no android studio

    1. Olá, Ana. Acredito que a funcionalidade Bluetooth do Android seja estável entre as versões. Então deve ser seguro utilizar o mesmo código. Tenho visto diversas pessoas que usam esse post como guia relatarem que ainda está funcionando bem nas novas versões. Apesar disso, inevitavelmente, se quiser desenvolver um app para uso geral e garantir que a abrangência dele seja ampla, vai ser preciso testar em diversos dispositivos. Sempre tem aquele risco de uma pequena alteração entre versões acabar bloqueando a funcionalidade.

  34. Oi David:

    Esta dando este erro:

    Could not find com.android.support:appcompat-v7:25.3.1.
    Required by:
    project :app

    acho que referente ao arquivo build.gradle

    já olhei e na pasta:
    C:\Users\edueandreia\AppData\Local\Android\Sdk\sources\android-29\com\android

    não tem a pasta nem arquivo “support”

    meu android studio é o 4.0, Sabe como resolver este problema. Também não tá sincronizando layout.

  35. Desculpe,

    Funcionou direitinho, eu cliquei em um link da mensagem de erro “update gradle” então baixou um arquivo e o projeto funcionou tudo direitinho.

    Parabens David e obrigado por disponibilizar este trabalho.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *