Bluetooth

A plataforma Android é compatível com a pilha de rede Bluetooth, o que permite a um dispositivo permutar dados em comunicação sem fios com outros dispositivos Bluetooth. A estrutura de trabalho do aplicativo oferece acesso à funcionalidade do Bluetooth por meio da Android Bluetooth API. Essas APIs permitem a conexão sem fio de aplicativos a outros dispositivos Bluetooth, permitindo recursos sem fio ponto a ponto e multiponto.

Ao usar Bluetooth APIs, um aplicativo Android pode executar as seguintes atividades:

  • Procurar outros dispositivos Bluetooth
  • Consultar o adaptador Bluetooth local para verificar a existência de dispositivos Bluetooth pareados
  • Estabelecer canais RFCOMM
  • Conectar-se a outros dispositivos por meio da descoberta de serviços
  • Transferir dados de e para outros dispositivos
  • Gerenciar várias conexões

Este documento descreve como usar o Classic Bluetooth. O Classic Bluetooth é a escolha certa para operações com maior consumo de bateria, como streaming e comunicação entre dispositivos Android. Para dispositivos Bluetooth com baixos requisitos de energia, o Android 4.3 (API de nível 18) introduz APIs compatíveis com o Bluetooth Low Energy. Para saber mais, consulte Bluetooth Low Energy.

Conceitos básicos

Este documento descreve como usar a Android Bluetooth API para executar as quatro principais tarefas necessárias para a comunicação usando Bluetooth: configurar o Bluetooth, encontrar dispositivos pareados ou disponíveis na área local, conectar dispositivos e transferir dados entre dispositivos.

Todas as Bluetooth APIs estão disponíveis no pacote android.bluetooth. Veja a seguir um resumo das classes e interfaces necessárias para criar conexões Bluetooth:

BluetoothAdapter
Representa o adaptador Bluetooth local (rádio Bluetooth). O BluetoothAdapter é o ponto de entrada para toda a interação com o Bluetooth. Use-o para descobrir outros dispositivos Bluetooth, consultar uma lista de dispositivos vinculados (pareados), instanciar um BluetoothDevice usando um endereço MAC conhecido e criar um BluetoothServerSocket para ouvir comunicações de outros dispositivos.
BluetoothDevice
Representa um dispositivo remoto Bluetooth. Use-o para solicitar uma conexão com um dispositivo remoto por meio de um BluetoothSocket ou consultar informações sobre o dispositivo, como nome, endereço, classe e estado de vinculação.
BluetoothSocket
Representa a interface de um soquete Bluetooth (semelhante a um Socket TCP). É o ponto de conexão que permite que um aplicativo permute dados com outro dispositivo Bluetooth por meio de InputStream e OutputStream.
BluetoothServerSocket
Representa um soquete de servidor aberto que ouve solicitações recebidas (semelhante a um ServerSocket TCP). Para conectar dois dispositivos Android, um dispositivo deve abrir um soquete de servidor com esta classe. Quando um dispositivo remoto Bluetooth estabelecer uma solicitação de conexão a esse dispositivo, o BluetoothServerSocket retornará um BluetoothSocket depois de aceita a conexão.
BluetoothClass
Descreve as características e recursos gerais de um dispositivo Bluetooth. Essa descrição é um conjunto de propriedades somente para leitura que definem as classes principais e secundárias do dispositivo e seus serviços. No entanto, ela não é uma descrição confiável de todos os perfis e serviços Bluetooth permitidos pelo dispositivo, mas pode ser útil como indicação do tipo de dispositivo.
BluetoothProfile
Interface que representa um perfil Bluetooth. O perfil Bluetooth é uma especificação de interface sem fio para comunicação Bluetooth entre dispositivos. Um exemplo é o perfil Hands-Free. Para ver uma discussão mais detalhada dos perfis, consulte Trabalho com perfis
BluetoothHeadset
Permite o uso de fones de ouvido Bluetooth com celulares. Isso abrange os perfis Bluetooth Headset e Hands-Free (v1.5).
BluetoothA2dp
Define como fazer streaming de áudio de alta qualidade de um dispositivo para outro usando uma conexão Bluetooth. "A2DP" significa Advanced Audio Distribution Profile (perfil avançado de distribuição de áudio).
BluetoothHealth
Representa um proxy do perfil Health Device que controla o serviço Bluetooth.
BluetoothHealthCallback
Classe abstrata usada para implementar retornos de chamada de BluetoothHealth. É necessário estender essa classe e implementar os métodos de retorno de chamada para receber atualizações sobre as alterações no estado do registro do aplicativo e o estado do canal Bluetooth.
BluetoothHealthAppConfiguration
Representa uma configuração de aplicativo registrado pelo aplicativo de saúde de terceiros do Bluetooth para comunicação com um dispositivo de saúde remoto do Bluetooth.
BluetoothProfile.ServiceListener
Interface que notifica clientes IPC do BluetoothProfile quando da conexão ou desconexão do serviço (ou seja, o serviço interno que executa um determinado perfil).

