Skip to content

Commit 121cb77

Browse files
authored
Prevent initial SpeechRecognizer timeout (#7020)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1211772822476890?focus=true ### Description - Prevents private voice search from timing out while waiting for voice input ### Steps to test this PR - [x] Enabled private voice search - [x] Tap the voice search button - [x] Verify that it does not time out quickly while waiting for input _Disable feature_ - [x] Go to feature flag inventory - [x] Disable `restartAfterTimeout` - [x] Tap the voice search button - [x] Verify that it times out quickly while waiting for input
1 parent 398ea2d commit 121cb77

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

voice-search/voice-search-impl/src/main/java/com/duckduckgo/voice/impl/listeningmode/OnDeviceSpeechRecognizer.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ import android.speech.RecognitionListener
2323
import android.speech.RecognizerIntent
2424
import android.speech.SpeechRecognizer
2525
import androidx.annotation.RequiresApi
26+
import com.duckduckgo.app.di.AppCoroutineScope
27+
import com.duckduckgo.common.utils.DispatcherProvider
2628
import com.duckduckgo.di.scopes.ActivityScope
2729
import com.duckduckgo.voice.impl.listeningmode.OnDeviceSpeechRecognizer.Companion
2830
import com.duckduckgo.voice.impl.listeningmode.OnDeviceSpeechRecognizer.Event
31+
import com.duckduckgo.voice.impl.remoteconfig.VoiceSearchFeature
2932
import com.squareup.anvil.annotations.ContributesBinding
33+
import kotlinx.coroutines.CoroutineScope
34+
import kotlinx.coroutines.launch
35+
import kotlinx.coroutines.withContext
3036
import logcat.LogPriority.ERROR
3137
import logcat.asLog
3238
import logcat.logcat
@@ -53,9 +59,13 @@ interface OnDeviceSpeechRecognizer {
5359
@ContributesBinding(ActivityScope::class)
5460
class DefaultOnDeviceSpeechRecognizer @Inject constructor(
5561
private val context: Context,
62+
private val voiceSearchFeature: VoiceSearchFeature,
63+
private val dispatcherProvider: DispatcherProvider,
64+
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
5665
) : OnDeviceSpeechRecognizer {
5766

5867
private var speechRecognizer: SpeechRecognizer? = null
68+
private var hasSpeechBegun = false
5969

6070
private val speechRecognizerIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).run {
6171
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
@@ -68,7 +78,7 @@ class DefaultOnDeviceSpeechRecognizer @Inject constructor(
6878
}
6979

7080
override fun onBeginningOfSpeech() {
71-
// Do nothing. User has started speaking
81+
hasSpeechBegun = true
7282
}
7383

7484
override fun onRmsChanged(rmsdB: Float) {
@@ -85,7 +95,18 @@ class DefaultOnDeviceSpeechRecognizer @Inject constructor(
8595

8696
override fun onError(error: Int) {
8797
when (error) {
88-
SpeechRecognizer.ERROR_NO_MATCH -> _eventHandler(Event.RecognitionTimedOut(error))
98+
SpeechRecognizer.ERROR_NO_MATCH -> {
99+
appCoroutineScope.launch(dispatcherProvider.io()) {
100+
val isEnabled = voiceSearchFeature.restartAfterTimeout().isEnabled()
101+
withContext(dispatcherProvider.main()) {
102+
if (isEnabled && !hasSpeechBegun) {
103+
speechRecognizer?.startListening(speechRecognizerIntent)
104+
} else {
105+
_eventHandler(Event.RecognitionTimedOut(error))
106+
}
107+
}
108+
}
109+
}
89110
else -> {
90111
logcat(ERROR) { "SpeechRecognizer error: $error" }
91112
_eventHandler(Event.RecognitionFailed(error))
@@ -121,6 +142,7 @@ class DefaultOnDeviceSpeechRecognizer @Inject constructor(
121142
@RequiresApi(VERSION_CODES.S)
122143
override fun start(eventHandler: (Event) -> Unit) {
123144
_eventHandler = eventHandler
145+
hasSpeechBegun = false
124146
runCatching {
125147
speechRecognizer = SpeechRecognizer.createOnDeviceSpeechRecognizer(context)
126148
}.onFailure {
@@ -133,6 +155,7 @@ class DefaultOnDeviceSpeechRecognizer @Inject constructor(
133155
}
134156

135157
override fun stop() {
158+
hasSpeechBegun = false
136159
speechRecognizer?.destroy()
137160
}
138161

voice-search/voice-search-impl/src/main/java/com/duckduckgo/voice/impl/remoteconfig/VoiceSearchFeature.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,11 @@ interface VoiceSearchFeature {
3636
*/
3737
@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
3838
fun self(): Toggle
39+
40+
/**
41+
* Kill switch to restart listening when ERROR_NO_MATCH occurs before speech begins
42+
* If the remote feature is not present defaults to `internal`
43+
*/
44+
@Toggle.DefaultValue(DefaultFeatureValue.INTERNAL)
45+
fun restartAfterTimeout(): Toggle
3946
}

0 commit comments

Comments
 (0)