Files

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 システム運用・開発モデル

  1. ローカル開発環境: Docker Compose を用いて、ホスト環境に依存せずコンテナ内で Gradle ビルドおよび単体テストを実行可能とする。
  2. CI/CD環境: Gitea への Push / Pull Request をトリガーとして Gitea Runner 上で同一コンテナ環境を起動し、自動テストおよび APK/AAB の生成を行う。
  3. エミュレータ・実機検証モデル: 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レイアウトを採用する。

  1. ステータス表示エリア (Top / Hero Banner)
    • アプリの状態 (Idle, Listening, Processing, Speaking, Error) を色とアイコンで視覚表示。
    • Listening 時はマイク入力音量レベル (RmsdB) に同期する波形アニメーション (WaveformIndicator) を描画 (60fps)。
  2. 対話・コマンド履歴エリア (Center Scroll Area)
    • ユーザー発話テキストとシステム応答テキスト(目的地情報含む)をタイムスタンプ付きチャット風UI (LazyColumn) で表示。
  3. 音声操作コントロールエリア (Bottom Floating Area)
    • マイクボタン (FAB): タップにより音声認識の開始/停止をトグル。
    • クリアボタン: 会話履歴のリセット。
  4. パーミッション誘導ダイアログ
    • マイク権限未許可時に許可リクエストを表示。永久拒否時は端末設定画面 (ACTION_APPLICATION_DETAILS_SETTINGS) への移行ダイアログを提示。

3.2 音声処理・オーディオ制御仕様

3.2.1 起動トリガー仕様

  • 常時マイク監視(WakeWord検知)によるバッテリー消費を防ぐため、トリガーを以下に一本化する。
    1. 画面上のマイクボタンタップ
    2. 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)

  1. NaviCon連携 (メイン軸 / 完全無料):
    • AIサーバーから目的地の緯度・経度・地点名を受信後、URLスキームを発行して起動。
    • 地点名 (name) パラメータは必ず UTF-8 URL エンコード(URLEncoder.encode(name, "UTF-8"))を行う。
    • フォーマット: navicon://point?ll={latitude},{longitude}&title={encoded_name}
  2. Google Maps連携 (フォールバック / 完全無料):
    • NaviCon未インストール時またはユーザー選択時、標準Intent(geo:{latitude},{longitude}?q={encoded_name})を発行して起動。
  3. 未インストール時の自動フォールバック順序:
    • パッケージ管理 (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 未読メッセージ・通知取得サービスおよびフォアグラウンドサービス仕様

  1. AppNotificationListenerService (未読メッセージ読み上げ):
    • 他アプリの通知本文を読み上げるため、NotificationListenerService を継承し AndroidManifest.xml に定義する。
  2. 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>
      

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)

  1. CI/CDパイプラインビルドの成功: Gitea Runner 上で ./gradlew testDebugUnitTest および ./gradlew assembleDebug が正常終了し、エラーなくAPKが出力されること。
  2. 単体テスト網羅性:
    • KanjiToNumberConverterTest: 漢数字(「三分」「7時」「15分」等)の変換正確性を検証。
    • CommandParserTest: アラーム、タイマー、NAVIGATE_AI 含む全コマンドの正規表現パラメータ抽出を検証。
    • MainViewModelTest: Turbine を用い、正常系フローおよび Error 遷移から3秒後に自動 Idle 復帰する非同期状態遷移を検証。
  3. 堅牢性とエラーハンドリング: モック応答環境において、Listening -> Processing -> Speaking -> Idle への単方向フローが一切クラッシュなく完了し、NaviCon未インストール時もGoogle Mapsへ安全にフォールバックすること。