LoginSignup
15
17

More than 3 years have passed since last update.

【Android】プログラム的にBluetoothのペアリングを行う方法【Bluetooth】

Last updated at Posted at 2020-02-01

目的

ペアリング時にPINコードの入力が必要となる場合において、プログラム的に (ユーザによる手入力なしで) Bluetoothのペアリングを行う方法をここにメモしたいと思います。

概要

大まかに書くと以下の手順を行うこととなります。

  1. NFCやUSBなどを経由して、周辺機器のBDアドレス (MACアドレス) とPINコードを受信する。
  2. BDアドレスより、その周辺機器の BluetoothDevice を生成する。
  3. BluetoothDevice.ACTION_PAIRING_REQUEST を受信するBroadcastReceiverを登録する。
  4. BluetoothDevice#createBond() によってペアリング要求を行う。
  5. BluetoothDevice#setPin() によってPINコードを入力する。

手順

それでは各手順ごとに説明していきます。

1. 周辺機器のBDアドレスとPINコードを受信

まず最初にペアリングを行いたい周辺機器のPINコード (とBDアドレス) を手に入れるところから始めます。
これを行う手段に決まりはないのですが、大抵はNFC経由で行われると思います。
この手順に関してはここでは詳しくは触れません。

(もしくは機器によってはPINコード入力を促されるものの、コードが固定の値 (00001234) である場合があるかもしれません。)

2. 周辺機器のBluetoothDeviceを取得

使用しているライブラリによって手順は異なるとは思いますが、先程の手順で取得したBDアドレス (MACアドレス) を元に BluetoothDevice のインスタンスを取得します。

Androidの標準ライブラリを使っている場合は
BluetoothAdapter#getRemoteDevice(address: String): BluetoothDevice
を使えばいいかと思います。

RxAndroidBleを使っている場合は
RxBleClient#getBleDevice(macAddress: String): RxBleDevice
RxBleDevice#getBluetoothDevice(): BluetoothDevice
で取得できます。

また、周辺機器のBDアドレス (MACアドレス) がない場合は、スキャンを掛けるなりして BluetoothDevice を取得してください。

3. BroadcastReceiverの登録

Bluetoothのペアリング要求が行われた際に BluetoothDevice.ACTION_PAIRING_REQUEST のメッセージを飛んで来るので、これを購読するBroadcastReceiverを登録します。

どのように実装してもいいのですが、私は以下のようにしました。

package net.aridai.pairingapp

import android.bluetooth.BluetoothDevice
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.polidea.rxandroidble2.RxBleClient
import com.polidea.rxandroidble2.RxBleDevice
import kotlinx.android.synthetic.main.main_activity.*
import org.koin.android.ext.android.inject

//  Activityのコードです。
//  一部省略している部分があります。
class MainActivity : AppCompatActivity() {

    //  私はRxAndroidBleを使っています。
    private val bleClient: RxBleClient by inject()

    //  IntentFilterの設定です。
    //  優先度を最大の999 (SYSTEM_HIGH_PRIORITY - 1) にしています。
    private val pairingRequestIntentFilter: IntentFilter by lazy {
        IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST).also {
            it.priority = IntentFilter.SYSTEM_HIGH_PRIORITY - 1
        }
    }

    //  BroadcastReceiverです。
    //  「PairingBroadcastReceiver」は私が定義したクラスです。
    private var pairingBroadcastReceiver: PairingBroadcastReceiver? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        //  省略...
    }

    override fun onResume() {
        super.onResume()

        //  ここでBroadcastReceiverの登録を行っています。
        this.pairingBroadcastReceiver = PairingBroadcastReceiver()
        this.registerReceiver(this.pairingBroadcastReceiver, this.pairingRequestIntentFilter)
    }

    override fun onPause() {
        super.onPause()

        //  ここでBroadcastReceiverの登録解除を行っています。
        this.unregisterReceiver(this.pairingBroadcastReceiver)
        this.pairingBroadcastReceiver = null
    }

    //  ペアリングを行います。
    private fun pair(address: String, pin: ByteArray) {
        //  私はRxAndroidBleを使っているのでこのように書きます。
        val bluetoothDevice = this.bleClient.getBleDevice(address).bluetoothDevice
        bluetoothDevice.createBond()

        //  Android標準ライブラリで行う場合は BluetoothAdapter#getRemoteDevice(address) を使うといいかと思います。
    }
}

4. ペアリングの要求

上記のコードの以下の部分です。

BluetoothDevice を取得して createBond() を呼んでいます。

//  RxAndroidBleを使っている場合
private fun pair(address: String, pin: ByteArray) {
    this.pairingBroadcastReceiver!!.pin = pin
    val bluetoothDevice = this.bleClient.getBleDevice(address).bluetoothDevice
    bluetoothDevice.createBond()
}
//  Android標準でやる場合
//  (this.adapter: BluetoothAdapter)
private fun pair(address: String, pin: ByteArray) {
    this.pairingBroadcastReceiver!!.pin = pin
    val bluetoothDevice = this.adapter.getRemoteDevice(address)
    bluetoothDevice.createBond()
}

5. PINコードの入力

正常にペアリング要求がされた場合、BroadcastReceiverがメッセージを受信しますので、そこでPINコードをプログラム的に入力します。

package net.aridai.pairingapp

import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent

class PairingBroadcastReceiver : BroadcastReceiver() {

    //  PINコードの受け渡し口
    //  (サンプルコードなので雑でも気にしないでね♡)
    var pin: ByteArray? = null

    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent == null) return

        if (intent.action == BluetoothDevice.ACTION_PAIRING_REQUEST) {
            val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)!!
            val pin = this.pin!!

            device.setPin(pin)

            //  他のBroadcastReceiverにメッセージが飛んでいかなないようにする。
            //  これを呼ばないとシステムのペアリング要求通知が出てきてしまう。
            this.abortBroadcast()
        }
    }
}

コード中で BroadcastReceiver#abortBroadcast() を呼んでいますが、これを呼ばないとAndroidのシステムの通知が出てきてしまいます。

ペアリング通知.png

また、PINコードの受け渡しに PairingBroadcastReceiver#pin をActivity側からセットさせてやっていますが、実際にはもっとちゃんとやりましょう。

//  ※ 例えばの実装例です。
//  こんなインタフェースを切って、
interface PairingPinProvider {
    fun providePin(address: String): ByteArray?
}

//  BroadcastReceiver購読元に実装させて、
class MainActivity : AppCompatActivity(), PairingPinProvider {
    //  いろいろと省略...

    override fun providePin(address: String): ByteArray? {
        //  ちゃんと実装
    }
}

//  特定のActivityじゃなくてインタフェースに依存させる。
override fun onReceive(context: Context?, intent: Intent?) {
    if (context !is PairingPinProvider || intent == null) return
    //  省略...
    val pin = context.providePin(device.address) ?: throw IllegalStateException("ペアリング要求したくせにPINねぇじゃんww")
}

その他

ペアリング (ボンディング) の状態が変更されたことを知るには BluetoothDevice.ACTION_BOND_STATE_CHANGED のBroadcastReceiverを使うといいでしょう。

package net.aridai.pairingapp

import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent

class BondStateChangedBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (context !is BondStateChangedListener || intent == null) return

        if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
            val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)!!
            val prevBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
            val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)

            context.onBondStateChanged(device, prevBondState, bondState)
        }
    }

    interface BondStateChangedListener {
        fun onBondStateChanged(device: BluetoothDevice, prevState: Int, currentState: Int)
    }
}

また AndroidManifest.xml にちゃんとパーミッションを書きましょう。

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

    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

参考

15
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
17