【Kotlin】省エネbluetooth規格 BLE通信機器情報を取得

#はじめに
bluetoothの中でも省エネタイプのBLE(Bluetooth Low Energy)を使用した通信機器が増えています。ここではBLE通信が行われている機器数や識別IDの取得方法を記載していきます。

#準備
他のサンプルとは異なり準備が意外と大変です。

マニフェストファイルに下記の5行を追加します。

```Manifest.xml
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
```

上から順にbluetoothを使用するためのパーミッション、管理者権限で使用するパーミッション。3行目4行目はOSの違いでどちらか片方しか必要ないですが合わせて位置情報を取得するパーミッションが必要であることを示します。

最後の行はbluetoothの中でもBLEしか使いませんよという意味だそうです。
さらにユーザーへの使用許可も複数必要です。

**requestPermission**でユーザーに確認を要求するわけですが今回は
Manifest.permission.BLUETOOTH
Manifest.permission.BLUETOOTH_ADMIN
Manifest.permission.ACCESS_FINE_LOCATION
の3つが必要となるため

```permission.kt
val permissions = arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION)
```

このように配列で定義しておいて**ActivityCompat.requestPermissions**を使用すると便利です。
パーミッションが取得出来たか確認する方法は複数形がないようなので

```permission.kt
    for (permission in permissions){
        if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) return false
    }
    return true
```

このようにループで確認します。

#確認の開始と停止

開始方法はAndroid6より前とそれ以降で異なります。Android8からはさらに別の方法も使えるのですが、ここではAndroid6以降の方法を使用します。

開始は

```start.kt
    val adapter = BluetoothAdapter.getDefaultAdapter()
    if (adapter == null) return
    val scanner = adapter!!.bluetoothLeScanner
    if (scanner == null) return
    scanner!!.startScan(scanFilterList,scanSettings,mScanCallback)
```

停止は

```stop.kt
    val adapter = BluetoothAdapter.getDefaultAdapter()
    if (adapter == null) return
    val scanner = adapter!!.bluetoothLeScanner
    if (scanner == null) return
    scanner!!.stopScan(mScanCallback)
```

コールバックは

```callback.kt
    mScanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            super.onScanResult(callbackType, result)
            Log.d("scanResult ", "${result.device.address} ${result.device.name}")
        }
        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            Log.d("ScanFailed",errCode.toString())
        }
    }
```

正常に取得出来たら**onScanResult**が、何か異常が発生したら**onScanFailed**が呼ばれます。

#注意
下記ソースで処理開始時に**start()**終了時に**stop()**を呼びますがstartを実行しても取得出来ない場合があります。またstartしたからといって常にスキャンが続くというわけではなく一定時間でスキャンは終了するようです。
そのため実際にプログラムにするときは**結果が一定時間返ってこない**事を判断してstop()からのstart()処理を行う必要があります。

#その他
下記ソースはそのまま使うとBLE機能を持つ様々な機器が反応を返します。
反応があった機器からは**result.device.address**に機器固有のIDが返ってきますがペアリングなどを行わない限りこれ以上の情報は取得出来ません。

そのBLE機器が何かを示す**UUID**を指定したい場合は**scanFilterAdd**にUUID "xxxxxxxx-9999-9999-9999-xxxxxxxxxxxx"形式の文字列を渡すと、その機器のみが結果を返すようになります。

#ソース
**isPermission**でパーミッションが取得出来ていない事を判断したら**requestPermission**でパーミッション取得処理を行います。
ユーザーによる使用許可が得られると**onRequestPermissionsResult**が処理されますのでその中で判断と開始処理を書けばスキャンが始まります。

````sample.kt
    override fun onRequestPermissionsResult(requestCode : Int,permissions : Array<String>,grantResults: IntArray){
        if (requestCode == bluetooth.requestCode && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            if (bluetooth.isPermission()) {
                bluetooth.start()
            }
        }
    }
````

```MainActivity.kt
class BluetoothLeScanner(private val activity : Activity){
    var requestCode : Int = 1000
    private var enable : Boolean = false
    private var adapter : BluetoothAdapter?  = null
    private var scanner : android.bluetooth.le.BluetoothLeScanner? = null
    private lateinit var mScanCallback : ScanCallback
    private val permissions = arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION)
    private var scanFilter: ScanFilter? = null
    private var scanFilterList: ArrayList<ScanFilter> = ArrayList()

    fun start(){
        if (enable) return
        val scanSettings: ScanSettings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
            .build()

        mScanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                super.onScanResult(callbackType, result)
                Log.d("scanResult ", "${result.device.address} ${result.device.name}")
            }
            override fun onScanFailed(errorCode: Int) {
                super.onScanFailed(errorCode)
            }
        }
        adapter = BluetoothAdapter.getDefaultAdapter()
        if (adapter == null) return
        scanner = adapter!!.bluetoothLeScanner
        if (scanner == null) return
        scanner!!.startScan(scanFilterList,scanSettings,mScanCallback)
        enable = true
    }

    fun stop(){
        if (!enable) return
        if (scanner == null) return
        scanner!!.stopScan(mScanCallback)
        enable = false
    }

    fun scanFilterAdd(uuid : String){
        scanFilter  = ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid)).build()
        scanFilterList.add(scanFilter!!)
    }

    fun scanFilterClr(){
        scanFilterList = ArrayList()
    }

    fun requestPermission(){
        ActivityCompat.requestPermissions(activity, permissions, requestCode)
    }

    fun isPermission() : Boolean{
        for (permission in permissions){
            if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) return false
        }
        return true
    }
}
```