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