1 package com.android.settings.tts; 2 3 import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH; 4 5 import android.app.settings.SettingsEnums; 6 import android.content.Context; 7 import android.os.Bundle; 8 import android.provider.SearchIndexableResource; 9 import android.speech.tts.TextToSpeech; 10 import android.speech.tts.TextToSpeech.EngineInfo; 11 import android.speech.tts.TtsEngines; 12 import android.util.Log; 13 import android.widget.Checkable; 14 15 import androidx.preference.PreferenceCategory; 16 17 import com.android.settings.R; 18 import com.android.settings.SettingsPreferenceFragment; 19 import com.android.settings.search.BaseSearchIndexProvider; 20 import com.android.settings.search.Indexable; 21 import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState; 22 import com.android.settingslib.search.SearchIndexable; 23 24 import java.util.Arrays; 25 import java.util.List; 26 27 @SearchIndexable 28 public class TtsEnginePreferenceFragment extends SettingsPreferenceFragment 29 implements RadioButtonGroupState { 30 private static final String TAG = "TtsEnginePrefFragment"; 31 32 private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; 33 34 /** The currently selected engine. */ 35 private String mCurrentEngine; 36 37 /** 38 * The engine checkbox that is currently checked. Saves us a bit of effort in deducing the right 39 * one from the currently selected engine. 40 */ 41 private Checkable mCurrentChecked; 42 43 /** 44 * The previously selected TTS engine. Useful for rollbacks if the users choice is not loaded or 45 * fails a voice integrity check. 46 */ 47 private String mPreviousEngine; 48 49 private PreferenceCategory mEnginePreferenceCategory; 50 51 private TextToSpeech mTts = null; 52 private TtsEngines mEnginesHelper = null; 53 54 @Override onCreate(Bundle savedInstanceState)55 public void onCreate(Bundle savedInstanceState) { 56 super.onCreate(savedInstanceState); 57 addPreferencesFromResource(R.xml.tts_engine_picker); 58 59 mEnginePreferenceCategory = 60 (PreferenceCategory) findPreference("tts_engine_preference_category"); 61 mEnginesHelper = new TtsEngines(getActivity().getApplicationContext()); 62 63 mTts = new TextToSpeech(getActivity().getApplicationContext(), null); 64 65 initSettings(); 66 } 67 68 @Override getMetricsCategory()69 public int getMetricsCategory() { 70 return SettingsEnums.TTS_ENGINE_SETTINGS; 71 } 72 73 @Override onDestroy()74 public void onDestroy() { 75 super.onDestroy(); 76 if (mTts != null) { 77 mTts.shutdown(); 78 mTts = null; 79 } 80 } 81 initSettings()82 private void initSettings() { 83 if (mTts != null) { 84 mCurrentEngine = mTts.getCurrentEngine(); 85 } 86 87 mEnginePreferenceCategory.removeAll(); 88 89 List<EngineInfo> engines = mEnginesHelper.getEngines(); 90 for (EngineInfo engine : engines) { 91 TtsEnginePreference enginePref = 92 new TtsEnginePreference(getPrefContext(), engine, this); 93 mEnginePreferenceCategory.addPreference(enginePref); 94 } 95 } 96 97 @Override getCurrentChecked()98 public Checkable getCurrentChecked() { 99 return mCurrentChecked; 100 } 101 102 @Override getCurrentKey()103 public String getCurrentKey() { 104 return mCurrentEngine; 105 } 106 107 @Override setCurrentChecked(Checkable current)108 public void setCurrentChecked(Checkable current) { 109 mCurrentChecked = current; 110 } 111 112 /** 113 * The initialization listener used when the user changes his choice of engine (as opposed to 114 * when then screen is being initialized for the first time). 115 */ 116 private final TextToSpeech.OnInitListener mUpdateListener = 117 new TextToSpeech.OnInitListener() { 118 @Override 119 public void onInit(int status) { 120 onUpdateEngine(status); 121 } 122 }; 123 updateDefaultEngine(String engine)124 private void updateDefaultEngine(String engine) { 125 Log.d(TAG, "Updating default synth to : " + engine); 126 127 // Keep track of the previous engine that was being used. So that 128 // we can reuse the previous engine. 129 // 130 // Note that if TextToSpeech#getCurrentEngine is not null, it means at 131 // the very least that we successfully bound to the engine service. 132 mPreviousEngine = mTts.getCurrentEngine(); 133 134 // Step 1: Shut down the existing TTS engine. 135 Log.i(TAG, "Shutting down current tts engine"); 136 if (mTts != null) { 137 try { 138 mTts.shutdown(); 139 mTts = null; 140 } catch (Exception e) { 141 Log.e(TAG, "Error shutting down TTS engine" + e); 142 } 143 } 144 145 // Step 2: Connect to the new TTS engine. 146 // Step 3 is continued on #onUpdateEngine (below) which is called when 147 // the app binds successfully to the engine. 148 Log.i(TAG, "Updating engine : Attempting to connect to engine: " + engine); 149 mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine); 150 Log.i(TAG, "Success"); 151 } 152 153 /** 154 * Step 3: We have now bound to the TTS engine the user requested. We will attempt to check 155 * voice data for the engine if we successfully bound to it, or revert to the previous engine if 156 * we didn't. 157 */ onUpdateEngine(int status)158 public void onUpdateEngine(int status) { 159 if (status == TextToSpeech.SUCCESS) { 160 Log.d( 161 162 TAG, 163 "Updating engine: Successfully bound to the engine: " 164 + mTts.getCurrentEngine()); 165 android.provider.Settings.Secure.putString( 166 getContentResolver(), TTS_DEFAULT_SYNTH, mTts.getCurrentEngine()); 167 } else { 168 Log.d(TAG, "Updating engine: Failed to bind to engine, reverting."); 169 if (mPreviousEngine != null) { 170 // This is guaranteed to at least bind, since mPreviousEngine would be 171 // null if the previous bind to this engine failed. 172 mTts = 173 new TextToSpeech( 174 getActivity().getApplicationContext(), null, mPreviousEngine); 175 } 176 mPreviousEngine = null; 177 } 178 } 179 180 @Override setCurrentKey(String key)181 public void setCurrentKey(String key) { 182 mCurrentEngine = key; 183 updateDefaultEngine(mCurrentEngine); 184 } 185 186 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 187 new BaseSearchIndexProvider() { 188 @Override 189 public List<SearchIndexableResource> getXmlResourcesToIndex( 190 Context context, boolean enabled) { 191 final SearchIndexableResource sir = new SearchIndexableResource(context); 192 sir.xmlResId = R.xml.tts_engine_picker; 193 return Arrays.asList(sir); 194 } 195 }; 196 } 197