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.Screen
import androidx.car.app.model.*
import farm.hobbs.clippyauto.VoiceLoop
/**
* The Android Auto screen for ClippyAuto.
@@ -15,6 +16,11 @@ import androidx.car.app.model.*
class ClippyScreen(carContext: CarContext) : Screen(carContext) {
private var statusText = "Tap to talk to Clippy"
private var voiceLoop: VoiceLoop? = null
fun attachVoiceLoop(loop: VoiceLoop) {
voiceLoop = loop
}
override fun onGetTemplate(): Template {
val action = Action.Builder()
@@ -51,11 +57,7 @@ class ClippyScreen(carContext: CarContext) : Screen(carContext) {
}
private fun onTalkPressed() {
// TODO: Wire to VoiceStateMachine
// IDLE → start listening
// SPEAKING → interrupt (fade TTS, activate STT)
statusText = "Listening..."
invalidate() // Refresh the screen
voiceLoop?.onTalkPressed()
}
fun updateStatus(status: String) {
@@ -5,26 +5,55 @@ import androidx.car.app.Screen
import androidx.car.app.Session
import androidx.lifecycle.DefaultLifecycleObserver
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.
* Sends session_start on create, session_end on destroy.
* Owns the VoiceLoop and passes it to the screen.
*/
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 {
lifecycle.addObserver(this)
return ClippyScreen(carContext)
val clippyScreen = ClippyScreen(carContext)
screen = clippyScreen
return clippyScreen
}
override fun onStart(owner: LifecycleOwner) {
// TODO: Send session_start signal to Matrix #clippy-voice room
// TODO: Start Matrix sync (fallback path)
// TODO: Subscribe to FCM response bus
val matrixClient = MatrixRestClient(HOMESERVER)
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) {
// TODO: Send session_end signal to Matrix
// TODO: Clean up voice pipeline
voiceLoop?.endSession()
voiceLoop?.destroy()
voiceLoop = null
}
}