182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler/* 282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Copyright (C) 2015 The Android Open Source Project 382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * 482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Licensed under the Apache License, Version 2.0 (the "License"); 582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * you may not use this file except in compliance with the License. 682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * You may obtain a copy of the License at 782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * 882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * http://www.apache.org/licenses/LICENSE-2.0 982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * 1082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Unless required by applicable law or agreed to in writing, software 1182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * distributed under the License is distributed on an "AS IS" BASIS, 1282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * See the License for the specific language governing permissions and 1482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * limitations under the License 1582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 1682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 1782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerpackage com.android.tv.settings.system; 1882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 1982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.app.AlertDialog; 2082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.content.ActivityNotFoundException; 2182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.content.ContentResolver; 2282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.content.Intent; 2382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.os.Bundle; 2482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.provider.Settings; 2582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.speech.tts.TextToSpeech; 2682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.speech.tts.TtsEngines; 2782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.speech.tts.UtteranceProgressListener; 2882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.support.v17.preference.LeanbackPreferenceFragment; 2982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.support.v7.preference.ListPreference; 3082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.support.v7.preference.Preference; 3182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.support.v7.preference.PreferenceCategory; 3282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.text.TextUtils; 3382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.util.Log; 3482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport android.widget.Checkable; 3582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 3682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport com.android.tv.settings.R; 3782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 3882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.ArrayList; 3982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.HashMap; 4082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.List; 4182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.Locale; 4282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.MissingResourceException; 4382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.Objects; 4482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerimport java.util.Set; 4582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 4682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantlerpublic class TextToSpeechFragment extends LeanbackPreferenceFragment implements 4782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, 4882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler TtsEnginePreference.RadioButtonGroupState { 4982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final String TAG = "TextToSpeechSettings"; 5082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final boolean DBG = false; 5182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 5282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** Preference key for the engine settings preference */ 5382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings"; 5482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 5582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** Preference key for the "play TTS example" preference. */ 5682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final String KEY_PLAY_EXAMPLE = "tts_play_example"; 5782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 5882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** Preference key for the TTS rate selection dialog. */ 5982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final String KEY_DEFAULT_RATE = "tts_default_rate"; 6082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 6182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** Preference key for the TTS status field. */ 6282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final String KEY_STATUS = "tts_status"; 6382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 6482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 6582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Preference key for the engine selection preference. 6682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 6782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final String KEY_ENGINE_PREFERENCE_SECTION = 6882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler "tts_engine_preference_section"; 6982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 7082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 7182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * These look like birth years, but they aren't mine. I'm much younger than this. 7282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 7382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final int GET_SAMPLE_TEXT = 1983; 7482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; 7582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 7682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private PreferenceCategory mEnginePreferenceCategory; 7782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private Preference mEngineSettingsPref; 7882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private ListPreference mDefaultRatePref; 7982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private Preference mPlayExample; 8082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private Preference mEngineStatus; 8182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 8282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE; 8382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 8482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 8582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * The currently selected engine. 8682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 8782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private String mCurrentEngine; 8882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 8982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 9082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * The engine checkbox that is currently checked. Saves us a bit of effort 9182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * in deducing the right one from the currently selected engine. 9282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 9382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private Checkable mCurrentChecked; 9482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 9582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 9682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * The previously selected TTS engine. Useful for rollbacks if the users 9782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * choice is not loaded or fails a voice integrity check. 9882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 9982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private String mPreviousEngine; 10082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 10182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private TextToSpeech mTts = null; 10282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private TtsEngines mEnginesHelper = null; 10382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 10482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private String mSampleText = null; 10582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 10682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 10782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Default locale used by selected TTS engine, null if not connected to any engine. 10882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 10982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private Locale mCurrentDefaultLocale; 11082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 11182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 11282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * List of available locals of selected TTS engine, as returned by 11382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * {@link TextToSpeech.Engine#ACTION_CHECK_TTS_DATA} activity. If empty, then activity 11482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * was not yet called. 11582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 11682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private List<String> mAvailableStrLocals; 11782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 11882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 11982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * The initialization listener used when we are initalizing the settings 12082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * screen for the first time (as opposed to when a user changes his choice 12182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * of engine). 12282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 12382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() { 12482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 12582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onInit(int status) { 12682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler onInitEngine(status); 12782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 12882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler }; 12982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 13082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 13182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * The initialization listener used when the user changes his choice of 13282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * engine (as opposed to when then screen is being initialized for the first 13382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * time). 13482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 13582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private final TextToSpeech.OnInitListener mUpdateListener = new TextToSpeech.OnInitListener() { 13682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 13782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onInit(int status) { 13882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler onUpdateEngine(status); 13982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 14082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler }; 14182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 14282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 14382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 14482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler addPreferencesFromResource(R.xml.tts_settings); 14582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 14682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEngineSettingsPref = findPreference(KEY_ENGINE_SETTINGS); 14782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 14882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPlayExample = findPreference(KEY_PLAY_EXAMPLE); 14982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPlayExample.setOnPreferenceClickListener(this); 15082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPlayExample.setEnabled(false); 15182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 15282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEnginePreferenceCategory = (PreferenceCategory) findPreference( 15382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler KEY_ENGINE_PREFERENCE_SECTION); 15482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRatePref = (ListPreference) findPreference(KEY_DEFAULT_RATE); 15582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 15682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEngineStatus = findPreference(KEY_STATUS); 15782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_checking); 15882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 15982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 16082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 16182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onCreate(Bundle savedInstanceState) { 16282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler super.onCreate(savedInstanceState); 16382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 16482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler getActivity().setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); 16582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 16682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener); 16782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEnginesHelper = new TtsEngines(getActivity().getApplicationContext()); 16882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 16982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler setTtsUtteranceProgressListener(); 17082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler initSettings(); 17182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 17282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 17382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 17482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onResume() { 17582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler super.onResume(); 17682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 17782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mTts == null || mCurrentDefaultLocale == null) { 17882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return; 17982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 18082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Locale ttsDefaultLocale = mTts.getDefaultLanguage(); 18182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (!mCurrentDefaultLocale.equals(ttsDefaultLocale)) { 18282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(false); 18382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler checkDefaultLocale(); 18482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 18582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 18682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 18782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void setTtsUtteranceProgressListener() { 18882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mTts == null) { 18982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return; 19082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 19182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() { 19282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 19382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onStart(String utteranceId) {} 19482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 19582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 19682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onDone(String utteranceId) {} 19782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 19882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 19982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onError(String utteranceId) { 20082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Error while trying to synthesize sample text"); 20182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 20282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler }); 20382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 20482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 20582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 20682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onDestroy() { 20782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler super.onDestroy(); 20882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mTts != null) { 20982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.shutdown(); 21082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts = null; 21182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 21282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 21382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 21482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void initSettings() { 21582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler final ContentResolver resolver = getActivity().getContentResolver(); 21682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 21782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Set up the default rate. 21882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 21982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRate = android.provider.Settings.Secure.getInt(resolver, 22082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Settings.Secure.TTS_DEFAULT_RATE); 22182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (Settings.SettingNotFoundException e) { 22282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Default rate setting not found, initialize it 22382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE; 22482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 22582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRatePref.setValue(String.valueOf(mDefaultRate)); 22682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRatePref.setOnPreferenceChangeListener(this); 22782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 22882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mCurrentEngine = mTts.getCurrentEngine(); 22982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 23082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEnginePreferenceCategory.removeAll(); 23182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 23282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler List<TextToSpeech.EngineInfo> engines = mEnginesHelper.getEngines(); 23382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler for (TextToSpeech.EngineInfo engine : engines) { 23482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler TtsEnginePreference enginePref = 23582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler new TtsEnginePreference(getPreferenceManager().getContext(), engine, 23682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler this); 23782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEnginePreferenceCategory.addPreference(enginePref); 23882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 23982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 24082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler checkVoiceData(mCurrentEngine); 24182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 24282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 24382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 24482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Called when the TTS engine is initialized. 24582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 24682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onInitEngine(int status) { 24782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (status == TextToSpeech.SUCCESS) { 24882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "TTS engine for settings screen initialized."); 24982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler checkDefaultLocale(); 25082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 25182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "TTS engine for settings screen failed to initialize successfully."); 25282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(false); 25382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 25482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 25582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 25682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void checkDefaultLocale() { 25782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Locale defaultLocale = mTts.getDefaultLanguage(); 25882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (defaultLocale == null) { 25982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Failed to get default language from engine " + mCurrentEngine); 26082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(false); 26182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_not_supported); 26282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return; 26382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 26482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 26582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // ISO-3166 alpha 3 country codes are out of spec. If we won't normalize, 26682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // we may end up with English (USA)and German (DEU). 26782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler final Locale oldDefaultLocale = mCurrentDefaultLocale; 26882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mCurrentDefaultLocale = mEnginesHelper.parseLocaleString(defaultLocale.toString()); 26982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (!Objects.equals(oldDefaultLocale, mCurrentDefaultLocale)) { 27082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mSampleText = null; 27182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 27282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 27382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.setLanguage(defaultLocale); 27482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (evaluateDefaultLocale() && mSampleText == null) { 27582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler getSampleText(); 27682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 27782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 27882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 27982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private boolean evaluateDefaultLocale() { 28082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Check if we are connected to the engine, and CHECK_VOICE_DATA returned list 28182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // of available languages. 28282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mCurrentDefaultLocale == null || mAvailableStrLocals == null) { 28382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return false; 28482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 28582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 28682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler boolean notInAvailableLangauges = true; 28782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 28882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Check if language is listed in CheckVoices Action result as available voice. 28982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler String defaultLocaleStr = mCurrentDefaultLocale.getISO3Language(); 29082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (!TextUtils.isEmpty(mCurrentDefaultLocale.getISO3Country())) { 29182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler defaultLocaleStr += "-" + mCurrentDefaultLocale.getISO3Country(); 29282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 29382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (!TextUtils.isEmpty(mCurrentDefaultLocale.getVariant())) { 29482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler defaultLocaleStr += "-" + mCurrentDefaultLocale.getVariant(); 29582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 29682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 29782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler for (String loc : mAvailableStrLocals) { 29882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (loc.equalsIgnoreCase(defaultLocaleStr)) { 29982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler notInAvailableLangauges = false; 30082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler break; 30182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 30282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 30382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (MissingResourceException e) { 30482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.wtf(TAG, "MissingResourceException", e); 30582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_not_supported); 30682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(false); 30782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return false; 30882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 30982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 31082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler int defaultAvailable = mTts.setLanguage(mCurrentDefaultLocale); 31182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (defaultAvailable == TextToSpeech.LANG_NOT_SUPPORTED || 31282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler defaultAvailable == TextToSpeech.LANG_MISSING_DATA || 31382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler notInAvailableLangauges) { 31482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Default locale for this TTS engine is not supported."); 31582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_not_supported); 31682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(false); 31782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return false; 31882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 31982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (isNetworkRequiredForSynthesis()) { 32082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_requires_network); 32182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 32282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_ok); 32382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 32482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(true); 32582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return true; 32682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 32782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 32882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 32982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 33082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Ask the current default engine to return a string of sample text to be 33182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * spoken to the user. 33282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 33382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void getSampleText() { 33482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler String currentEngine = mTts.getCurrentEngine(); 33582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 33682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (TextUtils.isEmpty(currentEngine)) currentEngine = mTts.getDefaultEngine(); 33782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 33882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // TODO: This is currently a hidden private API. The intent extras 33982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // and the intent action should be made public if we intend to make this 34082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // a public API. We fall back to using a canned set of strings if this 34182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // doesn't work. 34282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT); 34382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 34482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler intent.putExtra("language", mCurrentDefaultLocale.getLanguage()); 34582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler intent.putExtra("country", mCurrentDefaultLocale.getCountry()); 34682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler intent.putExtra("variant", mCurrentDefaultLocale.getVariant()); 34782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler intent.setPackage(currentEngine); 34882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 34982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 35082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Getting sample text: " + intent.toUri(0)); 35182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler startActivityForResult(intent, GET_SAMPLE_TEXT); 35282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (ActivityNotFoundException ex) { 35382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Failed to get sample text, no activity found for " + intent + ")"); 35482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 35582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 35682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 35782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 35882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Called when voice data integrity check returns 35982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 36082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 36182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onActivityResult(int requestCode, int resultCode, Intent data) { 36282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (requestCode == GET_SAMPLE_TEXT) { 36382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler onSampleTextReceived(resultCode, data); 36482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { 36582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler onVoiceDataIntegrityCheckDone(data); 36682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 36782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 36882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 36982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private String getDefaultSampleString() { 37082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mTts != null && mTts.getLanguage() != null) { 37182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 37282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler final String currentLang = mTts.getLanguage().getISO3Language(); 37382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler String[] strings = getActivity().getResources().getStringArray( 37482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler R.array.tts_demo_strings); 37582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler String[] langs = getActivity().getResources().getStringArray( 37682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler R.array.tts_demo_string_langs); 37782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 37882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler for (int i = 0; i < strings.length; ++i) { 37982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (langs[i].equals(currentLang)) { 38082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return strings[i]; 38182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 38282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 38382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (MissingResourceException e) { 38482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.wtf(TAG, "MissingResourceException", e); 38582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Ignore and fall back to default sample string 38682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 38782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 38882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return getString(R.string.tts_default_sample_string); 38982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 39082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 39182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private boolean isNetworkRequiredForSynthesis() { 39282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Set<String> features = mTts.getFeatures(mCurrentDefaultLocale); 39382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return features != null && 39482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler features.contains(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS) && 39582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler !features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS); 39682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 39782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 39882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void onSampleTextReceived(int resultCode, Intent data) { 39982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler String sample = getDefaultSampleString(); 40082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 40182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (resultCode == TextToSpeech.LANG_AVAILABLE && data != null) { 40282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (data.getStringExtra("sampleText") != null) { 40382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler sample = data.getStringExtra("sampleText"); 40482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 40582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Got sample text: " + sample); 40682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 40782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Using default sample text :" + sample); 40882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 40982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 41082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mSampleText = sample; 41182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mSampleText != null) { 41282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(true); 41382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 41482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Did not have a sample string for the requested language. Using default"); 41582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 41682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 41782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 41882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void speakSampleText() { 41982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler final boolean networkRequired = isNetworkRequiredForSynthesis(); 42082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (!networkRequired || 42182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.isLanguageAvailable(mCurrentDefaultLocale) >= TextToSpeech.LANG_AVAILABLE) { 42282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler HashMap<String, String> params = new HashMap<>(); 42382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "Sample"); 42482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 42582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.speak(mSampleText, TextToSpeech.QUEUE_FLUSH, params); 42682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 42782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.w(TAG, "Network required for sample synthesis for requested language"); 42882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler displayNetworkAlert(); 42982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 43082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 43182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 43282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 43382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public boolean onPreferenceChange(Preference preference, Object objValue) { 43482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (KEY_DEFAULT_RATE.equals(preference.getKey())) { 43582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Default rate 43682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRate = Integer.parseInt((String) objValue); 43782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 43882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler android.provider.Settings.Secure.putInt(getActivity().getContentResolver(), 43982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Settings.Secure.TTS_DEFAULT_RATE, mDefaultRate); 44082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mTts != null) { 44182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.setSpeechRate(mDefaultRate / 100.0f); 44282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 44382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate); 44482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (NumberFormatException e) { 44582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "could not persist default TTS rate setting", e); 44682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 44782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 44882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 44982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return true; 45082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 45182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 45282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /** 45382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Called when mPlayExample is clicked 45482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 45582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 45682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public boolean onPreferenceClick(Preference preference) { 45782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (preference == mPlayExample) { 45882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Get the sample text from the TTS engine; onActivityResult will do 45982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // the actual speaking 46082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler speakSampleText(); 46182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return true; 46282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 46382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 46482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return false; 46582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 46682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 46782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void updateWidgetState(boolean enable) { 46882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEngineSettingsPref.setEnabled(enable); 46982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPlayExample.setEnabled(enable); 47082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mDefaultRatePref.setEnabled(enable); 47182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEngineStatus.setEnabled(enable); 47282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 47382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 47482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void updateEngineStatus(int resourceId) { 47582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Locale locale = mCurrentDefaultLocale; 47682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (locale == null) { 47782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler locale = Locale.getDefault(); 47882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 47982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mEngineStatus.setSummary(getString(resourceId, locale.getDisplayName())); 48082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 48182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 48282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void displayNetworkAlert() { 48382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 48482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler builder.setTitle(android.R.string.dialog_alert_title) 48582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler .setMessage(getActivity().getString(R.string.tts_engine_network_required)) 48682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler .setCancelable(false) 48782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler .setPositiveButton(android.R.string.ok, null); 48882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 48982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler AlertDialog dialog = builder.create(); 49082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler dialog.show(); 49182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 49282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 49382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void updateDefaultEngine(String engine) { 49482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Updating default synth to : " + engine); 49582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 49682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Disable the "play sample text" preference and the speech 49782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // rate preference while the engine is being swapped. 49882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateWidgetState(false); 49982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateEngineStatus(R.string.tts_status_checking); 50082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 50182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Keep track of the previous engine that was being used. So that 50282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // we can reuse the previous engine. 50382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // 50482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Note that if TextToSpeech#getCurrentEngine is not null, it means at 50582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // the very least that we successfully bound to the engine service. 50682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPreviousEngine = mTts.getCurrentEngine(); 50782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 50882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Step 1: Shut down the existing TTS engine. 50982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 51082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.shutdown(); 51182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts = null; 51282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (Exception e) { 51382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Error shutting down TTS engine" + e); 51482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 51582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 51682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Step 2: Connect to the new TTS engine. 51782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Step 3 is continued on #onUpdateEngine (below) which is called when 51882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // the app binds successfully to the engine. 51982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Updating engine : Attempting to connect to engine: " + engine); 52082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine); 52182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler setTtsUtteranceProgressListener(); 52282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 52382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 52482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /* 52582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Step 3: We have now bound to the TTS engine the user requested. We will 52682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * attempt to check voice data for the engine if we successfully bound to it, 52782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * or revert to the previous engine if we didn't. 52882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 52982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void onUpdateEngine(int status) { 53082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (status == TextToSpeech.SUCCESS) { 53182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) { 53282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.d(TAG, "Updating engine: Successfully bound to the engine: " + 53382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.getCurrentEngine()); 53482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 53582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler checkVoiceData(mTts.getCurrentEngine()); 53682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } else { 53782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Updating engine: Failed to bind to engine, reverting."); 53882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mPreviousEngine != null) { 53982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // This is guaranteed to at least bind, since mPreviousEngine would be 54082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // null if the previous bind to this engine failed. 54182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener, 54282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPreviousEngine); 54382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler setTtsUtteranceProgressListener(); 54482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 54582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mPreviousEngine = null; 54682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 54782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 54882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 54982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /* 55082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Step 4: Check whether the voice data for the engine is ok. 55182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 55282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void checkVoiceData(String engine) { 55382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); 55482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler intent.setPackage(engine); 55582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler try { 55682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (DBG) Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0)); 55782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK); 55882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } catch (ActivityNotFoundException ex) { 55982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")"); 56082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 56182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 56282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 56382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler /* 56482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler * Step 5: The voice data check is complete. 56582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler */ 56682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler private void onVoiceDataIntegrityCheckDone(Intent data) { 56782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler final String engine = mTts.getCurrentEngine(); 56882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 56982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (engine == null) { 57082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Voice data check complete, but no engine bound"); 57182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return; 57282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 57382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 57482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (data == null){ 57582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Engine failed voice data integrity check (null return)" + 57682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mTts.getCurrentEngine()); 57782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return; 57882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 57982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 58082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler android.provider.Settings.Secure.putString(getActivity().getContentResolver(), 58182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Settings.Secure.TTS_DEFAULT_SYNTH, engine); 58282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 58382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mAvailableStrLocals = data.getStringArrayListExtra( 58482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); 58582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (mAvailableStrLocals == null) { 58682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler Log.e(TAG, "Voice data check complete, but no available voices found"); 58782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler // Set mAvailableStrLocals to empty list 58882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mAvailableStrLocals = new ArrayList<String>(); 58982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 59082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler if (evaluateDefaultLocale()) { 59182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler getSampleText(); 59282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 59382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 59482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler final TextToSpeech.EngineInfo engineInfo = mEnginesHelper.getEngineInfo(engine); 59582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler TtsEngineSettingsFragment.prepareArgs(mEngineSettingsPref.getExtras(), 59682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler engineInfo.name, engineInfo.label, data); 59782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 59882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 59982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 60082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public Checkable getCurrentChecked() { 60182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return mCurrentChecked; 60282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 60382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 60482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 60582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public String getCurrentKey() { 60682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler return mCurrentEngine; 60782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 60882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 60982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 61082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void setCurrentChecked(Checkable current) { 61182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mCurrentChecked = current; 61282a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 61382a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 61482a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler @Override 61582a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler public void setCurrentKey(String key) { 61682a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler mCurrentEngine = key; 61782a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler updateDefaultEngine(mCurrentEngine); 61882a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler } 61982a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 62082a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler 62182a7d036da6c8372a42c1d6bde48ea174d0d3e49Tony Mantler} 622