1+ package com.nlinterface.viewmodels
2+
3+ import android.app.Application
4+ import android.speech.SpeechRecognizer
5+ import android.speech.tts.TextToSpeech
6+ import android.speech.tts.TextToSpeech.OnInitListener
7+ import android.speech.tts.UtteranceProgressListener
8+ import android.util.Log
9+ import androidx.lifecycle.AndroidViewModel
10+ import androidx.lifecycle.LiveData
11+ import androidx.lifecycle.MutableLiveData
12+ import com.nlinterface.utility.STTInputType
13+ import com.nlinterface.utility.SpeechToTextUtility
14+ import com.nlinterface.utility.TextToSpeechUtility
15+ import kotlinx.coroutines.suspendCancellableCoroutine
16+ import java.util.Locale
17+ import kotlin.coroutines.resume
18+
19+ class BarcodeSettingsViewModel (
20+ application : Application
21+ ) : AndroidViewModel(application), OnInitListener {
22+
23+ private lateinit var tts: TextToSpeechUtility
24+ private val stt = SpeechToTextUtility ()
25+
26+ // holds boolean, whether the TTS system has successfully been initialized.
27+ // private MutableLiveData, so that only the ViewModel has access to the setter
28+ private val _ttsInitialized : MutableLiveData <Boolean > by lazy {
29+ MutableLiveData <Boolean >()
30+ }
31+
32+ // outward LiveData for _ttsInitialized. Immutable, so only access to the getter is given to
33+ // outside classes, and LiveData so that observers can be set.
34+ val ttsInitialized: LiveData <Boolean >
35+ get() = _ttsInitialized
36+
37+ // holds boolean, whether the STT system is currently listening.
38+ private val _isListening : MutableLiveData <Boolean > by lazy {
39+ MutableLiveData <Boolean >()
40+ }
41+
42+ // outward immutable LiveData for _isListening.
43+ val isListening: LiveData <Boolean >
44+ get() = _isListening
45+
46+ // holds the command extracted by the STT system
47+ private val _command : MutableLiveData <String > by lazy {
48+ MutableLiveData <String >()
49+ }
50+
51+ // outward immutable LiveData for _command.
52+ val command: LiveData <String >
53+ get() = _command
54+
55+ // holds the response extracted by the STT system
56+ private val _response : MutableLiveData <String > by lazy {
57+ MutableLiveData <String >()
58+ }
59+
60+ // outward immutable LiveData for _response.
61+ val response: LiveData <String >
62+ get() = _response
63+
64+ /* *
65+ * Initializes TTS variable. Must be done here (thus making tts late-init), because context can
66+ * only be accessed locally for a function.
67+ */
68+ fun initTTS () {
69+ tts = TextToSpeechUtility (getApplication<Application >().applicationContext, this )
70+ }
71+
72+ /* *
73+ * Calls the TTS system to read a given string out loud.
74+ *
75+ * @param text: String, the string to be read out loud.
76+ * @param queueMode: enum, defining how multiple speech outputs are queued.
77+ * TextToSpeech.QUEUE_FLUSH (default) overwrites the queue with the current
78+ * text, thus immediately reading it out loud
79+ * TextToSpeech.QUEUE_ADD appends the current text to the queue, only reading
80+ * it once all prior texts have been read out loud
81+ *
82+ * TODO: error handling
83+ */
84+ fun say (text : String , queueMode : Int = TextToSpeech .QUEUE_FLUSH , utteranceId : String? = "1") {
85+ if (ttsInitialized.value == true ) {
86+ tts.say(text, queueMode, utteranceId)
87+ }
88+ }
89+
90+ /* *
91+ * Reads a given text out loud and waits until the utterance is completed, before resuming. Is
92+ * required when performing an action immediately after the utterance is completed.
93+ *
94+ * @param text: String, the string to be read out loud.
95+ * @param queueMode: enum, defining how multiple speech outputs are queued.
96+ * TextToSpeech.QUEUE_FLUSH (default) overwrites the queue with the current
97+ * text, thus immediately reading it out loud
98+ * TextToSpeech.QUEUE_ADD appends the current text to the queue, only reading
99+ * it once all prior texts have been read out loud
100+ *
101+ * @param utteranceId: String? identifying the utterance to be made and completed
102+ */
103+ suspend fun sayAndAwait (
104+ text : String ,
105+ queueMode : Int = TextToSpeech .QUEUE_FLUSH ,
106+ utteranceId : String? = "1")
107+ = suspendCancellableCoroutine {
108+ tts.setUtteranceProgressListener(object : UtteranceProgressListener () {
109+
110+ override fun onDone (utteranceId : String? ) {
111+ Log .println (Log .DEBUG , " onDone" , " done" )
112+ it.resume(Unit )
113+ }
114+
115+ override fun onStart (utteranceId : String? ) {}
116+ override fun onError (utteranceId : String? ) {}
117+
118+ })
119+
120+ if (ttsInitialized.value == true ) {
121+ tts.say(text, queueMode, utteranceId)
122+ }
123+ }
124+
125+ /* *
126+ * Overrides the TextToSpeech.OnInitListener's onInit function. Called, once the TTS engine
127+ * initialization is completed. If initialization was successful, the TTS engine's locale is
128+ * set to the user's phone locale and the speed rate is set to default. Finally, the
129+ * MutableLiveData _ttsInitialized is set to true. If initialization was unsuccessful, an error
130+ * message is printed to the console output.
131+ *
132+ * @param status: Int representing the success status
133+ *
134+ * TODO: improve error handling
135+ */
136+ override fun onInit (status : Int ) {
137+
138+ if (status == TextToSpeech .SUCCESS ) {
139+ tts.setLocale(Locale .getDefault())
140+ tts.setSpeedRate()
141+ _ttsInitialized .value = true
142+ } else {
143+ Log .println (Log .ERROR , " tts onInit" , " Couldn't initialize TTS Engine" )
144+ }
145+ }
146+
147+ /* *
148+ * Initializes the STT system, by creating the SpeechRecognizer and setting the
149+ * SpeechRecognitionListener to handle Command functionalities.
150+ *
151+ * TODO: improve error handling
152+ */
153+ fun initSTT () {
154+ stt.createSpeechRecognizer(getApplication<Application >().applicationContext)
155+ setSTTSpeechRecognitionListener(STTInputType .COMMAND )
156+ }
157+
158+ /* *
159+ * Defines the functionality of the SpeechRecognitionListener, aka how to handle STT calls.
160+ * Once results are returned by the STT recognizer, listening is cancelled, matches are
161+ * retrieved and their text output can then be handled by this ViewModel. Listening is also
162+ * cancelled once the speech ends or when an error occurs.
163+ *
164+ * @param inputType: STTInputType, defines whether the voiceInput should be handled as a command
165+ * or as an answer to a system question.
166+ */
167+ fun setSTTSpeechRecognitionListener (inputType : STTInputType = STTInputType .COMMAND ) {
168+ stt.setSpeechRecognitionListener(
169+ onResults = {
170+ cancelSTTListening()
171+ val matches = it.getStringArrayList(SpeechRecognizer .RESULTS_RECOGNITION )
172+ if (matches != null && matches.size > 0 ) {
173+ // results are added in decreasing order of confidence to the list,
174+ // so choose the first one
175+ handleSTTResult(matches[0 ], inputType)
176+ }
177+ }, onEndOfSpeech = {
178+ cancelSTTListening()
179+ }, onError = {
180+ cancelSTTListening()
181+ })
182+ }
183+
184+ /* *
185+ * Handles the processing of the results returned by the STT system, depending on the current
186+ * InputType.
187+ *
188+ * @param s: String, output of the STT system onResults
189+ * @param inputType: STTInputType, defines whether the voiceInput should be handled as a command
190+ * or as an answer to a system question.
191+ *
192+ * TODO: streamline processing and command structure
193+ */
194+ private fun handleSTTResult (s : String , inputType : STTInputType ) {
195+
196+ when (inputType) {
197+ STTInputType .COMMAND -> _command .value = s.lowercase()
198+ STTInputType .ANSWER -> _response .value = s.lowercase()
199+ }
200+
201+ }
202+
203+ /* *
204+ * Called when STT begins listening to voice input. Sets _isListening to true and calls on the
205+ * STT system for further handling.
206+ *
207+ * TODO: error handling (What if stt.handleSpeechBegin() is unsuccessful?)
208+ */
209+ fun handleSTTSpeechBegin () {
210+ stt.handleSpeechBegin()
211+ _isListening .value = true
212+ }
213+
214+ /* *
215+ * Called when STT should stop listening. Calls on the STT system for further handling and sets
216+ * _isListening to false
217+ *
218+ * TODO: error handling (What if stt.cancelListening() is unsuccessful?)
219+ */
220+ fun cancelSTTListening () {
221+ stt.cancelListening()
222+ _isListening .value = false
223+ }
224+
225+ }
0 commit comments