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