はじめに
Android 4.3 から、BLE (Bluetooth Low Energy) 用の API が提供されるようになりました。その後、Android 5.0 で android.bluetooth.le
というパッケージが追加され、BLE 用の API が再定義されました。しかし、輪をかけて酷くなりました。
しばらく BLE の仕事を離れることになりそうなので (そして恐らく戻ってこないので)、Android BLE API に対して言いたいことを忘れないうちに書いておこうと思います。なお、主に BLE アドバータイズメントパケットのペイロードの話になります。BLE API 全体については扱いません。
1. ペイロードのフォーマット
BLE アドバータイズメントパケットのペイロード部のフォーマットが BLE の仕様でどのように定義されているかを知れば、あるべき API の姿が想像できます。そこで、まずはその説明をします。具体的には、LeScanCallback インターフェースで定義されている onLeScan メソッドの三番目の引数である byte[] scanRecord
の中身がどのようなフォーマットになっているかの説明をします。
1.1. ペイロードの構造
BLE アドバータイズメントパケットのペイロード部は、AD ストラクチャーというもののリストとなっています。個々の AD ストラクチャーは可変長です。下図は三つの AD ストラクチャーを含むペイロードを表しています。
1.2. AD ストラクチャーの構造
AD ストラクチャーは、1 バイト目に「AD ストラクチャー全体の長さ - 1」、2 バイト目に AD ストラクチャーのタイプ (AD タイプ)、3 バイト目以降はデータ (AD データ) となっています。データの長さとフォーマットは、2 バイト目の値が示す AD タイプによって変化します。次の表は AD ストラクチャーの構造を示しています。
位置 | 大分類 |
---|---|
1 バイト目 | 長さ |
2 バイト目 | AD タイプ |
3 バイト目以降 | AD データ |
例えば、AD タイプが 0x01
(Flags) である AD ストラクチャーは、データ部の長さは 1 バイトになるので (ただし仕様拡張により今後変化する可能性はあります)、全体としては 3 バイトになります。次の表は AD タイプが 0x01
のときの AD ストラクチャーの構造を示しています。
位置 | 大分類 | 中分類 | 値 |
---|---|---|---|
1 バイト目 | 長さ | 0x02 |
|
2 バイト目 | AD タイプ | Flags | 0x01 |
3 バイト目 | AD データ | フラグ群 | 0x?? |
なお、AD ストラクチャーの構造については、「Bluetooth Core Specification 4.2」の「11 ADVERTISING AND SCAN RESPONSE DATA FORMAT」で次のように説明されています。
Each AD structure shall have a Length field of one octet, which contains the Length value, and a Data field of Length octets. The first octet of the Data field contains the AD type field. The content of the remaining Length - 1 octet in the Data field depends on the value of the AD type field and is called the AD data.
1.3. AD タイプの種類
定義済みの AD タイプの種類は、「Generic Access Profile」ページにリストアップされています。下表は、当該ページ内の表から「Reference for Definition」欄を取り除いて見やすくしたものです。
値 | データタイプ名 |
---|---|
0x01 |
Flags |
0x02 |
Incomplete List of 16-bit Service Class UUIDs |
0x03 |
Complete List of 16-bit Service Class UUIDs |
0x04 |
Incomplete List of 32-bit Service Class UUIDs |
0x05 |
Complete List of 32-bit Service Class UUIDs |
0x06 |
Incomplete List of 128-bit Service Class UUIDs |
0x07 |
Complete List of 128-bit Service Class UUIDs |
0x08 |
Shortened Local Name |
0x09 |
Complete Local Name |
0x0A |
Tx Power Level |
0x0D |
Class of Device |
0x0E |
Simple Pairing Hash C |
0x0E |
Simple Pairing Hash C-192 |
0x0F |
Simple Pairing Randomizer R |
0x0F |
Simple Pairing Randomizer R-192 |
0x10 |
Device ID |
0x10 |
Security Manager TK Value |
0x11 |
Security Manager Out of Band Flags |
0x12 |
Slave Connection Interval Range |
0x14 |
List of 16-bit Service Solicitation UUIDs |
0x1F |
List of 32-bit Service Solicitation UUIDs |
0x15 |
List of 128-bit Service Solicitation UUIDs |
0x16 |
Service Data |
0x16 |
Service Data - 16-bit UUID |
0x20 |
Service Data - 32-bit UUID |
0x21 |
Service Data - 128-bit UUID |
0x22 |
LE Secure Connections Confirmation Value |
0x23 |
LE Secure Connections Random Value |
0x24 |
URI |
0x25 |
Indoor Positioning |
0x26 |
Transport Discovery Data |
0x17 |
Public Target Address |
0x18 |
Random Target Address |
0x19 |
Appearance |
0x1A |
Advertising Interval |
0x1B |
LE Bluetooth Device Address |
0x1C |
LE Role |
0x1D |
Simple Pairing Hash C-256 |
0x1E |
Simple Pairing Randomizer R-256 |
0x3D |
3D Information Data |
0xFF |
Manufacturer Specific Data |
1.4. 製造者固有データ
AD タイプ一覧表の末尾に、値が 0xFF
、データタイプ名が「Manufacturer Specific Data」(製造者固有データ) というエントリーがあります。これは、AD データのフォーマットを自由に拡張できるようにするためのものです。
AD タイプが 0xFF
の場合、AD データ部の先頭の 2 バイトには、企業を識別するための番号がリトルエンディアンで格納されます。AD データ部の 3 バイト目以降の内容は、その企業が自由に定義することができます。次の表は、AD タイプが 0xFF
のときの AD ストラクチャーの構造を示しています。
位置 | 大分類 | 中分類 |
---|---|---|
1 バイト目 | 長さ | |
2 バイト目 | AD タイプ | 製造者固有 |
3 ~ 4 バイト目 | AD データ | 企業 ID |
5 バイト目以降 | AD データ | 企業固有データ |
例えば、Apple 社が独自にデータ構造を定義する場合、Apple 社の企業 ID は 0x004C
ですので、AD ストラクチャーは次のようになります。企業 ID はリトルエンディアンで格納されるので注意してください。
位置 | 大分類 | 中分類 | 小分類 | 値 |
---|---|---|---|---|
1 バイト目 | 長さ | ? | ||
2 バイト目 | AD タイプ | 製造者固有 | 0xFF |
|
3 バイト目 | AD データ | 企業 ID | Apple 社 | 0x4C |
4 バイト目 | AD データ | 企業 ID | Apple 社 | 0x00 |
5 バイト目以降 | AD データ | 企業固有データ | Apple 社固有データ | ? |
1.5. 企業 ID
登録済み企業 ID は「Company Identifiers」ページにリストされています。Apple 社の企業 ID が 0x004C
であることも、このページを見ると分かります。
1.6. iBeacon
製造者固有データの一例として、iBeacon を紹介します。
iBeacon は、Apple 社が定めた独自フォーマットです。AD データ部は、先頭 2 バイトに Apple 社の企業 ID、次の 2 バイトが 0x02
と 0x015
で固定、以降、16 バイトの Proximity UUID、2 バイトのメジャー番号、2 バイトのマイナー番号、1 バイトの送信出力が続きます。Proximity UUID、メジャー番号、マイナー番号はビッグエンディアンで格納されます。
iBeacon フォーマットを図にすると次のようになります。
表形式で表現すると次のようになります。
位置 | 大分類 | 中分類 | 小分類 | 値 |
---|---|---|---|---|
1 バイト目 | 長さ | 26 | ||
2 バイト目 | AD タイプ | 製造者固有 | 0xFF |
|
3 バイト目 | AD データ | 企業 ID | Apple 社 | 0x4C |
4 バイト目 | AD データ | 企業 ID | Apple 社 | 0x00 |
5 バイト目 | AD データ | 企業固有データ | 固定値 | 0x02 |
6 バイト目 | AD データ | 企業固有データ | 固定値 | 0x15 |
7 ~ 22 バイト目 | AD データ | 企業固有データ | Proximity UUID | ? |
23 ~ 24 バイト目 | AD データ | 企業固有データ | メジャー番号 | ? |
25 ~ 26 バイト目 | AD データ | 企業固有データ | マイナー番号 | ? |
27 バイト目 | AD データ | 企業固有データ | 送信出力 | ? |
「iBeacon パケット」という言葉を耳にすることがあると思いますが、技術的に正確に表現すると、「iBeacon フォーマットの AD ストラクチャーを含むペイロードを持つ BLE アドバータイズメントパケット」となります。
1.7. Eddystone
iBeacon を紹介したので、Eddystone も紹介します。
Eddystone は Google 社が旗振り役なので、Google 社の企業 ID を持つ製造者固有データとして定義されていると思われるかもしれませんが、実はそうではなく、サービスデータの一種として定義されています。
サービスデータを表す AD タイプは複数あるのですが、Eddystone は 0x16
を使用します。この AD タイプの場合、AD データ部の先頭に置くサービス識別子が 2 バイトで表現されます。ちなみに、AD タイプ 0x20
と 0x21
もサービスデータを意味しますが、サービス識別子をそれぞれ 4 バイト、16 バイトで表現する点が異なります。
次の表は、AD タイプが 0x16
で、サービス識別番号に Eddystone を表す 0xFEAA
がセットされている AD ストラクチャーの構造を示しています。サービス識別番号はリトルエンディアンで格納されます。
位置 | 大分類 | 中分類 | 値 |
---|---|---|---|
1 バイト目 | 長さ | ? | |
2 バイト目 | AD タイプ | サービスデータ | 0x16 |
3 バイト目 | AD データ | サービス識別番号 | 0xAA |
4 バイト目 | AD データ | サービス識別番号 | 0xFE |
5 バイト目以降 | AD データ | サービス固有データ | ? |
5 バイト目以降のサービス固有データ部は、Eddystone 独自のフォーマットとなります。
まず、サービス固有データ部の 1 バイト目の上位 4 ビットで Eddystone のフレーム種別を指定します。Eddystone には次の三つのフレーム種別があり、その種別をこの 4 ビットで指定します。
サービス固有データ部の 2 バイト目以降のデータフォーマットは、フレーム種別毎に異なります。Eddystone の仕様は GitHub の google/eddystone で公開されているので、ここで詳細に解説はしませんが、図にまとめておきます。
1.8. ucode
iBeacon と Eddystone を知っておけば十分ですが、せっかく日本の組込業界の T-Engine フォーラムが定義した「Bluetooth LE ucode マーカーパケット仕様」というものがあるので、紹介しておきます。
この仕様は、T-Engine フォーラムが定義したグローバル ID である「ucode」の値を BLE アドバータイズメントパケットに埋め込むフォーマットを、製造者固有データの一種として定義しています。ITU-T H.642 としても採択されています。詳細は仕様書を参照してください。
2. 理想と現実
2.1. 理想
ここまで、BLE アドバータイズメントパケットのペイロード部のフォーマットについて見てきました。得られた知識をもとに、あるべき API の姿を考えてみます。
ペイロード部の値がバイト配列として与えられたとき、それをパースするメソッドは、結果として何を返せばよいでしょうか? ペイロード部は AD ストラクチャーのリストなので、それをそのまま表現する結果を返せばよいでしょう。例えば、Java で表現するとしたら、次のような形です。
// BLE アドバータイズメントパケットのペイロード部
byte[] payload = ...;
// ペイロードをパースして、AD ストラクチャーのリストとして受け取る。
List<ADStructure> list = parse(payload);
個々の AD ストラクチャーは、実際には、Flags を表しているかもしれませんし、iBeacon かもしれませんし、Eddystone URL かもしれません。この関係は、継承ツリーとして表現するのが自然でしょう。例えば、ここまでに紹介した、AD ストラクチャー、Flags、iBeacon、Eddystone、ucode の関係は、次のような継承ツリーを構成するのが望ましいと思われます。
このような継承ツリーが API でも表現されていれば、例えば Eddystone URL の情報を表示するプログラムは次のように書けるでしょう (階層構造を示すために、わざと if
を深くネストさせて書いています)。
// AD ストラクチャー
ADStructure ads = ...;
// どの AD ストラクチャーにも、長さとタイプがある。
System.out.println("長さ = " + ads.getLength());
System.out.println("タイプ = " + ads.getType());
// もしもサービスデータであれば、
if (ads instanceof ServiceData)
{
// サービスデータとして扱うことができる。
ServiceData sd = (ServiceData)ads;
// どのサービスデータにも、サービスを一意に特定するサービス UUID がある。
System.out.println("サービス UUID = " + sd.getServiceUUID());
// サービスデータの中でも、特に Eddystone であれば、
if (sd instanceof Eddystone)
{
// Eddystone データとして扱うことができる。
Eddystone es = (Eddystone)sd;
// どの Eddystone データにも、フレーム種別がある。
System.out.println("フレーム種別 = " + es.getFrameType());
// Eddystone URL であれば、
if (es instanceof EddystoneURL)
{
// Eddystone URL として扱うことができる。
EddystoneURL esurl = (EddystoneURL)es;
// Eddystone URL には、送信出力と URL の情報が含まれている。
System.out.println("送信出力 = " + esurl.getTxPower());
System.out.println("URL = " + esurl.getURL());
}
}
}
さらに、独自のデータ構造を定義し、それをペイロードをパースした結果のリストに含ませることを可能とするため、何かしら独自パーサーを定義して登録できるとよいでしょう。例えば、AD ストラクチャーの構成要素である「(1) 長さ、(2) AD タイプ、(3) AD データ」から AD ストラクチャーのサブクラスのインスタンスを生成して返すメソッドを持つインターフェースを次のように定義し、
public interface ADStructureBuilder
{
/**
* AD ストラクチャーを構築する。
*
* @param length
* AD ストラクチャーの長さ。 AD ストラクチャーの
* バイト配列の一番目のバイトの値。
*
* @param type
* AD タイプ。 AD ストラクチャーのバイト配列の
* 二番目のバイトの値。
*
* @param data
* AD データ。 AD ストラクチャーのバイト配列の
* 三番目以降のバイト群。
*
* @return
* ADStructure もしくはそのサブクラスのインスタンス。
* インスタンスを生成できないときは null を返す。
*/
ADStructure build(int length, int type, byte[] data);
}
このインターフェースを実装したクラスのインスタンスを何らかの方法で登録できるようなっていればよいでしょう。
2.2. 現実
さて、現実の世界ではどのような API となっているのでしょうか?
API リファレンスには、「BluetoothAdapter
クラスの startLeScan(LeScanCallback)
メソッドは API level 21 (Android 5.0) で deprecated 扱いとなったので、かわりに [startScan(List, ScanSettings, ScanCallback)](http://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#startScan(java.util.List, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback))
メソッドを使うように」と書いてあります。この新しいメソッドの三番目の引数は [ScanCallback](http://developer.android.com/reference/android/bluetooth/le/ScanCallback.html)
クラスのインスタンスです。クラスの名称から、アドバータイズメントパケットを受信したときに呼び出されるコールバックだということが分かります。Android 4.X から Android 5.X にバージョンアップしたときに、コールバックが [LeScanCallback](http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.LeScanCallback.html)
から [ScanCallback](http://developer.android.com/reference/android/bluetooth/le/ScanCallback.html)
に変更になったようです。
この ScanCallback
クラスには次のような三つのメソッドが定義されています。
void onBatchScanResults(List<ScanResult> results) { ... }
void onScanFailed(int errorCode) { ... }
void onScanResult(int callbackType, ScanResult result) { ... }
メソッド名が on
で始まることから、何らかのイベントを受け取るためにはこれらのメソッドをオーバーライドすればいいだろうということが推測できます。ただ、これら以外のメソッドが定義されていないことから、「なぜインターフェースではなくクラスとして定義したのか」という疑問は当然湧いてきて、早くも怪しげですが、次に進みます。
ScanCallback
クラスに定義されている onBatchScanResults
メソッドも [onScanResult](http://developer.android.com/reference/android/bluetooth/le/ScanCallback.html#onScanResult(int, android.bluetooth.le.ScanResult))
メソッドも、データ構造として ScanResult
クラスのインスタンスを受け取ることが分かります。そこで、この ScanResult
クラスの定義を見ると、幾つかメソッドがありますが、アドバータイズメントパケットをパースした結果は、getScanRecord()
メソッドにより ScanRecord
クラスのインスタンスとして取り出すことができることが分かります。
ScanRecord getScanRecord() { ... }
ではここで、ScanRecord
クラスのメソッド一覧を紹介します。
戻り値 | メソッド |
---|---|
int |
getAdvertiseFlags() |
byte[] |
getBytes() |
String |
getDeviceName() |
SparseArray<byte[]> |
getManufacturerSpecificData() |
byte[] |
getManufacturerSpecificData(int) |
byte[] |
getServiceData(ParcelUuid) |
Map<ParcelUuid,byte[]> |
getServiceData() |
List<ParcelUuid> |
getServiceUuids() |
int |
getTxPowerLevel() |
String |
toString() |
アドバータイズメントパケットのペイロードのフォーマットを知っていれば、これらのメソッドがそれぞれ何の情報を返そうというしているのかが分かります。getBytes()
メソッドと toString()
メソッドを除く他のメソッドは、ある特定の AD タイプを持つ AD ストラクチャーのデータを返そうとしているのです。表にまとめると次のようになります。
メソッド | AD タイプ |
---|---|
getAdvertiseFlags |
0x01 |
getDeviceName |
0x08 , 0x09
|
getManufacturerSpecificData |
0xFF |
getServiceData |
0x16 , 0x20 , 0x21
|
getServiceUuids |
0x02 ~ 0x07
|
getTxPowerLevel |
0x0A |
ペイロードのフォーマットをちゃんと理解していれば、ここで ScanRecord
クラスの設計の酷さに気付くはずです。
まず、1 つのペイロードに、上記表内に挙げられている全ての AD ストラクチャーが同時に含まれていることは、まずありません。また逆に、上記のいずれの AD ストラクチャーも含まないペイロードというのも当然ありえます。ですので、ScanRecord
インスタンスを受け取っても、上記のメソッド群が全て null
もしくは空のコレクションを返す場合があります。
次に、選択された AD タイプの恣意性です。なぜ、0x01
~ 0x0A
, 0x16
, 0x20
, 0x21
, 0xFF
は選ばれ、他は選ばれなかったのか。この非対称性はなんなのでしょうか。
ScanRecord
の設計を、動物を例に例えます。今、動物に関するデータが並んでいて、それをパースするとします。パースの結果を受け取るとき、受け手は普通、List<動物>
というデータ構造を期待します。しかし、ScanRecord
と同じ構造とするならば、受け手が受け取るのは、次のようなデータ構造です。
public class 動物レコード
{
getロシアンブルー() { ... }
getポメラニアン() { ... }
get鳥類タカ目() { ... }
get両生類() { ... }
get元データ() { ... }
}
この ScanRecord
クラスの設計は、Android API の中でも最悪レベルで、罵倒されてもやむをえません。この他、Android の BLE 関連の API は全般的に設計ミスの宝庫です。Android 4.X から 5.X にバージョンアップする際に BLE API を再設計したのにこの低品質で、かつそれが公式 API に採用されてしまったということは、Android の BLE 界隈はかなりの人材不足なのだと推測せざるをえません。
3. Android Beacon Library
3.1. Android Beacon Library の紹介
「アドバータイズメントパケットのペイロードをパースしたい」と、みんな思います。iBeacon や Eddystone のデータをアドバータイズメントパケットから取り出したいのです。独自フォーマットのアドバータイズメントパケットを投げるビーコンを作りたい企業もあるでしょう。
Android BLE API は前述の通り酷いので、ペイロードをパースしたい人は解を求めて Stack Overflow に行きます。するとすぐに Android Beacon Library というライブラリに辿りつきます。このライブラリはオープンソースで (AltBeacon/android-beacon-library)、Radius Networks という会社がサポートしています。Radius Networkds 社の関係者が積極的に回答していることもあり、Stack Overflow は Android Beacon Library の回答で溢れ返っています。
このライブラリにアドバータイズメントパケットをパースさせるには、対象となる AD ストラクチャーのレイアウトをパーサーに登録する必要があります。例えば iBeacon データを取り出すには、パーサーに次のようにレイアウトを登録します。
// iBeacon のレイアウトを登録する。
setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24");
レイアウトの書式の説明は、BeaconParser
クラスの setBeaconLayout
メソッドの JavaDoc に書かれています。だいたい想像がつく通り、フォーマットとバイト位置の組を列挙しています。
Android Beacon Library を使って Eddystone データを扱う方法は「How to detect Eddystone-Compatible Beacons」に記述されています。例えば Eddystone URL を検出するには、まず、パーサーに Eddystone URL のレイアウトを登録し、
mBeaconManager.getBeaconParsers().add(new BeaconParser().
setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v"));
アドバータイズメントパケットのペイロードのパース結果を得た時に、次のようにして Eddystone URL だということを判定して、情報を取り出します。
for (Beacon beacon: beacons) {
if (beacon.getServiceUuid() == 0xfeaa && beacon.getBeaconTypeCode() == 0x10) {
// This is a Eddystone-URL frame
String url = UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray());
Log.d(TAG, "I see a beacon transmitting a url: " + url +
" approximately " + beacon.getDistance() + " meters away.");
}
}
このコードは前出のドキュメントから転載したものですが、(1) Eddystone のサービス番号が 0xFEAA
ということ、及び (2) Eddystone URL のフレーム種別を示す値が 0x1?
であるということを知らないと、このコードは書けません。なお、正確には
beacon.getBeaconType() == 0x10
となっている箇所は
(beacon.getBeaconType() & 0xF0) == 0x10
と書くべきです。
3.2. Android Beacon Library の問題点
Android Beacon Library の設計は、AD ストラクチャーのレイアウトを登録するという原始的なものなので、パースされた AD ストラクチャーは全て Beacon
という汎用のデータ構造で返ってきます。逆に言うと、例えば下記のような便利なクラスのインスタンスが返ってくることはありません。
public class EddystoneURL extends Eddystone
{
/**
* Eddystone URL の AD ストラクチャーが保持する URL 情報を
* java.net.URL クラスのインスタンスとして返す。
*/
public URL getURL() { ... }
...
}
加えて、AD ストラクチャー内の構造が特殊になると、レイアウト書式では対応しきれなくなります。例えば Eddystone URL のエンコーディング方法は特殊なので、次のように色々複雑なことをやらないと欲しい情報が取り出せません。
String url = UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray());
Eddystone TLM が持つ温度情報も固定小数点フォーマット (Cornell University, Electrical Engineering 476, Fixed Point mathematical functions in GCC and assembler 参照) で格納されていて、この形式は Android Beacon Library のレイアウト書式が対応していないものなので、こちらも情報を得るには次のようなコードを書かないといけません。このコードは、Radius Networks 社の関係者による Stack Overflow の回答からの転載です。
long unsignedTemp = (beacon.getExtraDataFields().get(2) >> 8);
double temperature = unsignedTemp > 128 ?
unsignedTemp - 256 :
unsignedTemp +(beacon.getExtraDataFields().get(2) & 0xff)/256.0;
もしも Eddystone TLM を、次のような便利なクラスのインスタンスとして受け取ることができるのであれば、(1) 温度情報が固定小数点数として格納されていること、及び (2) 固定小数点数を float
に直す方法を知らずとも、簡単に温度情報を取り出すことができるでしょう。
public class EddystoneTLM extends Eddystone
{
/**
* Eddystone TLM の AD ストラクチャーが保持する、固定小数点
* フォーマットで表現された温度情報をパースして float として返す。
*/
public float getBeaconTemperature() { ... }
...
}
レイアウト登録というアプローチは一見汎用的で良い解のように思えますが、この設計は意外と安っぽいものであり、結局使い勝手の悪さとして跳ね返ってきます。さらに現実のデータ構造を正しく表現しきれないのです。「それでは」と言って使い勝手を求めると、汎用のはずの Beacon
クラスに mServiceUuid や mTxPower といった、本来サブクラスで持つべきフィールドが追加されていき、設計がどんどん汚くなっていきます。
いちいちレイアウトを登録しなければならない、パース処理の結果得られた Beacon
から取り出したデータをさらに自分でパースしなければならない、という問題点もさることながら、私がそれ以上に Android Beacon Library の設計上の問題として指摘したいのは、Android Beacon Library では AD ストラクチャーの階層構造を絶対に表現できないという点です。 この問題は、レイアウト登録というアプローチをとっている限り解消できません。
4. 繋ぎのソリューション
将来まともな Android BLE API が再々設計・実装されるまでの繋ぎのソリューションとして、手前味噌ではありますが nv-bluetooth ライブラリをお勧めします。 このライブラリを使うと、先に述べた理想の形でのプログラミングが可能となります。使用方法は下記のリンク先をご参照ください。
- [GitHub] nv-bluetooth README
- [ブログ] iBeacon as a kind of AD structures
- [ブログ] Eddystone パケットをパースする (Android)
また、下記の Stack Overflow の回答群もご参照ください。 Android Beacon Library を用いる方法での回答が同時についている質問もあるので、比較してみると違いがはっきり分かると思います。
- 「Programmatically, How to identify if a beacon belongs to Eddystone or iBeacon?」に対する回答
- 「How to read UDID, Major, Minor of beacon on android devices?」に対する回答
- 「How to identify Eddystone URL and uid?」に対する回答
- 「How to identify a Eddystone via scanRecord」に対する回答
- 「Android Bluetooth Low Energy - iBeacon」に対する回答
- 「Finding iBeacon using AltBeacon library」に対する回答
おわりに
Android BLE API の設計があまりにも酷いこと、Android Beacon Library が流行ってしまっていることは、Android を用いた BLE ソリューション発展の阻害要因になっていると考えています。 正しい設計・実装ができるエンジニアを集めて新しいチームを作った上で、ネイティブレイヤーから Android BLE API の再設計・再実装がおこなわれることを希望します。