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:
onUtteranceCompleted()
always arrives for utterance ID preceding the earcon. That utterance is an ordinary TTS utterance, not an earcon.- The earcon after that does play out (i.e. exactly as intended).
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()
:
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():
- currentSpeechItemCopy == null
- 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:
- By the time an earcon's onCompletion() is called, earcon's
mCurrentSpeechItem
is null. But why? - mCallbacksMap is empty. What is it and when does it ever get populated?
Any suggestions or other explanations for solving this mystery?