Permissões do Bluetooth

Para usar recursos do Bluetooth no aplicativo, você deve declarar a permissão BLUETOOTH do Bluetooth. Você precisa dessa permissão para executar todas as comunicações do Bluetooth, como solicitar uma conexão, aceitar uma conexão e transferir dados.

Se você quiser que o aplicativo inicie a descoberta de dispositivos ou manipule configurações do Bluetooth, será necessário declarar também a permissão BLUETOOTH_ADMIN. A maioria dos aplicativos precisa dessa permissão apenas para poder descobrir dispositivos Bluetooth locais. Os outros recursos concedidos por essa permissão não devem ser usados, a menos que o aplicativo seja um "gerenciador de energia" que modifique configurações do Bluetooth mediante solicitações do usuário. Observação: Se você usar a permissão BLUETOOTH_ADMIN, deverá também ter a permissão BLUETOOTH.

Declare as permissões do Bluetooth no arquivo de manifesto do aplicativo. Por exemplo:

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

Consulte a referência <uses-permission> para obter mais informações sobre a declaração de permissões do aplicativo.

Configurar o Bluetooth

Figura 1. A caixa de diálogo de ativação do Bluetooth.

Antes que o aplicativo se comunique usando o Bluetooth, é necessário verificar se o dispositivo permite Bluetooth e, caso permita, se o Bluetooth está ativado.

Se o dispositivo não permitir o Bluetooth, você deverá desativar todos os recursos do Bluetooth de forma ordenada. Se o dispositivo permitir o Bluetooth, você poderá solicitar que o usuário ative o Bluetooth sem sair do aplicativo. Essa configuração é efetuada em duas etapas, usando o BluetoothAdapter.

  1. Obtenha o BluetoothAdapter

    O BluetoothAdapter é necessário para todas as atividades do Bluetooth. Para obter o BluetoothAdapter, chame o método estático getDefaultAdapter(). Retornará um BluetoothAdapter que representa o próprio adaptador Bluetooth do dispositivo (o rádio Bluetooth). Há um adaptador Bluetooth para todo o sistema e o aplicativo pode usar esse objeto para interagir com ele. Se getDefaultAdapter() retornar nulo, o dispositivo não permite Bluetooth e fim de papo. Por exemplo:

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // Device does not support Bluetooth
    }
    
  2. Ativar Bluetooth

    Em seguida, é necessário assegurar a ativação do Bluetooth. Chame isEnabled() para verificar se o Bluetooth está ativado no momento. Se o método retornar false, o Bluetooth está desativado. Para solicitar a ativação do Bluetooth, chame startActivityForResult() com o Intent de ação ACTION_REQUEST_ENABLE. Será emitida uma solicitação de ativação do Bluetooth por meio das configurações do sistema (sem interromper o aplicativo). Por exemplo:

    if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    

    Será exibida uma caixa de diálogo que solicitará a permissão do usuário para ativar o Bluetooth como mostrado na Figura 1. Se o usuário responder "Yes", o sistema começará a ativar o Bluetooth e o foco retornará ao aplicativo após a conclusão (ou falha) do processo.

    A constante REQUEST_ENABLE_BT passada a startActivityForResult() é um inteiro definido localmente (que deve ser maior que 0) passado pelo sistema para a implementação de onActivityResult() como o parâmetro requestCode.

    Se ativação do Bluetooth for bem-sucedida, a atividade receberá o código de resultado RESULT_OK no retorno de chamada onActivityResult(). Se o Bluetooth não foi ativado devido a um erro (ou à resposta "No" do usuário), o código de resultado será RESULT_CANCELED.

