概要
targetSdkversionを31に上げて影響を調査していたときの話です。バックグランドからのフォアグランドサービス起動制限の影響を食らうだろうと思っていたのにテストしてみたら全然クラッシュしませんでした。理由がわからないと不安なので、時間の許す範囲で調べてみました。
条件・環境
端末:Pixel6
OS:Android12
経緯
ドキュメントを見るとバックグランドからフォアグランドサービス起動時はForegroundServiceStartNotAllowedExceptionがスローされるとあります。
しかし、バックグランドからのフォアグランドサービス起動をテストしても狙ったところでこの例外は発生せず、意図しないところで発生しました。
調査方法
Androidのソースコードを見ていると起動中のサービスについての情報がServiceRecordなるものに書かれている雰囲気なので、それが確認できればなぜ例外が発生しないのかわかりそうに思いました。検索すると下記の記事に表示方法がありました。
結果
アプリをバックグランドにしてしばらくしてから前回テストしたときと同じフォアグランドサービスを起動させてServiceRecordを確認したところ、次のような部分を見つけました。
code:BLUETOOTH_BROADCAST;
tempAllowListReason:<broadcast:1000:android.bluetooth.adapter.action.STATE_CHANGED,
reason:,reasonCode:BLUETOOTH_BROADCAST,duration:10000,callingUid:1000>
どうもBLUETOOTH_BROADCASTのため一時的にサービス起動が許可されているように見えます。
ドキュメントを見直すと、BLUETOOTH_CONNECT又はBLUETOOTH_SCANパーミッションが必要なBluetoothブロードキャストを受信したときは、この制限の例外とありました。
追加確認1
アプリをバックグランドにして、Bluetoothのブロードキャストを受信するフォアグラウンドサービスを起動した状態で、ブロードキャストを受信しない別のフォアグラウンドサービス(以後サービス2と呼ぶ)を起動してみました。サービス2のServiceRecord確認すると、tempAllowListResonはnullとなっています。アプリがホワイトリスト入りしたような状態になり、他のフォアグラウンドサービスの起動もできているように見えます。サービス2の動作も正常でした。
code:PROC_STATE_FGS;
tempAllowListReason:<null>;
追加確認2
アプリをバックグランドにして、Bluetoothのブロードキャストを受信するフォアグラウンドサービスを終了し、ブロードキャストを受信しないフォアグラウンドサービスを起動すると、このサービスのServiceRecordはありませんでした。logcatを確認してみるとForegroundServiceStartNotAllowedExceptionが発生していました。
追加確認1で「サービス2」が起動できたのは、「Bluetoothのブロードキャストを受信するフォアグラウンドサービス」のお陰だったようです。
補足
前述の「Androidのソースコードを見ていると起動中のサービスについての情報がServiceRecordなるものに書かれている雰囲気」についての補足です。
Androidのソースはこちらから確認しました。
「ForegroundServiceStartNotAllowedException」で検索すると、下記ファイルが怪しいと思いました。
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
※他の検索結果はファイルパスの途中にtestとかあったり、コメントだったりしました。
次の部分で問題の例外がスローされるようです。
if (fgRequired) {
logFgsBackgroundStart(r);
if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
String msg = "startForegroundService() not allowed due to "
mAllowStartForeground false: service
+ r.shortInstanceName;
Slog.w(TAG, msg);
showFgsBgRestrictedNotificationLocked(r);
logFGSStateChangeLocked(r,
FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
0);
if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
throw new ForegroundServiceStartNotAllowedException(msg);
}
return null;
}
例外スローまでに通るif分岐は3つ
1. if (fgRequired) {
2. if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r))
3. if(CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid))
1. if (fgRequired)
関数の参照元を辿るとStartForegroundSericeから呼ばれたならTrueになる模様。フォアグラウンドサービスなのでStartForegroundSerice()を使っておりTrueのはずです。
2-1(2の前半). r.mAllowStartForeground == REASON_DENIED
REASON_DENIED、mAllowStartForegroundでコード検索してもセットされている場所はヒットしませんでした。ただ、serviceRecord.javaのserviceRecordクラスにこの変数が定義されているようです。
2-2(2の後半). isBgFgsRestrictionEnabled(r)
関数の定義を見ると互換性フレームワークツール※のフラグをチェックしている様子。開発者オプションから確認するとすべてONなので、ここもTrueのはずです。(ざっとしか見てないので、違ってたらすみません。)
※互換性フレームワークについて
3. CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)
関数名からここも互換性フレームワークのフラグをチェックしていると判断。Trueのはずです。(ざっとしか見てないので、違ってたらすみません。)
補足のまとめ
結局、mAllowStartForegroundの値次第のようですが、何を判定してここにREASON_DENIED がセットされるかまでは追跡出来ませんでした。ソースコードにこだわると時間がかかるため、ServiceRecordの確認方法を探すことにしました。