android: LED characteristic を制御しよう (1)

2024/09/30

Androidアプリで LED characteristic の制御をしよう。

開発は Windows11 上で行う。
Giraffe は最新ではないのでそのうち上げたい。新規プロジェクトを作った後に SDK バージョンなどの変更が必要なのが面倒なのだ。
画面が黒っぽいのは High contrast テーマを使っているからである。

image

空プロジェクト作成

Windows でプロジェクトを作った場合だけだと思うが、設定で改行をLFにしていてもいくつかのファイルはCR/LFで作成されてしまう。

Git で制御するのもなんだかなあと as-is にしているのだが、Android プロジェクトについては何か変えた方がよいのかもしれない。 よくわからんが。

BT permissions

BLE を扱うにはいろいろと権限がいる。

Bluetooth の権限  -  Connectivity  -  Android Developers

アプリを使う Android が 12(API 31) 以降か 11 以前かで違いがある。
こういうときは移行ガイドで11から12に移行を見るとわかるかもしれない。

これを書いている時点で Pixel7a には OS 14(API 34) がインストールされていた。
Android は毎年8月31日 Google Play アプリの対象 API レベル要件が更新されるので注意しよう。 Google Play に新規で公開するかアップデートを公開するかによってちょっと扱いが違うが、数字を変更するだけではダメなときもあるので移行ガイドを見ながら対応しよう。 審査が殺到して公開まで時間がかかることがあるので、期間にも余裕を持たせた方がよい。

Central として使うならこんな感じになりそうだ(調査中)。

    <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30"
        tools:ignore="CoarseFineLocation" />

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

調査

Android Studio の新規プロジェクト作成ウィザードに名前だけ変更して作るとminSdk24だったので、今回は API 30 以下にも対応するよう作ることにした。

物理的な位置情報 ACCESS_FINE_LOCATION が nRF Connect for Mobile はこうなっていた。

image

試しに位置情報の許可を外してみると、SCAN ボタンを押したときに許可するよう言われた。

image

位置情報は許可しつつ「正確な位置情報」は無しにしたが、これもダメだった。 SCAN が始まらない。

スキャン

まずはログにスキャンした結果を出力させようとサンプルを打ち込んだ。

BLE デバイスを探す

これがまあ、ビルドが通らないというか、ビルドが通るコードじゃなかった。
いろいろ検索した情報から対応。

BLUETOOTH_ADMIN はいるのか?

デバイスの検出をするにはBLUETOOTH_ADMINがいるようなことが書かれている。

ローカルの Bluetooth デバイスを検出する

API レベルによる違いの後ろに書いてあるので共通なのだと思うが、API30 -> 31 には BLUETOOTHBLUETOOTH_ADMINBLUETOOTH_SCAN, BLUETOOTH_CONNECT などに書き換えるように書かれている。
なのでBLUETOOTH_ADMINは API 30 以前の話だと思う。

ACCESS_FINE_LOCATION はいるのか?

この設定では startScan() 後にコールバックされなかった。

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

これでもコールバックされたが、アプリに「位置情報」の許可が必要だった。 ただ「正確な位置情報を使用」をオフにするとコールバックされなくなった。

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

こうするとコールバックされてデバイスアドレスが取得できた。 「位置情報」の許可は不要。

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="s" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

usesPermissionFlagsは API 31 以降なので targetApi を付けた。 が、なくてもよいようだ。
どっちにせよ API 30 以前にも対応したいなら ACCESS_FINE_LOCATION はいるが、maxSdkVersion="30"にしておけばよいのかな。

    <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
        android:maxSdkVersion="30" />

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

「正確な位置情報を使用」がオフだと使えないのであれば ACCESS_COARSE_LOCATION はいらないのかもしれない。

API 30 のエミュレータを作って試したところ、この設定で位置情報を許可するとスキャンできている。

    <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30"
        tools:ignore="CoarseFineLocation" />

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

ただ面倒なことに、アプリで checkSelfPermission() するものを API バージョンで切り分けないといけない。

それともう1つ。
API 30, 31 では普通にアドレスが出力できたのだが、Pixel7a(API 34)ではこんな風に終わりの 2バイト分しか見えなくなっていた。

onScanResult: XX:XX:XX:XX:12:34

ScanResult.deviceBluetoothDevice.toString()になるから、API 34 以降での挙動になる。
おそらく toString() のときだけやっているのだろうから、それほど心配することはなかろう。

ここまでのプロジェクト。

commit