Android SpeechRecognizer: cancel() and stopListening() being completely ignored (but only at Android 9 and 10)

1.2k Views Asked by At

I have a project using RecognitionListener written in Kotlin. The speech-to-text function is a success but there are some problems after testing it at an Android 9 or 10 device.

After I start listening with startListening() function, it actually stops after some time of inactivity (within 1 second). I added some functions so the user can stop with a button and have the results, or cancel with a button and ignore the results. It worked pretty well testing at an Android 8 device. But when I tested at an Android 10 device, it never worked. (tested on Android 9 too and different devices with Android 10... same problem)

Here is the VoiceRecognition speech-to-text class code:

package com.primelan.primebot.botVoiceRecognition.voice_recognition

import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.speech.RecognitionListener
import android.speech.RecognizerIntent
import android.speech.SpeechRecognizer
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

class VoiceRecognition(private val activity: Activity, language: String = "pt_BR") : RecognitionListener {

    private val AudioLogTag = "AudioInput"

    var voiceRecognitionIntentHandler: VoiceRecognitionIntentHandler? = null
    var voiceRecognitionOnResultListener: VoiceRecognitionOnResultListener? = null //Must have this
    var voiceRecognitionLayoutChanger: VoiceRecognitionLayoutChanger? = null

    var isListening = false

    private var enableResult = true
    private val intent: Intent
    private var speech: SpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(activity)

    init {
        speech.setRecognitionListener(this)

        intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
        intent.putExtra(
            RecognizerIntent.EXTRA_LANGUAGE_MODEL,
            RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
        )
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language)
    }

    //It is important to put this function inside a clickListener
    fun listen(): Boolean {
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
            return false
        }

        speech.startListening(intent)

        Log.i(AudioLogTag, "startListening")

        return true
    }

    //Use this if you want to stop listening but still get recognition results
    fun endListening(){
        Log.i(AudioLogTag, "stopListening")

        speech.stopListening()
        isListening = false
    }

    fun cancelListening(){
        Log.i(AudioLogTag, "cancelListening")

        speech.cancel()
        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false
    }

    override fun onReadyForSpeech(p0: Bundle?) {
        Log.i(AudioLogTag, "onReadyForSpeech")

        voiceRecognitionLayoutChanger?.startListeningChangeLayout()
        isListening = true
    }

    override fun onRmsChanged(p0: Float) {
//        Log.i(LOG_TAG, "onRmsChanged: $p0")
//        progressBar.setProgress((Int) p0)
    }

    override fun onBufferReceived(p0: ByteArray?) {
        Log.i(AudioLogTag, "onBufferReceived: $p0")
    }

    override fun onPartialResults(p0: Bundle?) {
        Log.i(AudioLogTag, "onPartialResults")
    }

    override fun onEvent(p0: Int, p1: Bundle?) {
        Log.i(AudioLogTag, "onEvent")
    }

    override fun onBeginningOfSpeech() {
        Log.i(AudioLogTag, "onBeginningOfSpeech")
    }

    override fun onEndOfSpeech() {
        Log.i(AudioLogTag, "onEndOfSpeech")

        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false

        enableResult = true
    }

    override fun onError(p0: Int) {
        speech.cancel()
        val errorMessage = getErrorText(p0)
        Log.d(AudioLogTag, "FAILED: $errorMessage")
        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false
    }

    override fun onResults(p0: Bundle?) {
        if(enableResult) {
            enableResult = false
            Log.i(AudioLogTag, "onResults")

            val results: ArrayList<String> =
                p0?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION) as ArrayList<String>

            val voiceIntent: Int? = voiceRecognitionIntentHandler?.getIntent(results[0])
            if (voiceIntent != null && voiceIntent != 0) {
                voiceRecognitionIntentHandler?.handle(voiceIntent)
                return
            }

            voiceRecognitionOnResultListener!!.onResult(results[0])
        }
    }

    private fun getErrorText(errorCode: Int): String {
        val message: String
        when (errorCode) {
            SpeechRecognizer.ERROR_AUDIO -> message = "Audio recording error"

            SpeechRecognizer.ERROR_CLIENT -> message = "Client side error"

            SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS -> message = "Insufficient permissions"

            SpeechRecognizer.ERROR_NETWORK -> message = "Network error"

            SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> message = "Network timeout"

            SpeechRecognizer.ERROR_NO_MATCH -> message = "No match"

            SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> message = "RecognitionService busy"

            SpeechRecognizer.ERROR_SERVER -> message = "Error from server"

            SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> message = "No speech input"

            else -> message = "Didn't understand, please try again."
        }
        return message
    }

    //Use it in your overriden onPause function.
    fun onPause() {
        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false

        speech.cancel()
        Log.i(AudioLogTag, "pause")
    }

    //Use it in your overriden onDestroy function.
    fun onDestroy() {
        speech.destroy()
    }
}

As you can see, the functions that I mentioned are endListening() and cancelListening(). When those functions are called, you can actually see the Log, but the cancel() or the stopListening() won't have any effects if the current device is using android 9 or 10. Even the line voiceRecognitionLayoutChanger?.endListeningChangeLayout() from cancelListening() is executed and brings the desired results (changes a layout inside the app).

Is there a problem with android.speech library? Or... is there a way to fix it?

0

There are 0 best solutions below