Opcionalmente, o aplicativo também pode ouvir o Intent de transmissão ACTION_STATE_CHANGED, transmitido pelo sistema sempre que o estado do Bluetooth for alterado. Essa transmissão contém os campos adicionais EXTRA_STATE e EXTRA_PREVIOUS_STATE, que contêm respectivamente os estados novo e anterior do Bluetooth. Os valores possíveis dos campos adicionais são STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF e STATE_OFF. Pode ser útil ouvir essa transmissão para detectar alterações do estado do Bluetooth durante a execução do aplicativo.

Dica: A ativação da detecção do dispositivo ativará automaticamente o Bluetooth. Se você planeja sempre ativar a detecção do dispositivo antes de executar as atividades do Bluetooth, pode ignorar a etapa 2 acima. Leia baixo sobre a detecção do dispositivo.

Encontrar dispositivos

Use o BluetoothAdapter para encontrar dispositivos remotos Bluetooth por meio da descoberta de dispositivos ou consultando a lista de dispositivos pareados vinculados.

A descoberta de dispositivos é um procedimento de análise que procura dispositivos com Bluetooth ativado e solicita algumas informações de cada um deles (às vezes esse processo é denominado "descoberta", "consulta" ou "análise”) para pesquisar a área local. No entanto, os dispositivos Bluetooth na área local responderão a solicitações de descoberta somente se estiverem configurados como detectáveis naquele momento. Se o dispositivo estiver detectável, responderá à solicitação de descoberta compartilhando algumas informações, como o nome, a classe e o endereço MAC único do dispositivo. Com essas informações, o dispositivo que executa a descoberta pode optar por iniciar uma conexão com o dispositivo descoberto.

Após a conexão com um dispositivo remoto pela primeira vez, uma solicitação de pareamento será automaticamente apresentada ao usuário. Quando um dispositivo é pareado, as informações básicas sobre esse dispositivo (como nome, classe e endereço MAC do dispositivo) são salvas e podem ser lidas usando as Bluetooth APIs. Com o endereço MAC conhecido de um dispositivo remoto, é possível iniciar uma conexão com ele a qualquer momento sem executar a descoberta (desde que o dispositivo esteja dentro do alcance).

Lembre-se de que há uma diferença entre estar pareado e estar conectado. Estar pareado significa que os dois dispositivos estão cientes da existência um do outro, têm um link-chave compartilhado que pode ser usado para autenticação e podem estabelecer uma conexão criptografada entre si. Estar conectado significa que os dispositivos compartilham no momento um canal RFCOMM e podem transmitir dados entre si. A Android Bluetooth API atual exige que os dispositivos estejam pareados antes de estabelecer uma conexão RFCOMM. (O pareamento é executado automaticamente na inicialização de uma conexão criptografada com as APIs do Bluetooth.)

As sessões a seguir descrevem como encontrar dispositivos que já foram pareados ou descobrir novos dispositivos usando a descoberta de dispositivos.

Observação: Por padrão, dispositivos Android não são detectáveis. O usuário pode tornar o dispositivo detectável por tempo limitado por meio das configurações do sistema ou o aplicativo pode solicitar que o usuário ative a detecção do dispositivo sem sair do aplicativo. A forma de ativar a detecção do dispositivo é discutida a seguir.

Consultar dispositivos pareados

Antes de executar a descoberta de dispositivos, vale a pena consultar o conjunto de dispositivos pareados para verificar se o dispositivo desejado já é conhecido. Para isso, chame getBondedDevices(). É retornado um conjunto de BluetoothDevices, representando dispositivos pareados. Por exemplo: você pode consultar todos os dispositivos pareados e mostrar o nome de cada dispositivo ao usuário com ArrayAdapter:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
    // Loop through paired devices
    for (BluetoothDevice device : pairedDevices) {
        // Add the name and address to an array adapter to show in a ListView
        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
}

A única informação necessária do objeto BluetoothDevice para iniciar uma conexão é o endereço MAC. Nesse exemplo, ele é salvo como parte de um ArrayAdapter exibido ao usuário. É possível extrair posteriormente O endereço MAC para iniciar a conexão. Para saber mais sobre a criação de uma conexão, veja a seção Conectar dispositivos.

Descobrir dispositivos

Para começar a descobrir dispositivos, basta chamar startDiscovery(). O processo é assíncrono e o método retornará imediatamente um valor booleano indicando se a descoberta foi iniciada corretamente. Normalmente, o processo de descoberta envolve uma análise de consulta de cerca de 12 segundos, seguida por uma análise de página de cada dispositivo encontrado para recuperar seu nome do Bluetooth.

O aplicativo deve registrar um BroadcastReceiver para o Intent ACTION_FOUND para descobrir informações sobre cada dispositivo descoberto. Para cada dispositivo, o sistema transmitirá o intent ACTION_FOUND. Esse Intent contêm os campos adicionais EXTRA_DEVICE e EXTRA_CLASS, contendo respectivamente um BluetoothDevice e um BluetoothClass. Por exemplo, veja como é possível registrar-se para processar a transmissão quando dispositivos são descobertos:

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

A única informação necessária do objeto BluetoothDevice para iniciar uma conexão é o endereço MAC. Neste exemplo, ele é salvo como parte de um ArrayAdapter exibido ao usuário. O endereço MAC pode ser extraído posteriormente para iniciar a conexão. Você pode saber mais sobre a criação de uma conexão na seção Conectar dispositivos.

Atenção: A execução de uma descoberta de dispositivos é um procedimento pesado para o adaptador Bluetooth e consome muitos dos seus recursos. Assim que você encontrar um dispositivo para conexão, não deixe de interromper a descoberta com cancelDiscovery() antes de tentar a conexão. Além disso, se você já tem uma conexão com um dispositivo, a execução da descoberta pode reduzir consideravelmente a largura de banda disponível para a conexão. Portanto, você não deve executar a descoberta enquanto estiver conectado.

Ativar a detecção do dispositivo

Se você quer que o dispositivo local seja detectável por outros dispositivos, chame startActivityForResult(Intent, int) com o Intent de ação ACTION_REQUEST_DISCOVERABLE. Será emitida uma solicitação de ativação do modo detectável por meio das configurações do sistema (sem interromper o aplicativo). Por padrão, o dispositivo ficará detectável por 120 segundos. Para definir uma duração diferente, adicione o Intent EXTRA_DISCOVERABLE_DURATION. A duração máxima que um aplicativo pode definir é 3600 segundos, e um valor de 0 significa que o dispositivo está sempre detectável. Qualquer valor abaixo de 0 ou acima de 3600 é definido automaticamente como 120 segundos. Por exemplo, o snippet a seguir define a duração como 300:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
Figura 2. Caixa de diálogo de ativação da detecção do dispositivo.

Será exibida uma caixa de diálogo que solicitará a permissão do usuário para tornar o dispositivo detectável, como mostrado na Figura 2. Se o usuário responder "Yes", o dispositivo se tornará detectável pelo tempo especificado. A atividade receberá então uma chamada para o retorno de chamada onActivityResult()), com o código de resultado igual à duração do período em que o dispositivo permanecerá detectável. Se o usuário responder "No" ou se ocorrer um erro, o código de resultado será RESULT_CANCELED.

