Home | History | Annotate | Download | only in tts
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.tts;
     18 
     19 import android.app.AlertDialog;
     20 import android.content.ActivityNotFoundException;
     21 import android.content.ContentResolver;
     22 import android.content.Intent;
     23 import android.os.Bundle;
     24 import android.provider.Settings.SettingNotFoundException;
     25 import android.speech.tts.TextToSpeech;
     26 import android.speech.tts.TextToSpeech.EngineInfo;
     27 import android.speech.tts.TtsEngines;
     28 import android.speech.tts.UtteranceProgressListener;
     29 import android.support.v14.preference.SwitchPreference;
     30 import android.support.v7.preference.Preference;
     31 import android.support.v7.preference.PreferenceCategory;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 import android.widget.Checkable;
     35 
     36 import com.android.internal.logging.MetricsProto.MetricsEvent;
     37 import com.android.settings.R;
     38 import com.android.settings.SeekBarPreference;
     39 import com.android.settings.SettingsActivity;
     40 import com.android.settings.SettingsPreferenceFragment;
     41 import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
     42 
     43 import java.util.ArrayList;
     44 import java.util.HashMap;
     45 import java.util.List;
     46 import java.util.Locale;
     47 import java.util.MissingResourceException;
     48 import java.util.Objects;
     49 import java.util.Set;
     50 
     51 import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
     52 import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
     53 import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
     54 
     55 public class TextToSpeechSettings extends SettingsPreferenceFragment implements
     56         Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
     57         RadioButtonGroupState {
     58 
     59     private static final String TAG = "TextToSpeechSettings";
     60     private static final boolean DBG = false;
     61 
     62     /** Preference key for the "play TTS example" preference. */
     63     private static final String KEY_PLAY_EXAMPLE = "tts_play_example";;
     64 
     65     /** Preference key for the TTS pitch selection slider. */
     66     private static final String KEY_DEFAULT_PITCH = "tts_default_pitch";
     67 
     68     /** Preference key for the TTS rate selection slider. */
     69     private static final String KEY_DEFAULT_RATE = "tts_default_rate";
     70 
     71     /** Preference key for the TTS reset speech rate preference. */
     72     private static final String KEY_RESET_SPEECH_RATE = "reset_speech_rate";
     73 
     74     /** Preference key for the TTS reset speech pitch preference. */
     75     private static final String KEY_RESET_SPEECH_PITCH = "reset_speech_pitch";
     76 
     77     /** Preference key for the TTS status field. */
     78     private static final String KEY_STATUS = "tts_status";
     79 
     80     /**
     81      * Preference key for the engine selection preference.
     82      */
     83     private static final String KEY_ENGINE_PREFERENCE_SECTION =
     84             "tts_engine_preference_section";
     85 
     86     /**
     87      * These look like birth years, but they aren't mine. I'm much younger than this.
     88      */
     89     private static final int GET_SAMPLE_TEXT = 1983;
     90     private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
     91 
     92     /**
     93      * Speech rate value.
     94      * This value should be kept in sync with the max value set in tts_settings xml.
     95      */
     96     private static final int MAX_SPEECH_RATE = 600;
     97     private static final int MIN_SPEECH_RATE = 10;
     98 
     99     /**
    100      * Speech pitch value.
    101      * TTS pitch value varies from 25 to 400, where 100 is the value
    102      * for normal pitch. The max pitch value is set to 400, based on feedback from users
    103      * and the GoogleTTS pitch variation range. The range for pitch is not set in stone
    104      * and should be readjusted based on user need.
    105      * This value should be kept in sync with the max value set in tts_settings xml.
    106      */
    107     private static final int MAX_SPEECH_PITCH = 400;
    108     private static final int MIN_SPEECH_PITCH = 25;
    109 
    110     private PreferenceCategory mEnginePreferenceCategory;
    111     private SeekBarPreference mDefaultPitchPref;
    112     private SeekBarPreference mDefaultRatePref;
    113     private Preference mResetSpeechRate;
    114     private Preference mResetSpeechPitch;
    115     private Preference mPlayExample;
    116     private Preference mEngineStatus;
    117 
    118     private int mDefaultPitch = TextToSpeech.Engine.DEFAULT_PITCH;
    119     private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
    120 
    121     /**
    122      * The currently selected engine.
    123      */
    124     private String mCurrentEngine;
    125 
    126     /**
    127      * The engine checkbox that is currently checked. Saves us a bit of effort
    128      * in deducing the right one from the currently selected engine.
    129      */
    130     private Checkable mCurrentChecked;
    131 
    132     /**
    133      * The previously selected TTS engine. Useful for rollbacks if the users
    134      * choice is not loaded or fails a voice integrity check.
    135      */
    136     private String mPreviousEngine;
    137 
    138     private TextToSpeech mTts = null;
    139     private TtsEngines mEnginesHelper = null;
    140 
    141     private String mSampleText = null;
    142 
    143     /**
    144      * Default locale used by selected TTS engine, null if not connected to any engine.
    145      */
    146     private Locale mCurrentDefaultLocale;
    147 
    148     /**
    149      * List of available locals of selected TTS engine, as returned by
    150      * {@link TextToSpeech.Engine#ACTION_CHECK_TTS_DATA} activity. If empty, then activity
    151      * was not yet called.
    152      */
    153     private List<String> mAvailableStrLocals;
    154 
    155     /**
    156      * The initialization listener used when we are initalizing the settings
    157      * screen for the first time (as opposed to when a user changes his choice
    158      * of engine).
    159      */
    160     private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() {
    161         @Override
    162         public void onInit(int status) {
    163             onInitEngine(status);
    164         }
    165     };
    166 
    167     /**
    168      * The initialization listener used when the user changes his choice of
    169      * engine (as opposed to when then screen is being initialized for the first
    170      * time).
    171      */
    172     private final TextToSpeech.OnInitListener mUpdateListener = new TextToSpeech.OnInitListener() {
    173         @Override
    174         public void onInit(int status) {
    175             onUpdateEngine(status);
    176         }
    177     };
    178 
    179     @Override
    180     protected int getMetricsCategory() {
    181         return MetricsEvent.TTS_TEXT_TO_SPEECH;
    182     }
    183 
    184     @Override
    185     public void onCreate(Bundle savedInstanceState) {
    186         super.onCreate(savedInstanceState);
    187         addPreferencesFromResource(R.xml.tts_settings);
    188 
    189         getActivity().setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
    190 
    191         mPlayExample = findPreference(KEY_PLAY_EXAMPLE);
    192         mPlayExample.setOnPreferenceClickListener(this);
    193         mPlayExample.setEnabled(false);
    194 
    195         mResetSpeechRate = findPreference(KEY_RESET_SPEECH_RATE);
    196         mResetSpeechRate.setOnPreferenceClickListener(this);
    197         mResetSpeechPitch = findPreference(KEY_RESET_SPEECH_PITCH);
    198         mResetSpeechPitch.setOnPreferenceClickListener(this);
    199 
    200         mEnginePreferenceCategory = (PreferenceCategory) findPreference(
    201                 KEY_ENGINE_PREFERENCE_SECTION);
    202         mDefaultPitchPref = (SeekBarPreference) findPreference(KEY_DEFAULT_PITCH);
    203         mDefaultRatePref = (SeekBarPreference) findPreference(KEY_DEFAULT_RATE);
    204 
    205         mEngineStatus = findPreference(KEY_STATUS);
    206         updateEngineStatus(R.string.tts_status_checking);
    207 
    208         mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
    209         mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
    210 
    211         setTtsUtteranceProgressListener();
    212         initSettings();
    213 
    214         // Prevent restarting the TTS connection on rotation
    215         setRetainInstance(true);
    216     }
    217 
    218     @Override
    219     public void onResume() {
    220         super.onResume();
    221 
    222         if (mTts == null || mCurrentDefaultLocale == null) {
    223             return;
    224         }
    225         Locale ttsDefaultLocale = mTts.getDefaultLanguage();
    226         if (mCurrentDefaultLocale != null && !mCurrentDefaultLocale.equals(ttsDefaultLocale)) {
    227             updateWidgetState(false);
    228             checkDefaultLocale();
    229         }
    230     }
    231 
    232     private void setTtsUtteranceProgressListener() {
    233         if (mTts == null) {
    234             return;
    235         }
    236         mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
    237             @Override
    238             public void onStart(String utteranceId) {}
    239 
    240             @Override
    241             public void onDone(String utteranceId) {}
    242 
    243             @Override
    244             public void onError(String utteranceId) {
    245                 Log.e(TAG, "Error while trying to synthesize sample text");
    246             }
    247         });
    248     }
    249 
    250     @Override
    251     public void onDestroy() {
    252         super.onDestroy();
    253         if (mTts != null) {
    254             mTts.shutdown();
    255             mTts = null;
    256         }
    257     }
    258 
    259     private void initSettings() {
    260         final ContentResolver resolver = getContentResolver();
    261 
    262         // Set up the default rate and pitch.
    263         mDefaultRate = android.provider.Settings.Secure.getInt(
    264             resolver, TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
    265         mDefaultPitch = android.provider.Settings.Secure.getInt(
    266             resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
    267 
    268         mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate));
    269         mDefaultRatePref.setOnPreferenceChangeListener(this);
    270         mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE));
    271 
    272         mDefaultPitchPref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH,
    273               mDefaultPitch));
    274         mDefaultPitchPref.setOnPreferenceChangeListener(this);
    275         mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH,
    276               MAX_SPEECH_PITCH));
    277 
    278         if (mTts != null) {
    279             mCurrentEngine = mTts.getCurrentEngine();
    280             mTts.setSpeechRate(mDefaultRate/100.0f);
    281             mTts.setPitch(mDefaultPitch/100.0f);
    282         }
    283 
    284         SettingsActivity activity = null;
    285         if (getActivity() instanceof SettingsActivity) {
    286             activity = (SettingsActivity) getActivity();
    287         } else {
    288             throw new IllegalStateException("TextToSpeechSettings used outside a " +
    289                     "Settings");
    290         }
    291 
    292         mEnginePreferenceCategory.removeAll();
    293 
    294         List<EngineInfo> engines = mEnginesHelper.getEngines();
    295         for (EngineInfo engine : engines) {
    296             TtsEnginePreference enginePref = new TtsEnginePreference(getPrefContext(), engine,
    297                     this, activity);
    298             mEnginePreferenceCategory.addPreference(enginePref);
    299         }
    300 
    301         checkVoiceData(mCurrentEngine);
    302     }
    303 
    304     /**
    305      * The minimum speech pitch/rate value should be > 0 but the minimum value of a seekbar in
    306      * android is fixed at 0. Therefore, we increment the seekbar progress with MIN_SPEECH_VALUE
    307      * so that the minimum seekbar progress value is MIN_SPEECH_PITCH/RATE.
    308      *     SPEECH_VALUE = MIN_SPEECH_VALUE + SEEKBAR_PROGRESS
    309      */
    310     private int getValueFromSeekBarProgress(String preferenceKey, int progress) {
    311         if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
    312             return MIN_SPEECH_RATE + progress;
    313         } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
    314             return MIN_SPEECH_PITCH + progress;
    315         }
    316         return progress;
    317     }
    318 
    319     /**
    320      * Since we are appending the MIN_SPEECH value to the speech seekbar progress, the
    321      * speech seekbar progress should be set to (speechValue - MIN_SPEECH value).
    322      */
    323     private int getSeekBarProgressFromValue(String preferenceKey, int value) {
    324         if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
    325             return value - MIN_SPEECH_RATE;
    326         } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
    327             return value - MIN_SPEECH_PITCH;
    328         }
    329         return value;
    330     }
    331 
    332     /**
    333      * Called when the TTS engine is initialized.
    334      */
    335     public void onInitEngine(int status) {
    336         if (status == TextToSpeech.SUCCESS) {
    337             if (DBG) Log.d(TAG, "TTS engine for settings screen initialized.");
    338             checkDefaultLocale();
    339         } else {
    340             if (DBG) Log.d(TAG, "TTS engine for settings screen failed to initialize successfully.");
    341             updateWidgetState(false);
    342         }
    343     }
    344 
    345     private void checkDefaultLocale() {
    346         Locale defaultLocale = mTts.getDefaultLanguage();
    347         if (defaultLocale == null) {
    348             Log.e(TAG, "Failed to get default language from engine " + mCurrentEngine);
    349             updateWidgetState(false);
    350             updateEngineStatus(R.string.tts_status_not_supported);
    351             return;
    352         }
    353 
    354         // ISO-3166 alpha 3 country codes are out of spec. If we won't normalize,
    355         // we may end up with English (USA)and German (DEU).
    356         final Locale oldDefaultLocale = mCurrentDefaultLocale;
    357         mCurrentDefaultLocale = mEnginesHelper.parseLocaleString(defaultLocale.toString());
    358         if (!Objects.equals(oldDefaultLocale, mCurrentDefaultLocale)) {
    359             mSampleText = null;
    360         }
    361 
    362         int defaultAvailable = mTts.setLanguage(defaultLocale);
    363         if (evaluateDefaultLocale() && mSampleText == null) {
    364             getSampleText();
    365         }
    366     }
    367 
    368     private boolean evaluateDefaultLocale() {
    369         // Check if we are connected to the engine, and CHECK_VOICE_DATA returned list
    370         // of available languages.
    371         if (mCurrentDefaultLocale == null || mAvailableStrLocals == null) {
    372             return false;
    373         }
    374 
    375         boolean notInAvailableLangauges = true;
    376         try {
    377             // Check if language is listed in CheckVoices Action result as available voice.
    378             String defaultLocaleStr = mCurrentDefaultLocale.getISO3Language();
    379             if (!TextUtils.isEmpty(mCurrentDefaultLocale.getISO3Country())) {
    380                 defaultLocaleStr += "-" + mCurrentDefaultLocale.getISO3Country();
    381             }
    382             if (!TextUtils.isEmpty(mCurrentDefaultLocale.getVariant())) {
    383                 defaultLocaleStr += "-" + mCurrentDefaultLocale.getVariant();
    384             }
    385 
    386             for (String loc : mAvailableStrLocals) {
    387                 if (loc.equalsIgnoreCase(defaultLocaleStr)) {
    388                   notInAvailableLangauges = false;
    389                   break;
    390                 }
    391             }
    392         } catch (MissingResourceException e) {
    393             if (DBG) Log.wtf(TAG, "MissingResourceException", e);
    394             updateEngineStatus(R.string.tts_status_not_supported);
    395             updateWidgetState(false);
    396             return false;
    397         }
    398 
    399         int defaultAvailable = mTts.setLanguage(mCurrentDefaultLocale);
    400         if (defaultAvailable == TextToSpeech.LANG_NOT_SUPPORTED ||
    401                 defaultAvailable == TextToSpeech.LANG_MISSING_DATA ||
    402                 notInAvailableLangauges) {
    403             if (DBG) Log.d(TAG, "Default locale for this TTS engine is not supported.");
    404             updateEngineStatus(R.string.tts_status_not_supported);
    405             updateWidgetState(false);
    406             return false;
    407         } else {
    408             if (isNetworkRequiredForSynthesis()) {
    409                 updateEngineStatus(R.string.tts_status_requires_network);
    410             } else {
    411                 updateEngineStatus(R.string.tts_status_ok);
    412             }
    413             updateWidgetState(true);
    414             return true;
    415         }
    416     }
    417 
    418     /**
    419      * Ask the current default engine to return a string of sample text to be
    420      * spoken to the user.
    421      */
    422     private void getSampleText() {
    423         String currentEngine = mTts.getCurrentEngine();
    424 
    425         if (TextUtils.isEmpty(currentEngine)) currentEngine = mTts.getDefaultEngine();
    426 
    427         // TODO: This is currently a hidden private API. The intent extras
    428         // and the intent action should be made public if we intend to make this
    429         // a public API. We fall back to using a canned set of strings if this
    430         // doesn't work.
    431         Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT);
    432 
    433         intent.putExtra("language", mCurrentDefaultLocale.getLanguage());
    434         intent.putExtra("country", mCurrentDefaultLocale.getCountry());
    435         intent.putExtra("variant", mCurrentDefaultLocale.getVariant());
    436         intent.setPackage(currentEngine);
    437 
    438         try {
    439             if (DBG) Log.d(TAG, "Getting sample text: " + intent.toUri(0));
    440             startActivityForResult(intent, GET_SAMPLE_TEXT);
    441         } catch (ActivityNotFoundException ex) {
    442             Log.e(TAG, "Failed to get sample text, no activity found for " + intent + ")");
    443         }
    444     }
    445 
    446     /**
    447      * Called when voice data integrity check returns
    448      */
    449     @Override
    450     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    451         if (requestCode == GET_SAMPLE_TEXT) {
    452             onSampleTextReceived(resultCode, data);
    453         } else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
    454             onVoiceDataIntegrityCheckDone(data);
    455         }
    456     }
    457 
    458     private String getDefaultSampleString() {
    459         if (mTts != null && mTts.getLanguage() != null) {
    460             try {
    461                 final String currentLang = mTts.getLanguage().getISO3Language();
    462                 String[] strings = getActivity().getResources().getStringArray(
    463                         R.array.tts_demo_strings);
    464                 String[] langs = getActivity().getResources().getStringArray(
    465                         R.array.tts_demo_string_langs);
    466 
    467                 for (int i = 0; i < strings.length; ++i) {
    468                     if (langs[i].equals(currentLang)) {
    469                         return strings[i];
    470                     }
    471                 }
    472             } catch (MissingResourceException e) {
    473                 if (DBG) Log.wtf(TAG, "MissingResourceException", e);
    474                 // Ignore and fall back to default sample string
    475             }
    476         }
    477         return getString(R.string.tts_default_sample_string);
    478     }
    479 
    480     private boolean isNetworkRequiredForSynthesis() {
    481         Set<String> features = mTts.getFeatures(mCurrentDefaultLocale);
    482         if (features == null) {
    483           return false;
    484         }
    485         return features.contains(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS) &&
    486                 !features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
    487     }
    488 
    489     private void onSampleTextReceived(int resultCode, Intent data) {
    490         String sample = getDefaultSampleString();
    491 
    492         if (resultCode == TextToSpeech.LANG_AVAILABLE && data != null) {
    493             if (data != null && data.getStringExtra("sampleText") != null) {
    494                 sample = data.getStringExtra("sampleText");
    495             }
    496             if (DBG) Log.d(TAG, "Got sample text: " + sample);
    497         } else {
    498             if (DBG) Log.d(TAG, "Using default sample text :" + sample);
    499         }
    500 
    501         mSampleText = sample;
    502         if (mSampleText != null) {
    503             updateWidgetState(true);
    504         } else {
    505             Log.e(TAG, "Did not have a sample string for the requested language. Using default");
    506         }
    507     }
    508 
    509     private void speakSampleText() {
    510         final boolean networkRequired = isNetworkRequiredForSynthesis();
    511         if (!networkRequired || networkRequired &&
    512                 (mTts.isLanguageAvailable(mCurrentDefaultLocale) >= TextToSpeech.LANG_AVAILABLE)) {
    513             HashMap<String, String> params = new HashMap<String, String>();
    514             params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "Sample");
    515 
    516             mTts.speak(mSampleText, TextToSpeech.QUEUE_FLUSH, params);
    517         } else {
    518             Log.w(TAG, "Network required for sample synthesis for requested language");
    519             displayNetworkAlert();
    520         }
    521     }
    522 
    523     @Override
    524     public boolean onPreferenceChange(Preference preference, Object objValue) {
    525         if (KEY_DEFAULT_RATE.equals(preference.getKey())) {
    526             updateSpeechRate((Integer) objValue);
    527         } else if (KEY_DEFAULT_PITCH.equals(preference.getKey())) {
    528             updateSpeechPitchValue((Integer) objValue);
    529         }
    530         return true;
    531     }
    532 
    533     /**
    534      * Called when mPlayExample, mResetSpeechRate or mResetSpeechPitch is
    535      * clicked.
    536      */
    537     @Override
    538     public boolean onPreferenceClick(Preference preference) {
    539         if (preference == mPlayExample) {
    540             // Get the sample text from the TTS engine; onActivityResult will do
    541             // the actual speaking
    542             speakSampleText();
    543             return true;
    544         } else if (preference == mResetSpeechRate) {
    545           int speechRateSeekbarProgress = getSeekBarProgressFromValue(
    546               KEY_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
    547           mDefaultRatePref.setProgress(speechRateSeekbarProgress);
    548           updateSpeechRate(speechRateSeekbarProgress);
    549           return true;
    550         } else if (preference == mResetSpeechPitch) {
    551           int pitchSeekbarProgress = getSeekBarProgressFromValue(
    552               KEY_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
    553           mDefaultPitchPref.setProgress(pitchSeekbarProgress);
    554           updateSpeechPitchValue(pitchSeekbarProgress);
    555           return true;
    556         }
    557         return false;
    558     }
    559 
    560     private void updateSpeechRate(int speechRateSeekBarProgress) {
    561         mDefaultRate = getValueFromSeekBarProgress(KEY_DEFAULT_RATE,
    562             speechRateSeekBarProgress);
    563         try {
    564             android.provider.Settings.Secure.putInt(getContentResolver(),
    565                     TTS_DEFAULT_RATE, mDefaultRate);
    566             if (mTts != null) {
    567                 mTts.setSpeechRate(mDefaultRate / 100.0f);
    568             }
    569             if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate);
    570         } catch (NumberFormatException e) {
    571             Log.e(TAG, "could not persist default TTS rate setting", e);
    572         }
    573         return;
    574     }
    575 
    576     private void updateSpeechPitchValue(int speechPitchSeekBarProgress) {
    577         mDefaultPitch = getValueFromSeekBarProgress(KEY_DEFAULT_PITCH,
    578             speechPitchSeekBarProgress);
    579         try {
    580             android.provider.Settings.Secure.putInt(getContentResolver(),
    581                     TTS_DEFAULT_PITCH, mDefaultPitch);
    582             if (mTts != null) {
    583                 mTts.setPitch(mDefaultPitch / 100.0f);
    584             }
    585             if (DBG) Log.d(TAG, "TTS default pitch changed, now" + mDefaultPitch);
    586         } catch (NumberFormatException e) {
    587             Log.e(TAG, "could not persist default TTS pitch setting", e);
    588         }
    589         return;
    590     }
    591 
    592     private void updateWidgetState(boolean enable) {
    593         mPlayExample.setEnabled(enable);
    594         mDefaultRatePref.setEnabled(enable);
    595         mEngineStatus.setEnabled(enable);
    596     }
    597 
    598     private void updateEngineStatus(int resourceId) {
    599         Locale locale = mCurrentDefaultLocale;
    600         if (locale == null) {
    601             locale = Locale.getDefault();
    602         }
    603         mEngineStatus.setSummary(getString(resourceId, locale.getDisplayName()));
    604     }
    605 
    606     private void displayNetworkAlert() {
    607         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    608         builder.setTitle(android.R.string.dialog_alert_title)
    609                 .setMessage(getActivity().getString(R.string.tts_engine_network_required))
    610                 .setCancelable(false)
    611                 .setPositiveButton(android.R.string.ok, null);
    612 
    613         AlertDialog dialog = builder.create();
    614         dialog.show();
    615     }
    616 
    617     private void updateDefaultEngine(String engine) {
    618         if (DBG) Log.d(TAG, "Updating default synth to : " + engine);
    619 
    620         // Disable the "play sample text" preference and the speech
    621         // rate preference while the engine is being swapped.
    622         updateWidgetState(false);
    623         updateEngineStatus(R.string.tts_status_checking);
    624 
    625         // Keep track of the previous engine that was being used. So that
    626         // we can reuse the previous engine.
    627         //
    628         // Note that if TextToSpeech#getCurrentEngine is not null, it means at
    629         // the very least that we successfully bound to the engine service.
    630         mPreviousEngine = mTts.getCurrentEngine();
    631 
    632         // Step 1: Shut down the existing TTS engine.
    633         if (mTts != null) {
    634             try {
    635                 mTts.shutdown();
    636                 mTts = null;
    637             } catch (Exception e) {
    638                 Log.e(TAG, "Error shutting down TTS engine" + e);
    639             }
    640         }
    641 
    642         // Step 2: Connect to the new TTS engine.
    643         // Step 3 is continued on #onUpdateEngine (below) which is called when
    644         // the app binds successfully to the engine.
    645         if (DBG) Log.d(TAG, "Updating engine : Attempting to connect to engine: " + engine);
    646         mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine);
    647         setTtsUtteranceProgressListener();
    648     }
    649 
    650     /*
    651      * Step 3: We have now bound to the TTS engine the user requested. We will
    652      * attempt to check voice data for the engine if we successfully bound to it,
    653      * or revert to the previous engine if we didn't.
    654      */
    655     public void onUpdateEngine(int status) {
    656         if (status == TextToSpeech.SUCCESS) {
    657             if (DBG) {
    658                 Log.d(TAG, "Updating engine: Successfully bound to the engine: " +
    659                         mTts.getCurrentEngine());
    660             }
    661             checkVoiceData(mTts.getCurrentEngine());
    662         } else {
    663             if (DBG) Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
    664             if (mPreviousEngine != null) {
    665                 // This is guaranteed to at least bind, since mPreviousEngine would be
    666                 // null if the previous bind to this engine failed.
    667                 mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener,
    668                         mPreviousEngine);
    669                 setTtsUtteranceProgressListener();
    670             }
    671             mPreviousEngine = null;
    672         }
    673     }
    674 
    675     /*
    676      * Step 4: Check whether the voice data for the engine is ok.
    677      */
    678     private void checkVoiceData(String engine) {
    679         Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
    680         intent.setPackage(engine);
    681         try {
    682             if (DBG) Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0));
    683             startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK);
    684         } catch (ActivityNotFoundException ex) {
    685             Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")");
    686         }
    687     }
    688 
    689     /*
    690      * Step 5: The voice data check is complete.
    691      */
    692     private void onVoiceDataIntegrityCheckDone(Intent data) {
    693         final String engine = mTts.getCurrentEngine();
    694 
    695         if (engine == null) {
    696             Log.e(TAG, "Voice data check complete, but no engine bound");
    697             return;
    698         }
    699 
    700         if (data == null){
    701             Log.e(TAG, "Engine failed voice data integrity check (null return)" +
    702                     mTts.getCurrentEngine());
    703             return;
    704         }
    705 
    706         android.provider.Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine);
    707 
    708         mAvailableStrLocals = data.getStringArrayListExtra(
    709             TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
    710         if (mAvailableStrLocals == null) {
    711             Log.e(TAG, "Voice data check complete, but no available voices found");
    712             // Set mAvailableStrLocals to empty list
    713             mAvailableStrLocals = new ArrayList<String>();
    714         }
    715         if (evaluateDefaultLocale()) {
    716             getSampleText();
    717         }
    718 
    719         final int engineCount = mEnginePreferenceCategory.getPreferenceCount();
    720         for (int i = 0; i < engineCount; ++i) {
    721             final Preference p = mEnginePreferenceCategory.getPreference(i);
    722             if (p instanceof TtsEnginePreference) {
    723                 TtsEnginePreference enginePref = (TtsEnginePreference) p;
    724                 if (enginePref.getKey().equals(engine)) {
    725                     enginePref.setVoiceDataDetails(data);
    726                     break;
    727                 }
    728             }
    729         }
    730     }
    731 
    732     @Override
    733     public Checkable getCurrentChecked() {
    734         return mCurrentChecked;
    735     }
    736 
    737     @Override
    738     public String getCurrentKey() {
    739         return mCurrentEngine;
    740     }
    741 
    742     @Override
    743     public void setCurrentChecked(Checkable current) {
    744         mCurrentChecked = current;
    745     }
    746 
    747     @Override
    748     public void setCurrentKey(String key) {
    749         mCurrentEngine = key;
    750         updateDefaultEngine(mCurrentEngine);
    751     }
    752 
    753 }
    754