PageRenderTime 78ms CodeModel.GetById 13ms app.highlight 53ms RepoModel.GetById 2ms app.codeStats 0ms

/tts/src/com/google/tts/TTSService.java

http://eyes-free.googlecode.com/
Java | 1546 lines | 1075 code | 138 blank | 333 comment | 219 complexity | d4d5af6571cf868a170c9f01027ae751 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2 * Copyright (C) 2009 Google Inc.
   3 *
   4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   5 * use this file except in compliance with the License. You may obtain a copy of
   6 * 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, WITHOUT
  12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13 * License for the specific language governing permissions and limitations under
  14 * the License.
  15 */
  16
  17package com.google.tts;
  18
  19import android.app.Service;
  20import android.content.Context;
  21import android.content.Intent;
  22import android.content.pm.ActivityInfo;
  23import android.content.pm.PackageInfo;
  24import android.content.pm.PackageManager;
  25import android.content.pm.ResolveInfo;
  26import android.content.pm.PackageManager.NameNotFoundException;
  27import android.content.res.Resources;
  28import android.media.AudioManager;
  29import android.media.MediaPlayer;
  30import android.media.MediaPlayer.OnCompletionListener;
  31import android.net.Uri;
  32import android.os.IBinder;
  33import android.os.RemoteCallbackList;
  34import android.os.RemoteException;
  35import android.preference.PreferenceManager;
  36import com.google.tts.ITTSCallback;
  37import android.util.Log;
  38import android.util.TypedValue;
  39
  40import java.io.File;
  41import java.io.IOException;
  42import java.io.InputStream;
  43import java.util.ArrayList;
  44import java.util.Arrays;
  45import java.util.Enumeration;
  46import java.util.HashMap;
  47import java.util.List;
  48import java.util.Locale;
  49import java.util.Properties;
  50import java.util.concurrent.locks.ReentrantLock;
  51import java.util.concurrent.TimeUnit;
  52
  53import javax.xml.parsers.FactoryConfigurationError;
  54
  55/**
  56 * @hide Synthesizes speech from text. This is implemented as a service so that
  57 *       other applications can call the TTS without needing to bundle the TTS
  58 *       in the build.
  59 */
  60public class TTSService extends Service implements OnCompletionListener {
  61
  62    // This is for legacy purposes. The old TTS used languages of the format
  63    // "xx-rYY" (xx denotes language and YY denotes region).
  64    // This now needs to be mapped to Locale, which is of the format:
  65    // xxx-YYY-variant (xxx denotes language, YYY denotes country, and variant
  66    // is the name of the variant).
  67    static final HashMap<String, String> langRegionToLocale = new HashMap<String, String>();
  68    static {
  69        langRegionToLocale.put("af", "afr");
  70        langRegionToLocale.put("bs", "bos");
  71        langRegionToLocale.put("zh-rHK", "yue");
  72        langRegionToLocale.put("zh", "cmn");
  73        langRegionToLocale.put("hr", "hrv");
  74        langRegionToLocale.put("cz", "ces");
  75        langRegionToLocale.put("cs", "ces");
  76        langRegionToLocale.put("nl", "nld");
  77        langRegionToLocale.put("en", "eng");
  78        langRegionToLocale.put("en-rUS", "eng-USA");
  79        langRegionToLocale.put("en-rGB", "eng-GBR");
  80        langRegionToLocale.put("eo", "epo");
  81        langRegionToLocale.put("fi", "fin");
  82        langRegionToLocale.put("fr", "fra");
  83        langRegionToLocale.put("fr-rFR", "fra-FRA");
  84        langRegionToLocale.put("de", "deu");
  85        langRegionToLocale.put("de-rDE", "deu-DEU");
  86        langRegionToLocale.put("el", "ell");
  87        langRegionToLocale.put("hi", "hin");
  88        langRegionToLocale.put("hu", "hun");
  89        langRegionToLocale.put("is", "isl");
  90        langRegionToLocale.put("id", "ind");
  91        langRegionToLocale.put("it", "ita");
  92        langRegionToLocale.put("it-rIT", "ita-ITA");
  93        langRegionToLocale.put("ku", "kur");
  94        langRegionToLocale.put("la", "lat");
  95        langRegionToLocale.put("mk", "mkd");
  96        langRegionToLocale.put("no", "nor");
  97        langRegionToLocale.put("pl", "pol");
  98        langRegionToLocale.put("pt", "por");
  99        langRegionToLocale.put("ro", "ron");
 100        langRegionToLocale.put("ru", "rus");
 101        langRegionToLocale.put("sr", "srp");
 102        langRegionToLocale.put("sk", "slk");
 103        langRegionToLocale.put("es", "spa");
 104        langRegionToLocale.put("es-rES", "spa-ESP");
 105        langRegionToLocale.put("es-rMX", "spa-MEX");
 106        langRegionToLocale.put("sw", "swa");
 107        langRegionToLocale.put("sv", "swe");
 108        langRegionToLocale.put("ta", "tam");
 109        langRegionToLocale.put("tr", "tur");
 110        langRegionToLocale.put("vi", "vie");
 111        langRegionToLocale.put("cy", "cym");
 112    }
 113
 114    private static class SpeechItem {
 115        public static final int TEXT = 0;
 116
 117        public static final int EARCON = 1;
 118
 119        public static final int SILENCE = 2;
 120
 121        public static final int TEXT_TO_FILE = 3;
 122
 123        public String mText = "";
 124
 125        public ArrayList<String> mParams = null;
 126
 127        public int mType = TEXT;
 128
 129        public long mDuration = 0;
 130
 131        public String mFilename = null;
 132
 133        public String mCallingApp = "";
 134
 135        public SpeechItem(String source, String text, ArrayList<String> params, int itemType) {
 136            mText = text;
 137            mParams = params;
 138            mType = itemType;
 139            mCallingApp = source;
 140        }
 141
 142        public SpeechItem(String source, long silenceTime, ArrayList<String> params) {
 143            mDuration = silenceTime;
 144            mParams = params;
 145            mType = SILENCE;
 146            mCallingApp = source;
 147        }
 148
 149        public SpeechItem(String source, String text, ArrayList<String> params,
 150                int itemType, String filename) {
 151            mText = text;
 152            mParams = params;
 153            mType = itemType;
 154            mFilename = filename;
 155            mCallingApp = source;
 156        }
 157
 158    }
 159
 160    /**
 161     * Contains the information needed to access a sound resource; the name of
 162     * the package that contains the resource and the resID of the resource
 163     * within that package.
 164     */
 165    private static class SoundResource {
 166        public String mSourcePackageName = null;
 167
 168        public int mResId = -1;
 169
 170        public String mFilename = null;
 171
 172        public SoundResource(String packageName, int id) {
 173            mSourcePackageName = packageName;
 174            mResId = id;
 175            mFilename = null;
 176        }
 177
 178        public SoundResource(String file) {
 179            mSourcePackageName = null;
 180            mResId = -1;
 181            mFilename = file;
 182        }
 183    }
 184
 185    // If the speech queue is locked for more than 5 seconds, something has gone
 186    // very wrong with processSpeechQueue.
 187    private static final int SPEECHQUEUELOCK_TIMEOUT = 5000;
 188
 189    private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
 190
 191    private static final int MAX_FILENAME_LENGTH = 250;
 192
 193    // TODO use the TTS stream type when available
 194    private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
 195
 196    private static final String ACTION = "android.intent.action.USE_TTS";
 197
 198    private static final String CATEGORY = "android.intent.category.TTS";
 199
 200    private static final String BETA_ACTION = "com.google.intent.action.START_TTS_SERVICE_BETA";
 201
 202    private static final String BETA_CATEGORY = "com.google.intent.category.TTS_BETA";
 203
 204    private static final String PKGNAME = "android.tts";
 205
 206    // Change this to the system/lib path when in the framework
 207    private static final String DEFAULT_SYNTH = "com.svox.pico";
 208
 209    protected static final String SERVICE_TAG = "TtsService";
 210
 211    private final RemoteCallbackList<ITtsCallbackBeta> mCallbacks = new RemoteCallbackList<ITtsCallbackBeta>();
 212
 213    private HashMap<String, ITtsCallbackBeta> mCallbacksMap;
 214
 215    private final RemoteCallbackList<ITTSCallback> mCallbacksOld = new RemoteCallbackList<ITTSCallback>();
 216
 217    private boolean mIsSpeaking;
 218    
 219    private boolean mSynthBusy;
 220
 221    private ArrayList<SpeechItem> mSpeechQueue;
 222
 223    private HashMap<String, SoundResource> mEarcons;
 224
 225    private HashMap<String, SoundResource> mUtterances;
 226
 227    private MediaPlayer mPlayer;
 228
 229    private SpeechItem mCurrentSpeechItem;
 230
 231    private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that
 232
 233    // in-flight synth calls
 234    // are killed when stop is used.
 235    private TTSService mSelf;
 236
 237    // lock for the speech queue (mSpeechQueue) and the current speech item
 238    // (mCurrentSpeechItem)
 239    private final ReentrantLock speechQueueLock = new ReentrantLock();
 240
 241    private final ReentrantLock synthesizerLock = new ReentrantLock();
 242
 243    private static SynthProxyBeta sNativeSynth = null;
 244
 245    private String currentSpeechEngineSOFile = "";
 246
 247    private boolean deprecatedKeepBlockingFlag = false;
 248
 249
 250    @Override
 251    public void onCreate() {
 252        super.onCreate();
 253        Log.v(SERVICE_TAG, "TtsService.onCreate()");
 254
 255        // String soLibPath = "/data/data/com.google.tts/lib/libttspico.so";
 256        // Use this path when building in the framework:
 257        // setEngine("/system/lib/libttspico.so");
 258        // setEngine("/data/data/com.google.tts/lib/libespeakengine.so");
 259        // setEngine("/data/data/com.google.tts/lib/libttspico.so");
 260        // Also, switch to using the system settings in the framework.
 261        currentSpeechEngineSOFile = "";
 262        String preferredEngine = PreferenceManager.getDefaultSharedPreferences(this).getString(
 263                "tts_default_synth", DEFAULT_SYNTH);
 264        if (setEngine(preferredEngine) != TextToSpeechBeta.SUCCESS) {
 265            Log.e(SERVICE_TAG, "Unable to start up with " + preferredEngine
 266                    + ". Falling back to the default TTS engine.");
 267            setEngine(DEFAULT_SYNTH);
 268        }
 269
 270        mSelf = this;
 271        mIsSpeaking = false;
 272        mSynthBusy = false;
 273
 274        mEarcons = new HashMap<String, SoundResource>();
 275        mUtterances = new HashMap<String, SoundResource>();
 276        mCallbacksMap = new HashMap<String, ITtsCallbackBeta>();
 277
 278        mSpeechQueue = new ArrayList<SpeechItem>();
 279        mPlayer = null;
 280        mCurrentSpeechItem = null;
 281        mKillList = new HashMap<SpeechItem, Boolean>();
 282
 283        setDefaultSettings();
 284
 285        // Standalone library only - include a set of default earcons
 286        // and pre-recorded audio.
 287        // These are not in the framework due to concerns about the size.
 288        Resources res = getResources();
 289        InputStream fis = res.openRawResource(R.raw.soundsamples);
 290        try {
 291
 292            Properties soundsamples = new Properties();
 293            soundsamples.load(fis);
 294            Enumeration<Object> textKeys = soundsamples.keys();
 295            while (textKeys.hasMoreElements()) {
 296                String text = textKeys.nextElement().toString();
 297                String name = "com.google.tts:raw/" + soundsamples.getProperty(text);
 298                TypedValue value = new TypedValue();
 299                getResources().getValue(name, value, false);
 300                mUtterances.put(text, new SoundResource(PKGNAME, value.resourceId));
 301            }
 302
 303        } catch (FactoryConfigurationError e) {
 304            e.printStackTrace();
 305        } catch (IOException e) {
 306            e.printStackTrace();
 307        } catch (IllegalArgumentException e) {
 308            e.printStackTrace();
 309        } catch (SecurityException e) {
 310            e.printStackTrace();
 311        }
 312
 313        // Deprecated - these should be earcons from now on!
 314        // Leave this here for one more version before removing it completely.
 315        mUtterances.put("[tock]", new SoundResource(PKGNAME, R.raw.tock_snd));
 316        mUtterances.put("[slnc]", new SoundResource(PKGNAME, R.raw.slnc_snd));
 317
 318        mEarcons.put("[tock]", new SoundResource(PKGNAME, R.raw.tock_snd));
 319        mEarcons.put("[slnc]", new SoundResource(PKGNAME, R.raw.slnc_snd));
 320
 321        Log.e(SERVICE_TAG, "onCreate completed.");
 322    }
 323
 324    @Override
 325    public void onDestroy() {
 326        super.onDestroy();
 327
 328        killAllUtterances();
 329
 330        // Don't hog the media player
 331        cleanUpPlayer();
 332
 333        if (sNativeSynth != null) {
 334            sNativeSynth.shutdown();
 335        }
 336        sNativeSynth = null;
 337
 338        // Unregister all callbacks.
 339        mCallbacks.kill();
 340        mCallbacksOld.kill();
 341
 342        Log.v(SERVICE_TAG, "onDestroy() completed");
 343    }
 344
 345    private int setEngine(String enginePackageName) {
 346        if (isDefaultEnforced()){
 347            enginePackageName = getDefaultEngine();
 348        }
 349        
 350        // This is a hack to prevent the user from trying to run the Pico
 351        // engine if they are on Cupcake since the Pico auto-install only works
 352        // on
 353        // Donut or higher and no equivalent has not been backported.
 354        int sdkInt = 4;
 355        try {
 356            sdkInt = Integer.parseInt(android.os.Build.VERSION.SDK);
 357        } catch (NumberFormatException e) {
 358            Log.e(SERVICE_TAG, "Unable to parse SDK version: " + android.os.Build.VERSION.SDK);
 359        }
 360        if ((sdkInt < 4) && enginePackageName.equals("com.svox.pico")) {
 361            enginePackageName = "com.google.tts";
 362        }
 363
 364        String soFilename = "";
 365        // The SVOX TTS is an exception to how the TTS packaging scheme works
 366        // because
 367        // it is part of the system and not a 3rd party add-on; thus its binary
 368        // is
 369        // actually located under /system/lib/
 370        if (enginePackageName.equals("com.svox.pico")) {
 371            // This is the path to use when this is integrated with the
 372            // framework
 373            // soFilename = "/system/lib/libttspico.so";
 374            if (sdkInt < 5) {
 375                soFilename = "/data/data/com.google.tts/lib/libttspico_4.so";
 376            } else {
 377                soFilename = "/data/data/com.google.tts/lib/libttspico.so";
 378            }
 379        } else {
 380            // Find the package
 381            // This is the correct way to do this; but it won't work in Cupcake.
 382            // :(
 383            // Intent intent = new
 384            // Intent("android.intent.action.START_TTS_ENGINE");
 385            // intent.setPackage(enginePackageName);
 386            // ResolveInfo[] enginesArray = new ResolveInfo[0];
 387            // PackageManager pm = getPackageManager();
 388            // List <ResolveInfo> resolveInfos =
 389            // pm.queryIntentActivities(intent, 0);
 390            // if ((resolveInfos == null) || resolveInfos.isEmpty()) {
 391            // Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " +
 392            // enginePackageName);
 393            // return TextToSpeechBeta.ERROR;
 394            // }
 395            // enginesArray = resolveInfos.toArray(enginesArray);
 396            // // Generate the TTS .so filename from the package
 397            // ActivityInfo aInfo = enginesArray[0].activityInfo;
 398            // soFilename = aInfo.name.replace(aInfo.packageName + ".", "") +
 399            // ".so";
 400            // soFilename = soFilename.toLowerCase();
 401            // soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" +
 402            // soFilename;
 403
 404            /* Start of hacky way of doing this */
 405            // Using a loop since we can't set the package name for the intent
 406            // in
 407            // Cupcake
 408            Intent intent = new Intent("android.intent.action.START_TTS_ENGINE");
 409            ResolveInfo[] enginesArray = new ResolveInfo[0];
 410            PackageManager pm = getPackageManager();
 411            List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
 412            enginesArray = resolveInfos.toArray(enginesArray);
 413            ActivityInfo aInfo = null;
 414            for (int i = 0; i < enginesArray.length; i++) {
 415                if (enginesArray[i].activityInfo.packageName.equals(enginePackageName)) {
 416                    aInfo = enginesArray[i].activityInfo;
 417                    break;
 418                }
 419            }
 420            if (aInfo == null) {
 421                Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName);
 422                return TextToSpeechBeta.ERROR;
 423            }
 424
 425            // Try to get a platform SDK specific binary
 426            if (sdkInt < 5) {
 427                sdkInt = 4;
 428            }
 429            soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + "_" + sdkInt + ".so";
 430            soFilename = soFilename.toLowerCase();
 431            soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename;
 432            File f = new File(soFilename);
 433            // If no such binary is available, default to a generic binary
 434            if (!f.exists()) {
 435                soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so";
 436                soFilename = soFilename.toLowerCase();
 437                soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename;
 438            }
 439
 440            /* End of hacky way of doing this */
 441        }
 442
 443        if (currentSpeechEngineSOFile.equals(soFilename)) {
 444            return TextToSpeechBeta.SUCCESS;
 445        }
 446
 447        File f = new File(soFilename);
 448        if (!f.exists()) {
 449            Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename);
 450            return TextToSpeechBeta.ERROR;
 451        }
 452        if (sNativeSynth != null) {
 453            // Should really be a stopSync here, but that is not available in
 454            // Donut...
 455            // sNativeSynth.stopSync();
 456            sNativeSynth.stop();
 457            sNativeSynth.shutdown();
 458            sNativeSynth = null;
 459        }
 460        sNativeSynth = new SynthProxyBeta(soFilename);
 461        currentSpeechEngineSOFile = soFilename;
 462        return TextToSpeechBeta.SUCCESS;
 463    }
 464
 465    private void setDefaultSettings() {
 466        setLanguage("", getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant());
 467
 468        // speech rate
 469        setSpeechRate("", getDefaultRate());
 470    }
 471
 472    private boolean isDefaultEnforced() {
 473        return (PreferenceManager.getDefaultSharedPreferences(this).getInt("toggle_use_default_tts_settings",
 474                0) == 1);
 475        // In the framework, use the Secure settings instead by doing:
 476        //
 477        // return (android.provider.Settings.Secure.getInt(mResolver,
 478        // android.provider.Settings.Secure.TTS_USE_DEFAULTS,
 479        // TextToSpeechBeta.Engine.USE_DEFAULTS)
 480        // == 1 );
 481    }
 482
 483    private String getDefaultEngine() {
 484        // In the framework, use the Secure settings instead by doing:
 485        // String defaultEngine = android.provider.Settings.Secure.getString(mResolver,
 486        // android.provider.Settings.Secure.TTS_DEFAULT_SYNTH);
 487        String defaultEngine = PreferenceManager.getDefaultSharedPreferences(this).getString("tts_default_synth", DEFAULT_SYNTH);
 488        if (defaultEngine == null) {
 489            return TextToSpeechBeta.Engine.DEFAULT_SYNTH;
 490        } else {
 491            return defaultEngine;
 492        }
 493    }
 494
 495    private int getDefaultRate() {
 496        return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(this).getString(
 497                "rate_pref", "100"));
 498        // In the framework, use the Secure settings instead by doing:
 499        //    	
 500        // return android.provider.Settings.Secure.getInt(mResolver,
 501        // android.provider.Settings.Secure.TTS_DEFAULT_RATE,
 502        // TextToSpeechBeta.Engine.DEFAULT_RATE);
 503    }
 504
 505    private int getDefaultPitch() {
 506        // Pitch is not user settable; the default pitch is always 100.
 507        return 100;
 508    }
 509
 510    private String getDefaultLanguage() {
 511        return PreferenceManager.getDefaultSharedPreferences(this).getString(
 512                "tts_default_lang", Locale.getDefault().getISO3Language());
 513
 514        // In the framework, use the Secure settings instead by doing:
 515        //       	
 516        // String defaultLang =
 517        // android.provider.Settings.Secure.getString(mResolver,
 518        // android.provider.Settings.Secure.TTS_DEFAULT_LANG);
 519    }
 520
 521    private String getDefaultCountry() {
 522        return PreferenceManager.getDefaultSharedPreferences(this).getString(
 523                "tts_default_country", Locale.getDefault().getISO3Country());
 524        
 525        // In the framework, use the Secure settings instead by doing:
 526        //       	        
 527        // String defaultCountry =
 528        // android.provider.Settings.Secure.getString(mResolver,
 529        // android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY);
 530    }
 531
 532    private String getDefaultLocVariant() {
 533        return PreferenceManager.getDefaultSharedPreferences(this).getString(
 534                "tts_default_variant", Locale.getDefault().getVariant());
 535        // In the framework, use the Secure settings instead by doing:
 536        //       	          	
 537        // String defaultVar =
 538        // android.provider.Settings.Secure.getString(mResolver,
 539        // android.provider.Settings.Secure.TTS_DEFAULT_VARIANT);
 540    }
 541
 542    private int setSpeechRate(String callingApp, int rate) {
 543        int res = TextToSpeechBeta.ERROR;
 544        try {
 545            if (isDefaultEnforced()) {
 546                res = sNativeSynth.setSpeechRate(getDefaultRate());
 547            } else {
 548                res = sNativeSynth.setSpeechRate(rate);
 549            }
 550        } catch (NullPointerException e) {
 551            // synth will become null during onDestroy()
 552            res = TextToSpeechBeta.ERROR;
 553        }
 554        return res;
 555    }
 556
 557    private int setPitch(String callingApp, int pitch) {
 558        int res = TextToSpeechBeta.ERROR;
 559        try {
 560            res = sNativeSynth.setPitch(pitch);
 561        } catch (NullPointerException e) {
 562            // synth will become null during onDestroy()
 563            res = TextToSpeechBeta.ERROR;
 564        }
 565        return res;
 566    }
 567
 568    private int isLanguageAvailable(String lang, String country, String variant) {
 569        int res = TextToSpeechBeta.LANG_NOT_SUPPORTED;
 570        try {
 571            res = sNativeSynth.isLanguageAvailable(lang, country, variant);
 572        } catch (NullPointerException e) {
 573            // synth will become null during onDestroy()
 574            res = TextToSpeechBeta.LANG_NOT_SUPPORTED;
 575        }
 576        return res;
 577    }
 578
 579    private String[] getLanguage() {
 580        try {
 581            return sNativeSynth.getLanguage();
 582        } catch (Exception e) {
 583            return null;
 584        }
 585    }
 586
 587    private int setLanguage(String callingApp, String lang, String country, String variant) {
 588        Log
 589                .v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant
 590                        + ")");
 591        int res = TextToSpeechBeta.ERROR;
 592        try {
 593            if (isDefaultEnforced()) {
 594                res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(),
 595                        getDefaultLocVariant());
 596            } else {
 597                res = sNativeSynth.setLanguage(lang, country, variant);
 598            }
 599        } catch (NullPointerException e) {
 600            // synth will become null during onDestroy()
 601            res = TextToSpeechBeta.ERROR;
 602        }
 603        return res;
 604    }
 605
 606    /**
 607     * Adds a sound resource to the TTS.
 608     * 
 609     * @param text The text that should be associated with the sound resource
 610     * @param packageName The name of the package which has the sound resource
 611     * @param resId The resource ID of the sound within its package
 612     */
 613    private void addSpeech(String callingApp, String text, String packageName, int resId) {
 614        mUtterances.put(text, new SoundResource(packageName, resId));
 615    }
 616
 617    /**
 618     * Adds a sound resource to the TTS.
 619     * 
 620     * @param text The text that should be associated with the sound resource
 621     * @param filename The filename of the sound resource. This must be a
 622     *            complete path like: (/sdcard/mysounds/mysoundbite.mp3).
 623     */
 624    private void addSpeech(String callingApp, String text, String filename) {
 625        mUtterances.put(text, new SoundResource(filename));
 626    }
 627
 628    /**
 629     * Adds a sound resource to the TTS as an earcon.
 630     * 
 631     * @param earcon The text that should be associated with the sound resource
 632     * @param packageName The name of the package which has the sound resource
 633     * @param resId The resource ID of the sound within its package
 634     */
 635    private void addEarcon(String callingApp, String earcon, String packageName, int resId) {
 636        mEarcons.put(earcon, new SoundResource(packageName, resId));
 637    }
 638
 639    /**
 640     * Adds a sound resource to the TTS as an earcon.
 641     * 
 642     * @param earcon The text that should be associated with the sound resource
 643     * @param filename The filename of the sound resource. This must be a
 644     *            complete path like: (/sdcard/mysounds/mysoundbite.mp3).
 645     */
 646    private void addEarcon(String callingApp, String earcon, String filename) {
 647        mEarcons.put(earcon, new SoundResource(filename));
 648    }
 649
 650    /**
 651     * Speaks the given text using the specified queueing mode and parameters.
 652     * 
 653     * @param text The text that should be spoken
 654     * @param queueMode TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts
 655     *            all previous utterances), TextToSpeech.TTS_QUEUE_ADD for
 656     *            queued
 657     * @param params An ArrayList of parameters. This is not implemented for all
 658     *            engines.
 659     */
 660    private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) {
 661        Log.v(SERVICE_TAG, "TTS service received " + text);
 662        if (queueMode == TextToSpeechBeta.QUEUE_FLUSH) {
 663            stop(callingApp);
 664        } else if (queueMode == 2) {
 665            stopAll(callingApp);
 666        }
 667        mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT));
 668        if (!mIsSpeaking) {
 669            processSpeechQueue();
 670        }
 671        return TextToSpeechBeta.SUCCESS;
 672    }
 673
 674    /**
 675     * Plays the earcon using the specified queueing mode and parameters.
 676     * 
 677     * @param earcon The earcon that should be played
 678     * @param queueMode TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts
 679     *            all previous utterances), TextToSpeech.TTS_QUEUE_ADD for
 680     *            queued
 681     * @param params An ArrayList of parameters. This is not implemented for all
 682     *            engines.
 683     */
 684    private int playEarcon(String callingApp, String earcon, int queueMode, ArrayList<String> params) {
 685        if (queueMode == TextToSpeechBeta.QUEUE_FLUSH) {
 686            stop(callingApp);
 687        } else if (queueMode == 2) {
 688            stopAll(callingApp);
 689        }
 690        mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON));
 691        if (!mIsSpeaking) {
 692            processSpeechQueue();
 693        }
 694        return TextToSpeechBeta.SUCCESS;
 695    }
 696
 697    /**
 698     * Stops all speech output and removes any utterances still in the queue for
 699     * the calling app.
 700     */
 701    private int stop(String callingApp) {
 702        int result = TextToSpeechBeta.ERROR;
 703        boolean speechQueueAvailable = false;
 704        try{
 705            speechQueueAvailable =
 706                    speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
 707            if (speechQueueAvailable) {
 708                Log.i(SERVICE_TAG, "Stopping");
 709                for (int i = mSpeechQueue.size() - 1; i > -1; i--){
 710                    if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){
 711                        mSpeechQueue.remove(i);
 712                    }
 713                }
 714                if ((mCurrentSpeechItem != null) &&
 715                     mCurrentSpeechItem.mCallingApp.equals(callingApp)) {
 716                    try {
 717                        result = sNativeSynth.stop();
 718                    } catch (NullPointerException e1) {
 719                        // synth will become null during onDestroy()
 720                        result = TextToSpeechBeta.ERROR;
 721                    }
 722                    mKillList.put(mCurrentSpeechItem, true);
 723                    if (mPlayer != null) {
 724                        try {
 725                            mPlayer.stop();
 726                        } catch (IllegalStateException e) {
 727                            // Do nothing, the player is already stopped.
 728                        }
 729                    }
 730                    mIsSpeaking = false;
 731                    mCurrentSpeechItem = null;
 732                } else {
 733                    result = TextToSpeechBeta.SUCCESS;
 734                }
 735                Log.i(SERVICE_TAG, "Stopped");
 736            } else {
 737                Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected");
 738                result = TextToSpeechBeta.ERROR;
 739            }
 740        } catch (InterruptedException e) {
 741          Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted");
 742          e.printStackTrace();
 743        } finally {
 744            // This check is needed because finally will always run; even if the
 745            // method returns somewhere in the try block.
 746            if (speechQueueAvailable) {
 747                speechQueueLock.unlock();
 748            }
 749            return result;
 750        }
 751    }
 752
 753    /**
 754     * Stops all speech output, both rendered to a file and directly spoken, and
 755     * removes any utterances still in the queue globally. Files that were being
 756     * written are deleted.
 757     */
 758    @SuppressWarnings("finally")
 759    private int killAllUtterances() {
 760        int result = TextToSpeechBeta.ERROR;
 761        boolean speechQueueAvailable = false;
 762
 763        try {
 764            speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT,
 765                    TimeUnit.MILLISECONDS);
 766            if (speechQueueAvailable) {
 767                // remove every single entry in the speech queue
 768                mSpeechQueue.clear();
 769
 770                // clear the current speech item
 771                if (mCurrentSpeechItem != null) {
 772                    // Should be a stopSync - only using a stop for Donut
 773                    // compatibility
 774                    // result = sNativeSynth.stopSync();
 775                    result = sNativeSynth.stop();
 776                    mKillList.put(mCurrentSpeechItem, true);
 777                    mIsSpeaking = false;
 778
 779                    // was the engine writing to a file?
 780                    if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) {
 781                        // delete the file that was being written
 782                        if (mCurrentSpeechItem.mFilename != null) {
 783                            File tempFile = new File(mCurrentSpeechItem.mFilename);
 784                            Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename);
 785                            if (tempFile.exists()) {
 786                                Log.v(SERVICE_TAG, "About to delete "
 787                                        + mCurrentSpeechItem.mFilename);
 788                                if (tempFile.delete()) {
 789                                    Log.v(SERVICE_TAG, "file successfully deleted");
 790                                }
 791                            }
 792                        }
 793                    }
 794
 795                    mCurrentSpeechItem = null;
 796                }
 797            } else {
 798                Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected");
 799                result = TextToSpeechBeta.ERROR;
 800            }
 801        } catch (InterruptedException e) {
 802            Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted");
 803            result = TextToSpeechBeta.ERROR;
 804        } finally {
 805            // This check is needed because finally will always run, even if the
 806            // method returns somewhere in the try block.
 807            if (speechQueueAvailable) {
 808                speechQueueLock.unlock();
 809            }
 810            return result;
 811        }
 812    }
 813
 814    /**
 815     * Stops all speech output and removes any utterances still in the queue
 816     * globally, except those intended to be synthesized to file.
 817     */
 818    private int stopAll(String callingApp) {
 819        int result = TextToSpeechBeta.ERROR;
 820        boolean speechQueueAvailable = false;
 821        try{
 822            speechQueueAvailable =
 823                    speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
 824            if (speechQueueAvailable) {
 825                for (int i = mSpeechQueue.size() - 1; i > -1; i--){
 826                    if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){
 827                        mSpeechQueue.remove(i);
 828                    }
 829                }
 830                if ((mCurrentSpeechItem != null) &&
 831                    ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) ||
 832                      mCurrentSpeechItem.mCallingApp.equals(callingApp))) {
 833                    try {
 834                        result = sNativeSynth.stop();
 835                    } catch (NullPointerException e1) {
 836                        // synth will become null during onDestroy()
 837                        result = TextToSpeechBeta.ERROR;
 838                    }
 839                    mKillList.put(mCurrentSpeechItem, true);
 840                    if (mPlayer != null) {
 841                        try {
 842                            mPlayer.stop();
 843                        } catch (IllegalStateException e) {
 844                            // Do nothing, the player is already stopped.
 845                        }
 846                    }
 847                    mIsSpeaking = false;
 848                    mCurrentSpeechItem = null;
 849                } else {
 850                    result = TextToSpeechBeta.SUCCESS;
 851                }
 852                Log.i(SERVICE_TAG, "Stopped all");
 853            } else {
 854                Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected");
 855                result = TextToSpeechBeta.ERROR;
 856            }
 857        } catch (InterruptedException e) {
 858          Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted");
 859          e.printStackTrace();
 860        } finally {
 861            // This check is needed because finally will always run; even if the
 862            // method returns somewhere in the try block.
 863            if (speechQueueAvailable) {
 864                speechQueueLock.unlock();
 865            }
 866            return result;
 867        }
 868    }
 869
 870    public void onCompletion(MediaPlayer arg0) {
 871        // mCurrentSpeechItem may become null if it is stopped at the same
 872        // time it completes.
 873        SpeechItem currentSpeechItemCopy = mCurrentSpeechItem;
 874        if (currentSpeechItemCopy != null) {
 875            String callingApp = currentSpeechItemCopy.mCallingApp;
 876            ArrayList<String> params = currentSpeechItemCopy.mParams;
 877            String utteranceId = "";
 878            if (params != null) {
 879                for (int i = 0; i < params.size() - 1; i = i + 2) {
 880                    String param = params.get(i);
 881                    if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)) {
 882                        utteranceId = params.get(i + 1);
 883                    }
 884                }
 885            }
 886            if (utteranceId.length() > 0) {
 887                dispatchUtteranceCompletedCallback(utteranceId, callingApp);
 888            }
 889        }
 890        processSpeechQueue();
 891    }
 892
 893    private int playSilence(String callingApp, long duration, int queueMode,
 894            ArrayList<String> params) {
 895        if (queueMode == TextToSpeechBeta.QUEUE_FLUSH) {
 896            stop(callingApp);
 897        }
 898        mSpeechQueue.add(new SpeechItem(callingApp, duration, params));
 899        if (!mIsSpeaking) {
 900            processSpeechQueue();
 901        }
 902        return TextToSpeechBeta.SUCCESS;
 903    }
 904
 905    private void silence(final SpeechItem speechItem) {
 906        class SilenceThread implements Runnable {
 907            public void run() {
 908                String utteranceId = "";
 909                if (speechItem.mParams != null){
 910                    for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
 911                        String param = speechItem.mParams.get(i);
 912                        if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)){
 913                            utteranceId = speechItem.mParams.get(i+1);
 914                        }
 915                    }
 916                }
 917                try {
 918                    Thread.sleep(speechItem.mDuration);
 919                } catch (InterruptedException e) {
 920                    e.printStackTrace();
 921                } finally {
 922                    if (utteranceId.length() > 0){
 923                        dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
 924                    }
 925                    processSpeechQueue();
 926                }
 927            }
 928        }
 929        Thread slnc = (new Thread(new SilenceThread()));
 930        slnc.setPriority(Thread.MIN_PRIORITY);
 931        slnc.start();
 932    }
 933
 934    boolean synthThreadBusy = false;
 935    
 936    private void speakInternalOnly(final SpeechItem speechItem) {
 937        Log.e(SERVICE_TAG, "Creating synth thread for: " + speechItem.mText);
 938        class SynthThread implements Runnable {
 939            public void run() {
 940                boolean synthAvailable = false;
 941                String utteranceId = "";
 942                try {
 943                    synthAvailable = synthesizerLock.tryLock();
 944                    if (!synthAvailable) {
 945                        mSynthBusy = true;
 946                        Thread.sleep(100);
 947                        Thread synth = (new Thread(new SynthThread()));
 948                        synth.start();
 949                        mSynthBusy = false;
 950                        return;
 951                    }
 952                    int streamType = DEFAULT_STREAM_TYPE;
 953                    String language = "";
 954                    String country = "";
 955                    String variant = "";
 956                    String speechRate = "";
 957                    String engine = "";
 958                    String pitch = "";
 959                    if (speechItem.mParams != null){
 960                        for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
 961                            String param = speechItem.mParams.get(i);
 962                            if (param != null) {
 963                                if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_RATE)) {
 964                                    speechRate = speechItem.mParams.get(i + 1);
 965                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_LANGUAGE)) {
 966                                    language = speechItem.mParams.get(i + 1);
 967                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_COUNTRY)) {
 968                                    country = speechItem.mParams.get(i + 1);
 969                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_VARIANT)) {
 970                                    variant = speechItem.mParams.get(i + 1);
 971                                } else if (param
 972                                        .equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)) {
 973                                    utteranceId = speechItem.mParams.get(i + 1);
 974                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_STREAM)) {
 975                                    try {
 976                                        streamType = Integer
 977                                                .parseInt(speechItem.mParams.get(i + 1));
 978                                    } catch (NumberFormatException e) {
 979                                        streamType = DEFAULT_STREAM_TYPE;
 980                                    }
 981                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_ENGINE)) {
 982                                    engine = speechItem.mParams.get(i + 1);
 983                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_PITCH)) {
 984                                    pitch = speechItem.mParams.get(i + 1);
 985                                }
 986                            }
 987                        }
 988                    }
 989                    // Only do the synthesis if it has not been killed by a subsequent utterance.
 990                    if (mKillList.get(speechItem) == null) {
 991                        if (engine.length() > 0) {
 992                            setEngine(engine);
 993                        } else {
 994                            setEngine(getDefaultEngine());
 995                        }
 996                        if (language.length() > 0){
 997                            setLanguage("", language, country, variant);
 998                        } else {
 999                            setLanguage("", getDefaultLanguage(), getDefaultCountry(),
1000                                    getDefaultLocVariant());
1001                        }
1002                        if (speechRate.length() > 0){
1003                            setSpeechRate("", Integer.parseInt(speechRate));
1004                        } else {
1005                            setSpeechRate("", getDefaultRate());
1006                        }
1007                        if (pitch.length() > 0){
1008                            setPitch("", Integer.parseInt(pitch));
1009                        } else {
1010                            setPitch("", getDefaultPitch());
1011                        }
1012                        try {
1013                            sNativeSynth.speak(speechItem.mText, streamType);
1014                        } catch (NullPointerException e) {
1015                            // synth will become null during onDestroy()
1016                            Log.v(SERVICE_TAG, " null synth, can't speak");
1017                        }
1018                    }
1019                } catch (InterruptedException e) {
1020                    Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted");
1021                    e.printStackTrace();
1022                } finally {
1023                    // This check is needed because finally will always run;
1024                    // even if the
1025                    // method returns somewhere in the try block.
1026                    if (utteranceId.length() > 0){
1027                        dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
1028                    }
1029                    if (synthAvailable) {
1030                        synthesizerLock.unlock();
1031                        processSpeechQueue();
1032                    }
1033                }
1034            }
1035        }
1036        Thread synth = (new Thread(new SynthThread()));
1037        synth.setPriority(Thread.MAX_PRIORITY);
1038        synth.start();
1039    }
1040
1041    private void synthToFileInternalOnly(final SpeechItem speechItem) {
1042        class SynthThread implements Runnable {
1043            public void run() {
1044                boolean synthAvailable = false;
1045                String utteranceId = "";
1046                Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename);
1047                try {
1048                    synthAvailable = synthesizerLock.tryLock();
1049                    if (!synthAvailable) {
1050                        synchronized (this) {
1051                            mSynthBusy = true;
1052                        }
1053                        Thread.sleep(100);
1054                        Thread synth = (new Thread(new SynthThread()));
1055                        // synth.setPriority(Thread.MIN_PRIORITY);
1056                        synth.start();
1057                        synchronized (this) {
1058                            mSynthBusy = false;
1059                        }
1060                        return;
1061                    }
1062                    String language = "";
1063                    String country = "";
1064                    String variant = "";
1065                    String speechRate = "";
1066                    String engine = "";
1067                    String pitch = "";
1068                    if (speechItem.mParams != null){
1069                        for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
1070                            String param = speechItem.mParams.get(i);
1071                            if (param != null) {
1072                                if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_RATE)) {
1073                                    speechRate = speechItem.mParams.get(i + 1);
1074                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_LANGUAGE)) {
1075                                    language = speechItem.mParams.get(i + 1);
1076                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_COUNTRY)) {
1077                                    country = speechItem.mParams.get(i + 1);
1078                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_VARIANT)) {
1079                                    variant = speechItem.mParams.get(i + 1);
1080                                } else if (param
1081                                        .equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)) {
1082                                    utteranceId = speechItem.mParams.get(i + 1);
1083                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_ENGINE)) {
1084                                    engine = speechItem.mParams.get(i + 1);
1085                                } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_PITCH)) {
1086                                    pitch = speechItem.mParams.get(i + 1);
1087                                }
1088                            }
1089                        }
1090                    }
1091                    // Only do the synthesis if it has not been killed by a subsequent utterance.
1092                    if (mKillList.get(speechItem) == null){
1093                        if (engine.length() > 0) {
1094                            setEngine(engine);
1095                        } else {
1096                            setEngine(getDefaultEngine());
1097                        }
1098                        if (language.length() > 0){
1099                            setLanguage("", language, country, variant);
1100                        } else {
1101                            setLanguage("", getDefaultLanguage(), getDefaultCountry(),
1102                                    getDefaultLocVariant());
1103                        }
1104                        if (speechRate.length() > 0){
1105                            setSpeechRate("", Integer.parseInt(speechRate));
1106                        } else {
1107                            setSpeechRate("", getDefaultRate());
1108                        }
1109                        if (pitch.length() > 0){
1110                            setPitch("", Integer.parseInt(pitch));
1111                        } else {
1112                            setPitch("", getDefaultPitch());
1113                        }
1114                        try {
1115                            sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename);
1116                        } catch (NullPointerException e) {
1117                            // synth will become null during onDestroy()
1118                            Log.v(SERVICE_TAG, " null synth, can't synthesize to file");
1119                        }
1120                    }
1121                } catch (InterruptedException e) {
1122                    Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted");
1123                    e.printStackTrace();
1124                } finally {
1125                    // This check is needed because finally will always run;
1126                    // even if the
1127                    // method returns somewhere in the try block.
1128                    if (utteranceId.length() > 0){
1129                        dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
1130                    }
1131                    if (synthAvailable) {
1132                        synthesizerLock.unlock();
1133                        processSpeechQueue();
1134                    }
1135                    deprecatedKeepBlockingFlag = false;
1136                }
1137            }
1138        }
1139        Thread synth = (new Thread(new SynthThread()));
1140        synth.setPriority(Thread.MAX_PRIORITY);
1141        synth.start();
1142    }
1143
1144    private SoundResource getSoundResource(SpeechItem speechItem) {
1145        SoundResource sr = null;
1146        String text = speechItem.mText;
1147        if (speechItem.mType == SpeechItem.SILENCE) {
1148            // Do nothing if this is just silence
1149        } else if (speechItem.mType == SpeechItem.EARCON) {
1150            sr = mEarcons.get(text);
1151        } else {
1152            sr = mUtterances.get(text);
1153        }
1154        return sr;
1155    }
1156
1157    private void broadcastTtsQueueProcessingCompleted() {
1158        Intent i = new Intent(TextToSpeechBeta.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
1159        sendBroadcast(i);
1160    }
1161
1162    private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) {
1163        /* Legacy support for TTS */
1164        final int oldN = mCallbacksOld.beginBroadcast();
1165        for (int i = 0; i < oldN; i++) {
1166            try {
1167                mCallbacksOld.getBroadcastItem(i).markReached("");
1168            } catch (RemoteException e) {
1169                // The RemoteCallbackList will take care of removing
1170                // the dead object for us.
1171            }
1172        }
1173        try {
1174            mCallbacksOld.finishBroadcast();
1175        } catch (IllegalStateException e) {
1176            // May

Large files files are truncated, but you can click here to view the full file