1 /* 2 * Copyright (C) 2014 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.tv.settings.system; 18 19 import com.android.internal.view.RotationPolicy; 20 import com.android.tv.settings.ActionBehavior; 21 import com.android.tv.settings.BaseSettingsActivity; 22 23 import static android.provider.Settings.Secure.TTS_DEFAULT_RATE; 24 import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH; 25 import com.android.tv.settings.ActionKey; 26 import com.android.tv.settings.R; 27 import com.android.tv.settings.system.DeveloperOptionsActivity.MyApplicationInfo; 28 import com.android.tv.settings.util.SettingsHelper; 29 import com.android.tv.settings.dialog.old.Action; 30 import com.android.tv.settings.dialog.old.ActionAdapter; 31 import com.android.tv.settings.dialog.old.ContentFragment; 32 import com.android.tv.settings.util.SettingsHelper; 33 34 import android.accessibilityservice.AccessibilityServiceInfo; 35 import android.content.ActivityNotFoundException; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.view.accessibility.AccessibilityManager; 39 import android.widget.TextView; 40 import android.content.Intent; 41 import android.content.pm.ResolveInfo; 42 import android.content.pm.ServiceInfo; 43 import android.os.Bundle; 44 import android.provider.Settings; 45 import android.speech.tts.TextToSpeech; 46 import android.text.TextUtils.SimpleStringSplitter; 47 import android.speech.tts.TtsEngines; 48 import android.util.Log; 49 import android.util.Pair; 50 import android.speech.tts.TextToSpeech.EngineInfo; 51 import android.speech.tts.UtteranceProgressListener; 52 import android.text.TextUtils; 53 54 import java.util.ArrayList; 55 import java.util.List; 56 import java.util.Collections; 57 import java.util.Comparator; 58 import java.util.HashMap; 59 import java.util.HashSet; 60 import java.util.Locale; 61 import java.util.Set; 62 63 public class AccessibilityActivity extends BaseSettingsActivity implements ActionAdapter.Listener { 64 65 private static final String TAG = "AccessibilityActivity"; 66 private static final boolean DEBUG = false; 67 68 private static final int GET_SAMPLE_TEXT = 1983; 69 private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; 70 71 private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'; 72 73 private SettingsHelper mHelper; 74 75 private boolean mTtsSettingsEnabled; 76 private TextToSpeech mTts = null; 77 private TtsEngines mEnginesHelper = null; 78 private String mCurrentEngine; 79 private String mPreviousEngine; 80 private Intent mVoiceCheckData; 81 82 private String mServiceSettingTitle; 83 private String mSelectedServiceComponent; 84 private String mSelectedServiceSettings; 85 private boolean mSelectedServiceEnabled; 86 87 /** 88 * The initialization listener used when we are initalizing the settings 89 * screen for the first time (as opposed to when a user changes his choice 90 * of engine). 91 */ 92 private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() { 93 @Override 94 public void onInit(int status) { 95 onInitEngine(status); 96 } 97 }; 98 99 /** 100 * The initialization listener used when the user changes his choice of 101 * engine (as opposed to when then screen is being initialized for the first 102 * time). 103 */ 104 private final TextToSpeech.OnInitListener mUpdateListener = new TextToSpeech.OnInitListener() { 105 @Override 106 public void onInit(int status) { 107 onUpdateEngine(status); 108 } 109 }; 110 111 @Override 112 public void onCreate(Bundle savedInstanceState) { 113 mResources = getResources(); 114 mHelper = new SettingsHelper(this); 115 mActions = new ArrayList<Action>(); 116 117 mTts = new TextToSpeech(getApplicationContext(), mInitListener); 118 mEnginesHelper = new TtsEngines(getApplicationContext()); 119 initSettings(); 120 121 super.onCreate(savedInstanceState); 122 } 123 124 @Override 125 protected void refreshActionList() { 126 mActions.clear(); 127 switch ((ActionType) mState) { 128 case ACCESSIBILITY_OVERVIEW: 129 mActions.add(ActionType.ACCESSIBILITY_CAPTIONS.toAction(mResources)); 130 mActions.add(ActionType.ACCESSIBILITY_SERVICES.toAction(mResources)); 131 mActions.add(ActionType.ACCESSIBILITY_SPEAK_PASSWORDS.toAction(mResources, 132 mHelper.getSecureStatusIntSetting( 133 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD))); 134 mActions.add(ActionType.ACCESSIBILITY_TTS_OUTPUT.toAction(mResources, 135 getDisplayNameForEngine(mTts.getCurrentEngine()))); 136 break; 137 case ACCESSIBILITY_SERVICES: 138 mActions = getInstalledServicesActions(); 139 break; 140 case ACCESSIBILITY_SERVICES_SETTINGS: 141 mActions.add(ActionType.ACCESSIBILITY_SERVICES_STATUS.toAction(mResources, 142 mHelper.getStatusStringFromBoolean(mSelectedServiceEnabled))); 143 if (mSelectedServiceSettings != null) { 144 mActions.add(ActionType.ACCESSIBILITY_SERVICE_CONFIG.toAction(mResources)); 145 } 146 break; 147 case ACCESSIBILITY_SERVICES_STATUS: 148 mActions = getEnableActions(mServiceSettingTitle, mSelectedServiceEnabled); 149 break; 150 case ACCESSIBILITY_SERVICES_CONFIRM_ON: 151 mActions.add(ActionType.AGREE.toAction(mResources)); 152 mActions.add(ActionType.DISAGREE.toAction(mResources)); 153 break; 154 case ACCESSIBILITY_SERVICES_CONFIRM_OFF: 155 mActions.add(ActionType.OK.toAction(mResources)); 156 mActions.add(ActionType.CANCEL.toAction(mResources)); 157 break; 158 case ACCESSIBILITY_SPEAK_PASSWORDS: 159 mActions = getEnableActions(((ActionType) mState).name(), getProperty()); 160 break; 161 case ACCESSIBILITY_TTS_OUTPUT: 162 mActions.add(ActionType.ACCESSIBILITY_PREFERRED_ENGINE.toAction( 163 mResources, getDisplayNameForEngine(mTts.getCurrentEngine()))); 164 if (mTtsSettingsEnabled) { 165 if (mTts.getLanguage() != null) { 166 mActions.add(ActionType.ACCESSIBILITY_LANGUAGE.toAction( 167 mResources, mTts.getLanguage().getDisplayName())); 168 } else { 169 mActions.add(ActionType.ACCESSIBILITY_LANGUAGE.toAction( 170 mResources, " ")); 171 } 172 } 173 mActions.add(ActionType.ACCESSIBILITY_INSTALL_VOICE_DATA.toAction(mResources)); 174 mActions.add(ActionType.ACCESSIBILITY_SPEECH_RATE.toAction( 175 mResources, getTtsRate(mHelper.getSecureIntSetting( 176 TTS_DEFAULT_RATE, getString(R.string.tts_rate_default_value))))); 177 if (mTtsSettingsEnabled) { 178 mActions.add(ActionType.ACCESSIBILITY_PLAY_SAMPLE.toAction(mResources)); 179 } 180 break; 181 case ACCESSIBILITY_PREFERRED_ENGINE: 182 mActions = getEngines(); 183 break; 184 case ACCESSIBILITY_LANGUAGE: 185 final ArrayList<String> available = mVoiceCheckData.getStringArrayListExtra( 186 TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); 187 if (available != null && available.size() > 0) { 188 mActions = updateDefaultLocalePref(available); 189 if (mTts.getLanguage() != null) { 190 String currLang = getLanguageString(mTts.getLanguage()); 191 checkSelectedAction(mActions, currLang); 192 } 193 } 194 break; 195 case ACCESSIBILITY_SPEECH_RATE: 196 mActions = Action.createActionsFromArrays( 197 mResources.getStringArray(R.array.tts_rate_values), 198 mResources.getStringArray(R.array.tts_rate_entries)); 199 checkSelectedAction(mActions, mHelper.getSecureIntSetting( 200 TTS_DEFAULT_RATE, getString(R.string.tts_rate_default_value))); 201 break; 202 default: 203 break; 204 } 205 } 206 207 @Override 208 protected void updateView() { 209 refreshActionList(); 210 switch ((ActionType) mState) { 211 case ACCESSIBILITY_SERVICES_SETTINGS: 212 setView(mServiceSettingTitle, 213 ((ActionType) getPrevState()).getTitle(mResources), 214 null, R.drawable.ic_settings_accessibility); 215 break; 216 case ACCESSIBILITY_SERVICES_STATUS: 217 setView(((ActionType) mState).getTitle(mResources), mServiceSettingTitle, 218 null, 219 R.drawable.ic_settings_accessibility); 220 return; 221 case ACCESSIBILITY_SERVICES_CONFIRM_ON: 222 String onTitle = getString( 223 R.string.system_accessibility_service_on_confirm_title, 224 mServiceSettingTitle); 225 setView(onTitle, mServiceSettingTitle, 226 getString(R.string.system_accessibility_service_on_confirm_desc, 227 mServiceSettingTitle), R.drawable.ic_settings_accessibility); 228 return; 229 case ACCESSIBILITY_SERVICES_CONFIRM_OFF: 230 String offTitle = getString( 231 R.string.system_accessibility_service_off_confirm_title, 232 mServiceSettingTitle); 233 setView(offTitle, mServiceSettingTitle, 234 getString(R.string.system_accessibility_service_off_confirm_desc, 235 mServiceSettingTitle), R.drawable.ic_settings_accessibility); 236 return; 237 default: 238 setView(((ActionType) mState).getTitle(mResources), 239 getPrevState() != null ? 240 ((ActionType) getPrevState()).getTitle(mResources) : 241 getString(R.string.settings_app_name), 242 ((ActionType) mState).getDescription(mResources), 243 R.drawable.ic_settings_accessibility); 244 } 245 } 246 247 private String getTtsRate(String value) { 248 String[] values = mResources.getStringArray(R.array.tts_rate_values); 249 String[] entries = mResources.getStringArray(R.array.tts_rate_entries); 250 for (int index = 0; index < values.length; ++index) { 251 if (values[index].equals(value)) { 252 return entries[index]; 253 } 254 } 255 return ""; 256 } 257 258 private ArrayList<Action> getInstalledServicesActions() { 259 ArrayList<Action> actions = new ArrayList<Action>(); 260 final List<AccessibilityServiceInfo> installedServiceInfos = AccessibilityManager 261 .getInstance(this).getInstalledAccessibilityServiceList(); 262 263 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(this); 264 265 final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(), 266 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; 267 268 for (AccessibilityServiceInfo accInfo : installedServiceInfos) { 269 ServiceInfo serviceInfo = accInfo.getResolveInfo().serviceInfo; 270 ComponentName componentName = new ComponentName(serviceInfo.packageName, 271 serviceInfo.name); 272 final boolean serviceEnabled = accessibilityEnabled 273 && enabledServices.contains(componentName); 274 String title = accInfo.getResolveInfo().loadLabel(getPackageManager()).toString(); 275 actions.add(new Action.Builder() 276 .key(componentName.flattenToString()) 277 .title(title) 278 .description(mHelper.getStatusStringFromBoolean(serviceEnabled)) 279 .build()); 280 } 281 return actions; 282 } 283 284 private String getSettingsForService(String serviceComponentName) { 285 final List<AccessibilityServiceInfo> installedServiceInfos = AccessibilityManager 286 .getInstance(this).getInstalledAccessibilityServiceList(); 287 ComponentName comp = ComponentName.unflattenFromString(serviceComponentName); 288 289 if (comp != null) { 290 for (AccessibilityServiceInfo accInfo : installedServiceInfos) { 291 ServiceInfo serviceInfo = accInfo.getResolveInfo().serviceInfo; 292 if (serviceInfo.packageName.equals(comp.getPackageName()) && 293 serviceInfo.name.equals(comp.getClassName())) { 294 String settingsClassName = accInfo.getSettingsActivityName(); 295 if (!TextUtils.isEmpty(settingsClassName)) { 296 ComponentName settingsComponent = 297 new ComponentName(comp.getPackageName(), settingsClassName); 298 return settingsComponent.flattenToString(); 299 } else { 300 return null; 301 } 302 } 303 } 304 } 305 return null; 306 } 307 308 private static Set<ComponentName> getEnabledServicesFromSettings(Context context) { 309 String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), 310 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 311 if (enabledServicesSetting == null) { 312 enabledServicesSetting = ""; 313 } 314 Set<ComponentName> enabledServices = new HashSet<ComponentName>(); 315 SimpleStringSplitter colonSplitter = new SimpleStringSplitter( 316 ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 317 colonSplitter.setString(enabledServicesSetting); 318 while (colonSplitter.hasNext()) { 319 String componentNameString = colonSplitter.next(); 320 ComponentName enabledService = ComponentName.unflattenFromString( 321 componentNameString); 322 if (enabledService != null) { 323 enabledServices.add(enabledService); 324 } 325 } 326 return enabledServices; 327 } 328 329 private ArrayList<Action> getEnableActions(String type, boolean enabled) { 330 ArrayList<Action> actions = new ArrayList<Action>(); 331 actions.add(ActionBehavior.ON.toAction(ActionBehavior.getOnKey(type), mResources, enabled)); 332 actions.add(ActionBehavior.OFF.toAction(ActionBehavior.getOffKey(type), mResources, 333 !enabled)); 334 return actions; 335 } 336 337 private void checkSelectedAction(ArrayList<Action> actions, String selectedKey) { 338 for (Action action : actions) { 339 if (action.getKey().equalsIgnoreCase(selectedKey)) { 340 action.setChecked(true); 341 break; 342 } 343 } 344 } 345 346 private String getLanguageString(Locale lang) { 347 if (lang.getLanguage().isEmpty()) 348 return ""; 349 350 StringBuilder builder = new StringBuilder(); 351 builder.append(lang.getLanguage()); 352 353 if (!lang.getCountry().isEmpty()) { 354 builder.append('-'); 355 builder.append(lang.getCountry()); 356 } 357 358 if (!lang.getVariant().isEmpty()) { 359 builder.append('-'); 360 builder.append(lang.getVariant()); 361 } 362 363 return builder.toString(); 364 } 365 366 private void updateDefaultEngine(String engine) { 367 if (DEBUG) { 368 Log.d(TAG, "Updating default synth to : " + engine); 369 } 370 371 // TODO Disable the "play sample text" preference and the speech 372 // rate preference while the engine is being swapped. 373 374 // Keep track of the previous engine that was being used. So that 375 // we can reuse the previous engine. 376 // 377 // Note that if TextToSpeech#getCurrentEngine is not null, it means at 378 // the very least that we successfully bound to the engine service. 379 mPreviousEngine = mTts.getCurrentEngine(); 380 381 // Step 1: Shut down the existing TTS engine. 382 if (mTts != null) { 383 try { 384 mTts.shutdown(); 385 mTts = null; 386 } catch (Exception e) { 387 Log.e(TAG, "Error shutting down TTS engine" + e); 388 } 389 } 390 391 // Step 2: Connect to the new TTS engine. 392 // Step 3 is continued on #onUpdateEngine (below) which is called when 393 // the app binds successfully to the engine. 394 if (DEBUG) { 395 Log.d(TAG, "Updating engine : Attempting to connect to engine: " + engine); 396 } 397 mTts = new TextToSpeech(getApplicationContext(), mUpdateListener, engine); 398 setTtsUtteranceProgressListener(); 399 } 400 401 @Override 402 public void onActionClicked(Action action) { 403 /* 404 * For list preferences 405 */ 406 final String key = action.getKey(); 407 final String title = action.getTitle(); 408 switch((ActionType)mState){ 409 case ACCESSIBILITY_SERVICES: 410 mServiceSettingTitle = action.getTitle(); 411 mSelectedServiceComponent = action.getKey(); 412 mSelectedServiceEnabled = mHelper.getStatusFromString(action.getDescription()); 413 mSelectedServiceSettings = getSettingsForService(mSelectedServiceComponent); 414 if (mSelectedServiceSettings != null) { 415 // Service provides a settings component, so go to the Status/Settings screen 416 setState(ActionType.ACCESSIBILITY_SERVICES_SETTINGS, true); 417 } else { 418 // Service does not provide Settings, so go straight to Enable/Disable 419 setState(ActionType.ACCESSIBILITY_SERVICES_STATUS, true); 420 } 421 return; 422 case ACCESSIBILITY_PREFERRED_ENGINE: 423 mCurrentEngine = key; 424 updateDefaultEngine(mCurrentEngine); 425 // Delay the goBack here until we are done binding to the service, so we have 426 // the Language data available for the previous screen. 427 return; 428 case ACCESSIBILITY_LANGUAGE: 429 updateLanguageTo( 430 !TextUtils.isEmpty(key) ? mEnginesHelper.parseLocaleString(key) : null); 431 goBack(); 432 return; 433 case ACCESSIBILITY_SPEECH_RATE: 434 mHelper.setSecureIntValueSetting(TTS_DEFAULT_RATE, key); 435 goBack(); 436 return; 437 } 438 439 /* 440 * For regular states 441 */ 442 ActionKey<ActionType, ActionBehavior> actionKey = new ActionKey<ActionType, ActionBehavior>( 443 ActionType.class, ActionBehavior.class, action.getKey()); 444 final ActionType type = actionKey.getType(); 445 if (type != null) { 446 switch (type) { 447 case ACCESSIBILITY_PLAY_SAMPLE: 448 getSampleText(); 449 return; 450 case ACCESSIBILITY_INSTALL_VOICE_DATA: 451 installVoiceData(); 452 return; 453 case ACCESSIBILITY_SERVICE_CONFIG: { 454 ComponentName comp = ComponentName.unflattenFromString( 455 mSelectedServiceSettings); 456 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(comp); 457 startActivity(settingsIntent); 458 return; 459 } 460 case ACCESSIBILITY_CAPTIONS: { 461 ComponentName comp = new ComponentName(this, CaptionSetupActivity.class); 462 Intent captionsIntent = new Intent(Intent.ACTION_MAIN).setComponent(comp); 463 startActivity(captionsIntent); 464 return; 465 } 466 case AGREE: 467 setProperty(true); // Agreed to turn ON service 468 return; 469 case DISAGREE: 470 setProperty(false); // Disagreed to turn service ON 471 return; 472 case OK: 473 setProperty(false); // ok to STOP Service 474 return; 475 case CANCEL: 476 goBack(); // Cancelled request to STOP service 477 return; 478 default: 479 } 480 } 481 482 final ActionBehavior behavior = actionKey.getBehavior(); 483 switch (behavior) { 484 case ON: 485 setProperty(true); 486 return; 487 case OFF: 488 setProperty(false); 489 return; 490 default: 491 } 492 setState(type, true); 493 } 494 495 @Override 496 protected void setProperty(boolean enable) { 497 switch ((ActionType) mState) { 498 case ACCESSIBILITY_SPEAK_PASSWORDS: 499 mHelper.setSecureIntSetting(Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, enable); 500 break; 501 case ACCESSIBILITY_SERVICES_STATUS: 502 // Accessibility Service ON/OFF requires an extra confirmation screen. 503 if (enable) { 504 setState(ActionType.ACCESSIBILITY_SERVICES_CONFIRM_ON, true); 505 } else { 506 if (mSelectedServiceEnabled) { 507 setState(ActionType.ACCESSIBILITY_SERVICES_CONFIRM_OFF, true); 508 } else { 509 goBack(); 510 } 511 } 512 return; 513 case ACCESSIBILITY_SERVICES_CONFIRM_ON: 514 setAccessibilityServiceState(mSelectedServiceComponent, enable); 515 mSelectedServiceEnabled = enable; 516 // go back twice: Remove the ON/OFF screen from the stack 517 goBack(); 518 break; 519 case ACCESSIBILITY_SERVICES_CONFIRM_OFF: 520 setAccessibilityServiceState(mSelectedServiceComponent, enable); 521 mSelectedServiceEnabled = enable; 522 // go back twice: Remove the ON/OFF screen from the stack 523 goBack(); 524 break; 525 } 526 goBack(); 527 } 528 529 private boolean getProperty() { 530 if ((ActionType) mState == ActionType.ACCESSIBILITY_SPEAK_PASSWORDS) { 531 return mHelper.getSecureIntValueSettingToBoolean( 532 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD); 533 } 534 return false; 535 } 536 537 private Set<ComponentName> getInstalledServices() { 538 Set<ComponentName> installedServices = new HashSet<ComponentName>(); 539 installedServices.clear(); 540 541 List<AccessibilityServiceInfo> installedServiceInfos = 542 AccessibilityManager.getInstance(this) 543 .getInstalledAccessibilityServiceList(); 544 if (installedServiceInfos == null) { 545 return installedServices; 546 } 547 548 final int installedServiceInfoCount = installedServiceInfos.size(); 549 for (int i = 0; i < installedServiceInfoCount; i++) { 550 ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo(); 551 ComponentName installedService = new ComponentName( 552 resolveInfo.serviceInfo.packageName, 553 resolveInfo.serviceInfo.name); 554 installedServices.add(installedService); 555 } 556 return installedServices; 557 } 558 559 public void setAccessibilityServiceState(String preferenceKey, boolean enabled) { 560 // Parse the enabled services. 561 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(this); 562 563 // Determine enabled services and accessibility state. 564 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey); 565 boolean accessibilityEnabled = false; 566 if (enabled) { 567 enabledServices.add(toggledService); 568 // Enabling at least one service enables accessibility. 569 accessibilityEnabled = true; 570 } else { 571 enabledServices.remove(toggledService); 572 // Check how many enabled and installed services are present. 573 Set<ComponentName> installedServices = getInstalledServices(); 574 for (ComponentName enabledService : enabledServices) { 575 if (installedServices.contains(enabledService)) { 576 // Disabling the last service disables accessibility. 577 accessibilityEnabled = true; 578 break; 579 } 580 } 581 } 582 583 // Update the enabled services setting. 584 StringBuilder enabledServicesBuilder = new StringBuilder(); 585 // Keep the enabled services even if they are not installed since we 586 // have no way to know whether the application restore process has 587 // completed. In general the system should be responsible for the 588 // clean up not settings. 589 for (ComponentName enabledService : enabledServices) { 590 enabledServicesBuilder.append(enabledService.flattenToString()); 591 enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 592 } 593 final int enabledServicesBuilderLength = enabledServicesBuilder.length(); 594 if (enabledServicesBuilderLength > 0) { 595 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); 596 } 597 Settings.Secure.putString(getContentResolver(), 598 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 599 enabledServicesBuilder.toString()); 600 601 // Update accessibility enabled. 602 Settings.Secure.putInt(getContentResolver(), 603 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0); 604 } 605 606 private ArrayList<Action> getEngines() { 607 ArrayList<Action> actions = new ArrayList<Action>(); 608 List<EngineInfo> engines = mEnginesHelper.getEngines(); 609 int totalEngine = engines.size(); 610 for (int i = 0; i < totalEngine; i++) { 611 Action action = new Action.Builder() 612 .key(engines.get(i).name) 613 .title(engines.get(i).label) 614 .build(); 615 actions.add(action); 616 } 617 mCurrentEngine = mTts.getCurrentEngine(); 618 checkVoiceData(mCurrentEngine); 619 return actions; 620 } 621 622 private String getDisplayNameForEngine(String enginePackageName) { 623 List<EngineInfo> engines = mEnginesHelper.getEngines(); 624 int totalEngine = engines.size(); 625 for (int i = 0; i < totalEngine; i++) { 626 if (engines.get(i).name.equals(enginePackageName)) { 627 return engines.get(i).label; 628 } 629 } 630 // Not found, return package name then 631 return enginePackageName; 632 } 633 634 /* 635 * Check whether the voice data for the engine is ok. 636 */ 637 private void checkVoiceData(String engine) { 638 Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); 639 intent.setPackage(engine); 640 try { 641 if (DEBUG) { 642 Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0)); 643 } 644 startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK); 645 } catch (ActivityNotFoundException ex) { 646 Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")"); 647 } 648 } 649 650 /** 651 * Called when the TTS engine is initialized. 652 */ 653 public void onInitEngine(int status) { 654 if (status == TextToSpeech.SUCCESS) { 655 if (DEBUG) { 656 Log.d(TAG, "TTS engine for settings screen initialized."); 657 } 658 } else { 659 if (DEBUG) { 660 Log.d(TAG, "TTS engine for settings screen failed to initialize successfully."); 661 } 662 } 663 } 664 665 private void initSettings() { 666 mCurrentEngine = mTts.getCurrentEngine(); 667 668 checkVoiceData(mCurrentEngine); 669 } 670 671 /* 672 * Step 3: We have now bound to the TTS engine the user requested. We will 673 * attempt to check voice data for the engine if we successfully bound to it, 674 * or revert to the previous engine if we didn't. 675 */ 676 public void onUpdateEngine(int status) { 677 if (status == TextToSpeech.SUCCESS) { 678 if (DEBUG) { 679 Log.d(TAG, "Updating engine: Successfully bound to the engine: " + 680 mTts.getCurrentEngine()); 681 } 682 checkVoiceData(mTts.getCurrentEngine()); 683 } else { 684 if (DEBUG) { 685 Log.d(TAG, "Updating engine: Failed to bind to engine, reverting."); 686 } 687 if (mPreviousEngine != null) { 688 // This is guaranteed to at least bind, since mPreviousEngine 689 // would be 690 // null if the previous bind to this engine failed. 691 mTts = new TextToSpeech(getApplicationContext(), mInitListener, 692 mPreviousEngine); 693 setTtsUtteranceProgressListener(); 694 } 695 mPreviousEngine = null; 696 } 697 goBack(); 698 } 699 700 private void setTtsUtteranceProgressListener() { 701 if (mTts == null) { 702 return; 703 } 704 mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() { 705 @Override 706 public void onStart(String utteranceId) { 707 } 708 709 @Override 710 public void onDone(String utteranceId) { 711 } 712 713 @Override 714 public void onError(String utteranceId) { 715 Log.e(TAG, "Error while trying to synthesize sample text"); 716 } 717 }); 718 } 719 720 private ArrayList<Action> updateDefaultLocalePref(ArrayList<String> availableLangs) { 721 ArrayList<Action> actions = new ArrayList<Action>(); 722 Locale currentLocale = mEnginesHelper.getLocalePrefForEngine(mCurrentEngine); 723 724 ArrayList<Pair<String, Locale>> entryPairs = 725 new ArrayList<Pair<String, Locale>>(availableLangs.size()); 726 for (int i = 0; i < availableLangs.size(); i++) { 727 Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i)); 728 if (locale != null) { 729 entryPairs.add(new Pair<String, Locale>( 730 locale.getDisplayName(), locale)); 731 } 732 } 733 734 // Sort it 735 Collections.sort(entryPairs, new Comparator<Pair<String, Locale>>() { 736 @Override 737 public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) { 738 return lhs.first.compareToIgnoreCase(rhs.first); 739 } 740 }); 741 742 // Get two arrays out of one of pairs 743 int selectedLanguageIndex = -1; 744 int i = 0; 745 for (Pair<String, Locale> entry : entryPairs) { 746 if (entry.second.equals(currentLocale)) { 747 selectedLanguageIndex = i; 748 } 749 Action action = new Action.Builder() 750 .key(entry.second.toString()) 751 .title(entry.first).build(); 752 actions.add(action); 753 } 754 return actions; 755 } 756 757 private void updateLanguageTo(Locale locale) { 758 mEnginesHelper.updateLocalePrefForEngine(mCurrentEngine, locale); 759 if (mCurrentEngine.equals(mTts.getCurrentEngine())) { 760 // Null locale means "use system default" 761 mTts.setLanguage((locale != null) ? locale : Locale.getDefault()); 762 } 763 } 764 765 /** 766 * Called when voice data integrity check returns 767 */ 768 @Override 769 public void onActivityResult(int requestCode, int resultCode, Intent data) { 770 if (requestCode == GET_SAMPLE_TEXT) { 771 onSampleTextReceived(resultCode, data); 772 } else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { 773 onVoiceDataIntegrityCheckDone(data); 774 } 775 } 776 777 /** 778 * Ask the current default engine to return a string of sample text to be 779 * spoken to the user. 780 */ 781 private void getSampleText() { 782 String currentEngine = mTts.getCurrentEngine(); 783 784 if (TextUtils.isEmpty(currentEngine)) 785 currentEngine = mTts.getDefaultEngine(); 786 787 Locale defaultLocale = mTts.getDefaultLanguage(); 788 if (defaultLocale == null) { 789 Log.e(TAG, "Failed to get default language from engine " + currentEngine); 790 return; 791 } 792 mTts.setLanguage(defaultLocale); 793 794 // TODO: This is currently a hidden private API. The intent extras 795 // and the intent action should be made public if we intend to make this 796 // a public API. We fall back to using a canned set of strings if this 797 // doesn't work. 798 Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT); 799 800 intent.putExtra("language", defaultLocale.getLanguage()); 801 intent.putExtra("country", defaultLocale.getCountry()); 802 intent.putExtra("variant", defaultLocale.getVariant()); 803 intent.setPackage(currentEngine); 804 805 try { 806 if (DEBUG) { 807 Log.d(TAG, "Getting sample text: " + intent.toUri(0)); 808 } 809 startActivityForResult(intent, GET_SAMPLE_TEXT); 810 } catch (ActivityNotFoundException ex) { 811 Log.e(TAG, "Failed to get sample text, no activity found for " + intent + ")"); 812 } 813 } 814 815 private String getDefaultSampleString() { 816 if (mTts != null && mTts.getLanguage() != null) { 817 final String currentLang = mTts.getLanguage().getISO3Language(); 818 String[] strings = mResources.getStringArray(R.array.tts_demo_strings); 819 String[] langs = mResources.getStringArray(R.array.tts_demo_string_langs); 820 821 for (int i = 0; i < strings.length; ++i) { 822 if (langs[i].equals(currentLang)) { 823 return strings[i]; 824 } 825 } 826 } 827 return null; 828 } 829 830 private void onSampleTextReceived(int resultCode, Intent data) { 831 String sample = getDefaultSampleString(); 832 833 if (resultCode == TextToSpeech.LANG_AVAILABLE && data != null) { 834 if (data != null && data.getStringExtra("sampleText") != null) { 835 sample = data.getStringExtra("sampleText"); 836 } 837 if (DEBUG) { 838 Log.d(TAG, "Got sample text: " + sample); 839 } 840 } else { 841 if (DEBUG) { 842 Log.d(TAG, "Using default sample text :" + sample); 843 } 844 } 845 846 if (sample != null && mTts != null) { 847 // The engine is guaranteed to have been initialized here 848 // because this preference is not enabled otherwise. 849 850 final boolean networkRequired = isNetworkRequiredForSynthesis(); 851 if (!networkRequired || networkRequired && 852 (mTts.isLanguageAvailable(mTts.getLanguage()) >= TextToSpeech.LANG_AVAILABLE)) { 853 HashMap<String, String> params = new HashMap<String, String>(); 854 params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "Sample"); 855 856 mTts.speak(sample, TextToSpeech.QUEUE_FLUSH, params); 857 } else { 858 Log.w(TAG, "Network required for sample synthesis for requested language"); 859 // TODO displayNetworkAlert(); 860 } 861 } else { 862 // TODO: Display an error here to the user. 863 Log.e(TAG, "Did not have a sample string for the requested language"); 864 } 865 } 866 867 private boolean isNetworkRequiredForSynthesis() { 868 Set<String> features = mTts.getFeatures(mTts.getLanguage()); 869 return features.contains(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS) && 870 !features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS); 871 } 872 873 /* 874 * Step 5: The voice data check is complete. 875 */ 876 private void onVoiceDataIntegrityCheckDone(Intent data) { 877 final String engine = mTts.getCurrentEngine(); 878 879 if (engine == null) { 880 Log.e(TAG, "Voice data check complete, but no engine bound"); 881 return; 882 } 883 884 if (data == null) { 885 Log.e(TAG, "Engine failed voice data integrity check (null return)" + 886 mTts.getCurrentEngine()); 887 return; 888 } 889 890 Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine); 891 892 setVoiceDataDetails(data); 893 } 894 895 public void setVoiceDataDetails(Intent data) { 896 mVoiceCheckData = data; 897 // This might end up running before getView above, in which 898 // case mSettingsIcon && mRadioButton will be null. In this case 899 // getView will set the right values. 900 if (mVoiceCheckData != null) { 901 mTtsSettingsEnabled = true; 902 } else { 903 mTtsSettingsEnabled = false; 904 } 905 } 906 907 /** 908 * Ask the current default engine to launch the matching INSTALL_TTS_DATA 909 * activity so the required TTS files are properly installed. 910 */ 911 private void installVoiceData() { 912 if (TextUtils.isEmpty(mCurrentEngine)) { 913 return; 914 } 915 Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); 916 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 917 intent.setPackage(mCurrentEngine); 918 try { 919 Log.v(TAG, "Installing voice data: " + intent.toUri(0)); 920 startActivity(intent); 921 } catch (ActivityNotFoundException ex) { 922 Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")"); 923 } 924 } 925 926 @Override 927 protected Object getInitialState() { 928 return ActionType.ACCESSIBILITY_OVERVIEW; 929 } 930 } 931