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 17package com.android.settings.tts; 18 19import android.content.ActivityNotFoundException; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.os.Bundle; 25import android.preference.CheckBoxPreference; 26import android.preference.ListPreference; 27import android.preference.Preference; 28import android.preference.Preference.OnPreferenceChangeListener; 29import android.preference.Preference.OnPreferenceClickListener; 30import android.preference.PreferenceScreen; 31import android.speech.tts.TextToSpeech; 32import android.speech.tts.TtsEngines; 33import android.text.TextUtils; 34import android.util.Log; 35import android.util.Pair; 36 37import com.android.settings.R; 38import com.android.settings.SettingsPreferenceFragment; 39 40import java.util.ArrayList; 41import java.util.Collections; 42import java.util.Comparator; 43import java.util.Locale; 44 45 46public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implements 47 OnPreferenceClickListener, OnPreferenceChangeListener { 48 private static final String TAG = "TtsEngineSettings"; 49 private static final boolean DBG = false; 50 51 private static final String KEY_ENGINE_LOCALE = "tts_default_lang"; 52 private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings"; 53 private static final String KEY_INSTALL_DATA = "tts_install_data"; 54 55 private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; 56 57 private TtsEngines mEnginesHelper; 58 private ListPreference mLocalePreference; 59 private Preference mEngineSettingsPreference; 60 private Preference mInstallVoicesPreference; 61 private Intent mEngineSettingsIntent; 62 private Intent mVoiceDataDetails; 63 64 private TextToSpeech mTts; 65 66 private int mSelectedLocaleIndex = -1; 67 private int mSystemLocaleIndex = -1; 68 69 private final TextToSpeech.OnInitListener mTtsInitListener = new TextToSpeech.OnInitListener() { 70 @Override 71 public void onInit(int status) { 72 if (status != TextToSpeech.SUCCESS) { 73 finishFragment(); 74 } else { 75 getActivity().runOnUiThread(new Runnable() { 76 @Override 77 public void run() { 78 mLocalePreference.setEnabled(true); 79 } 80 }); 81 } 82 } 83 }; 84 85 private final BroadcastReceiver mLanguagesChangedReceiver = new BroadcastReceiver() { 86 @Override 87 public void onReceive(Context context, Intent intent) { 88 // Installed or uninstalled some data packs 89 if (TextToSpeech.Engine.ACTION_TTS_DATA_INSTALLED.equals(intent.getAction())) { 90 checkTtsData(); 91 } 92 } 93 }; 94 95 public TtsEngineSettingsFragment() { 96 super(); 97 } 98 99 @Override 100 public void onCreate(Bundle savedInstanceState) { 101 super.onCreate(savedInstanceState); 102 addPreferencesFromResource(R.xml.tts_engine_settings); 103 mEnginesHelper = new TtsEngines(getActivity()); 104 105 final PreferenceScreen root = getPreferenceScreen(); 106 mLocalePreference = (ListPreference) root.findPreference(KEY_ENGINE_LOCALE); 107 mLocalePreference.setOnPreferenceChangeListener(this); 108 mEngineSettingsPreference = root.findPreference(KEY_ENGINE_SETTINGS); 109 mEngineSettingsPreference.setOnPreferenceClickListener(this); 110 mInstallVoicesPreference = root.findPreference(KEY_INSTALL_DATA); 111 mInstallVoicesPreference.setOnPreferenceClickListener(this); 112 113 root.setTitle(getEngineLabel()); 114 root.setKey(getEngineName()); 115 mEngineSettingsPreference.setTitle(getResources().getString( 116 R.string.tts_engine_settings_title, getEngineLabel())); 117 118 mEngineSettingsIntent = mEnginesHelper.getSettingsIntent(getEngineName()); 119 if (mEngineSettingsIntent == null) { 120 mEngineSettingsPreference.setEnabled(false); 121 } 122 mInstallVoicesPreference.setEnabled(false); 123 mLocalePreference.setEnabled(false); 124 125 mLocalePreference.setEntries(new CharSequence[0]); 126 mLocalePreference.setEntryValues(new CharSequence[0]); 127 128 mVoiceDataDetails = getArguments().getParcelable(TtsEnginePreference.FRAGMENT_ARGS_VOICES); 129 130 mTts = new TextToSpeech(getActivity().getApplicationContext(), mTtsInitListener, 131 getEngineName()); 132 133 // Check if data packs changed 134 checkTtsData(); 135 136 getActivity().registerReceiver(mLanguagesChangedReceiver, 137 new IntentFilter(TextToSpeech.Engine.ACTION_TTS_DATA_INSTALLED)); 138 } 139 140 @Override 141 public void onDestroy() { 142 getActivity().unregisterReceiver(mLanguagesChangedReceiver); 143 mTts.shutdown(); 144 super.onDestroy(); 145 } 146 147 private final void checkTtsData() { 148 Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); 149 intent.setPackage(getEngineName()); 150 try { 151 if (DBG) Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0)); 152 startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK); 153 } catch (ActivityNotFoundException ex) { 154 Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")"); 155 } 156 } 157 158 @Override 159 public void onActivityResult(int requestCode, int resultCode, Intent data) { 160 if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { 161 mVoiceDataDetails = data; 162 updateVoiceDetails(); 163 } 164 } 165 166 private void updateVoiceDetails() { 167 if (DBG) Log.d(TAG, "Parsing voice data details, data: " + mVoiceDataDetails.toUri(0)); 168 169 final ArrayList<String> available = mVoiceDataDetails.getStringArrayListExtra( 170 TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); 171 final ArrayList<String> unavailable = mVoiceDataDetails.getStringArrayListExtra( 172 TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES); 173 174 if (unavailable != null && unavailable.size() > 0) { 175 mInstallVoicesPreference.setEnabled(true); 176 } else { 177 mInstallVoicesPreference.setEnabled(false); 178 } 179 180 if (available == null){ 181 Log.e(TAG, "TTS data check failed (available == null)."); 182 mLocalePreference.setEnabled(false); 183 return; 184 } else { 185 updateDefaultLocalePref(available); 186 } 187 } 188 189 private void updateDefaultLocalePref(ArrayList<String> availableLangs) { 190 if (availableLangs == null || availableLangs.size() == 0) { 191 mLocalePreference.setEnabled(false); 192 return; 193 } 194 String currentLocale = mEnginesHelper.getLocalePrefForEngine( 195 getEngineName()); 196 197 ArrayList<Pair<String, String>> entryPairs = 198 new ArrayList<Pair<String, String>>(availableLangs.size()); 199 for (int i = 0; i < availableLangs.size(); i++) { 200 String[] langCountryVariant = availableLangs.get(i).split("-"); 201 Locale loc = null; 202 if (langCountryVariant.length == 1){ 203 loc = new Locale(langCountryVariant[0]); 204 } else if (langCountryVariant.length == 2){ 205 loc = new Locale(langCountryVariant[0], langCountryVariant[1]); 206 } else if (langCountryVariant.length == 3){ 207 loc = new Locale(langCountryVariant[0], langCountryVariant[1], 208 langCountryVariant[2]); 209 } 210 if (loc != null){ 211 entryPairs.add(new Pair<String, String>( 212 loc.getDisplayName(), availableLangs.get(i))); 213 } 214 } 215 216 // Sort it 217 Collections.sort(entryPairs, new Comparator<Pair<String, String>>() { 218 @Override 219 public int compare(Pair<String, String> lhs, Pair<String, String> rhs) { 220 return lhs.first.compareToIgnoreCase(rhs.first); 221 } 222 }); 223 224 String defaultLocaleStr = mEnginesHelper.getDefaultLocale(); 225 226 // Get two arrays out of one of pairs 227 mSelectedLocaleIndex = -1; 228 mSystemLocaleIndex = -1; 229 CharSequence[] entries = new CharSequence[availableLangs.size()]; 230 CharSequence[] entryValues = new CharSequence[availableLangs.size()]; 231 int i = 0; 232 for (Pair<String, String> entry : entryPairs) { 233 if (entry.second.equalsIgnoreCase(currentLocale)) { 234 mSelectedLocaleIndex = i; 235 } 236 if (entry.second.equalsIgnoreCase(defaultLocaleStr)) { 237 mSystemLocaleIndex = i; 238 } 239 entries[i] = entry.first; 240 entryValues[i++] = entry.second; 241 } 242 243 mLocalePreference.setEntries(entries); 244 mLocalePreference.setEntryValues(entryValues); 245 mLocalePreference.setEnabled(true); 246 setLocalePreference(mSelectedLocaleIndex); 247 } 248 249 /** Set entry from entry table in mLocalePreference */ 250 private void setLocalePreference(int index) { 251 if (index < 0) { 252 mLocalePreference.setValue(""); 253 mLocalePreference.setSummary(R.string.tts_lang_not_selected); 254 } else { 255 mLocalePreference.setValueIndex(index); 256 mLocalePreference.setSummary(mLocalePreference.getEntries()[index]); 257 } 258 } 259 260 /** 261 * Ask the current default engine to launch the matching INSTALL_TTS_DATA activity 262 * so the required TTS files are properly installed. 263 */ 264 private void installVoiceData() { 265 if (TextUtils.isEmpty(getEngineName())) return; 266 Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); 267 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 268 intent.setPackage(getEngineName()); 269 try { 270 Log.v(TAG, "Installing voice data: " + intent.toUri(0)); 271 startActivity(intent); 272 } catch (ActivityNotFoundException ex) { 273 Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")"); 274 } 275 } 276 277 @Override 278 public boolean onPreferenceClick(Preference preference) { 279 if (preference == mInstallVoicesPreference) { 280 installVoiceData(); 281 return true; 282 } else if (preference == mEngineSettingsPreference) { 283 startActivity(mEngineSettingsIntent); 284 return true; 285 } 286 287 return false; 288 } 289 290 @Override 291 public boolean onPreferenceChange(Preference preference, Object newValue) { 292 if (preference == mLocalePreference) { 293 updateLanguageTo((String) newValue); 294 return true; 295 } 296 return false; 297 } 298 299 private void updateLanguageTo(String locale) { 300 int selectedLocaleIndex = -1; 301 for (int i=0; i < mLocalePreference.getEntryValues().length; i++) { 302 if (locale.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) { 303 selectedLocaleIndex = i; 304 break; 305 } 306 } 307 308 if (selectedLocaleIndex == -1) { 309 Log.w(TAG, "updateLanguageTo called with unknown locale argument"); 310 return; 311 } 312 mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]); 313 mSelectedLocaleIndex = selectedLocaleIndex; 314 315 if (mSelectedLocaleIndex == mSystemLocaleIndex) { 316 // Use empty locale, it will default to the system language 317 mEnginesHelper.updateLocalePrefForEngine(getEngineName(), ""); 318 } else { 319 mEnginesHelper.updateLocalePrefForEngine(getEngineName(), locale); 320 } 321 322 if (getEngineName().equals(mTts.getCurrentEngine())) { 323 String[] localeArray = TtsEngines.parseLocalePref(locale); 324 if (localeArray != null) { 325 mTts.setLanguage(new Locale(localeArray[0], localeArray[1], localeArray[2])); 326 } 327 } 328 } 329 330 private String getEngineName() { 331 return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_NAME); 332 } 333 334 private String getEngineLabel() { 335 return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_LABEL); 336 } 337} 338