17 KiB
システム機能・構成仕様書:Android Dockerビルド&Gitea CI/CD環境およびAI音声アシスタントアプリ (改訂完全統合版)
1. システム概要および運用モデル
1.1 背景と目的
本仕様書は、Android OS 8.0+ (API Level 26 以上 / Target API Level 34) を対象とした「ハンズフリーAI音声ナビゲーション連携アシスタントアプリ」の設計要件、およびDockerコンテナとGitea Runner (Act Runner) を活用した自動ビルド・CI/CDパイプライン構築要件を統合定義する。
運転中や作業中の手動操作を排除し、Bluetoothヘッドセットや画面タップによる音声入力、AIサーバーとの連携(JSON API)、TTS音声応答(リアクション)、ならびに NaviCon / Google Maps 経由での車載ナビ目的地自動転送を安全かつ低コストで実現する。
1.2 システム運用・開発モデル
- ローカル開発環境: Docker Compose を用いて、ホスト環境に依存せずコンテナ内で Gradle ビルドおよび単体テストを実行可能とする。
- CI/CD環境: Gitea への Push / Pull Request をトリガーとして Gitea Runner 上で同一コンテナ環境を起動し、自動テストおよび APK/AAB の生成を行う。
- エミュレータ・実機検証モデル: Android Studio エミュレータ(Android 8.0〜14)での仮想動作確認を標準とし、最終動作を Bluetooth ヘッドセット(エレコム LBT-HSC41BK-EC 等)および車載ナビ連携環境にて検証する。
2. システム要件および技術スタック
2.1 動作環境・開発基盤
- ターゲットプラットフォーム: Linux (x86_64) コンテナ環境(ビルド用)
- 対象端末OS: Android OS 8.0 (API Level 26) 以上推奨 / Target API Level 34 (Android 14)
- 開発言語・フレームワーク: Kotlin 1.9+ / Jetpack Compose / Coroutines & Flow / Clean Architecture + MVVM + Hilt
- コンテナ・CI/CD環境: Docker Engine 20.10+ / Docker Compose V2 / Gitea Runner (Act Runner)
- ビルドツール・JDK: OpenJDK 17 (または AGP 互換 JDK 21) / Gradle (AGP 8.x 対応)
2.2 必須権限およびサービス宣言 (AndroidManifest.xml)
- android.permission.RECORD_AUDIO (マイク音声入力)
- android.permission.INTERNET / ACCESS_NETWORK_STATE (AIサーバー通信・ネットワーク確認)
- android.permission.ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION (GPS現在地情報付与)
- android.permission.MODIFY_AUDIO_SETTINGS (オーディオフォーカス・音量制御)
- android.permission.POST_NOTIFICATIONS (通知出力)
- android.permission.BIND_NOTIFICATION_LISTENER_SERVICE (未読メッセージ・通知本文取得用バインド権限)
- android.permission.SCHEDULE_EXACT_ALARM (アラーム・タイマー設定権限)
- android.permission.FOREGROUND_SERVICE / android.permission.FOREGROUND_SERVICE_MICROPHONE / android.permission.FOREGROUND_SERVICE_LOCATION (Android 14 / API 34 必須フォアグラウンドサービス権限)
3. アプリ主要機能仕様
3.1 画面仕様 (Jetpack Compose UI)
シングルアクティビティ構成のレスポンシブなJetpack Composeレイアウトを採用する。
- ステータス表示エリア (Top / Hero Banner)
- アプリの状態 (Idle, Listening, Processing, Speaking, Error) を色とアイコンで視覚表示。
- Listening 時はマイク入力音量レベル (RmsdB) に同期する波形アニメーション (WaveformIndicator) を描画 (60fps)。
- 対話・コマンド履歴エリア (Center Scroll Area)
- ユーザー発話テキストとシステム応答テキスト(目的地情報含む)をタイムスタンプ付きチャット風UI (LazyColumn) で表示。
- 音声操作コントロールエリア (Bottom Floating Area)
- マイクボタン (FAB): タップにより音声認識の開始/停止をトグル。
- クリアボタン: 会話履歴のリセット。
- パーミッション誘導ダイアログ
- マイク権限未許可時に許可リクエストを表示。永久拒否時は端末設定画面 (ACTION_APPLICATION_DETAILS_SETTINGS) への移行ダイアログを提示。
3.2 音声処理・オーディオ制御仕様
3.2.1 起動トリガー仕様
- 常時マイク監視(WakeWord検知)によるバッテリー消費を防ぐため、トリガーを以下に一本化する。
- 画面上のマイクボタンタップ
- Bluetoothヘッドセット (LBT-HSC41BK-EC 等) のボタン操作・メディアキーインテント (Intent.ACTION_MEDIA_BUTTON / ACTION_VOICE_COMMAND)
- バックグラウンド起動制限 (Background Activity Launch restrictions) 対策:
Android 10+ ではスリープ中・バックグラウンドからのダイレクト Activity 起動が制限されるため、常駐フォアグラウンドサービス (VoiceAssistantService) およびMediaSessionCompatを併用する。Bluetooth メディアボタン押下時はMediaSessionCompatの Callback にてイベントを補捉し、フォアグラウンドサービス経由で音声認識および TTS 処理を開始する。
3.2.2 音声認識モジュール (IVoiceRecognizer / VoiceRecognitionManager)
-
技術方式: Android標準 android.speech.SpeechRecognizer のラッパーモジュール。
-
インターフェース定義:
interface IVoiceRecognizer {
val state: StateFlow
val rmsDb: StateFlow
fun startListening()
fun stopListening()
fun destroy()
} -
排他制御・フィードバック防止:
- ITextToSpeech の状態が Speaking の間は startListening() を呼び出し不可とし、スピーカー音声の誤認識ループを防止。
3.2.3 音声合成 (TTS) モジュール (ITextToSpeech / TextToSpeechManager)
- 技術方式: Android標準 android.speech.tts.TextToSpeech のラッパーモジュール。
- 状態管理の単一化:
- 状態変化は val status: StateFlow に集約。speak() は Unit を返す。
sealed interface TtsStatus {
object Idle : TtsStatus
object Speaking : TtsStatus
object Completed : TtsStatus
data class Error(val message: String) : TtsStatus
}
interface ITextToSpeech {
val status: StateFlow
fun speak(text: String, queueMode: Int = TextToSpeech.QUEUE_FLUSH)
fun stop()
fun destroy()
}
- オーディオフォーカス制御:
- speak() 実行時に AudioManager.requestAudioFocus (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) を呼び出し、他アプリの音量をミュート/減衰。発話完了 (UtteranceProgressListener.onDone) 時に abandonAudioFocus を呼出し解放する。
3.3 AIサーバー連携およびコマンドパース仕様
3.3.1 AIサーバー通信インターフェース仕様 (JSON API)
-
エンドポイント: POST /api/v1/navigate
-
認証ヘッダー:
Authorization: Bearer <API_KEY>またはX-API-Key: <API_KEY> -
通信タイムアウト & リトライ方針:
- Connect Timeout: 5,000 ms / Read Timeout: 10,000 ms
- ネットワークエラー発生時は Exponential Backoff(初期遅延 1秒、倍率 2、最大 3 回試行)でリトライを実施。
-
リクエスト構造 (App -> Server):
{ "user_id": "string", "query_text": "仕事で行く〇〇会社に設定して", "current_location": { "latitude": 35.681236, "longitude": 139.767125 }, "timestamp": 1700000000 } -
成功レスポンス構造 (Server -> App / HTTP 200):
{ "status": "success", "tts_message": "〇〇会社を目的地にセットします。", "destination": { "name": "〇〇会社 本社", "address": "東京都千代田区丸の内1-1-1", "latitude": 35.681236, "longitude": 139.767125 } } -
エラーレスポンス構造 (Server -> App / HTTP 400, 404, 500):
{ "status": "error", "error_code": "LOCATION_NOT_FOUND", "message": "該当する目的地を見つけることができませんでした。" }
3.3.2 漢数字・表記揺れコンバータ (KanjiToNumberConverter)
- 正規表現マッチングの前に、入力テキスト内の漢数字(「一」「二」「三分」「七時」等)および全角数字をアラビア数字(「1」「2」「3分」「7時」)へ事前変換する処理を実行する。
3.3.3 コマンドパターンおよびパラメータ抽出定義
| コマンド識別子 | 正規表現パターン (前処理後) | 抽出パラメータキー | 実行処理・連携内容 | 音声応答メッセージ例 |
|---|---|---|---|---|
| NAVIGATE_AI | .*(ナビ | 行きたい | 向かう | セット).* |
query_text | AIサーバー通信 & NaviCon転送 | tts_message (レスポンス依存) |
| GET_TIME_DATE | .*(今何時 | 何時ですか | 日付 | 今日は何日).* |
なし | システム日時取得 | 「現在は14時30分です」 |
| SET_ALARM_TIMER | .*((\d+)\s*(分 | 秒 | 時間)タイマー | (\d+)時にアラーム).* |
timer_val, unit / alarm_hour | アラーム・タイマーインテント | 「5分タイマーをセットしました」 |
| CHANGE_SETTINGS | .*(音量を | ダークモード | ライトモード).* |
setting_type, value | 設定変更 | 「音量を変更しました」 |
| READ_MESSAGES | .*(メッセージ | 未読 | 通知).* |
なし | AppNotificationListenerService 連携 | 「未読メッセージは〇件です」 |
| GET_SYSTEM_INFO | .*(バッテリー | 電池 | Wi-Fi | ネットワーク).* |
info_type | バッテリー/通信状態取得 | 「バッテリー残量は80%です」 |
| UNKNOWN_FALLBACK | 上記いずれにも未マッチ | なし | ガイダンス応答 | 「申し訳ありません。よく理解できませんでした。」 |
3.4 外部ナビ連携仕様 (NaviCon & Google Maps)
- NaviCon連携 (メイン軸 / 完全無料):
- AIサーバーから目的地の緯度・経度・地点名を受信後、URLスキームを発行して起動。
- 地点名 (
name) パラメータは必ず UTF-8 URL エンコード(URLEncoder.encode(name, "UTF-8"))を行う。 - フォーマット:
navicon://point?ll={latitude},{longitude}&title={encoded_name}
- Google Maps連携 (フォールバック / 完全無料):
- NaviCon未インストール時またはユーザー選択時、標準Intent(
geo:{latitude},{longitude}?q={encoded_name})を発行して起動。
- NaviCon未インストール時またはユーザー選択時、標準Intent(
- 未インストール時の自動フォールバック順序:
- パッケージ管理 (
PackageManager.getPackageInfo("jp.co.denso.navicon.user", 0)) により NaviCon インストール状態を確認する。 - (1) NaviCon が検出可能 -> NaviCon Intent を発行。
- (2) NaviCon 未検出 且つ Google Maps が利用可能 -> 音声で「NaviConが未検出のため、Google Mapsで表示します」とアナウンスし、Google Maps Intent を発行。
- (3) いずれも未検出時 -> Playストアの NaviCon ページ (
market://details?id=jp.co.denso.navicon.user) を起動。
- パッケージ管理 (
3.5 未読メッセージ・通知取得サービスおよびフォアグラウンドサービス仕様
- AppNotificationListenerService (未読メッセージ読み上げ):
- 他アプリの通知本文を読み上げるため、NotificationListenerService を継承し AndroidManifest.xml に定義する。
- VoiceAssistantService (常駐フォアグラウンドサービス):
- Android 14 (API 34) 準拠のフォアグラウンドサービスとして定義し、
foregroundServiceType="microphone|location"を付与する。 AndroidManifest.xml定義例:<service android:name=".service.VoiceAssistantService" android:exported="false" android:foregroundServiceType="microphone|location" /> <service android:name=".service.AppNotificationListenerService" android:label="メッセージ読み上げサービス" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" android:exported="true"> <intent-filter> <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service>
- Android 14 (API 34) 準拠のフォアグラウンドサービスとして定義し、
4. 状態遷移設計 (State Machine Design)
4.1 アプリケーション状態モデル (VoiceAppState)
sealed interface VoiceAppState {
object Idle : VoiceAppState
data class Listening(val rmsDb: Float = 0f) : VoiceAppState
object Processing : VoiceAppState
data class Speaking(val text: String) : VoiceAppState
data class Error(val message: String, val errorCode: Int? = null) : VoiceAppState
}
4.2 状態遷移およびエラー時 3秒自動復帰制御
- MainViewModel において、VoiceAppState.Error へ遷移した場合は viewModelScope.launch 内で delay(3000) を実行し、ユーザーの手動操作なしで3秒後に自動的に VoiceAppState.Idle へ安全に復帰させる。
stateDiagram-v2
[*] --> Idle
Idle --> Listening : マイクタップ / Bluetoothボタン
Listening --> Processing : 音声入力完了 (onResults)
Listening --> Error : 認識エラー / タイムアウト (onError)
Listening --> Idle : キャンセル操作
Processing --> Speaking : AI応答 / コマンド解析成功 (TTS開始)
Processing --> Error : AI通信エラー / 解析失敗
Speaking --> Idle : TTS再生完了 (onDone)
Speaking --> Error : TTS出力エラー
Error --> Idle : 3秒経過による自動復帰 (delay 3000ms)
5. ディレクトリ構造および成果物定義
5.1 プロジェクトディレクトリ構造
.
├── .gitea/
│ └── workflows/
│ └── build.yaml # Gitea Runner 用 CI/CD ワークフロー定義ファイル
├── docker/
│ ├── Dockerfile # Android ビルド環境定義 Dockerfile (Ubuntu 22.04 + OpenJDK 17 + Android SDK)
│ └── entrypoint.sh # 権限・初期化処理スクリプト
├── app/ # Android アプリソースコード (Kotlin / Jetpack Compose)
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/com/example/voiceapp/
│ │ │ ├── VoiceApplication.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── di/ # Hilt DI モジュール (VoiceModule, DomainModule)
│ │ │ ├── domain/ # UseCases, CommandParser, KanjiToNumberConverter
│ │ │ │ ├── model/ # CommandId, CommandMatchResult, CommandResult
│ │ │ │ ├── executor/ # NavigateAiExecutor, GetTimeDateExecutor, etc.
│ │ │ │ └── converter/ # KanjiToNumberConverter
│ │ │ ├── voice/ # SpeechRecognizer / TTS ラッパー
│ │ │ ├── service/ # VoiceAssistantService, AppNotificationListenerService
│ │ │ └── ui/ # Jetpack Compose UI / ViewModels
│ │ └── AndroidManifest.xml
│ └── test/ # ユニットテスト (JUnit 5, MockK, Turbine)
├── docker-compose.yml # ローカル環境用 Docker 実行定義ファイル
└── README.md # 仕様書・環境構築/実行マニュアル
5.2 Gitea CI/CD 実行モデル仕様
- Runner 構成: Gitea Act Runner の Docker Runner モデルを採用。
- 実行環境: Push / Pull Request 発生時、Runner 上で
docker/Dockerfileにて生成されるコンテナイメージを起動。 - 自動パイプライン: コンテナ内で
./gradlew testDebugUnitTest(単体テスト) および./gradlew assembleDebug(Debug APK 生成) を逐次実行し、ビルド成果物をアーティファクトとして保存する。
6. 受入・評価基準 (Acceptance Criteria)
- CI/CDパイプラインビルドの成功: Gitea Runner 上で ./gradlew testDebugUnitTest および ./gradlew assembleDebug が正常終了し、エラーなくAPKが出力されること。
- 単体テスト網羅性:
- KanjiToNumberConverterTest: 漢数字(「三分」「7時」「15分」等)の変換正確性を検証。
- CommandParserTest: アラーム、タイマー、NAVIGATE_AI 含む全コマンドの正規表現パラメータ抽出を検証。
- MainViewModelTest: Turbine を用い、正常系フローおよび Error 遷移から3秒後に自動 Idle 復帰する非同期状態遷移を検証。
- 堅牢性とエラーハンドリング: モック応答環境において、Listening -> Processing -> Speaking -> Idle への単方向フローが一切クラッシュなく完了し、NaviCon未インストール時もGoogle Mapsへ安全にフォールバックすること。