1 /* Copyright (C) 2013, 2018, 2019, 2020 Olga Yakovleva <yakovleva.o.v@gmail.com> */ 2 3 /* This program is free software: you can redistribute it and/or modify */ 4 /* it under the terms of the GNU Lesser General Public License as published by */ 5 /* the Free Software Foundation, either version 2.1 of the License, or */ 6 /* (at your option) any later version. */ 7 8 /* This program is distributed in the hope that it will be useful, */ 9 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 10 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 11 /* GNU Lesser General Public License for more details. */ 12 13 /* You should have received a copy of the GNU Lesser General Public License */ 14 /* along with this program. If not, see <http://www.gnu.org/licenses/>. */ 15 16 package com.github.olga_yakovleva.rhvoice.android; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.content.SharedPreferences; 23 import android.media.AudioFormat; 24 import android.os.Build; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.preference.PreferenceManager; 28 import android.speech.tts.SynthesisCallback; 29 import android.speech.tts.SynthesisRequest; 30 import android.speech.tts.TextToSpeech; 31 import android.speech.tts.TextToSpeechService; 32 import android.speech.tts.Voice; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 36 import com.github.olga_yakovleva.rhvoice.*; 37 import com.github.olga_yakovleva.rhvoice.LanguageInfo; 38 import java.io.File; 39 import java.nio.ByteBuffer; 40 import java.nio.ByteOrder; 41 import java.util.ArrayList; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Locale; 46 import java.util.Map; 47 import java.util.Set; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 51 public final class RHVoiceService extends TextToSpeechService 52 { 53 public static final String KEY_PARAM_TEST_VOICE="com.github.olga_yakovleva.rhvoice.android.param_test_voice"; 54 55 private static final String TAG="RHVoiceTTSService"; 56 57 private static final int[] languageSupportConstants={TextToSpeech.LANG_NOT_SUPPORTED,TextToSpeech.LANG_AVAILABLE,TextToSpeech.LANG_COUNTRY_AVAILABLE,TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE}; 58 private static final Pattern DEFAULT_VOICE_NAME_PATTERN=Pattern.compile("^([a-z]{3})-default$"); 59 60 public static final String ACTION_CHECK_DATA="com.github.olga_yakovleva.rhvoice.android.action.service_check_data"; 61 private final BroadcastReceiver dataStateReceiver=new BroadcastReceiver() 62 { 63 @Override 64 public void onReceive(Context context,Intent intent) 65 { 66 if(BuildConfig.DEBUG) 67 Log.v(TAG,"Checking data"); 68 List<String> oldPaths=paths; 69 paths=Data.getPaths(context); 70 boolean changed=!paths.equals(oldPaths); 71 if(BuildConfig.DEBUG) 72 Log.v(TAG,"Paths changed: "+changed); 73 if(changed) 74 initialize(); 75 } 76 }; 77 78 private final BroadcastReceiver packageReceiver=new OnPackageReceiver(); 79 80 private static interface SettingValueTranslator 81 { load(SharedPreferences prefs,String key)82 public Object load(SharedPreferences prefs,String key); translate(Object value)83 public String translate(Object value); 84 } 85 86 private static final SettingValueTranslator prosodySettingValueTranslator=new SettingValueTranslator() 87 { 88 public Object load(SharedPreferences prefs,String key) 89 { 90 return prefs.getString(key,"100"); 91 } 92 93 public String translate(Object value) 94 { 95 try 96 { 97 int n=Integer.parseInt((String)value); 98 float f=n/100.0f; 99 return String.valueOf(f); 100 } 101 catch(NumberFormatException e) 102 { 103 return "1"; 104 } 105 } 106 }; 107 108 private static final SettingValueTranslator yesSettingValueTranslator=new SettingValueTranslator() 109 { 110 public Object load(SharedPreferences prefs,String key) 111 { 112 Boolean value=prefs.getBoolean(key,true); 113 return value; 114 } 115 116 public String translate(Object value) 117 { 118 return String.valueOf(value).toLowerCase(); 119 } 120 }; 121 122 private static final SettingValueTranslator qualitySettingValueTranslator=new SettingValueTranslator() 123 { 124 public Object load(SharedPreferences prefs,String key) 125 { 126 return prefs.getString(key,"std"); 127 } 128 129 public String translate(Object value) 130 { 131 return value.toString(); 132 } 133 }; 134 135 private static class MappedSetting 136 { 137 public final String prefKey; 138 public Object prefValue; 139 public final String nativeKey; 140 public final SettingValueTranslator valueTranslator; 141 MappedSetting(String prefKey,String nativeKey,SettingValueTranslator valueTranslator)142 public MappedSetting(String prefKey,String nativeKey,SettingValueTranslator valueTranslator) 143 { 144 this.prefKey=prefKey; 145 this.nativeKey=nativeKey; 146 this.valueTranslator=valueTranslator; 147 } 148 } 149 150 private static class Tts 151 { 152 public TTSEngine engine=null; 153 public List<AndroidVoiceInfo> voices; 154 public Map<String,LanguageInfo> languageIndex; 155 public Map<String,AndroidVoiceInfo> voiceIndex; 156 public List<MappedSetting> mappedSettings; 157 Tts()158 public Tts() 159 { 160 voices=new ArrayList<AndroidVoiceInfo>(); 161 languageIndex=new HashMap<String,LanguageInfo>(); 162 voiceIndex=new HashMap<String,AndroidVoiceInfo>(); 163 mappedSettings=new ArrayList<MappedSetting>(); 164 } 165 Tts(Tts other,boolean passEngine)166 public Tts(Tts other,boolean passEngine) 167 { 168 this.voices=other.voices; 169 this.languageIndex=other.languageIndex; 170 this.voiceIndex=other.voiceIndex; 171 this.mappedSettings=other.mappedSettings; 172 if(!passEngine) 173 return; 174 this.engine=other.engine; 175 other.engine=null; 176 } 177 } 178 179 private static class TtsManager 180 { 181 private Tts tts; 182 private boolean done; 183 reset(Tts newTts)184 public synchronized void reset(Tts newTts) 185 { 186 if(newTts==null) 187 throw new IllegalArgumentException(); 188 if(tts!=null&&tts.engine!=null) 189 tts.engine.shutdown(); 190 tts=newTts; 191 } 192 destroy()193 public synchronized void destroy() 194 { 195 done=true; 196 if(tts!=null&&tts.engine!=null) 197 tts.engine.shutdown(); 198 tts=null; 199 } 200 acquireForSynthesis()201 public synchronized Tts acquireForSynthesis() 202 { 203 if(done) 204 return null; 205 if(tts==null) 206 return null; 207 if(tts.engine==null) 208 return null; 209 if(tts.voices.isEmpty()) 210 return null; 211 Tts result=new Tts(tts,true); 212 return result; 213 } 214 release(Tts usedTts)215 public synchronized void release(Tts usedTts) 216 { 217 if(usedTts==null||usedTts.engine==null) 218 throw new IllegalArgumentException(); 219 if(done||tts.engine!=null) 220 usedTts.engine.shutdown(); 221 else 222 tts=new Tts(usedTts,true); 223 } 224 get()225 public synchronized Tts get() 226 { 227 if(tts==null) 228 return null; 229 return new Tts(tts,false); 230 } 231 } 232 233 private final TtsManager ttsManager=new TtsManager(); 234 private volatile AndroidVoiceInfo currentVoice; 235 private volatile boolean speaking=false; 236 private List<String> paths=new ArrayList<String>(); 237 private Handler handler; 238 239 private class Player implements TTSClient 240 { 241 private SynthesisCallback callback; 242 private int sampleRate; 243 Player(SynthesisCallback callback)244 public Player(SynthesisCallback callback) 245 { 246 this.callback=callback; 247 } 248 setSampleRate(int sr)249 public boolean setSampleRate(int sr) 250 { 251 if(sampleRate!=0) 252 return true; 253 sampleRate=sr; 254 callback.start(sampleRate,AudioFormat.ENCODING_PCM_16BIT,1); 255 return true; 256 } 257 playSpeech(short[] samples)258 public boolean playSpeech(short[] samples) 259 { 260 if(!speaking) 261 return false; 262 if(BuildConfig.DEBUG&&sampleRate==0) 263 throw new IllegalStateException(); 264 final ByteBuffer buffer=ByteBuffer.allocate(samples.length*2); 265 buffer.order(ByteOrder.LITTLE_ENDIAN); 266 buffer.asShortBuffer().put(samples); 267 final byte[] bytes=buffer.array(); 268 final int size=callback.getMaxBufferSize(); 269 int offset=0; 270 int count; 271 while(offset<bytes.length) 272 { 273 if(!speaking) 274 return false; 275 count=Math.min(size,bytes.length-offset); 276 if(callback.audioAvailable(bytes,offset,count)!=TextToSpeech.SUCCESS) 277 return false; 278 offset+=count; 279 } 280 return true; 281 } 282 }; 283 284 static private class Candidate 285 { 286 public AndroidVoiceInfo voice; 287 public int score; 288 Candidate()289 public Candidate() 290 { 291 voice=null; 292 score=0; 293 } 294 Candidate(AndroidVoiceInfo voice,String language,String country,String variant)295 public Candidate(AndroidVoiceInfo voice,String language,String country,String variant) 296 { 297 this.voice=voice; 298 score=voice.getSupportLevel(language,country,variant); 299 } 300 } 301 302 private static class LanguageSettings 303 { 304 public AndroidVoiceInfo voice; 305 public boolean detect; 306 } 307 logLanguage(String language,String country,String variant)308 private void logLanguage(String language,String country,String variant) 309 { 310 Log.v(TAG,"Language: "+language); 311 if(TextUtils.isEmpty(country)) 312 return; 313 Log.v(TAG,"Country: "+country); 314 if(TextUtils.isEmpty(variant)) 315 return; 316 Log.v(TAG,"Variant: "+variant); 317 } 318 getLanguageSettings(Tts tts)319 private Map<String,LanguageSettings> getLanguageSettings(Tts tts) 320 { 321 Map<String,LanguageSettings> result=new HashMap<String,LanguageSettings>(); 322 SharedPreferences prefs=PreferenceManager.getDefaultSharedPreferences(this); 323 for(String language: tts.languageIndex.keySet()) 324 { 325 LanguageSettings settings=new LanguageSettings(); 326 String prefVoice=prefs.getString("language."+language+".voice",null); 327 for(AndroidVoiceInfo voice: tts.voices) 328 { 329 if(!voice.getSource().getLanguage().getAlpha3Code().equals(language)) 330 continue; 331 String voiceName=voice.getSource().getName(); 332 if(settings.voice==null) 333 { 334 settings.voice=voice; 335 if(prefVoice==null) 336 break; 337 } 338 if(voiceName.equals(prefVoice)) 339 { 340 settings.voice=voice; 341 break; 342 } 343 } 344 settings.detect=prefs.getBoolean("language."+language+".detect",true); 345 result.put(language,settings); 346 } 347 return result; 348 } 349 parseDefaultVoiceName(Tts tts,String name)350 private String parseDefaultVoiceName(Tts tts,String name) 351 { 352 Matcher matcher=DEFAULT_VOICE_NAME_PATTERN.matcher(name); 353 if(!matcher.find()) 354 return null; 355 String code=matcher.group(1); 356 if(!tts.languageIndex.containsKey(code)) 357 return null; 358 return code; 359 } 360 findBestVoice(Tts tts,String language,String country,String variant,String voiceName,boolean testing,Map<String,LanguageSettings> languageSettings)361 private Candidate findBestVoice(Tts tts,String language,String country,String variant,String voiceName,boolean testing,Map<String,LanguageSettings> languageSettings) 362 { 363 if(!TextUtils.isEmpty(voiceName)) 364 { 365 AndroidVoiceInfo voice=tts.voiceIndex.get(voiceName.toLowerCase()); 366 if(voice!=null) 367 { 368 if(testing) 369 { 370 Candidate c=new Candidate(); 371 c.voice=voice; 372 c.score=3; 373 return c; 374 } 375 else 376 return new Candidate(voice,language,country,""); 377 } 378 else if(testing) 379 return new Candidate(); 380 } 381 Candidate best=new Candidate(); 382 for(AndroidVoiceInfo voice: tts.voices) 383 { 384 Candidate candidate=new Candidate(voice,language,country,variant); 385 if(candidate.score>best.score||best.voice==null) 386 best=candidate; 387 } 388 if(!TextUtils.isEmpty(variant)&&best.score==3) 389 return best; 390 if(best.voice==null) 391 best.voice=tts.voices.get(0); 392 LanguageSettings settings=null; 393 if(languageSettings!=null) 394 settings=languageSettings.get(best.voice.getLanguage()); 395 if(settings==null) 396 return best; 397 if(settings.voice!=null) 398 best.voice=settings.voice; 399 return best; 400 } 401 initialize()402 private void initialize() 403 { 404 if(BuildConfig.DEBUG) 405 Log.i(TAG,"Initializing the engine"); 406 if(paths.isEmpty()) 407 { 408 Log.w(TAG,"No voice data"); 409 return; 410 } 411 Tts tts=new Tts(); 412 try 413 { 414 File configDir=Config.getDir(this); 415 tts.engine=new TTSEngine("",configDir.getAbsolutePath(),paths,CoreLogger.instance); 416 } 417 catch(Exception e) 418 { 419 if(BuildConfig.DEBUG) 420 Log.e(TAG,"Error during engine initialization",e); 421 return; 422 } 423 List<VoiceInfo> engineVoices=tts.engine.getVoices(); 424 if(engineVoices.isEmpty()) 425 { 426 if(BuildConfig.DEBUG) 427 Log.w(TAG,"No voices"); 428 tts.engine.shutdown(); 429 return; 430 } 431 for(VoiceInfo engineVoice: engineVoices) 432 { 433 AndroidVoiceInfo nextVoice=new AndroidVoiceInfo(engineVoice); 434 if(BuildConfig.DEBUG) 435 Log.i(TAG,"Found voice "+nextVoice.toString()); 436 tts.voices.add(nextVoice); 437 tts.voiceIndex.put(nextVoice.getName().toLowerCase(),nextVoice); 438 LanguageInfo engineLanguage=engineVoice.getLanguage(); 439 tts.languageIndex.put(engineLanguage.getAlpha3Code(),engineLanguage); 440 } 441 for(String lang: tts.languageIndex.keySet()) 442 { 443 tts.mappedSettings.add(new MappedSetting("language."+lang+".volume","languages."+lang+".default_volume",prosodySettingValueTranslator)); 444 tts.mappedSettings.add(new MappedSetting("language."+lang+".rate","languages."+lang+".default_rate",prosodySettingValueTranslator)); 445 tts.mappedSettings.add(new MappedSetting("language."+lang+".use_pseudo_english","languages."+lang+".use_pseudo_english",yesSettingValueTranslator)); 446 } 447 tts.mappedSettings.add(new MappedSetting("quality","quality",qualitySettingValueTranslator)); 448 ttsManager.reset(tts); 449 } 450 registerPackageReceiver()451 private void registerPackageReceiver() 452 { 453 IntentFilter filter=new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 454 filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); 455 filter.addDataScheme("package"); 456 registerReceiver(packageReceiver,filter); 457 } 458 459 private class VoiceInstaller implements Runnable 460 { 461 private final String languageCode; 462 private final String countryCode; 463 VoiceInstaller(String languageCode,String countryCode)464 public VoiceInstaller(String languageCode,String countryCode) 465 { 466 this.languageCode=languageCode; 467 this.countryCode=countryCode; 468 } 469 run()470 public void run() 471 { 472 LanguagePack lang=Data.findMatchingLanguage(languageCode,countryCode); 473 if(lang==null) 474 return; 475 for(VoicePack voice: lang.getVoices()) 476 { 477 if(voice.getEnabled(RHVoiceService.this)) 478 return; 479 } 480 VoicePack voice=lang.getDefaultVoice(); 481 voice.setEnabled(RHVoiceService.this,true); 482 } 483 } 484 485 @Override onCreate()486 public void onCreate() 487 { 488 if(BuildConfig.DEBUG) 489 Log.i(TAG,"Starting the service"); 490 handler=new Handler(); 491 paths=Data.getPaths(this); 492 Data.scheduleSync(this,false); 493 LocalBroadcastManager.getInstance(this).registerReceiver(dataStateReceiver,new IntentFilter(ACTION_CHECK_DATA)); 494 registerPackageReceiver(); 495 initialize(); 496 super.onCreate(); 497 } 498 499 @Override onDestroy()500 public void onDestroy() 501 { 502 super.onDestroy(); 503 unregisterReceiver(packageReceiver); 504 LocalBroadcastManager.getInstance(this).unregisterReceiver(dataStateReceiver); 505 ttsManager.destroy(); 506 } 507 508 @Override onGetLanguage()509 protected String[] onGetLanguage() 510 { 511 if(BuildConfig.DEBUG) 512 Log.v(TAG,"onGetLanguage called"); 513 String[] result={"rus","RUS",""}; 514 AndroidVoiceInfo voice=currentVoice; 515 if(voice==null) 516 { 517 Tts tts=ttsManager.get(); 518 if(tts!=null) 519 { 520 Locale locale=Locale.getDefault(); 521 Candidate bestMatch=findBestVoice(tts,locale.getISO3Language(),locale.getISO3Country(),"","",false,getLanguageSettings(tts)); 522 if(bestMatch.voice!=null) 523 voice=bestMatch.voice; 524 } 525 } 526 if(voice==null) 527 return result; 528 result[0]=voice.getLanguage(); 529 result[1]=voice.getCountry(); 530 if(BuildConfig.DEBUG) 531 Log.v(TAG,"onGetLanguage returns "+result[0]+"-"+result[1]); 532 return result; 533 } 534 535 @Override onIsLanguageAvailable(String language,String country,String variant)536 protected int onIsLanguageAvailable(String language,String country,String variant) 537 { 538 if(BuildConfig.DEBUG) 539 { 540 Log.v(TAG,"onIsLanguageAvailable called"); 541 logLanguage(language,country,variant); 542 } 543 Tts tts=ttsManager.get(); 544 if(tts==null) 545 { 546 if(BuildConfig.DEBUG) 547 Log.w(TAG,"Not initialized yet"); 548 return TextToSpeech.LANG_NOT_SUPPORTED; 549 } 550 Candidate bestMatch=findBestVoice(tts,language,country,variant,"",false,null); 551 int result=languageSupportConstants[bestMatch.score]; 552 if(BuildConfig.DEBUG) 553 Log.v(TAG,"Result: "+result); 554 return result; 555 } 556 557 @Override onLoadLanguage(String language,String country,String variant)558 protected int onLoadLanguage(String language,String country,String variant) 559 { 560 if(BuildConfig.DEBUG) 561 Log.v(TAG,"onLoadLanguage called"); 562 return onIsLanguageAvailable(language,country,variant); 563 } 564 565 @Override onStop()566 protected void onStop() 567 { 568 speaking=false; 569 } 570 applyMappedSettings(Tts tts)571 private void applyMappedSettings(Tts tts) 572 { 573 SharedPreferences prefs=PreferenceManager.getDefaultSharedPreferences(this); 574 Object oldPrefValue; 575 String nativeValue; 576 for(MappedSetting setting: tts.mappedSettings) 577 { 578 oldPrefValue=setting.prefValue; 579 setting.prefValue=setting.valueTranslator.load(prefs,setting.prefKey); 580 if(oldPrefValue!=null&&oldPrefValue.equals(setting.prefValue)) 581 continue; 582 nativeValue=setting.valueTranslator.translate(setting.prefValue); 583 tts.engine.configure(setting.nativeKey,nativeValue); 584 } 585 } 586 587 @Override onSynthesizeText(SynthesisRequest request,SynthesisCallback callback)588 protected void onSynthesizeText(SynthesisRequest request,SynthesisCallback callback) 589 { 590 if(BuildConfig.DEBUG) 591 { 592 Log.v(TAG,"onSynthesize called"); 593 logLanguage(request.getLanguage(),request.getCountry(),request.getVariant()); 594 } 595 boolean testing=false; 596 Bundle requestParams=request.getParams(); 597 if(requestParams!=null&&requestParams.containsKey(KEY_PARAM_TEST_VOICE)) 598 testing=true; 599 Tts tts=ttsManager.acquireForSynthesis(); 600 if(tts==null) 601 { 602 if(BuildConfig.DEBUG) 603 Log.w(TAG,"Not initialized yet"); 604 if(!testing) 605 handler.post(new VoiceInstaller(request.getLanguage(),request.getCountry())); 606 callback.error(); 607 return; 608 } 609 try 610 { 611 speaking=true; 612 String language=request.getLanguage(); 613 String country=request.getCountry(); 614 String variant=request.getVariant(); 615 Map<String,LanguageSettings> languageSettings=getLanguageSettings(tts); 616 String voiceName=""; 617 if(testing) 618 voiceName=requestParams.getString(KEY_PARAM_TEST_VOICE); 619 else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP) 620 { 621 voiceName=request.getVoiceName(); 622 if(!TextUtils.isEmpty(voiceName)) 623 { 624 if(BuildConfig.DEBUG) 625 Log.v(TAG,"Voice name: "+voiceName); 626 String code=parseDefaultVoiceName(tts,voiceName); 627 if(code!=null) 628 { 629 if(BuildConfig.DEBUG) 630 Log.v(TAG,"Default voice for "+code); 631 language=code; 632 country=""; 633 variant=""; 634 voiceName=""; 635 } 636 } 637 } 638 final Candidate bestMatch=findBestVoice(tts,language,country,variant,voiceName,testing,languageSettings); 639 if(bestMatch.voice==null) 640 { 641 if(BuildConfig.DEBUG) 642 Log.e(TAG,"Unsupported language"); 643 if(!testing) 644 handler.post(new VoiceInstaller(language,country)); 645 callback.error(); 646 return; 647 } 648 if(BuildConfig.DEBUG) 649 Log.v(TAG,"Selected voice: "+bestMatch.voice.getSource().getName()); 650 currentVoice=bestMatch.voice; 651 StringBuilder voiceProfileSpecBuilder=new StringBuilder(); 652 voiceProfileSpecBuilder.append(bestMatch.voice.getSource().getName()); 653 for(Map.Entry<String,LanguageSettings> entry: languageSettings.entrySet()) 654 { 655 if(entry.getKey().equals(bestMatch.voice.getLanguage())) 656 continue; 657 if(entry.getValue().detect) 658 { 659 String name=entry.getValue().voice.getSource().getName(); 660 voiceProfileSpecBuilder.append("+").append(name); 661 } 662 } 663 String profileSpec=voiceProfileSpecBuilder.toString(); 664 if(BuildConfig.DEBUG) 665 Log.v(TAG,"Synthesizing the following text: "+request.getText()); 666 int rate=request.getSpeechRate(); 667 if(BuildConfig.DEBUG) 668 Log.v(TAG,"rate="+rate); 669 int pitch=request.getPitch(); 670 if(BuildConfig.DEBUG) 671 Log.v(TAG,"pitch="+pitch); 672 if(BuildConfig.DEBUG) 673 Log.v(TAG,"Profile: "+profileSpec); 674 applyMappedSettings(tts); 675 final SynthesisParameters params=new SynthesisParameters(); 676 params.setVoiceProfile(profileSpec); 677 params.setRate(((double)rate)/100.0); 678 params.setPitch(((double)pitch)/100.0); 679 final Player player=new Player(callback); 680 tts.engine.speak(request.getText(),params,player); 681 player.setSampleRate(24000); 682 callback.done(); 683 } 684 catch(RHVoiceException e) 685 { 686 if(BuildConfig.DEBUG) 687 Log.e(TAG,"Synthesis error",e); 688 callback.error(); 689 } 690 finally 691 { 692 speaking=false; 693 ttsManager.release(tts); 694 } 695 } 696 697 @Override onGetDefaultVoiceNameFor(String language,String country,String variant)698 public String onGetDefaultVoiceNameFor(String language,String country,String variant) 699 { 700 if(BuildConfig.DEBUG) 701 { 702 Log.v(TAG,"onGetDefaultVoiceNameFor called"); 703 logLanguage(language,country,variant); 704 } 705 Tts tts=ttsManager.get(); 706 if(tts==null) 707 return null; 708 if(!tts.languageIndex.containsKey(language)) 709 return null; 710 String name=language+"-default"; 711 if(BuildConfig.DEBUG) 712 Log.v(TAG,"Voice name: "+name); 713 return name; 714 } 715 716 @Override onGetVoices()717 public List<Voice> onGetVoices() 718 { 719 if(BuildConfig.DEBUG) 720 Log.v(TAG,"onGetVoices called"); 721 List<Voice> result=new ArrayList<Voice>(); 722 Tts tts=ttsManager.get(); 723 if(tts==null) 724 return result; 725 Voice v=null; 726 for(AndroidVoiceInfo voice: tts.voices) 727 { 728 v=voice.getAndroidVoice(); 729 if(BuildConfig.DEBUG) 730 Log.v(TAG,"Voice: "+v.toString()); 731 result.add(v); 732 } 733 for(LanguageInfo lang: tts.languageIndex.values()) 734 { 735 Locale loc=new Locale(lang.getAlpha2Code(),lang.getAlpha2CountryCode()); 736 v=new Voice(lang.getAlpha3Code()+"-default",loc,Voice.QUALITY_NORMAL,Voice.LATENCY_NORMAL,false,new HashSet<String>()); 737 if(BuildConfig.DEBUG) 738 Log.v(TAG,"Default voice: "+v.toString()); 739 result.add(v); 740 } 741 return result; 742 } 743 744 @Override onIsValidVoiceName(String name)745 public int onIsValidVoiceName(String name) 746 { 747 if(BuildConfig.DEBUG) 748 Log.v(TAG,"onIsValidVoiceName called for name "+name); 749 Tts tts=ttsManager.get(); 750 if(tts==null) 751 return TextToSpeech.ERROR; 752 String code=parseDefaultVoiceName(tts,name); 753 if(code!=null) 754 { 755 if(BuildConfig.DEBUG) 756 Log.v(TAG,"Default voice name for "+code); 757 return TextToSpeech.SUCCESS; 758 } 759 if(tts.voiceIndex.containsKey(name.toLowerCase())) 760 { 761 if(BuildConfig.DEBUG) 762 Log.v(TAG,"Voice found"); 763 return TextToSpeech.SUCCESS; 764 } 765 else 766 { 767 if(BuildConfig.DEBUG) 768 Log.v(TAG,"Voice not found"); 769 return TextToSpeech.ERROR; 770 } 771 } 772 773 @Override onLoadVoice(String name)774 public int onLoadVoice(String name) 775 { 776 if(BuildConfig.DEBUG) 777 Log.v(TAG,"onLoadVoice called with voice name "+name); 778 return onIsValidVoiceName(name); 779 } 780 } 781