TextToSpeechService.java revision 992ea1553c3ae6766505f084061c5ef2321229b7
1b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch/*
2b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * Copyright (C) 2011 The Android Open Source Project
3b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
4b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch * use this file except in compliance with the License. You may obtain a copy of
6014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch * the License at
7b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
8014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch * http://www.apache.org/licenses/LICENSE-2.0
9014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch *
10014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch * Unless required by applicable law or agreed to in writing, software
11b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * License for the specific language governing permissions and limitations under
14b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * the License.
15b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch */
16b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochpackage android.speech.tts;
17b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
18b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.app.Service;
19b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.content.Intent;
20b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.media.AudioAttributes;
21b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.media.AudioSystem;
22b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.net.Uri;
23b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.Binder;
24b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.Bundle;
25b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.Handler;
26b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.HandlerThread;
27b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.IBinder;
28b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.Looper;
29b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.Message;
30b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.MessageQueue;
31b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.ParcelFileDescriptor;
32b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.RemoteCallbackList;
33b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.os.RemoteException;
34b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.provider.Settings;
35b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.speech.tts.TextToSpeech.Engine;
36b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.text.TextUtils;
37b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport android.util.Log;
38b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
39014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdochimport java.io.FileOutputStream;
40b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.io.IOException;
41b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.util.ArrayList;
42b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.util.HashMap;
43b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.util.List;
44b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.util.Locale;
45b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.util.MissingResourceException;
46b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochimport java.util.Set;
47b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
48b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
49b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch/**
50b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * Abstract base class for TTS engine implementations. The following methods
51b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * need to be implemented:
52b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <ul>
53b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onIsLanguageAvailable}</li>
54b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onLoadLanguage}</li>
55b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onGetLanguage}</li>
56b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onSynthesizeText}</li>
57b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onStop}</li>
58b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * </ul>
59b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * The first three deal primarily with language management, and are used to
60b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * query the engine for it's support for a given language and indicate to it
61b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * that requests in a given language are imminent.
62b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
63b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * {@link #onSynthesizeText} is central to the engine implementation. The
64b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * implementation should synthesize text as per the request parameters and
65b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * return synthesized data via the supplied callback. This class and its helpers
66b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * will then consume that data, which might mean queuing it for playback or writing
67b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * it to a file or similar. All calls to this method will be on a single thread,
68b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * which will be different from the main thread of the service. Synthesis must be
69b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * synchronous which means the engine must NOT hold on to the callback or call any
70b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * methods on it after the method returns.
71b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
72b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * {@link #onStop} tells the engine that it should stop
73bcf72ee8e3b26f1d0726869c7ddb3921c68b09a8Ben Murdoch * all ongoing synthesis, if any. Any pending data from the current synthesis
74b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * will be discarded.
75b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
76b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only
77b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * called on earlier versions of Android.
78b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
79b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * API Level 20 adds support for Voice objects. Voices are an abstraction that allow the TTS
80014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch * service to expose multiple backends for a single locale. Each one of them can have a different
81b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * features set. In order to fully take advantage of voices, an engine should implement
82b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * the following methods:
83b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <ul>
84b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onGetVoices()}</li>
85b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onIsValidVoiceName(String)}</li>
86b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onLoadVoice(String)}</li>
87b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * <li>{@link #onGetDefaultVoiceNameFor(String, String, String)}</li>
88b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * </ul>
89b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * The first three methods are siblings of the {@link #onGetLanguage},
90b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * {@link #onIsLanguageAvailable} and {@link #onLoadLanguage} methods. The last one,
91b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * {@link #onGetDefaultVoiceNameFor(String, String, String)} is a link between locale and voice
92b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * based methods. Since API level 21 {@link TextToSpeech#setLanguage} is implemented by
93b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * calling {@link TextToSpeech#setVoice} with the voice returned by
94b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * {@link #onGetDefaultVoiceNameFor(String, String, String)}.
95b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
96b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * If the client uses a voice instead of a locale, {@link SynthesisRequest} will contain the
97b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * requested voice name.
98b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch *
9913e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch * The default implementations of Voice-related methods implement them using the
100b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch * pre-existing locale-based implementation.
101b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch */
102b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdochpublic abstract class TextToSpeechService extends Service {
103b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
104b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private static final boolean DBG = false;
105b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private static final String TAG = "TextToSpeechService";
106b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
107b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private static final String SYNTH_THREAD_NAME = "SynthThread";
108014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
10913e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch    private SynthHandler mSynthHandler;
110b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    // A thread and it's associated handler for playing back any audio
111b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    // associated with this TTS engine. Will handle all requests except synthesis
112b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    // to file requests, which occur on the synthesis thread.
113b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private AudioPlaybackHandler mAudioPlaybackHandler;
114b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private TtsEngines mEngineHelper;
115b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
116b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private CallbackMap mCallbacks;
117b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private String mPackageName;
118b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
119b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private final Object mVoicesInfoLock = new Object();
120b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
121014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    @Override
122b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    public void onCreate() {
123b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (DBG) Log.d(TAG, "onCreate()");
124b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        super.onCreate();
125b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
126b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        SynthThread synthThread = new SynthThread();
127b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        synthThread.start();
128b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        mSynthHandler = new SynthHandler(synthThread.getLooper());
129b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
130b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        mAudioPlaybackHandler = new AudioPlaybackHandler();
131b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        mAudioPlaybackHandler.start();
132b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
133b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        mEngineHelper = new TtsEngines(this);
134b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
135b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        mCallbacks = new CallbackMap();
136b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
137b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        mPackageName = getApplicationInfo().packageName;
138b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
139b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        String[] defaultLocale = getSettingsLocale();
140b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
141b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        // Load default language
142b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
143b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
144b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
145b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    @Override
146b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    public void onDestroy() {
147b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (DBG) Log.d(TAG, "onDestroy()");
148b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
149958fae7ec3f466955f8e5b50fa5b8d38b9e91675Emily Bernier        // Tell the synthesizer to stop
150014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        mSynthHandler.quit();
151014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        // Tell the audio playback thread to stop.
152014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        mAudioPlaybackHandler.quit();
153958fae7ec3f466955f8e5b50fa5b8d38b9e91675Emily Bernier        // Unregister all callbacks.
154958fae7ec3f466955f8e5b50fa5b8d38b9e91675Emily Bernier        mCallbacks.kill();
155958fae7ec3f466955f8e5b50fa5b8d38b9e91675Emily Bernier
156b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        super.onDestroy();
157b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
158b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
159b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
160b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Checks whether the engine supports a given language.
161b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
162b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Can be called on multiple threads.
163b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
164b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Its return values HAVE to be consistent with onLoadLanguage.
165b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
166b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param lang ISO-3 language code.
167b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param country ISO-3 country code. May be empty or null.
168b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param variant Language variant. May be empty or null.
169b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return Code indicating the support status for the locale.
170b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         One of {@link TextToSpeech#LANG_AVAILABLE},
171b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
172b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
173b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_MISSING_DATA}
174b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
175b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
176b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
177b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
178b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
179b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Returns the language, country and variant currently being used by the TTS engine.
180b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
181b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * This method will be called only on Android 4.2 and before (API <= 17). In later versions
182b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * this method is not called by the Android TTS framework.
183b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
184b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Can be called on multiple threads.
185b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
186b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return A 3-element array, containing language (ISO 3-letter code),
187b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         country (ISO 3-letter code) and variant used by the engine.
188b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         The country and variant may be {@code ""}. If country is empty, then variant must
189b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         be empty too.
190b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @see Locale#getISO3Language()
191b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @see Locale#getISO3Country()
192b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @see Locale#getVariant()
193b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
194b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    protected abstract String[] onGetLanguage();
195b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
196b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
197b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Notifies the engine that it should load a speech synthesis language. There is no guarantee
198b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * that this method is always called before the language is used for synthesis. It is merely
199b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * a hint to the engine that it will probably get some synthesis requests for this language
200b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * at some point in the future.
201b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
202b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Can be called on multiple threads.
203b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
204b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
205b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
206b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param lang ISO-3 language code.
207b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param country ISO-3 country code. May be empty or null.
208b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param variant Language variant. May be empty or null.
209b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return Code indicating the support status for the locale.
210b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         One of {@link TextToSpeech#LANG_AVAILABLE},
211b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
212b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
213b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_MISSING_DATA}
214b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
215b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
216b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    protected abstract int onLoadLanguage(String lang, String country, String variant);
217b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
218b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
219b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Notifies the service that it should stop any in-progress speech synthesis.
2203b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch     * This method can be called even if no speech synthesis is currently in progress.
2213b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch     *
2223b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch     * Can be called on multiple threads, but not on the synthesis thread.
2233b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch     */
2243b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch    protected abstract void onStop();
2253b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch
2263b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch    /**
227b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Tells the service to synthesize speech from the given text. This method
228b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * should block until the synthesis is finished. Used for requests from V1
229b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * clients ({@link android.speech.tts.TextToSpeech}). Called on the synthesis
230b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * thread.
231b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
232b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param request The synthesis request.
233b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param callback The callback that the engine must use to make data
234b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *            available for playback or for writing to a file.
235b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
236b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    protected abstract void onSynthesizeText(SynthesisRequest request,
237b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SynthesisCallback callback);
238b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
239b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
240b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Queries the service for a set of features supported for a given language.
241b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
242b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Can be called on multiple threads.
243b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
244b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param lang ISO-3 language code.
245b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param country ISO-3 country code. May be empty or null.
246b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param variant Language variant. May be empty or null.
247b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return A list of features supported for the given language.
248b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
249b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
250b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        return null;
251b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
252b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
253b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private int getExpectedLanguageAvailableStatus(Locale locale) {
254b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
255b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (locale.getVariant().isEmpty()) {
256b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (locale.getCountry().isEmpty()) {
257b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                expectedStatus = TextToSpeech.LANG_AVAILABLE;
258b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else {
259b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE;
260b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
261b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
262b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        return expectedStatus;
263b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
264b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
2653b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch    /**
2663b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch     * Queries the service for a set of supported voices.
267b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
268b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Can be called on multiple threads.
269b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
270b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * The default implementation tries to enumerate all available locales, pass them to
271b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * {@link #onIsLanguageAvailable(String, String, String)} and create Voice instances (using
272b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * the locale's BCP-47 language tag as the voice name) for the ones that are supported.
273b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Note, that this implementation is suitable only for engines that don't have multiple voices
274b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * for a single locale. Also, this implementation won't work with Locales not listed in the
275b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * set returned by the {@link Locale#getAvailableLocales()} method.
276b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
277b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return A list of voices supported.
278b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
279b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    public List<Voice> onGetVoices() {
280b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        // Enumerate all locales and check if they are available
281014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        ArrayList<Voice> voices = new ArrayList<Voice>();
282b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        for (Locale locale : Locale.getAvailableLocales()) {
283014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            int expectedStatus = getExpectedLanguageAvailableStatus(locale);
284b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            try {
285b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
286b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        locale.getISO3Country(), locale.getVariant());
287b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (localeStatus != expectedStatus) {
288b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    continue;
289b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
290b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } catch (MissingResourceException e) {
291b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                // Ignore locale without iso 3 codes
292b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                continue;
293b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
294b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(),
295b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    locale.getISO3Country(), locale.getVariant());
296b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            String voiceName = onGetDefaultVoiceNameFor(locale.getISO3Language(),
297b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    locale.getISO3Country(), locale.getVariant());
298b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            voices.add(new Voice(voiceName, locale, Voice.QUALITY_NORMAL,
299b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    Voice.LATENCY_NORMAL, false, features));
300b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
301014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        return voices;
302014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    }
303b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
304014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    /**
305014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     * Return a name of the default voice for a given locale.
306b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
307b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * This method provides a mapping between locales and available voices. This method is
308b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * used in {@link TextToSpeech#setLanguage}, which calls this method and then calls
309b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * {@link TextToSpeech#setVoice} with the voice returned by this method.
310b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
311b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Also, it's used by {@link TextToSpeech#getDefaultVoice()} to find a default voice for
312b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * the default locale.
313b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
314b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param lang ISO-3 language code.
315b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param country ISO-3 country code. May be empty or null.
316b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param variant Language variant. May be empty or null.
317b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
318b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return A name of the default voice for a given locale.
319014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     */
320014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    public String onGetDefaultVoiceNameFor(String lang, String country, String variant) {
321b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int localeStatus = onIsLanguageAvailable(lang, country, variant);
322b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        Locale iso3Locale = null;
323b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        switch (localeStatus) {
324b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            case TextToSpeech.LANG_AVAILABLE:
325b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                iso3Locale = new Locale(lang);
326b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                break;
327b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            case TextToSpeech.LANG_COUNTRY_AVAILABLE:
328b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                iso3Locale = new Locale(lang, country);
329b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                break;
330b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
331b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                iso3Locale = new Locale(lang, country, variant);
332b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                break;
333b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            default:
334b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return null;
335b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
336b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        Locale properLocale = TtsEngines.normalizeTTSLocale(iso3Locale);
337b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        String voiceName = properLocale.toLanguageTag();
338b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (onIsValidVoiceName(voiceName) == TextToSpeech.SUCCESS) {
339b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return voiceName;
340b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        } else {
341b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return null;
342b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
343b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
344b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
345b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
346b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Notifies the engine that it should load a speech synthesis voice. There is no guarantee
347b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * that this method is always called before the voice is used for synthesis. It is merely
348b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * a hint to the engine that it will probably get some synthesis requests for this voice
349b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * at some point in the future.
350b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
351b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Will be called only on synthesis thread.
352b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
353b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * The default implementation creates a Locale from the voice name (by interpreting the name as
354b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * a BCP-47 tag for the locale), and passes it to
355b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * {@link #onLoadLanguage(String, String, String)}.
356014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     *
357b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @param voiceName Name of the voice.
358b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}.
359b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
360014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    public int onLoadVoice(String voiceName) {
361b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        Locale locale = Locale.forLanguageTag(voiceName);
362b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (locale == null) {
363b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return TextToSpeech.ERROR;
364b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
365b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int expectedStatus = getExpectedLanguageAvailableStatus(locale);
366b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        try {
367b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
368014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    locale.getISO3Country(), locale.getVariant());
369b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (localeStatus != expectedStatus) {
370b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
371b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
372014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            onLoadLanguage(locale.getISO3Language(),
373b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    locale.getISO3Country(), locale.getVariant());
374b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return TextToSpeech.SUCCESS;
375b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        } catch (MissingResourceException e) {
376b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return TextToSpeech.ERROR;
377b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
378014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    }
379b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
380b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
381b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Checks whether the engine supports a voice with a given name.
382014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     *
383b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Can be called on multiple threads.
384014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     *
385014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     * The default implementation treats the voice name as a language tag, creating a Locale from
386b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * the voice name, and passes it to {@link #onIsLanguageAvailable(String, String, String)}.
387b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     *
388014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     * @param voiceName Name of the voice.
389b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}.
390b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
391b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    public int onIsValidVoiceName(String voiceName) {
392b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        Locale locale = Locale.forLanguageTag(voiceName);
393b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (locale == null) {
394b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return TextToSpeech.ERROR;
395b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
396b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int expectedStatus = getExpectedLanguageAvailableStatus(locale);
397b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        try {
398b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
399b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    locale.getISO3Country(), locale.getVariant());
400014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            if (localeStatus != expectedStatus) {
401014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                return TextToSpeech.ERROR;
402014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
403014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            return TextToSpeech.SUCCESS;
404014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        } catch (MissingResourceException e) {
405014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            return TextToSpeech.ERROR;
406014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
407b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
408b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
409b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private int getDefaultSpeechRate() {
410b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
411b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
412014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
413b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private String[] getSettingsLocale() {
414b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        final Locale locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
415b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        return TtsEngines.toOldLocaleStringFormat(locale);
416b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
417b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
418b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private int getSecureSettingInt(String name, int defaultValue) {
419014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
420b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
421b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
422b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
423b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Synthesizer thread. This thread is used to run {@link SynthHandler}.
424b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
425b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
426b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
427b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private boolean mFirstIdle = true;
428014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
429b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public SynthThread() {
430014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
431b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
432b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
433b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
434b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void onLooperPrepared() {
435b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            getLooper().getQueue().addIdleHandler(this);
436014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
437b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
438b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
439b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean queueIdle() {
440b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (mFirstIdle) {
441014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                mFirstIdle = false;
442b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else {
443b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                broadcastTtsQueueProcessingCompleted();
444014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
445b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
446b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
447b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
448b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private void broadcastTtsQueueProcessingCompleted() {
449b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
450b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (DBG) Log.d(TAG, "Broadcasting: " + i);
451b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            sendBroadcast(i);
452b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
453b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
454b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
455b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class SynthHandler extends Handler {
456b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private SpeechItem mCurrentSpeechItem = null;
457b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
458b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private ArrayList<Object> mFlushedObjects = new ArrayList<Object>();
459b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private boolean mFlushAll;
460014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
461b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public SynthHandler(Looper looper) {
462014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            super(looper);
463b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
464b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
465b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private void startFlushingSpeechItems(Object callerIdentity) {
466b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (mFlushedObjects) {
467014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                if (callerIdentity == null) {
468b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    mFlushAll = true;
469014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                } else {
470b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    mFlushedObjects.add(callerIdentity);
471b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
472b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
473b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
474b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private void endFlushingSpeechItems(Object callerIdentity) {
475b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (mFlushedObjects) {
476b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (callerIdentity == null) {
477b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    mFlushAll = false;
478b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                } else {
479014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    mFlushedObjects.remove(callerIdentity);
480b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
481b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
482b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
483b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private boolean isFlushed(SpeechItem speechItem) {
484b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (mFlushedObjects) {
485b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return mFlushAll || mFlushedObjects.contains(speechItem.getCallerIdentity());
486b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
487b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
488b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
489b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private synchronized SpeechItem getCurrentSpeechItem() {
490b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mCurrentSpeechItem;
491b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
492b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
493b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
494b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem old = mCurrentSpeechItem;
495b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCurrentSpeechItem = speechItem;
496b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return old;
497b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
498b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
499b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
500b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (mCurrentSpeechItem != null &&
501b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
502b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                SpeechItem current = mCurrentSpeechItem;
503b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCurrentSpeechItem = null;
504b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return current;
505b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
506b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
507b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return null;
508b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
509b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
510b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isSpeaking() {
511b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getCurrentSpeechItem() != null;
512b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
513b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
514b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void quit() {
515b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Don't process any more speech items
516b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            getLooper().quit();
517b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Stop the current speech item
518014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            SpeechItem current = setCurrentSpeechItem(null);
519b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (current != null) {
520b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                current.stop();
521b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
522b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // The AudioPlaybackHandler will be destroyed by the caller.
523b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
524b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
525b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
526b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Adds a speech item to the queue.
527b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         *
528b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Called on a service binder thread.
529b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
530b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
531b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            UtteranceProgressDispatcher utterenceProgress = null;
532b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (speechItem instanceof UtteranceProgressDispatcher) {
533b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                utterenceProgress = (UtteranceProgressDispatcher) speechItem;
534b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
535b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
536b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!speechItem.isValid()) {
537014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                if (utterenceProgress != null) {
538b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    utterenceProgress.dispatchOnError(
539b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            TextToSpeech.ERROR_INVALID_REQUEST);
540b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
541b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
542b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
543b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
544b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (queueMode == TextToSpeech.QUEUE_FLUSH) {
545b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                stopForApp(speechItem.getCallerIdentity());
546b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
547b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                stopAll();
548b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
549b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Runnable runnable = new Runnable() {
550b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                @Override
551b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                public void run() {
552b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    if (isFlushed(speechItem)) {
553b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        speechItem.stop();
554b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    } else {
5553b9bc31999c9787eb726ecdbfd5796bfdec32a18Ben Murdoch                        setCurrentSpeechItem(speechItem);
556b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        speechItem.play();
557b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        setCurrentSpeechItem(null);
558b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    }
559014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                }
560b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            };
561b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Message msg = Message.obtain(this, runnable);
562b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
563b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // The obj is used to remove all callbacks from the given app in
564b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // stopForApp(String).
565b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            //
566b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Note that this string is interned, so the == comparison works.
567b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            msg.obj = speechItem.getCallerIdentity();
568b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
569b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (sendMessage(msg)) {
570b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.SUCCESS;
571b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else {
572b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.w(TAG, "SynthThread has quit");
573014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                if (utterenceProgress != null) {
574b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE);
575b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
576b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
577014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
578b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
579b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
580b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
581b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Stops all speech output and removes any utterances still in the queue for
582b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * the calling app.
583b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         *
584b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Called on a service binder thread.
585b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
586b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int stopForApp(final Object callerIdentity) {
587b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (callerIdentity == null) {
588b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
589014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
590b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
591b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Flush pending messages from callerIdentity
592b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            startFlushingSpeechItems(callerIdentity);
593b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
594b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // This stops writing data to the file / or publishing
595b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // items to the audio playback handler.
596014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            //
597b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Note that the current speech item must be removed only if it
598b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // belongs to the callingApp, else the item will be "orphaned" and
599b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // not stopped correctly if a stop request comes along for the item
600b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // from the app it belongs to.
601b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
602014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            if (current != null) {
603b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                current.stop();
604b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
605b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
606b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Remove any enqueued audio too.
607b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mAudioPlaybackHandler.stopForApp(callerIdentity);
608b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
609b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Stop flushing pending messages
610b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Runnable runnable = new Runnable() {
611b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                @Override
612b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                public void run() {
613b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    endFlushingSpeechItems(callerIdentity);
614b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
615b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            };
616b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            sendMessage(Message.obtain(this, runnable));
617b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return TextToSpeech.SUCCESS;
618b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
619b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
620014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        public int stopAll() {
621b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Order to flush pending messages
622b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            startFlushingSpeechItems(null);
623b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
624b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Stop the current speech item unconditionally .
625b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem current = setCurrentSpeechItem(null);
626b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (current != null) {
627b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                current.stop();
628b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
629b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Remove all pending playback as well.
630b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mAudioPlaybackHandler.stop();
631b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
632b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Message to stop flushing pending messages
633b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Runnable runnable = new Runnable() {
634b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                @Override
635b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                public void run() {
636b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    endFlushingSpeechItems(null);
637b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
638b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            };
639b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            sendMessage(Message.obtain(this, runnable));
640b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
641b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
642b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return TextToSpeech.SUCCESS;
643b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
644b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
645014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
646b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    interface UtteranceProgressDispatcher {
647b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnStop();
648b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnSuccess();
649b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnStart();
650b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnError(int errorCode);
651b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
652b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
653b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /** Set of parameters affecting audio output. */
654b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    static class AudioOutputParams {
655b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
656b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Audio session identifier. May be used to associate audio playback with one of the
657b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * {@link android.media.audiofx.AudioEffect} objects. If not specified by client,
658b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * it should be equal to {@link AudioSystem#AUDIO_SESSION_ALLOCATE}.
659b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
660b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public final int mSessionId;
661b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
662014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        /**
663b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Volume, in the range [0.0f, 1.0f]. The default value is
664b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
665b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
666b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public final float mVolume;
667b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
668b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
669b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Left/right position of the audio, in the range [-1.0f, 1.0f].
670b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
671b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
672b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public final float mPan;
673b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
674b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
675b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
676b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Audio attributes, set by {@link TextToSpeech#setAudioAttributes}
677b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * or created from the value of {@link TextToSpeech.Engine#KEY_PARAM_STREAM}.
678b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
679b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public final AudioAttributes mAudioAttributes;
680014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
681b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /** Create AudioOutputParams with default values */
682b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        AudioOutputParams() {
683b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
684b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mVolume = Engine.DEFAULT_VOLUME;
685b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mPan = Engine.DEFAULT_PAN;
686b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mAudioAttributes = null;
687b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
688b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
689b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        AudioOutputParams(int sessionId, float volume, float pan,
690b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                AudioAttributes audioAttributes) {
691b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mSessionId = sessionId;
692b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mVolume = volume;
693b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mPan = pan;
694b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mAudioAttributes = audioAttributes;
695b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
696b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
697b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /** Create AudioOutputParams from A {@link SynthesisRequest#getParams()} bundle */
698b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        static AudioOutputParams createFromV1ParamsBundle(Bundle paramsBundle,
699b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                boolean isSpeech) {
700b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (paramsBundle == null) {
701b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return new AudioOutputParams();
702b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
703b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
704b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            AudioAttributes audioAttributes =
705014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    (AudioAttributes) paramsBundle.getParcelable(
706b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            Engine.KEY_PARAM_AUDIO_ATTRIBUTES);
707b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (audioAttributes == null) {
708b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                int streamType = paramsBundle.getInt(
709b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
710b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                audioAttributes = (new AudioAttributes.Builder())
711b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        .setLegacyStreamType(streamType)
712b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        .setContentType((isSpeech ?
713b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                                AudioAttributes.CONTENT_TYPE_SPEECH :
714b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                                AudioAttributes.CONTENT_TYPE_SONIFICATION))
715b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        .build();
716b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
717b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
718b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return new AudioOutputParams(
719b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    paramsBundle.getInt(
720b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            Engine.KEY_PARAM_SESSION_ID,
721b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            AudioSystem.AUDIO_SESSION_ALLOCATE),
722014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    paramsBundle.getFloat(
723b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            Engine.KEY_PARAM_VOLUME,
724b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            Engine.DEFAULT_VOLUME),
725b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    paramsBundle.getFloat(
726b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            Engine.KEY_PARAM_PAN,
727b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                            Engine.DEFAULT_PAN),
728b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    audioAttributes);
729b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
730b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
731b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
732b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
733b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
734b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * An item in the synth thread queue.
735b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
736b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private abstract class SpeechItem {
737b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final Object mCallerIdentity;
738b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final int mCallerUid;
739b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final int mCallerPid;
740b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private boolean mStarted = false;
741014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        private boolean mStopped = false;
742b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
743b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public SpeechItem(Object caller, int callerUid, int callerPid) {
744b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCallerIdentity = caller;
745b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCallerUid = callerUid;
746b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCallerPid = callerPid;
747b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
748b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
749b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public Object getCallerIdentity() {
750b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mCallerIdentity;
751b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
752b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
753b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int getCallerUid() {
754b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mCallerUid;
755b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
756b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
757b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int getCallerPid() {
758b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mCallerPid;
759b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
760b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
761b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
762b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Checker whether the item is valid. If this method returns false, the item should not
763b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * be played.
764b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
765b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public abstract boolean isValid();
766b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
767b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
768b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Plays the speech item. Blocks until playback is finished.
769014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch         * Must not be called more than once.
770b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         *
771b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Only called on the synthesis thread.
772b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
773b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void play() {
774b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (this) {
775b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (mStarted) {
776b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    throw new IllegalStateException("play() called twice");
777b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
778b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mStarted = true;
779b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
780b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            playImpl();
781b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
782b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
783b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected abstract void playImpl();
784b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
785b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /**
786014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch         * Stops the speech item.
787b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Must not be called more than once.
788b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         *
789b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * Can be called on multiple threads,  but not on the synthesis thread.
790b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
791b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void stop() {
792b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (this) {
793b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (mStopped) {
794b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    throw new IllegalStateException("stop() called twice");
795b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
796b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mStopped = true;
797b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
798b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            stopImpl();
799b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
800b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
801b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected abstract void stopImpl();
802b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
803014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        protected synchronized boolean isStopped() {
804b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch             return mStopped;
805b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
806b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
807b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected synchronized boolean isStarted() {
808b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mStarted;
809b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch       }
810b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
811b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
812b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
813b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * An item in the synth thread queue that process utterance (and call back to client about
814b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * progress).
815b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
816b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private abstract class UtteranceSpeechItem extends SpeechItem
817b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        implements UtteranceProgressDispatcher  {
818b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
819b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
820b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            super(caller, callerUid, callerPid);
821b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
822014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
823b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
824b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnSuccess() {
825b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            final String utteranceId = getUtteranceId();
826014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            if (utteranceId != null) {
827b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
828b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
829b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
830b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
831b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
832b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnStop() {
833b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            final String utteranceId = getUtteranceId();
834b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (utteranceId != null) {
835b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
836b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
837b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
838b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
839014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        @Override
840b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnStart() {
841b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            final String utteranceId = getUtteranceId();
842b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (utteranceId != null) {
843b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
844b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
845b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
846b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
847b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
848b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnError(int errorCode) {
849b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            final String utteranceId = getUtteranceId();
850b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (utteranceId != null) {
851b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
852014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
853b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
854b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
855b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        abstract public String getUtteranceId();
856b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
857b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        String getStringParam(Bundle params, String key, String defaultValue) {
858b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return params == null ? defaultValue : params.getString(key, defaultValue);
859b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
860b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
861b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int getIntParam(Bundle params, String key, int defaultValue) {
862014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            return params == null ? defaultValue : params.getInt(key, defaultValue);
863b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
864b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
865b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        float getFloatParam(Bundle params, String key, float defaultValue) {
866b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return params == null ? defaultValue : params.getFloat(key, defaultValue);
867b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
868b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
869b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
870b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
871b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * UtteranceSpeechItem for V1 API speech items. V1 API speech items keep
872b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * synthesis parameters in a single Bundle passed as parameter. This class
873b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * allow subclasses to access them conveniently.
874b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
875b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private abstract class SpeechItemV1 extends UtteranceSpeechItem {
876b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected final Bundle mParams;
877014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        protected final String mUtteranceId;
878b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
879b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        SpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
880b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Bundle params, String utteranceId) {
881b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            super(callerIdentity, callerUid, callerPid);
882b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mParams = params;
883b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mUtteranceId = utteranceId;
884b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
885b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
886b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        boolean hasLanguage() {
887b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
888b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
889014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
890b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int getSpeechRate() {
891b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
892b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
893b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
894b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        int getPitch() {
895b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getIntParam(mParams, Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
896b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
897b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
898b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
899b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String getUtteranceId() {
90013e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch            return mUtteranceId;
90113e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        }
90213e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch
90313e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        AudioOutputParams getAudioParams() {
90413e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch            return AudioOutputParams.createFromV1ParamsBundle(mParams, true);
905b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
90613e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch    }
90713e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch
90813e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch    class SynthesisSpeechItemV1 extends SpeechItemV1 {
90913e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        // Never null.
910b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final CharSequence mText;
91113e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        private final SynthesisRequest mSynthesisRequest;
91213e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        private final String[] mDefaultLocale;
91313e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        // Non null after synthesis has started, and all accesses
91413e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        // guarded by 'this'.
91513e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        private AbstractSynthesisCallback mSynthesisCallback;
91613e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        private final EventLoggerV1 mEventLogger;
91713e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        private final int mCallerUid;
91813e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch
91913e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch        public SynthesisSpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
92013e2dadd00298019ed862f2b2fc5068bba730bcfBen Murdoch                Bundle params, String utteranceId, CharSequence text) {
921b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            super(callerIdentity, callerUid, callerPid, params, utteranceId);
922b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mText = text;
923b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCallerUid = callerUid;
924b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mSynthesisRequest = new SynthesisRequest(mText, mParams);
925b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mDefaultLocale = getSettingsLocale();
926b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            setRequestParams(mSynthesisRequest);
927b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mEventLogger = new EventLoggerV1(mSynthesisRequest, callerUid, callerPid,
928014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    mPackageName);
929b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
930b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
931b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public CharSequence getText() {
932b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mText;
933b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
934b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
935b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
936b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isValid() {
937b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (mText == null) {
938b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.e(TAG, "null synthesis text");
939b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return false;
940b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
941b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
942b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.w(TAG, "Text too long: " + mText.length() + " chars");
943b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return false;
944b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
945b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
946014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
947b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
948b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
949b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void playImpl() {
950b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            AbstractSynthesisCallback synthesisCallback;
951b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mEventLogger.onRequestProcessingStart();
952b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (this) {
953b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                // stop() might have been called before we enter this
954b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                // synchronized block.
955b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (isStopped()) {
956014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    return;
957b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
958b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mSynthesisCallback = createSynthesisCallback();
959b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                synthesisCallback = mSynthesisCallback;
960b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
961b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
962b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
963b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
964b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // Fix for case where client called .start() & .error(), but did not called .done()
965b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
966b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                synthesisCallback.done();
967b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
968b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
969b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
970014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        protected AbstractSynthesisCallback createSynthesisCallback() {
971b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return new PlaybackSynthesisCallback(getAudioParams(),
972b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
973b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
974b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
975b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private void setRequestParams(SynthesisRequest request) {
976b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            String voiceName = getVoiceName();
977b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            request.setLanguage(getLanguage(), getCountry(), getVariant());
978b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!TextUtils.isEmpty(voiceName)) {
979b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                request.setVoiceName(getVoiceName());
980b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
981b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            request.setSpeechRate(getSpeechRate());
982b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            request.setCallerUid(mCallerUid);
983014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            request.setPitch(getPitch());
984b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
985b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
986b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
987b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void stopImpl() {
988b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            AbstractSynthesisCallback synthesisCallback;
989b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (this) {
990b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                synthesisCallback = mSynthesisCallback;
991b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
992b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (synthesisCallback != null) {
993b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                // If the synthesis callback is null, it implies that we haven't
994b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                // entered the synchronized(this) block in playImpl which in
995b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                // turn implies that synthesis would not have started.
996014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                synthesisCallback.stop();
997b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                TextToSpeechService.this.onStop();
998b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else {
999b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                dispatchOnStop();
1000014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
1001b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1002b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1003b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private String getCountry() {
1004b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!hasLanguage()) return mDefaultLocale[1];
1005b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
1006b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1007b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1008b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private String getVariant() {
1009b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!hasLanguage()) return mDefaultLocale[2];
1010b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
1011014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
1012b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1013b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String getLanguage() {
1014b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
1015014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
1016b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1017b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String getVoiceName() {
1018b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getStringParam(mParams, Engine.KEY_PARAM_VOICE_NAME, "");
1019b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1020b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1021b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1022b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class SynthesisToFileOutputStreamSpeechItemV1 extends SynthesisSpeechItemV1 {
1023b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final FileOutputStream mFileOutputStream;
1024b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1025b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public SynthesisToFileOutputStreamSpeechItemV1(Object callerIdentity, int callerUid,
1026b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                int callerPid, Bundle params, String utteranceId, CharSequence text,
1027b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                FileOutputStream fileOutputStream) {
1028b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            super(callerIdentity, callerUid, callerPid, params, utteranceId, text);
1029014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            mFileOutputStream = fileOutputStream;
1030b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1031b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1032b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1033014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        protected AbstractSynthesisCallback createSynthesisCallback() {
1034b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return new FileSynthesisCallback(mFileOutputStream.getChannel(),
1035b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    this, getCallerIdentity(), false);
1036b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1037b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1038b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1039b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void playImpl() {
1040b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            dispatchOnStart();
1041014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            super.playImpl();
1042b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            try {
1043b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch              mFileOutputStream.close();
1044b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } catch(IOException e) {
1045b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch              Log.w(TAG, "Failed to close output file", e);
1046b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1047b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1048b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1049014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1050b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class AudioSpeechItemV1 extends SpeechItemV1 {
1051b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final AudioPlaybackQueueItem mItem;
1052b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1053b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public AudioSpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
1054b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Bundle params, String utteranceId, Uri uri) {
1055014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            super(callerIdentity, callerUid, callerPid, params, utteranceId);
1056b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
1057b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    TextToSpeechService.this, uri, getAudioParams());
1058b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1059b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1060b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1061b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isValid() {
1062b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
1063b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1064b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1065b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1066b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void playImpl() {
1067b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mAudioPlaybackHandler.enqueue(mItem);
1068b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1069014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1070b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1071b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void stopImpl() {
1072014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            // Do nothing.
1073b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1074b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1075b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1076b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String getUtteranceId() {
1077b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
1078b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1079b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1080b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1081b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        AudioOutputParams getAudioParams() {
1082b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return AudioOutputParams.createFromV1ParamsBundle(mParams, false);
1083b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1084b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1085b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1086b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class SilenceSpeechItem extends UtteranceSpeechItem {
1087b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final long mDuration;
1088014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        private final String mUtteranceId;
1089b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1090b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
1091b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                String utteranceId, long duration) {
1092014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            super(callerIdentity, callerUid, callerPid);
1093b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mUtteranceId = utteranceId;
1094b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mDuration = duration;
1095b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1096b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1097b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1098b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isValid() {
1099b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
1100b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1101b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1102b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1103b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void playImpl() {
1104b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
1105b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    this, getCallerIdentity(), mDuration));
1106b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1107b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1108014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        @Override
1109b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void stopImpl() {
1110014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1111014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
1112014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1113014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        @Override
1114014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        public String getUtteranceId() {
1115b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mUtteranceId;
1116b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1117014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    }
1118014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1119014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    /**
1120b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1121014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch     */
1122014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    private class LoadLanguageItem extends SpeechItem {
1123014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        private final String mLanguage;
1124b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final String mCountry;
1125b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final String mVariant;
1126b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1127014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
1128b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                String language, String country, String variant) {
1129b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            super(callerIdentity, callerUid, callerPid);
1130b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mLanguage = language;
1131b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCountry = country;
1132b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mVariant = variant;
1133b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1134b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1135b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1136b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isValid() {
1137b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
1138b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1139b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1140b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1141b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void playImpl() {
1142014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
1143b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1144b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1145b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1146b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void stopImpl() {
1147b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // No-op
1148b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1149b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1150b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1151b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
1152b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1153b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
1154b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class LoadVoiceItem extends SpeechItem {
1155b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private final String mVoiceName;
1156b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1157b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public LoadVoiceItem(Object callerIdentity, int callerUid, int callerPid,
1158b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                String voiceName) {
1159014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            super(callerIdentity, callerUid, callerPid);
1160b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mVoiceName = voiceName;
1161b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1162b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1163b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1164b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isValid() {
1165b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
1166b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1167b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1168b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1169b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void playImpl() {
1170b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            TextToSpeechService.this.onLoadVoice(mVoiceName);
1171b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1172b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1173b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1174b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        protected void stopImpl() {
1175b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // No-op
1176b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1177b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1178b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1179b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1180b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    @Override
1181014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch    public IBinder onBind(Intent intent) {
1182b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
1183b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mBinder;
1184b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1185b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        return null;
1186b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1187b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1188b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    /**
1189b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
1190b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     * called called from several different threads.
1191b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch     */
1192b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    // NOTE: All calls that are passed in a calling app are interned so that
1193b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    // they can be used as message objects (which are tested for equality using ==).
1194b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
1195b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1196014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        public int speak(IBinder caller, CharSequence text, int queueMode, Bundle params,
1197b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                String utteranceId) {
1198b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(caller, text, params)) {
1199b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1200b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1201b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1202b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem item = new SynthesisSpeechItemV1(caller,
1203b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    Binder.getCallingUid(), Binder.getCallingPid(), params, utteranceId, text);
1204b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mSynthHandler.enqueueSpeechItem(queueMode, item);
1205014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        }
1206b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1207b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1208b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int synthesizeToFileDescriptor(IBinder caller, CharSequence text, ParcelFileDescriptor
1209b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                fileDescriptor, Bundle params, String utteranceId) {
1210b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(caller, text, fileDescriptor, params)) {
1211b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1212b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1213b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1214014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            // In test env, ParcelFileDescriptor instance may be EXACTLY the same
1215b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // one that is used by client. And it will be closed by a client, thus
1216b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // preventing us from writing anything to it.
1217b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
1218b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    fileDescriptor.detachFd());
1219014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1220b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV1(caller,
1221b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    Binder.getCallingUid(), Binder.getCallingPid(), params, utteranceId, text,
1222b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
1223014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
1224b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1225b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1226b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1227b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params,
1228b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                String utteranceId) {
1229b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(caller, audioUri, params)) {
1230b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1231b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1232b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1233b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem item = new AudioSpeechItemV1(caller,
1234014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    Binder.getCallingUid(), Binder.getCallingPid(), params, utteranceId, audioUri);
1235b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mSynthHandler.enqueueSpeechItem(queueMode, item);
1236b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1237b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1238b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1239b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int playSilence(IBinder caller, long duration, int queueMode, String utteranceId) {
1240b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(caller)) {
1241b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1242b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1243b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1244b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            SpeechItem item = new SilenceSpeechItem(caller,
1245014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                    Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, duration);
1246b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mSynthHandler.enqueueSpeechItem(queueMode, item);
1247b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1248b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1249b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1250b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public boolean isSpeaking() {
1251b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
1252b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1253b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1254b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1255b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int stop(IBinder caller) {
1256b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(caller)) {
1257b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1258014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
1259b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1260b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return mSynthHandler.stopForApp(caller);
1261b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1262014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1263b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1264b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String[] getLanguage() {
1265b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return onGetLanguage();
1266b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1267b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1268b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1269b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String[] getClientDefaultLanguage() {
1270b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return getSettingsLocale();
1271b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1272b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1273b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /*
1274b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * If defaults are enforced, then no language is "available" except
1275b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * perhaps the default language selected by the user.
1276b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
1277014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        @Override
1278b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int isLanguageAvailable(String lang, String country, String variant) {
1279b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(lang)) {
1280b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1281b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1282b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1283b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return onIsLanguageAvailable(lang, country, variant);
1284b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1285b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1286b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1287b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String[] getFeaturesForLanguage(String lang, String country, String variant) {
1288b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
1289b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            String[] featuresArray = null;
1290b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (features != null) {
1291b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                featuresArray = new String[features.size()];
1292014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                features.toArray(featuresArray);
1293b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else {
1294b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                featuresArray = new String[0];
1295b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1296b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return featuresArray;
1297b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1298b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1299b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        /*
1300b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * There is no point loading a non default language if defaults
1301b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         * are enforced.
1302b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch         */
1303b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1304b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int loadLanguage(IBinder caller, String lang, String country, String variant) {
1305b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(lang)) {
1306b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return TextToSpeech.ERROR;
1307b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1308b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            int retVal = onIsLanguageAvailable(lang, country, variant);
1309b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1310b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (retVal == TextToSpeech.LANG_AVAILABLE ||
1311b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
1312b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1313014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1314b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
1315b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        Binder.getCallingPid(), lang, country, variant);
1316b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1317b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
1318b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        TextToSpeech.SUCCESS) {
1319b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    return TextToSpeech.ERROR;
1320b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
1321b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1322b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return retVal;
1323b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1324b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1325b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1326b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public List<Voice> getVoices() {
1327b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return onGetVoices();
1328b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1329b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1330b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1331b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public int loadVoice(IBinder caller, String voiceName) {
1332b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(voiceName)) {
1333014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                return TextToSpeech.ERROR;
1334b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1335b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            int retVal = onIsValidVoiceName(voiceName);
1336b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1337b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (retVal == TextToSpeech.SUCCESS) {
1338b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                SpeechItem item = new LoadVoiceItem(caller, Binder.getCallingUid(),
1339b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        Binder.getCallingPid(), voiceName);
1340b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
1341b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                        TextToSpeech.SUCCESS) {
1342b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    return TextToSpeech.ERROR;
1343b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
1344b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1345b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return retVal;
1346b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1347b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1348b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public String getDefaultVoiceNameFor(String lang, String country, String variant) {
1349b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(lang)) {
1350014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                return null;
1351b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1352b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            int retVal = onIsLanguageAvailable(lang, country, variant);
1353b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1354b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (retVal == TextToSpeech.LANG_AVAILABLE ||
1355b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
1356b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1357b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return onGetDefaultVoiceNameFor(lang, country, variant);
1358b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } else {
1359b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return null;
1360b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1361b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1362b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1363b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1364b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1365014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            // Note that passing in a null callback is a valid use case.
1366b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (!checkNonNull(caller)) {
1367b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                return;
1368b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1369b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1370b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            mCallbacks.setCallback(caller, cb);
1371b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1372b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1373b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private String intern(String in) {
1374b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            // The input parameter will be non null.
1375b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return in.intern();
1376b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1377b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1378014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        private boolean checkNonNull(Object... args) {
1379014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            for (Object o : args) {
1380014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                if (o == null) return false;
1381b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1382b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return true;
1383b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1384b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    };
1385b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1386b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
1387014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
1388b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                = new HashMap<IBinder, ITextToSpeechCallback>();
1389b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1390b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1391b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (mCallerToCallback) {
1392b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                ITextToSpeechCallback old;
1393b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (cb != null) {
1394b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    register(cb, caller);
1395b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    old = mCallerToCallback.put(caller, cb);
1396b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                } else {
1397b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    old = mCallerToCallback.remove(caller);
1398b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
1399b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                if (old != null && old != cb) {
1400b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                    unregister(old);
1401b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                }
1402014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
1403b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1404014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
1405014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
1406014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1407b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (cb == null) return;
1408014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            try {
1409014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch                cb.onStop(utteranceId, started);
1410b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } catch (RemoteException e) {
1411b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.e(TAG, "Callback onStop failed: " + e);
1412b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1413b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1414b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1415014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch        public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
1416b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1417b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (cb == null) return;
1418b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            try {
1419b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                cb.onSuccess(utteranceId);
1420b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } catch (RemoteException e) {
1421b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.e(TAG, "Callback onDone failed: " + e);
1422b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1423b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1424b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1425b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnStart(Object callerIdentity, String utteranceId) {
1426b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1427b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            if (cb == null) return;
1428b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            try {
1429b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                cb.onStart(utteranceId);
1430b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } catch (RemoteException e) {
1431b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.e(TAG, "Callback onStart failed: " + e);
1432b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1433b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1434b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1435b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1436b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void dispatchOnError(Object callerIdentity, String utteranceId,
1437b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                int errorCode) {
1438b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1439014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            if (cb == null) return;
1440b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            try {
1441b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                cb.onError(utteranceId, errorCode);
1442b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            } catch (RemoteException e) {
1443b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                Log.e(TAG, "Callback onError failed: " + e);
1444b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1445b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1446b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1447b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1448b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
1449b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            IBinder caller = (IBinder) cookie;
1450b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (mCallerToCallback) {
1451b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCallerToCallback.remove(caller);
1452014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            }
1453b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            //mSynthHandler.stopForApp(caller);
1454b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1455b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1456b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        @Override
1457b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        public void kill() {
1458b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            synchronized (mCallerToCallback) {
1459b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                mCallerToCallback.clear();
1460b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                super.kill();
1461b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1462b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1463b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1464b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        private ITextToSpeechCallback getCallbackFor(Object caller) {
1465b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            ITextToSpeechCallback cb;
1466b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            IBinder asBinder = (IBinder) caller;
1467014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch            synchronized (mCallerToCallback) {
1468b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch                cb = mCallerToCallback.get(asBinder);
1469b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            }
1470b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch
1471b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch            return cb;
1472b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch        }
1473b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch    }
1474b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch}
1475b8a8cc1952d61a2f3a2568848933943a543b5d3eBen Murdoch