Observação: Se o Bluetooth não foi ativado no dispositivo, ele será ativado automaticamente pela ativação da detecção do dispositivo.

O dispositivo permanecerá silenciosamente no modo detectável pelo tempo especificado. Se você quiser ser notificado quando o modo detectável mudar, registre um BroadcastReceiver para o Intent ACTION_SCAN_MODE_CHANGED. Ele conterá os campos adicionais EXTRA_SCAN_MODE e EXTRA_PREVIOUS_SCAN_MODE, que informam respectivamente o modo de análise novo e anterior. Os valores possíveis para cada um deles são SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, ou SCAN_MODE_NONE, que indicam respectivamente que o dispositivo está no modo detectável, não está no modo detectável mas pode receber conexões ou não está no modo detectável e não pode receber conexões.

Você não precisa ativar a detecção do dispositivo se iniciar a conexão a um dispositivo remoto. A ativação da detecção do dispositivo só é necessária se você quiser que o aplicativo hospede um soquete de servidor que aceite as conexões recebidas, pois os dispositivos remotos devem ser capazes de descobrir o dispositivo antes de iniciar a conexão.

Conectar dispositivos

Para criar uma conexão entre o aplicativo nos dois dispositivos, você deve implementar os mecanismos do lado do cliente e do servidor porque um dispositivo deve abrir um soquete de servidor e o outro deve iniciar a conexão (usando o endereço MAC do dispositivo servidor para iniciar a conexão). O servidor e o cliente serão considerados conectados entre si quando cada um deles tiver um BluetoothSocket conectado no mesmo canal RFCOMM. Nesse momento, cada dispositivo pode obter streams de entrada e saída e a transferência de dados pode começar, o que é discutido na seção Gerenciar uma conexão. Esta seção descreve como iniciar a conexão entre dois dispositivos.

