Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 69e67a3e authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

Fix bug 2043140.

A race condition is encountered when an application invokes shutdown()
on its TextToSpeech object while is has speak() requests still running.
Since the TTS service destructor releases the synthesizer resources and
sets the corresponding synth reference to null, an NPE was observed.
The fix consists in catching NPEs whenever the sNativeSynth object is
accessed, and return the matching error for the call.
This change is a "low risk" version of the fix for bug 2025765i (same
issue) which was reverted because it was higher risk than this CL:
it affected the logic of each call to sNativeSynth. This CL only sets
an error code when an NPE is fired because sNativeSynth is null.
parent 021fa3fa
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1032,7 +1032,7 @@ public class TextToSpeech {
            }
            try {
                String[] locStrings =  mITts.getLanguage();
                if (locStrings.length == 3) {
                if ((locStrings != null) && (locStrings.length == 3)) {
                    return new Locale(locStrings[0], locStrings[1], locStrings[2]);
                } else {
                    return null;
+79 −18
Original line number Diff line number Diff line
@@ -172,10 +172,18 @@ public class TtsService extends Service implements OnCompletionListener {
    @Override
    public void onDestroy() {
        super.onDestroy();

        // TODO replace the call to stopAll() with a method to clear absolutely all upcoming
        // uses of the native synth, including synthesis to a file, and delete files for which
        // synthesis was not complete.
        stopAll("");

        // Don't hog the media player
        cleanUpPlayer();

        if (sNativeSynth != null) {
            sNativeSynth.shutdown();
        }
        sNativeSynth = null;

        // Unregister all callbacks.
@@ -243,38 +251,70 @@ public class TtsService extends Service implements OnCompletionListener {


    private int setSpeechRate(String callingApp, int rate) {
        int res = TextToSpeech.ERROR;
        try {
            if (isDefaultEnforced()) {
            return sNativeSynth.setSpeechRate(getDefaultRate());
                res = sNativeSynth.setSpeechRate(getDefaultRate());
            } else {
            return sNativeSynth.setSpeechRate(rate);
                res = sNativeSynth.setSpeechRate(rate);
            }
        } catch (NullPointerException e) {
            // synth will become null during onDestroy()
            res = TextToSpeech.ERROR;
        }
        return res;
    }


    private int setPitch(String callingApp, int pitch) {
        return sNativeSynth.setPitch(pitch);
        int res = TextToSpeech.ERROR;
        try {
            res = sNativeSynth.setPitch(pitch);
        } catch (NullPointerException e) {
            // synth will become null during onDestroy()
            res = TextToSpeech.ERROR;
        }
        return res;
    }


    private int isLanguageAvailable(String lang, String country, String variant) {
        //Log.v("TtsService", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")");
        return sNativeSynth.isLanguageAvailable(lang, country, variant);
        int res = TextToSpeech.LANG_NOT_SUPPORTED;
        try {
            res = sNativeSynth.isLanguageAvailable(lang, country, variant);
        } catch (NullPointerException e) {
            // synth will become null during onDestroy()
            res = TextToSpeech.LANG_NOT_SUPPORTED;
        }
        return res;
    }


    private String[] getLanguage() {
        try {
            return sNativeSynth.getLanguage();
        } catch (Exception e) {
            return null;
        }
    }


    private int setLanguage(String callingApp, String lang, String country, String variant) {
        Log.v("TtsService", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")");
        int res = TextToSpeech.ERROR;
        try {
            if (isDefaultEnforced()) {
            return sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(),
                res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(),
                        getDefaultLocVariant());
            } else {
            return sNativeSynth.setLanguage(lang, country, variant);
                res = sNativeSynth.setLanguage(lang, country, variant);
            }
        } catch (NullPointerException e) {
            // synth will become null during onDestroy()
            res = TextToSpeech.ERROR;
        }
        return res;
    }


@@ -402,7 +442,12 @@ public class TtsService extends Service implements OnCompletionListener {
                }
                if ((mCurrentSpeechItem != null) &&
                     mCurrentSpeechItem.mCallingApp.equals(callingApp)) {
                    try {
                        result = sNativeSynth.stop();
                    } catch (NullPointerException e1) {
                        // synth will become null during onDestroy()
                        result = TextToSpeech.ERROR;
                    }
                    mKillList.put(mCurrentSpeechItem, true);
                    if (mPlayer != null) {
                        try {
@@ -434,7 +479,8 @@ public class TtsService extends Service implements OnCompletionListener {


    /**
     * Stops all speech output and removes any utterances still in the queue globally.
     * Stops all speech output and removes any utterances still in the queue globally, except
     * those intended to be synthesized to file.
     */
    private int stopAll(String callingApp) {
        int result = TextToSpeech.ERROR;
@@ -451,7 +497,12 @@ public class TtsService extends Service implements OnCompletionListener {
                if ((mCurrentSpeechItem != null) &&
                    ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) ||
                      mCurrentSpeechItem.mCallingApp.equals(callingApp))) {
                    try {
                        result = sNativeSynth.stop();
                    } catch (NullPointerException e1) {
                        // synth will become null during onDestroy()
                        result = TextToSpeech.ERROR;
                    }
                    mKillList.put(mCurrentSpeechItem, true);
                    if (mPlayer != null) {
                        try {
@@ -591,7 +642,12 @@ public class TtsService extends Service implements OnCompletionListener {
                        if (speechRate.length() > 0){
                            setSpeechRate("", Integer.parseInt(speechRate));
                        }
                        try {
                            sNativeSynth.speak(speechItem.mText, streamType);
                        } catch (NullPointerException e) {
                            // synth will become null during onDestroy()
                            Log.v("TtsService", " null synth, can't speak");
                        }
                    }
                } catch (InterruptedException e) {
                    Log.e("TtsService", "TTS speakInternalOnly(): tryLock interrupted");
@@ -660,7 +716,12 @@ public class TtsService extends Service implements OnCompletionListener {
                        if (speechRate.length() > 0){
                            setSpeechRate("", Integer.parseInt(speechRate));
                        }
                        try {
                            sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename);
                        } catch (NullPointerException e) {
                            // synth will become null during onDestroy()
                            Log.v("TtsService", " null synth, can't synthesize to file");
                        }
                    }
                } catch (InterruptedException e) {
                    Log.e("TtsService", "TTS synthToFileInternalOnly(): tryLock interrupted");