feat: integrate chat voice and diagnostics client

This commit is contained in:
Edison Jwa
2026-05-23 06:51:55 +09:00
parent 7e28791ec2
commit 7d5d8c2c90
93 changed files with 10273 additions and 3347 deletions
@@ -5,6 +5,18 @@
protocol layer to dial a TeamSpeak-compatible server over UDP. -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- SRS-100: Network diagnostics. Allows the core to query active
network type and state for the diagnostic export. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- SDD-115: Prevent CPU sleep during active voice sessions.
Keeps the audio pipeline running when the screen locks. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- SRS-025: Haptic feedback on PTT key press. Enables
VibrationEffect for transmit-mode tactile confirmation. -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- SDD-trace: SDD-106 AndroidPermissionRequester.
Required for the audio engine (chanora_audio) to open the
capture stream for voice transmission. The runtime grant
@@ -56,7 +68,8 @@
<application
android:label="Chanora"
android:name="app.chanora.chanora_flutter.ChanoraApplication"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true">
<activity
android:name=".MainActivity"
android:exported="true"
@@ -324,13 +324,11 @@ class AndroidPermissionRequester {
callback: ((PermissionState) -> Unit)?,
) {
callback?.invoke(state)
// The stateChangeListener is wired by MainActivity to invoke
// METHOD_PERMISSION_STATE_CHANGED on ANDROID_PERMISSIONS, which
// publishes BridgeEvent::PermissionState to the bridge event
// stream. See MainActivity.configureFlutterEngine (SDD-106 §5).
stateChangeListener?.invoke(permission, state)
// TODO(Wave 2B follow-up): wire this into a Flutter MethodChannel
// named MethodChannels.ANDROID_PERMISSIONS, invoking method
// MethodChannels.METHOD_PERMISSION_STATE_CHANGED. The Dart
// handler then publishes the corresponding
// BridgeEvent::PermissionState onto the bridge event stream
// (SDD-106 §5).
}
/**
@@ -71,24 +71,9 @@ class AndroidVoiceForegroundService : Service() {
*/
private const val CHANNEL_ID = "chanora.voice.session"
/**
* Notification channel user-visible name.
*
* TODO(localization): SDD-107 §4 requires this to be sourced
* from a product string resource. Hard-coded here for the
* Wave 2B implementation slice; localised strings land
* alongside the broader Android string-resource pass.
*/
private const val CHANNEL_NAME = "Voice session"
/**
* Notification channel description (product copy only, no
* server-supplied content per SDD-107 §5 privacy note).
*
* TODO(localization): see [CHANNEL_NAME].
*/
private const val CHANNEL_DESCRIPTION =
"Shown while a Chanora voice session is active."
// SDD-107 §4: notification strings are sourced from
// res/values/strings.xml and accessed via the service context
// in instance methods below (ensureChannel, buildNotification).
/**
* Stable notification id ("CHAN") per SDD-107 §5. Must remain
@@ -298,10 +283,10 @@ class AndroidVoiceForegroundService : Service() {
if (existing != null) return
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
getString(R.string.voice_session_channel_name),
NotificationManager.IMPORTANCE_LOW,
).apply {
description = CHANNEL_DESCRIPTION
description = getString(R.string.voice_session_channel_description)
setShowBadge(false)
}
nm.createNotificationChannel(channel)
@@ -329,14 +314,9 @@ class AndroidVoiceForegroundService : Service() {
}
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_speakerphone)
// TODO(SDD-107 §5): replace stat_sys_speakerphone with the
// product icon `ic_chanora_voice` once the drawable lands
// in res/drawable. Using a platform-provided icon as the
// interim placeholder keeps the build green without
// touching res/* (out of this slice's file set).
.setContentTitle("Chanora — Voice session active")
.setContentText("Microphone may be in use.")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.voice_session_notification_title))
.setContentText(getString(R.string.voice_session_notification_text))
.setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setPriority(NotificationCompat.PRIORITY_LOW)
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- SDD-107 §4: Chinese (Simplified) localized notification strings.
Product-owned copy; no server-supplied content per SDD-107 §5. -->
<string name="voice_session_channel_name">语音会话</string>
<string name="voice_session_channel_description">Chanora 语音会话处于活动状态时显示。</string>
<string name="voice_session_notification_title">Chanora — 语音会话进行中</string>
<string name="voice_session_notification_text">麦克风可能正在使用中。</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- SDD-107 §4: Voice session foreground service notification strings.
These are product-owned strings; server-supplied content is never
placed in notification copy per SDD-107 §5 privacy note. -->
<string name="voice_session_channel_name">Voice session</string>
<string name="voice_session_channel_description">Shown while a Chanora voice session is active.</string>
<string name="voice_session_notification_title">Chanora — Voice session active</string>
<string name="voice_session_notification_text">Microphone may be in use.</string>
</resources>