Home » Android » android – What in TtsService could explain the lack of onUtteranceCompleted() for playEarcon()?

android – What in TtsService could explain the lack of onUtteranceCompleted() for playEarcon()?

Posted by: admin April 23, 2020 Leave a comment

Questions:

A while ago, I discovered that playEarcon() never produces onUtteranceCompleted().

At the time I just interpreted the documentation that said “Called when an utterance has been synthesized” as onUtteranceCompleted() being not applicable for earcons because, an earcon isn’t really a result of TTS synthesization.

But looking again Android’s source code, I simply can’t find an explanation that would justify my interpretation.

A few facts about my test jig:

  1. onUtteranceCompleted() always arrives for utterance ID preceding the earcon. That utterance is an ordinary TTS utterance, not an earcon.
  2. The earcon after that does play out (i.e. exactly as intended).
  3. onUtteranceCompleted() for that earcon never shows up. This is very consistent and reproducible behavior.

Delving deep into the TtsService source code, there seem to be only 2 methods that could affect the arrival (or absence) of onUtteranceCompleted():

  1. TtsService.processSpeechQueue()
  2. TtsService.onCompletion()

If you examine that code, you will see that a 3rd candidate, TtsService.getSoundResource() is ruled out (as being responsible for the lack of onUtteranceComplete for my earcon) because of fact #2 above: The earcon always plays, hence getSoundResource() cannot possibly return null.

Using the same logic, the 1st candidate, TtsService.processSpeechQueue(), can also be ruled out, for the same fact #2: The earcon always plays, hence the following 2 critical statements are always executed:

1108   mPlayer.setOnCompletionListener(this);
...
1111   mPlayer.start();

So, we are left with the 2nd candidate only, TtsService.onCompletion(), as a possible explanation for why a playEarcon() never produces onUtteranceCompleted():

public void onCompletion(MediaPlayer arg0) {
  // mCurrentSpeechItem may become null if it is stopped at the same
  // time it completes.
  SpeechItem currentSpeechItemCopy = mCurrentSpeechItem;
  if (currentSpeechItemCopy != null) {
    String callingApp = currentSpeechItemCopy.mCallingApp;
    ArrayList<String> params = currentSpeechItemCopy.mParams;
    String utteranceId = "";
    if (params != null) {
      for (int i = 0; i < params.size() - 1; i = i + 2) {
        String param = params.get(i);
        if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) {
          utteranceId = params.get(i + 1);
        }
      }
    }
    if (utteranceId.length() > 0) {
      dispatchUtteranceCompletedCallback(utteranceId, callingApp);
    }
  }
  processSpeechQueue();
}

In there, there are only 2 conditions that would fail to produce dispatchUtteranceCompletedCallback():

  1. currentSpeechItemCopy == null
  2. utteranceId.length() == 0

But I know for sure that condition #2 can be ruled out because I log all utteranceIds and the earcon’s are definitely there.

Also, examining the entire system log:

Log.v(SERVICE_TAG, "TTS callback: dispatch started");

The missing onUtteranceCompleted() could be the result of dispatchUtteranceCompletedCallback() not being called, but it could also be the result of mCallbacksMap.get(packageName) returning null.

So, we are left again with 2 possibilities, both of which don’t make to me much sense:

  1. By the time an earcon’s onCompletion() is called, earcon’s mCurrentSpeechItem is null. But why?
  2. mCallbacksMap is empty. What is it and when does it ever get populated?

Any suggestions or other explanations for solving this mystery?

How to&Answers:

Check android.speech.tts.TextToSpeech#playEarcon() at line 807. The params argument passed to the text-to-speech service binder is null, which means the service never receives your utterance ID.

 public int playEarcon(String earcon, int queueMode,
         HashMap<String,String> params) {
     synchronized (mStartLock) {
         ...
         result = mITts.playEarcon(mPackageName, earcon, queueMode, null);
     }
     ...
 }