Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/eval/SkillHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.stypox.dicio.settings.datastore.UserSettingsModule
import org.stypox.dicio.skills.calculator.CalculatorInfo
import org.stypox.dicio.skills.current_time.CurrentTimeInfo
import org.stypox.dicio.skills.fallback.text.TextFallbackInfo
import org.stypox.dicio.skills.listening.ListeningInfo
import org.stypox.dicio.skills.lyrics.LyricsInfo
import org.stypox.dicio.skills.media.MediaInfo
import org.stypox.dicio.skills.navigation.NavigationInfo
Expand Down Expand Up @@ -50,6 +51,7 @@ class SkillHandler @Inject constructor(
TimerInfo,
CurrentTimeInfo,
MediaInfo,
ListeningInfo,
)

// TODO add more fallback skills (e.g. search)
Expand Down
19 changes: 18 additions & 1 deletion app/src/main/kotlin/org/stypox/dicio/io/wake/WakeService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.stypox.dicio.di.WakeDeviceWrapper
import org.stypox.dicio.eval.SkillEvaluator
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -45,7 +46,7 @@ class WakeService : Service() {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)

private var listening = AtomicBoolean(false)
private val listening = AtomicBoolean(false)

@Inject
lateinit var skillEvaluator: SkillEvaluator
Expand Down Expand Up @@ -211,6 +212,8 @@ class WakeService : Service() {
nextWakeWordAllowed = Instant.now().plusMillis(WAKE_WORD_BACKOFF_MILLIS)
onWakeWordDetected()
}

lastHeard.set(Instant.now())
}
} finally {
ar.stop()
Expand Down Expand Up @@ -324,6 +327,18 @@ class WakeService : Service() {
ContextCompat.startForegroundService(context, intent)
}

fun stop(context: Context) {
try {
context.startService(Intent(context, WakeService::class.java)
.apply { action = ACTION_STOP_WAKE_SERVICE })
} catch (e: IllegalStateException) {
// Must not have been running. No problem with that.
}
}

// Consider the service running if it processed any audio data within the past half second.
fun isRunning(): Boolean = lastHeard.get().isAfter(Instant.now().minusMillis(500))

/**
* On Android 10+ cancels any notification telling the user that the Dicio wake word was
* triggered, which is not needed anymore after the main activity starts.
Expand All @@ -335,6 +350,8 @@ class WakeService : Service() {
}
}

private val lastHeard = AtomicReference<Instant>()

private val TAG = WakeService::class.simpleName
private const val FOREGROUND_NOTIFICATION_CHANNEL_ID =
"org.stypox.dicio.io.wake.WakeService.FOREGROUND"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.stypox.dicio.skills.listening

import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Hearing
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillInfo
import org.stypox.dicio.R
import org.stypox.dicio.sentences.Sentences

object ListeningInfo : SkillInfo("listening") {
override fun name(context: Context) =
context.getString(R.string.skill_name_listening)

override fun sentenceExample(context: Context) =
context.getString(R.string.skill_sentence_example_listening)

@Composable
override fun icon() =
rememberVectorPainter(Icons.Default.Hearing)

override fun isAvailable(ctx: SkillContext): Boolean {
return Sentences.Listening[ctx.sentencesLanguage] != null
}

override fun build(ctx: SkillContext): Skill<*> {
return ListeningSkill(ListeningInfo, Sentences.Listening[ctx.sentencesLanguage]!!)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.stypox.dicio.skills.listening

import org.dicio.skill.context.SkillContext
import org.stypox.dicio.R
import org.stypox.dicio.io.graphical.HeadlineSpeechSkillOutput
import org.stypox.dicio.util.getString

class ListeningOutput(
private val previousEnable: Boolean,
private val newEnable: Boolean
) : HeadlineSpeechSkillOutput {
override fun getSpeechOutput(ctx: SkillContext): String = if (previousEnable) {
if (newEnable) {
ctx.getString(R.string.skill_listening_already_listening)
} else {
ctx.getString(R.string.skill_listening_stop_listening)
}
} else {
if (newEnable) {
ctx.getString(R.string.skill_listening_started_listening)
} else {
ctx.getString(R.string.skill_listening_not_listening)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.stypox.dicio.skills.listening

import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillInfo
import org.dicio.skill.skill.SkillOutput
import org.dicio.skill.standard.StandardRecognizerData
import org.dicio.skill.standard.StandardRecognizerSkill
import org.stypox.dicio.io.wake.WakeService
import org.stypox.dicio.sentences.Sentences.Listening

class ListeningSkill(correspondingSkillInfo: SkillInfo, data: StandardRecognizerData<Listening>) :
StandardRecognizerSkill<Listening>(correspondingSkillInfo, data) {

override suspend fun generateOutput(ctx: SkillContext, inputData: Listening): SkillOutput {
val wasRunning = WakeService.isRunning()
val shouldBeRunning = when (inputData) {
is Listening.Start -> true
is Listening.Stop -> false
}
if (shouldBeRunning) {
WakeService.start(ctx.android)
} else {
WakeService.stop(ctx.android)
}
return ListeningOutput(wasRunning, shouldBeRunning)
}
}
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@
<string name="retry">Retry</string>
<string name="stop">Stop</string>
<string name="grant_permissions">Grant permissions</string>
<string name="skill_listening_already_listening">I am still listening for the wake word</string>
<string name="skill_listening_started_listening">Okay, I\'ll listen for the wake word</string>
<string name="skill_listening_not_listening">I wasn\'t listening</string>
<string name="skill_listening_stop_listening">Okay</string>
<string name="skill_name_listening">Control wake word listening</string>
<string name="skill_sentence_example_listening">Stop listening</string>
<string name="pref_weather_temperature_unit">Temperature unit</string>
<string name="use_system_default">Use system default</string>
<string name="pref_weather_unit_system_instructions">Uses the unit configured in \"System settings > System > Languages &amp; input > Regional preferences\"</string>
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/sentences/en/listening.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
stop:
- stop listening
- mute microphone?
- turn off microphone

start:
- start listening
- unmute|(on mute) microphone?
- turn on microphone
6 changes: 6 additions & 0 deletions app/src/main/sentences/skill_definitions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ skills:
- id: what
type: string

- id: listening
specificity: high
sentences:
- id: stop
- id: start

- id: search
specificity: low
sentences:
Expand Down