Side Button Accessでアプリの強制起動を回避する
これはZennで公開した記事のミラーです。現物はこちら
日本でしか使えないSide Button Accessでは、サイドボタンから起動するアシスタントをSiriの代替として、他のサードパーティ製の音声アシスタントに置き換えることができます。
ざっくり実装方法を言うと、
- Side Button AccessのEntitlementを追加する
- activateスキーマに準拠したApp Intentを作成する
だけです。
この記事ではactivateスキーマに関することを話します。
フォアグラウンドになることを強制されてしまう
activateスキーマではsupportedModesを.foregroundに設定しないと、ビルド時にエラーを吐かれてしまいます。
@AppIntent(schema: .assistant.activate)
struct ActivateVoiceBasedConversationSceneIntent {
static let supportedModes: IntentModes = .foreground // これ
@MainActor
func perform() async throws -> some IntentResult {
return .result()
}
}
これは当たり前で、サードパーティのアプリはバックグラウンドからマイクを開始することができず、音声アシスタントを開始するにはアプリをフォアグラウンドにする必要があるからです。
でもつまんない。
実は後から上書きできる
ドキュメントではstatic letで宣言されてるし、一見するとどうにもならないように見えます。でもこれ実はstatic varでもOKです。例えば以下のようにしてみましょう。
@AppIntent(schema: .assistant.activate)
struct ActivateVoiceBasedConversationSceneIntent {
static var supportedModes: IntentModes = .foreground // varにする
// すぐに書き換える
init() {
Self.supportedModes = .background
}
@MainActor
func perform() async throws -> some IntentResult {
// ついでにメッセージも残す
print("Hello from background!")
return .result()
}
}
これでもビルドは通ります。
ビルド時に文句を言われないように宣言時には.foregroundとしていますが、実際にはインスタンスが最初に作成されたときに.backgroundに変更されます。後から動的に変更が可能なんですね。
実際に実機で試してみるとわかりますが、サイドボタンを長押ししてもアプリは起動しません。しかしperform()はちゃんと実行されるので、コンソールには「Hello from background!」とメッセージが流れます。
バグっぽい挙動に注意
perform()で時間のかかる処理をすると、完了するまでの間ずっと何故かサイドボタンで画面をロックできなくなります。
とはいえperform()は約30秒以上処理が終わらないと強制終了されるので、もしもの事があっても30秒待つかアプリを強制終了すれば回復します。
一応Task.detachedすればこの問題を回避できますが、バックグラウンドでちゃんと実行できるか保証はありませんし、そこまでは検証していません。
規約に注意
ADPLAでサイドボタンを長押しするApp Intentでは、音声アシスタントを起動しなければならない(must)と書かれています。
なのでこの記事に書かれていることをする場合、
- まずフォアグラウンドでマイクを開始する
- バックグラウンドに行ってもマイクは維持するが、音声認識はしない
- マイク起動中は
supportedModesを.backgroundにする - サイドボタンを長押しする
- アプリは起動せずに音声認識を開始する
みたいな使い方をしないと、ライセンス違反になるかと思います。実際にSide Searchではこういう使い方をしています。
おわり
Side Button Access自体全然使われていないのに、こんなマニアックな内容の記事に存在価値はあるのか。。