O dispositivo servidor e o dispositivo cliente obtêm o BluetoothSocket necessário de formas diferentes. No servidor, ele é recebido quando uma conexão de entrada é aceita. No cliente, ele é recebido quando abre um canal RFCOMM para o servidor.

Figura 3. Caixa de diálogo de pareamento do Bluetooth.

Uma das técnicas de implementação é preparar automaticamente cada dispositivo como servidor para que cada um tenha um soquete de servidor aberto e ouça conexões. Qualquer um dos dispositivos pode iniciar a conexão com o outro e se tornar o cliente. Como alternativa, um dispositivo pode "hospedar" explicitamente a conexão e abrir um soquete de servidor sob demanda e o outro dispositivo pode simplesmente iniciar a conexão.

Observação: Se os dois dispositivos não foram pareados previamente, a estrutura de trabalho do Android mostrará automaticamente uma notificação ou caixa de diálogo de solicitação de pareamento ao usuário durante o procedimento de conexão, como mostrado na Figura 3. Portanto, ao tentar conectar dispositivos, o aplicativo não precisa se preocupar com o pareamento dos dispositivos. A tentativa de conexão RFCOMM ficará bloqueada até que o usuário execute o pareamento corretamente ou falhará se o usuário rejeitar o pareamento ou se o pareamento falhar ou esgotar o tempo limite.

Conectar como servidor

Para conectar dois dispositivos, um deve atuar como servidor, mantendo um BluetoothServerSocket aberto. O objetivo do soquete do servidor é ouvir solicitações de conexão de entrada e, quando uma for aceita, fornecer um BluetoothSocket conectado. Quando o BluetoothSocket é adquirido do BluetoothServerSocket, o BluetoothServerSocket pode (e deve) ser descartado a menos que você queria aceitar mais conexões.

Veja a seguir o procedimento básico para configurar um soquete de servidor e aceitar uma conexão:

  1. Obtenha um BluetoothServerSocket chamando o listenUsingRfcommWithServiceRecord(String, UUID).

    O string é um nome identificável do serviço e será gravado automaticamente pelo sistema em uma nova entrada de banco de dados do Service Discovery Protocol (SDP) no dispositivo (o nome é arbitrário e pode ser simplesmente o nome do aplicativo). O UUID também está incluído na entrada SDP e será a base para o acordo de conexão com o dispositivo cliente. Ou seja, quando o cliente tentar se conectar com este dispositivo, ele terá um UUID que identifica unicamente o serviço com o qual quer se conectar. Esses UUIDs devem corresponder para que a conexão seja aceita (na próxima etapa).

  2. Comece a ouvir solicitações de conexão chamando accept().

    Essa é uma chamada bloqueadora. Ela retornará quando uma conexão for aceita ou ocorrer uma exceção. A conexão só será aceita quando um dispositivo remoto enviar uma solicitação de conexão com um UUID correspondente ao registrado nesse soquete do servidor ouvinte. Quando bem-sucedido, accept() retornará um BluetoothSocket conectado.

  3. A menos que você queira aceitar mais conexões, chame close().

    Isso libera o soquete do servidor e todos os seus recursos, mas não fecha o BluetoothSocket conectado retornado por accept(). Ao contrário do TCP/IP, o RFCOMM somente permite um cliente conectado por canal em um determinado momento. Portanto, na maioria dos casos, faz sentido chamar close() no BluetoothServerSocket imediatamente depois de aceitar um soquete conectado.

Não se deve executar a chamada de accept() no encadeamento de IU da atividade principal, pois é uma chamada bloqueadora que impede todas as interações com o aplicativo. Normalmente, faz sentido fazer todo o trabalho com um BluetoothServerSocket ou BluetoothSocket em um novo encadeamento, gerenciado pelo aplicativo. Para cancelar uma chamada bloqueada como accept(), chame close() no BluetoothServerSocket (ou BluetoothSocket) de outro encadeamento e a chamada bloqueada retornará imediatamente. Observe que todos os métodos em um BluetoothServerSocket ou BluetoothSocket podem ser usados com segurança no encadeamento.

