Wire VoiceLoop into ClippySession lifecycle and ClippyScreen talk button

This commit is contained in:
2026-03-05 23:09:48 +00:00
parent a6f8433eb4
commit d1450cab7e
2 changed files with 43 additions and 12 deletions
@@ -3,6 +3,7 @@ package farm.hobbs.clippyauto.auto
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.* import androidx.car.app.model.*
import farm.hobbs.clippyauto.VoiceLoop
/** /**
* The Android Auto screen for ClippyAuto. * The Android Auto screen for ClippyAuto.
@@ -15,6 +16,11 @@ import androidx.car.app.model.*
class ClippyScreen(carContext: CarContext) : Screen(carContext) { class ClippyScreen(carContext: CarContext) : Screen(carContext) {
private var statusText = "Tap to talk to Clippy" private var statusText = "Tap to talk to Clippy"
private var voiceLoop: VoiceLoop? = null
fun attachVoiceLoop(loop: VoiceLoop) {
voiceLoop = loop
}
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
val action = Action.Builder() val action = Action.Builder()
@@ -51,11 +57,7 @@ class ClippyScreen(carContext: CarContext) : Screen(carContext) {
} }
private fun onTalkPressed() { private fun onTalkPressed() {
// TODO: Wire to VoiceStateMachine voiceLoop?.onTalkPressed()
// IDLE → start listening
// SPEAKING → interrupt (fade TTS, activate STT)
statusText = "Listening..."
invalidate() // Refresh the screen
} }
fun updateStatus(status: String) { fun updateStatus(status: String) {
@@ -5,26 +5,55 @@ import androidx.car.app.Screen
import androidx.car.app.Session import androidx.car.app.Session
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import farm.hobbs.clippyauto.VoiceLoop
import farm.hobbs.clippyauto.matrix.MatrixRestClient
/** /**
* Android Auto session — manages the lifecycle of a Clippy voice interaction. * Android Auto session — manages the lifecycle of a Clippy voice interaction.
* Sends session_start on create, session_end on destroy. * Owns the VoiceLoop and passes it to the screen.
*/ */
class ClippySession : Session(), DefaultLifecycleObserver { class ClippySession : Session(), DefaultLifecycleObserver {
private var voiceLoop: VoiceLoop? = null
private var screen: ClippyScreen? = null
// TODO: Move to config / SharedPreferences / SetupActivity
companion object {
const val HOMESERVER = "https://mess.hobbs.farm"
const val ROOM_ID = "" // Set after #clippy-voice room is created
const val CLIPPY_USER = "@clippy:hobbs.farm"
}
override fun onCreateScreen(intent: Intent): Screen { override fun onCreateScreen(intent: Intent): Screen {
lifecycle.addObserver(this) lifecycle.addObserver(this)
return ClippyScreen(carContext) val clippyScreen = ClippyScreen(carContext)
screen = clippyScreen
return clippyScreen
} }
override fun onStart(owner: LifecycleOwner) { override fun onStart(owner: LifecycleOwner) {
// TODO: Send session_start signal to Matrix #clippy-voice room val matrixClient = MatrixRestClient(HOMESERVER)
// TODO: Start Matrix sync (fallback path)
// TODO: Subscribe to FCM response bus voiceLoop = VoiceLoop(
context = carContext,
matrixClient = matrixClient,
roomId = ROOM_ID,
clippyUserId = CLIPPY_USER,
onStateChange = { state, status ->
screen?.updateStatus(status)
}
)
voiceLoop?.initialize()
voiceLoop?.startSession()
// Give the screen a reference so the talk button works
screen?.attachVoiceLoop(voiceLoop!!)
} }
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {
// TODO: Send session_end signal to Matrix voiceLoop?.endSession()
// TODO: Clean up voice pipeline voiceLoop?.destroy()
voiceLoop = null
} }
} }