mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
voice reply keeps listening for cancel, mic asked for in setup
The card now closes after no, cancel or no answer instead of popping back up. The setup guide asks for the microphone even with car audio off, and its adb commands use the installed package name. Sensor events are logged once per type plus a count.
This commit is contained in:
1 parent
d3592457cf
commit
c6b7f0e178
4 files changed
+64
-19
No files matched your search
@@ -451,6 +451,28 @@ class GearslipRunner(
|
||||
openChannel(sensors.serviceId)
|
||||
}
|
||||
|
||||
/**
|
||||
* The car may stream sensors many times a second, on the same thread that reads its audio and
|
||||
* video messages. Log each type's first reading in full, then only a count every
|
||||
* [SENSOR_LOG_EVERY_MS], so the log can never hold that thread up.
|
||||
*/
|
||||
private val sensorTypesLogged = HashSet<Int>()
|
||||
private var sensorEventsSinceLog = 0
|
||||
private var sensorLoggedAt = 0L
|
||||
|
||||
private fun logSensorEvent(body: ByteArray) {
|
||||
val types = Wire.fields(body).map { it.number }.toSet()
|
||||
if (sensorTypesLogged.addAll(types)) log.i("<- SensorEvent (first of its kind):\n" + Protobuf.describe(body))
|
||||
sensorEventsSinceLog++
|
||||
val now = System.currentTimeMillis()
|
||||
if (sensorLoggedAt == 0L) sensorLoggedAt = now
|
||||
if (now - sensorLoggedAt >= SENSOR_LOG_EVERY_MS) {
|
||||
log.i("<- $sensorEventsSinceLog sensor events in the last ${(now - sensorLoggedAt) / 1000}s")
|
||||
sensorEventsSinceLog = 0
|
||||
sensorLoggedAt = now
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSensorMessage(messageId: Int, body: ByteArray) {
|
||||
when (messageId) {
|
||||
MSG_CHANNEL_OPEN_RESPONSE -> {
|
||||
@@ -477,7 +499,7 @@ class GearslipRunner(
|
||||
|
||||
MSG_SENSOR_EVENT_INDICATION -> {
|
||||
CarSensors.onEvent(body)
|
||||
log.i("<- SensorEvent:\n" + Protobuf.describe(body))
|
||||
logSensorEvent(body)
|
||||
}
|
||||
|
||||
// SensorError { required SensorType sensor_type = 1; required SensorErrorType sensor_error_type = 2; }
|
||||
@@ -1000,6 +1022,7 @@ class GearslipRunner(
|
||||
const val MSG_SENSOR_START_RESPONSE = 32770
|
||||
const val MSG_SENSOR_EVENT_INDICATION = 32771
|
||||
const val MSG_SENSOR_ERROR = 32772
|
||||
private const val SENSOR_LOG_EVERY_MS = 10_000L
|
||||
|
||||
const val STREAM_MEDIA = 3
|
||||
const val CODEC_H264_BP = 3
|
||||
|
||||
@@ -103,7 +103,7 @@ object VoiceReply {
|
||||
val until = SystemClock.uptimeMillis() + OFFER_MS
|
||||
_state.value = _state.value.copy(until = until, span = OFFER_MS)
|
||||
// The recognizer gives up on its own; this only covers one that doesn't.
|
||||
main.postAtTime({ if (id == run && _state.value.until != 0L) cancel() }, until + OFFER_GRACE_MS)
|
||||
main.postAtTime({ if (id == run && _state.value.until != 0L) { CarAssistant.stopListening(); close() } }, until + OFFER_GRACE_MS)
|
||||
CarAssistant.dictate(
|
||||
onPartial = { text -> if (id == run) _state.value = _state.value.copy(heard = text) },
|
||||
onSpeaking = {
|
||||
@@ -118,11 +118,11 @@ object VoiceReply {
|
||||
_state.value = _state.value.copy(phase = Phase.ASKING, heard = "", until = 0L, span = 0L, held = 0f)
|
||||
CarAssistant.say("Go ahead.") { if (id == run) listen(id) }
|
||||
} else {
|
||||
finish()
|
||||
close()
|
||||
}
|
||||
},
|
||||
// No answer: the card has already offered Reply and the quick replies all along.
|
||||
onError = { if (id == run) finish() },
|
||||
// No answer counts as no: the message has been heard, so the card goes.
|
||||
onError = { if (id == run) close() },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ object VoiceReply {
|
||||
if (id != run) return@dictate
|
||||
when (command(text)) {
|
||||
Command.CANCEL -> {
|
||||
finish()
|
||||
close()
|
||||
CarAssistant.say("Not sent.") {}
|
||||
}
|
||||
Command.CHANGE -> {
|
||||
@@ -238,11 +238,21 @@ object VoiceReply {
|
||||
ask(id)
|
||||
}
|
||||
Command.SEND -> send()
|
||||
// Talk that wasn't meant for us, a passenger or the radio: carry on counting.
|
||||
null -> startCountdown(id, RESUME_MS)
|
||||
// Talk that wasn't meant for us, a passenger or the radio: carry on counting,
|
||||
// and keep listening so a "cancel" after it still lands.
|
||||
null -> {
|
||||
startCountdown(id, RESUME_MS)
|
||||
listenForCommand(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { if (id == run && _state.value.until == 0L) startCountdown(id, RESUME_MS) },
|
||||
// Silence ends the recognizer before the countdown does: listen again until it sends.
|
||||
onError = {
|
||||
if (id != run) return@dictate
|
||||
if (_state.value.until == 0L) startCountdown(id, RESUME_MS)
|
||||
// A short gap, so a recognizer that fails at once can't spin.
|
||||
main.postDelayed({ if (id == run) listenForCommand(id) }, RELISTEN_MS)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -308,11 +318,20 @@ object VoiceReply {
|
||||
}
|
||||
}
|
||||
|
||||
/** Stops wherever it is. Nothing is sent. */
|
||||
/** Stops wherever it is. Nothing is sent, and the message card closes. */
|
||||
fun cancel() {
|
||||
if (!active) return
|
||||
CarAssistant.stopListening()
|
||||
CarAssistant.stopSpeaking()
|
||||
close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends without sending and takes the message card with it. Without this the card's own popup
|
||||
* came back with a fresh bar, as if the message had just arrived.
|
||||
*/
|
||||
private fun close() {
|
||||
_state.value.target?.let { CarNotifications.dismissPopup(it.id) }
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -380,4 +399,5 @@ object VoiceReply {
|
||||
private const val OFFER_GRACE_MS = 1_500L
|
||||
/** After talk that wasn't a command, the reply waits this long again before it goes. */
|
||||
private const val RESUME_MS = 3_000L
|
||||
private const val RELISTEN_MS = 400L
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import app.seb3thehacker.gearslip.BuildConfig
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -61,14 +62,14 @@ private fun setupSteps(onRequestCallScreening: () -> Unit) = listOf(
|
||||
"Connect the phone to a computer with USB debugging turned on, and run this command. " +
|
||||
"It tells Android to trust Gearslip with audio from now on. Skip it and everything " +
|
||||
"still works, but you have to tap Start once each time you set off.",
|
||||
command = "adb shell appops set app.seb3thehacker.gearslip PROJECT_MEDIA allow",
|
||||
command = "adb shell appops set ${BuildConfig.APPLICATION_ID} PROJECT_MEDIA allow",
|
||||
),
|
||||
Step(
|
||||
"Optional: keep notifications readable while music plays.",
|
||||
"Android treats sending audio to the car like sharing your screen, and hides what your " +
|
||||
"notifications say. Run this command too, and Gearslip switches that off only while " +
|
||||
"music is going to the car, then puts it back.",
|
||||
command = "adb shell pm grant app.seb3thehacker.gearslip android.permission.WRITE_SECURE_SETTINGS",
|
||||
command = "adb shell pm grant ${BuildConfig.APPLICATION_ID} android.permission.WRITE_SECURE_SETTINGS",
|
||||
),
|
||||
Step(
|
||||
"Optional: let the car screen answer and decline calls.",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import app.seb3thehacker.gearslip.BuildConfig
|
||||
import android.Manifest
|
||||
import android.app.AppOpsManager
|
||||
import android.app.NotificationManager
|
||||
@@ -43,7 +44,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.seb3thehacker.gearslip.AppSettings
|
||||
import app.seb3thehacker.gearslip.call.CarCalls
|
||||
import app.seb3thehacker.gearslip.car.CarSettings
|
||||
import app.seb3thehacker.gearslip.car.Prefetch
|
||||
import app.seb3thehacker.gearslip.host.AndroidAuto
|
||||
import app.seb3thehacker.gearslip.notify.GearslipNotificationListener
|
||||
@@ -104,13 +104,14 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
)
|
||||
|
||||
// --- Permissions: skipped outright once already granted, nothing left to ask -----
|
||||
if (CarSettings.pipeAudio.value && !granted(context, arrayOf(Manifest.permission.RECORD_AUDIO))) {
|
||||
// Asked whether or not car audio is on: voice reply and the assistant need it too.
|
||||
if (!granted(context, arrayOf(Manifest.permission.RECORD_AUDIO))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Microphone",
|
||||
explanation = "Android will not pass a music app's audio to the car without this " +
|
||||
"permission. Gearslip does not listen to you: it copies what is already " +
|
||||
"playing, nothing else.",
|
||||
explanation = "Gearslip needs the microphone for two jobs: passing your music " +
|
||||
"app's sound to the car, and hearing you when you reply to a message or ask " +
|
||||
"the assistant. It listens only then, and your speech stays on the phone.",
|
||||
permissions = arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
),
|
||||
)
|
||||
@@ -236,7 +237,7 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
explanation = "Connect the phone to a computer with USB debugging on, and run this " +
|
||||
"command. It tells Android to trust Gearslip with audio from now on. Skip it " +
|
||||
"and everything still works, but you tap Start once each time you set off.",
|
||||
command = "adb shell appops set app.seb3thehacker.gearslip PROJECT_MEDIA allow",
|
||||
command = "adb shell appops set ${BuildConfig.APPLICATION_ID} PROJECT_MEDIA allow",
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -247,7 +248,7 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
explanation = "Android treats sending audio to the car like sharing your screen, and " +
|
||||
"hides what your notifications say. This command switches that off only while " +
|
||||
"music is going to the car, then puts it back.",
|
||||
command = "adb shell pm grant app.seb3thehacker.gearslip android.permission.WRITE_SECURE_SETTINGS",
|
||||
command = "adb shell pm grant ${BuildConfig.APPLICATION_ID} android.permission.WRITE_SECURE_SETTINGS",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user