Exemplo

Veja a seguir um encadeamento simplificado do componente de servidor que aceita conexões de entrada:

private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;

    public AcceptThread() {
        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp = null;
        try {
            // MY_UUID is the app's UUID string, also used by the client code
            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned
        while (true) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            // If a connection was accepted
            if (socket != null) {
                // Do work to manage the connection (in a separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }

    /** Will cancel the listening socket, and cause the thread to finish */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) { }
    }
}

Nesse exemplo, só se quer uma conexão de entrada. Portanto, assim que uma conexão for aceita e o BluetoothSocket for adquirido, o aplicativo enviará o BluetoothSocket adquirido a um encadeamento separado, fechará o BluetoothServerSocket e interromperá o loop.

Observe que, quando accept() retorna o BluetoothSocket, o soquete já está conectado. Portanto, não se deve chamar connect() (como é feito no lado do cliente).

manageConnectedSocket() é um método fictício no aplicativo que inicia o encadeamento para transferir dados, o que é discutido na seção Gerenciar uma conexão.

Normalmente, deve-se fechar o BluetoothServerSocket assim que acabar de ouvir conexões de entrada. Nesse exemplo, close() é chamado assim que o BluetoothSocket é adquirido. Também pode ser necessário fornecer um método público no encadeamento que possa fechar o BluetoothSocket privado se for preciso parar de ouvir no soquete do servidor.

Conectar como cliente

Para iniciar uma conexão com um dispositivo remoto (um dispositivo com um soquete de servidor aberto), é necessário antes obter um objeto BluetoothDevice que representa o dispositivo remoto. (A obtenção de um BluetoothDevice é discutida na seção acima Encontrar dispositivos.) Em seguida, use BluetoothDevice para adquirir um BluetoothSocket e iniciar a conexão.

Eis o procedimento básico:

  1. Usando o BluetoothDevice, chame createRfcommSocketToServiceRecord(UUID) para obter um BluetoothSocket.

    Isso inicializa um BluetoothSocket que se conectará ao BluetoothDevice. O UUID passado aqui deve corresponder ao UUID usado pelo dispositivo servidor quando abriu seu BluetoothServerSocket (com listenUsingRfcommWithServiceRecord(String, UUID)). Para usar o mesmo UUID, basta inserir a string de UUID no aplicativo e referenciá-la no código do servidor e do cliente.

  2. Chame connect() para iniciar a conexão.

    Nessa chamada, o sistema executará uma pesquisa de SDP no dispositivo remoto para corresponder o UUID. Se a pesquisa for bem-sucedida e o dispositivo remoto aceitar a conexão, ele compartilhará o canal RFCOMM para uso durante a conexão e connect() retornará. Esse método é uma chamada bloqueadora. Se, por qualquer motivo, a conexão falhar ou o método connect() esgotar o tempo limite (depois de cerca de 12 segundos), será acionada uma exceção.

    Como connect() é uma chamada bloqueadora, esse procedimento de conexão deve sempre ser executado em um encadeamento separado do encadeamento da atividade principal.

    Observação: Deve-se sempre garantir que o dispositivo não esteja executando nenhuma descoberta de dispositivos quando chama connect(). Se houver uma descoberta em andamento, a tentativa de conexão será retardada substancialmente e, provavelmente, falhará.

Exemplo

Eis um exemplo básico de um encadeamento que inicia uma conexão Bluetooth:

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;

    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;

        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }

    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();

        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }

        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }

    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

Observe que cancelDiscovery() é chamado antes que a conexão seja feita. Isso deve sempre ser feito antes da conexão. É seguro fazer a chamada sem realmente verificar se está executando ou não (mas, se você quiser verificar, chame isDiscovering()).

manageConnectedSocket() é um método fictício no aplicativo que inicia o encadeamento para transferir dados, o que é discutido na seção Gerenciar uma conexão.

Quando você não precisar mais do BluetoothSocket, chame sempre close() para a limpeza. Isso fechará o soquete conectado imediatamente e limpará todos os recursos internos.

Gerenciar uma conexão

