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