【Android】altbeacon ライブラリを使ってフォアグラウンドで Beacon の領域監視を行う

## 概要
今回は前に書いた記事の続きで、フォアグラウンドで Beacon を検知してみたいと思います。
フォアグラウンドで検知処理を行うことで、アプリをキルした状態でも Beacon の領域監視を行うことができます。
ライブラリは altbeacon という Bluetooth ライブラリを使用します。

altbeacon ライブラリを使った Beacon 検知についての詳しい説明については前回の記事で解説させていただきましたので、使い方をご存知でない方は[こちらの記事](https://qiita.com/kenmaeda51415/items/ac5a2d5a15783bbe9192)を参考にしていただけますと幸いです。

## 環境
Android Studio：2021.1.1 Patch 3
kotlin：1.6.20
targetSdkVersion：32
minSdkVersion：27

## 準備
まずは実装の準備として以下のライブラリを gradle(appレベル) に追加します。

``` build.gradle
implementation 'org.altbeacon:android-beacon-library:2.19.4'
```

次に Manifest ファイルに以下の権限を追加します。
Bluetooth 関連のほか、フォアグラウンドにおけるサービスの実行を許可する必要があります。( Bluetooth 関連の権限呼び出し方法は割愛します)

``` AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

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

<uses-feature
  android:name="android.hardware.bluetooth_le"
  android:required="true" />
```
次に Beacon 検知を開始する Activity に```RangeNotifier```, ```MonitorNotifier```インターフェースを引き継ぎます。

``` MainActivity.kt
class MainActivity : AppCompatActivity(), RangeNotifier, MonitorNotifier {
```

その後各種メソッドをオーバーライドします。

``` MainActivity.kt
override fun didEnterRegion(region: Region?) {
    //領域への入場を検知
    Log.d("iBeacon", "Enter Region ${region?.uniqueId}")
}

override fun didExitRegion(region: Region?) {
    //領域からの退場を検知
    Log.d("iBeacon", "Exit Region ${region?.uniqueId}")
}

override fun didRangeBeaconsInRegion(beacons: MutableCollection<Beacon>?, region: Region?) {
    // 検知したBeaconの情報
    Log.d("MainActivity", "beacons.size ${beacons?.size}")
    beacons?.let {
        for (beacon in beacons) {
            Log.d("MainActivity", "UUID: ${beacon.id1}, major: ${beacon.id2}, minor: ${beacon.id3}, RSSI: ${beacon.rssi}, TxPower: ${beacon.txPower}, Distance: ${beacon.distance}")
        }
    }
}

override fun didDetermineStateForRegion(state: Int, region: Region?) {
    //領域への入退場のステータス変化を検知（INSIDE: 1, OUTSIDE: 0）
    Log.d("MainActivity", "Determine State: $state")
}
```

ここまで出来たら監視する領域を定義する ```Region``` と、Beacon を扱うための行うための ```BeaconManager``` を作成します。

``` MainActivity.kt
private lateinit var mRegion: Region
private lateinit var beaconManager: BeaconManager
private val IBEACON_FORMAT = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"

--- 省略 ---

mRegion = Region("iBeacon", Identifier.parse(uuid), null, null)
beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.beaconParsers.add(BeaconParser().setBeaconLayout(IBEACON_FORMAT)) // iBeaconのフォーマット指定
```

## 実装
ここからフォアグラウンドにおける領域監視の実装を行います。
通常フォアグラウンドで何らかの処理を行う際はフォアグラウンドサービスを作成する必要がありますが、altbeacon ライブラリのメソッドにはフォアグラウンドサービスを開始する処理が含まれているため、自分で作成する必要はありません。

フォアグラウンドで領域監視を始めるコードは以下になります。
``` MainActivity.kt
if (!beaconManager.isAnyConsumerBound) {
    // 通知を作成
    val channelId = "0"
    val channel = NotificationChannel(channelId, "Beacon service", NotificationManager.IMPORTANCE_HIGH)
    val manager =  getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    manager.createNotificationChannel(channel)

    val builder = Notification.Builder(this, channelId)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("Beacon検知中")
        .setContentText("領域監視を実行しています。")
    
    // PendingIntentを作成
    val intent = Intent(this, MainActivity::class.java)
    val pendingIntent = PendingIntent.getActivity(
        this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
    builder.setContentIntent(pendingIntent) 
   
    // フォアグラウンドサービスを許可   
    beaconManager.enableForegroundServiceScanning(builder.build(), 0)
    beaconManager.setEnableScheduledScanJobs(false)
    beaconManager.foregroundBetweenScanPeriod = 0
    beaconManager.foregroundScanPeriod = 1100
    
    // 領域監視開始
    beaconManager.addMonitorNotifier(this)
    beaconManager.addRangeNotifier(this)
    beaconManager.startMonitoring(mRegion)
    beaconManager.startRangingBeacons(mRegion)
}
```

まず、１行目の ```if``` 文でフォアグラウンドでの検知が開始済みだった場合にアプリが落ちるのを防ぎます。
次にフォアグラウンドで処理を行っていることをユーザーに認識させるために通知センターに表示する通知を作成します。
以下は上記のコードのうち通知に関する部分を抜粋したものです。

``` MainActivity.kt
// 通知を作成
val channelId = "0"
val channel = NotificationChannel(channelId, "Beacon service", NotificationManager.IMPORTANCE_HIGH)
val manager =  getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)

val builder = Notification.Builder(this, channelId)
    .setSmallIcon(R.mipmap.ic_launcher)
    .setContentTitle("Beacon検知中")
    .setContentText("領域監視を実行しています。")

// PendingIntentを作成
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
    this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
builder.setContentIntent(pendingIntent) 
```
通知を作成後に ```PendingIntent``` を作成し、ユーザーが通知をタップした時に ```MainActivity``` を呼び出せるようにしています。
なお、通知を作成する際は ```setSmallIcon()``` メソッドでアイコンを指定する必要があります。


そして作成した通知を ```enableForegroundServiceScanning()``` メソッドの引数に渡します。
``` MainActivity.kt
// フォアグラウンドサービスを許可 
beaconManager.enableForegroundServiceScanning(builder.build(), 0)
beaconManager.setEnableScheduledScanJobs(false)
```
ちなみに、
``` MainActivity.kt
beaconManager.foregroundBetweenScanPeriod = 0
beaconManager.foregroundScanPeriod = 1100
```
ではフォアグラウンドでのスキャン時間およびスキャンとスキャンの間隔を指定しています。(デフォルト値と同じ値を設定しています。)

ここまで書けたら以下の領域監視を開始するコードを書いてください。（これより前に書くとエラーになります）
``` MainActivity.kt
// 領域監視開始
beaconManager.addMonitorNotifier(this)
beaconManager.addRangeNotifier(this)
beaconManager.startMonitoring(mRegion)
beaconManager.startRangingBeacons(mRegion)
```

なお、フォアグラウンドでの領域監視を停止するときですが、停止したい箇所に以下のコードを記述してください。

``` MainActivity.kt
beaconManager.stopMonitoring(mRegion)
beaconManager.stopRangingBeacons(mRegion)
```


## まとめ
今回はフォアグラウンドでの領域監視を行う方法について説明しました。
自分でフォアグラウンドサービスを立ち上げる必要がなく、手軽に実装できるのでおすすめです。
ご興味のある方はぜひ参考にしていただければと思います。

## 参考
[Android Beacon Library](https://altbeacon.github.io/android-beacon-library/foreground-service.html)