Quando você conectar com sucesso dois (ou mais) dispositivos, cada um deles terá um BluetoothSocket conectado. É quando a diversão começa, pois você pode compartilhar dados entre dispositivos. Usando o BluetoothSocket, o procedimento geral para transferir dados arbitrários é simples:

  1. Obtenha o InputStream e o OutputStream que processam transmissões pelo soquete por meio de getInputStream() e getOutputStream() respectivamente.
  2. Leia e grave dados nos streams com read(byte[]) e write(byte[]).

Pronto.

Naturalmente, é necessário considerar detalhes de implementação. Antes de mais nada, deve-se usar um encadeamento dedicado para todas as leituras e gravações do stream. Isso é importante porque os métodos read(byte[]) e write(byte[]) são chamadas bloqueadoras. read(byte[]) bloqueará até que haja algo para ler no stream. Normalmente, write(byte[]) não bloqueia, mas poderá bloquear para controle de fluxo se o dispositivo remoto não estiver chamando read(byte[]) com a rapidez suficiente e os buffers intermediários estiverem cheios. Portanto, o loop principal do encadeamento deve ser dedicado à leitura de InputStream. Pode-se usar um método público separado no encadeamento para iniciar gravações no OutputStream.

Exemplo

Eis um exemplo desse processo:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()

        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }

    /* Call this from the main activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

O construtor adquire os streams necessários e, quando executado, o encadeamento aguarda a chegada dos dados por meio de InputStream. Quando read(byte[]) retorna com bytes do streams, os dados são enviados à atividade principal usando um Handler de membros da classe pai. Em seguida, ele retorna e aguarda mais bytes do stream.

Para enviar dados de saída, basta chamar o método write() do encadeamento da atividade principal e passar os bytes a enviar. Em seguida, o método simplesmente chama write(byte[]) para enviar os dados ao dispositivo remoto.

O método cancel() do encadeamento é importante para que a conexão possa ser encerrada a qualquer momento, fechando o BluetoothSocket. Isso deverá ser feito sempre que você não precisar mais da conexão Bluetooth.

Para ver uma demonstração do uso das Bluetooth APIs, consulte o aplicativo de amostra Bluetooth Chat.

Trabalhar com perfis

A partir do Android 3.0., a Bluetooth API permite trabalhar com perfis do Bluetooth. O perfil Bluetooth é uma especificação de interface sem fio para comunicação Bluetooth entre dispositivos. Um exemplo é o perfil Hands-Free. Para um celular se conectar a um fone de ouvido sem fio, os dois dispositivos devem ser compatíveis com o perfil Hands-Free.

Você pode implementar a interface BluetoothProfile para escrever suas próprias classes e permitir um determinado perfil Bluetooth. A Android Bluetooth API oferece implementações para os seguintes perfis Bluetooth:

  • Headset. O perfil Headset permite usar fones de ouvido Bluetooth com celulares. O Android oferece a classe BluetoothHeadset, que é um proxy para controlar o Bluetooth Headset Service por meio de comunicação entre processos (IPC). Isso abrange os perfis Bluetooth Headset e Hands-Free (v1.5). A classe BluetoothHeadset permite comandos AT. Para ver uma discussão mais detalhada desse tópico, consulte Comandos AT específicos de fornecedores
  • A2DP. O perfil Advanced Audio Distribution Profile (A2DP) define como fazer streaming de áudio de alta qualidade de um dispositivo para outro usando uma conexão Bluetooth. O Android oferece a classe BluetoothA2dp, que é um proxy para controlar o Bluetooth A2DP Service por meio de IPC.
  • Health Device. O Android 4.0 (API de nível 14) introduz a compatibilidade com o perfil Health Device (HDP) do Bluetooth. Esse perfil permite criar aplicativos que usam o Bluetooth para comunicação com dispositivos de saúde compatíveis com Bluetooth, como monitores de frequência cardíaca, medidores de sangue, termômetros, balanças etc. Para obter uma lista de dispositivos compatíveis e seus códigos de especialização de dados de dispositivo correspondentes, consulte Bluetooth Assigned Numbers em www.bluetooth.org. Observe que esses valores também são referenciados na especificação ISO/IEEE 11073-20601 [7] como MDC_DEV_SPEC_PROFILE_* no Anexo de Códigos de Nomenclatura. Para ver uma discussão mais detalhada do HDP, consulte Perfil Health Device.

Veja a seguir as etapas básicas para trabalhar com um perfil:

  1. Obtenha o adaptador padrão conforme descrito em Configurar o Bluetooth.
  2. Use getProfileProxy() para estabelecer uma conexão ao objeto de proxy de perfil associado ao perfil. No exemplo abaixo, o objeto de proxy de perfil é uma instância de BluetoothHeadset.
  3. Configure um BluetoothProfile.ServiceListener. Esse ouvinte notifica os clientes IPC de BluetoothProfile quando são conectados ou desconectados do serviço.
  4. Em onServiceConnected(), obtenha um manipulador para o objeto de proxy de perfil.
  5. Após obter o objeto de proxy de perfil, você pode usá-lo para monitorar o estado da conexão e executar outras operações relevantes para esse perfil.

Por exemplo, este snippet de código mostra como conectar-se a um objeto de proxy de BluetoothHeadset para poder controlar o perfil Headset:

BluetoothHeadset mBluetoothHeadset;

// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);

private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = (BluetoothHeadset) proxy;
        }
    }
    public void onServiceDisconnected(int profile) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = null;
        }
    }
};

// ... call functions on mBluetoothHeadset

// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

Comandos AT específicos de fornecedores

A partir do Android 3.0, os aplicativos podem registrar-se para receber transmissões de sistema de comandos AT predefinidos específicos de fornecedor, enviados por fones de ouvido (como um comando +XEVENT da Plantronics). Por exemplo, um aplicativo pode receber transmissões que indicam o nível de bateria de um dispositivo conectado, bem como notificar o usuário ou executar outra ação necessária. Crie um receptor de transmissão para o intent ACTION_VENDOR_SPECIFIC_HEADSET_EVENT para processar comandos AT específicos de fornecedor para o fone de ouvido.

Perfil Health Device

O Android 4.0 (API de nível 14) introduz a compatibilidade com o perfil Health Device (HDP) do Bluetooth. Esse perfil permite criar aplicativos que usam o Bluetooth para comunicação com dispositivos de saúde compatíveis com Bluetooth, como monitores de frequência cardíaca, medidores de sangue, termômetros etc. A Bluetooth Health API contém as classes BluetoothHealth, BluetoothHealthCallback e BluetoothHealthAppConfiguration, descritas em Conceitos básicos.

A compreensão destes conceitos principais do HDP pode ajudar a usar a Bluetooth Health API:

Conceito Descrição
Origem Uma função definida no HDP. Origem é um dispositivo de saúde que transmite dados médicos (balança, medidor de glucose, termômetro etc.) para um dispositivo inteligente como um telefone ou tablet Android.
Coletor Uma função definida no HDP. No HDP, um coletor é o dispositivo inteligente que recebe dados médicos. Em aplicativos HDP do Android, o coletor é representado por um objeto BluetoothHealthAppConfiguration.
Registro É o registro de um coletor para um determinado dispositivo de saúde.
Conexão É a abertura de um canal entre um dispositivo de saúde e um dispositivo inteligente como um telefone ou tablet Android.

Criar um aplicativo HDP

Veja a seguir as etapas básicas envolvidas na criação de um aplicativo HDP do Android:

  1. Obtenha uma referência ao objeto de proxy de BluetoothHealth.

    Da mesma forma que nos dispositivos com perfil Headset e A2DP, você deve chamar getProfileProxy() com um BluetoothProfile.ServiceListener e o tipo de perfil HEALTH para estabelecer uma conexão com o objeto de proxy de perfil.

  2. Crie um BluetoothHealthCallback e registre uma configuração de aplicativo (BluetoothHealthAppConfiguration) que atue como um coletor de saúde.
  3. Estabeleça uma conexão a um dispositivo de saúde. Alguns dispositivos iniciam a conexão. Não é necessário executar esta etapa para esses dispositivos.
  4. Quando conectado a um dispositivo de saúde, leia/grave dados nele usando o descritor de arquivo.

    Os dados recebidos precisam ser interpretados usando um gerenciador de saúde que implemente as especificações IEEE 11073-xxxxx.

  5. Quando terminar, feche o canal de saúde de cancele o registro do aplicativo. O canal também fechará quando houver um longo período de inatividade.

Para obter um exemplo de código completo que ilustra essas etapas, consulte HDP (perfil Health Device) do Bluetooth.