150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert/*
250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Copyright (C) 2011 The Android Open Source Project
350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert *
450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License"); you may not
550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * use this file except in compliance with the License. You may obtain a copy of
650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * the License at
750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert *
850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * http://www.apache.org/licenses/LICENSE-2.0
950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert *
1050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Unless required by applicable law or agreed to in writing, software
1150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * License for the specific language governing permissions and limitations under
1450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * the License.
1550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */
1650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertpackage android.speech.tts;
1750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
1850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.app.Service;
1950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.content.Intent;
2050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.net.Uri;
21492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamathimport android.os.Binder;
2250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.Bundle;
2350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.Handler;
2450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.HandlerThread;
2550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.IBinder;
2650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.Looper;
2750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.Message;
2850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.MessageQueue;
295acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniakimport android.os.ParcelFileDescriptor;
3050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.RemoteCallbackList;
3150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.RemoteException;
3250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.provider.Settings;
3350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.speech.tts.TextToSpeech.Engine;
3450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.text.TextUtils;
3550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.util.Log;
3650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
375acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniakimport java.io.FileDescriptor;
385acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniakimport java.io.FileOutputStream;
39fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniakimport java.io.IOException;
4050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.util.HashMap;
4150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.util.Locale;
42748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamathimport java.util.Set;
4350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
4450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
4550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert/**
46e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * Abstract base class for TTS engine implementations. The following methods
47e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * need to be implemented.
48e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
49e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * <ul>
50e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onIsLanguageAvailable}</li>
51e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onLoadLanguage}</li>
52e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onGetLanguage}</li>
53e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onSynthesizeText}</li>
54e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onStop}</li>
55e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * </ul>
56e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
57e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * The first three deal primarily with language management, and are used to
58e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * query the engine for it's support for a given language and indicate to it
59e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * that requests in a given language are imminent.
60e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
61e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * {@link #onSynthesizeText} is central to the engine implementation. The
62e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * implementation should synthesize text as per the request parameters and
63e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * return synthesized data via the supplied callback. This class and its helpers
64e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * will then consume that data, which might mean queueing it for playback or writing
65e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * it to a file or similar. All calls to this method will be on a single
66e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * thread, which will be different from the main thread of the service. Synthesis
67e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * must be synchronous which means the engine must NOT hold on the callback or call
68e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * any methods on it after the method returns
69e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
70e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
71e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * any. Any pending data from the current synthesis will be discarded.
72e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath *
7350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */
7450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertpublic abstract class TextToSpeechService extends Service {
7550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
7650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final boolean DBG = false;
7750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final String TAG = "TextToSpeechService";
7850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
792d940bcbd1c472f8b11ce1495354f340604b4f2cPrzemyslaw Szczepaniak
8050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final String SYNTH_THREAD_NAME = "SynthThread";
8150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
8250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private SynthHandler mSynthHandler;
838d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    // A thread and it's associated handler for playing back any audio
848d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    // associated with this TTS engine. Will handle all requests except synthesis
858d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    // to file requests, which occur on the synthesis thread.
868d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    private AudioPlaybackHandler mAudioPlaybackHandler;
87e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private TtsEngines mEngineHelper;
8850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
8950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private CallbackMap mCallbacks;
906dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private String mPackageName;
917a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath
9250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    @Override
9350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    public void onCreate() {
9450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        if (DBG) Log.d(TAG, "onCreate()");
9550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        super.onCreate();
9650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
9750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        SynthThread synthThread = new SynthThread();
9850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        synthThread.start();
9950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mSynthHandler = new SynthHandler(synthThread.getLooper());
10050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
1014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mAudioPlaybackHandler = new AudioPlaybackHandler();
1024924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mAudioPlaybackHandler.start();
103c90f1c815dc06d5bb563474a340f7bb158fab2e2Narayan Kamath
104e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        mEngineHelper = new TtsEngines(this);
105e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
10650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mCallbacks = new CallbackMap();
10750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
1086dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mPackageName = getApplicationInfo().packageName;
1096dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
110e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String[] defaultLocale = getSettingsLocale();
11150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        // Load default language
112e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
11350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
11450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
11550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    @Override
11650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    public void onDestroy() {
11750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        if (DBG) Log.d(TAG, "onDestroy()");
11850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
11950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        // Tell the synthesizer to stop
12050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mSynthHandler.quit();
1218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        // Tell the audio playback thread to stop.
1228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        mAudioPlaybackHandler.quit();
12350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        // Unregister all callbacks.
12450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mCallbacks.kill();
12550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
12650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        super.onDestroy();
12750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
12850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
12950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
13050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Checks whether the engine supports a given language.
13150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
13250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads.
13350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
13413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak     * Its return values HAVE to be consistent with onLoadLanguage.
13513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak     *
13650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param lang ISO-3 language code.
13750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param country ISO-3 country code. May be empty or null.
13850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param variant Language variant. May be empty or null.
13950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @return Code indicating the support status for the locale.
14050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         One of {@link TextToSpeech#LANG_AVAILABLE},
14150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
14250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
14350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_MISSING_DATA}
14450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
14550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
14650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
14750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
14850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
14950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Returns the language, country and variant currently being used by the TTS engine.
15050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
15150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads.
15250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
15350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @return A 3-element array, containing language (ISO 3-letter code),
15450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         country (ISO 3-letter code) and variant used by the engine.
15550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         The country and variant may be {@code ""}. If country is empty, then variant must
15650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         be empty too.
15750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @see Locale#getISO3Language()
15850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @see Locale#getISO3Country()
15950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @see Locale#getVariant()
16050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
16150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract String[] onGetLanguage();
16250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
16350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
16450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Notifies the engine that it should load a speech synthesis language. There is no guarantee
16550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * that this method is always called before the language is used for synthesis. It is merely
16650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * a hint to the engine that it will probably get some synthesis requests for this language
16750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * at some point in the future.
16850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
16950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads.
17013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak     * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
17113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak     * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
17250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
17350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param lang ISO-3 language code.
17450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param country ISO-3 country code. May be empty or null.
17550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param variant Language variant. May be empty or null.
17650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @return Code indicating the support status for the locale.
17750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         One of {@link TextToSpeech#LANG_AVAILABLE},
17850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
17950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
18050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_MISSING_DATA}
18150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
18250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
18350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract int onLoadLanguage(String lang, String country, String variant);
18450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
18550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
18650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Notifies the service that it should stop any in-progress speech synthesis.
18750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * This method can be called even if no speech synthesis is currently in progress.
18850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
18950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads, but not on the synthesis thread.
19050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
19150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract void onStop();
19250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
19350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
19450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Tells the service to synthesize speech from the given text. This method should
19550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * block until the synthesis is finished.
19650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
19750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Called on the synthesis thread.
19850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
199e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath     * @param request The synthesis request.
200e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath     * @param callback The callback the the engine must use to make data available for
201e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath     *         playback or for writing to a file.
20250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
203e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath    protected abstract void onSynthesizeText(SynthesisRequest request,
204e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            SynthesisCallback callback);
20550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
206748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath    /**
207748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * Queries the service for a set of features supported for a given language.
208748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     *
209748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @param lang ISO-3 language code.
210748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @param country ISO-3 country code. May be empty or null.
211748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @param variant Language variant. May be empty or null.
212748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @return A list of features supported for the given language.
213748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     */
214748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath    protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
215748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath        return null;
216748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath    }
217748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath
21850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private int getDefaultSpeechRate() {
21950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
22050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
22150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
222e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String[] getSettingsLocale() {
223e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
224e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return TtsEngines.parseLocalePref(locale);
22550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
22650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
22750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private int getSecureSettingInt(String name, int defaultValue) {
22850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
22950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
23050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
23150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
23250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Synthesizer thread. This thread is used to run {@link SynthHandler}.
23350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
23450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
23550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
23650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean mFirstIdle = true;
23750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
23850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public SynthThread() {
23984deb60cf1055385b9922811e5942076dd72e315Narayan Kamath            super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
24050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
24150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
24250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
24350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void onLooperPrepared() {
24450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            getLooper().getQueue().addIdleHandler(this);
24550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
24650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
24750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
24850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean queueIdle() {
24950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (mFirstIdle) {
25050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                mFirstIdle = false;
25150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } else {
25250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                broadcastTtsQueueProcessingCompleted();
25350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
25450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
25550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
25650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
25750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private void broadcastTtsQueueProcessingCompleted() {
25850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
25950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (DBG) Log.d(TAG, "Broadcasting: " + i);
26050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            sendBroadcast(i);
26150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
26250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
26350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
26450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class SynthHandler extends Handler {
26550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private SpeechItem mCurrentSpeechItem = null;
26650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
26750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public SynthHandler(Looper looper) {
26850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            super(looper);
26950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
27050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
27150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private synchronized SpeechItem getCurrentSpeechItem() {
27250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mCurrentSpeechItem;
27350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
27450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
27550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
27650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            SpeechItem old = mCurrentSpeechItem;
27750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mCurrentSpeechItem = speechItem;
27850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return old;
27950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
28050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
281492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
282a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (mCurrentSpeechItem != null &&
28313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
284a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                SpeechItem current = mCurrentSpeechItem;
285a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                mCurrentSpeechItem = null;
286a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                return current;
287a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            }
288a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
289a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            return null;
290a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        }
291a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
29250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isSpeaking() {
29350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getCurrentSpeechItem() != null;
29450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
29550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
29650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void quit() {
29750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            // Don't process any more speech items
29850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            getLooper().quit();
29950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            // Stop the current speech item
30050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            SpeechItem current = setCurrentSpeechItem(null);
30150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (current != null) {
30250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                current.stop();
30350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
304be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // The AudioPlaybackHandler will be destroyed by the caller.
30550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
30650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
30750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
30850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Adds a speech item to the queue.
30950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
31050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Called on a service binder thread.
31150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
31250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
31313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            UtteranceProgressDispatcher utterenceProgress = null;
31413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            if (speechItem instanceof UtteranceProgressDispatcher) {
31513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                utterenceProgress = (UtteranceProgressDispatcher) speechItem;
31613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            }
31713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
31850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (!speechItem.isValid()) {
31913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                if (utterenceProgress != null) {
32013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    utterenceProgress.dispatchOnError();
32113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                }
32250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.ERROR;
32350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
3244924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
32550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (queueMode == TextToSpeech.QUEUE_FLUSH) {
326492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                stopForApp(speechItem.getCallerIdentity());
327abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
328a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                stopAll();
32950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
33050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            Runnable runnable = new Runnable() {
33150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                @Override
33250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                public void run() {
33350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    setCurrentSpeechItem(speechItem);
3348d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath                    speechItem.play();
33550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    setCurrentSpeechItem(null);
33650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
33750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            };
33850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            Message msg = Message.obtain(this, runnable);
33913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
340a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // The obj is used to remove all callbacks from the given app in
341a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // stopForApp(String).
342abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            //
343abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            // Note that this string is interned, so the == comparison works.
344492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            msg.obj = speechItem.getCallerIdentity();
34550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (sendMessage(msg)) {
34650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.SUCCESS;
34750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } else {
34850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                Log.w(TAG, "SynthThread has quit");
34913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                if (utterenceProgress != null) {
35013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    utterenceProgress.dispatchOnError();
35113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                }
35250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.ERROR;
35350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
35450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
35550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
35650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
35750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Stops all speech output and removes any utterances still in the queue for
35850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * the calling app.
35950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
36050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Called on a service binder thread.
36150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
362492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int stopForApp(Object callerIdentity) {
363492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (callerIdentity == null) {
36450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.ERROR;
36550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
3664924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
367492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            removeCallbacksAndMessages(callerIdentity);
368be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // This stops writing data to the file / or publishing
369be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // items to the audio playback handler.
370a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            //
371a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Note that the current speech item must be removed only if it
372a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // belongs to the callingApp, else the item will be "orphaned" and
373a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // not stopped correctly if a stop request comes along for the item
374a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // from the app it belongs to.
375492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
376a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (current != null) {
37750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                current.stop();
37850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
3798d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
3804924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath            // Remove any enqueued audio too.
38167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mAudioPlaybackHandler.stopForApp(callerIdentity);
3824924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
38350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return TextToSpeech.SUCCESS;
38450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
385a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
386a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        public int stopAll() {
38713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            // Stop the current speech item unconditionally .
388a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            SpeechItem current = setCurrentSpeechItem(null);
389a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (current != null) {
390a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                current.stop();
391a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            }
392a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Remove all other items from the queue.
393a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            removeCallbacksAndMessages(null);
394a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Remove all pending playback as well.
39567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mAudioPlaybackHandler.stop();
396a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
397a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            return TextToSpeech.SUCCESS;
398a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        }
39950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
40050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
401754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath    interface UtteranceProgressDispatcher {
402754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnDone();
403754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnStart();
404754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnError();
4058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    }
4068d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
40750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
40850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * An item in the synth thread queue.
40950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
41013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    private abstract class SpeechItem {
411492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final Object mCallerIdentity;
412b956f37e375bb2588208d4b5e8a40fae6fae5f86Narayan Kamath        protected final Bundle mParams;
413492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final int mCallerUid;
414492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final int mCallerPid;
41550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean mStarted = false;
41650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean mStopped = false;
41750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
418492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
419492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallerIdentity = caller;
42050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mParams = params;
421492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallerUid = callerUid;
422492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallerPid = callerPid;
42350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
42450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
425492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public Object getCallerIdentity() {
426492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            return mCallerIdentity;
42750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
42850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
42913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
43013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        public int getCallerUid() {
43113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            return mCallerUid;
43213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
43313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
43413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        public int getCallerPid() {
43513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            return mCallerPid;
43613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
43713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
43850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
43950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Checker whether the item is valid. If this method returns false, the item should not
44050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * be played.
44150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
44250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public abstract boolean isValid();
44350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
44450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
44550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Plays the speech item. Blocks until playback is finished.
44650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Must not be called more than once.
44750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
44850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Only called on the synthesis thread.
44950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
45050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
45150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
45250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int play() {
45350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
45450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (mStarted) {
45550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    throw new IllegalStateException("play() called twice");
45650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
45750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                mStarted = true;
45850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
45950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return playImpl();
46050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
46150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
46213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        protected abstract int playImpl();
46313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
46450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
46550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Stops the speech item.
46650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Must not be called more than once.
46750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
46850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Can be called on multiple threads,  but not on the synthesis thread.
46950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
47050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void stop() {
47150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
47250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (mStopped) {
47350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    throw new IllegalStateException("stop() called twice");
47450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
47550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                mStopped = true;
47650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
47750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            stopImpl();
47850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
47950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
48013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        protected abstract void stopImpl();
48113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
48213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        protected synchronized boolean isStopped() {
48313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak             return mStopped;
48413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
48513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    }
48613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
48713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    /**
48813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak     * An item in the synth thread queue that process utterance.
48913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak     */
49013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    private abstract class UtteranceSpeechItem extends SpeechItem
49113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        implements UtteranceProgressDispatcher  {
49213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
49313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
49413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            super(caller, callerUid, callerPid, params);
49513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
49613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
497754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        @Override
498754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnDone() {
499754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            final String utteranceId = getUtteranceId();
50068e2af55d65d2e61fbf8096eccaa2e4ca02b6c5aNarayan Kamath            if (utteranceId != null) {
501492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
502754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
503754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
504754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
505754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        @Override
506754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnStart() {
507754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            final String utteranceId = getUtteranceId();
50868e2af55d65d2e61fbf8096eccaa2e4ca02b6c5aNarayan Kamath            if (utteranceId != null) {
509492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
510754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
511754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
512754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
513754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        @Override
514754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnError() {
5158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            final String utteranceId = getUtteranceId();
51668e2af55d65d2e61fbf8096eccaa2e4ca02b6c5aNarayan Kamath            if (utteranceId != null) {
517492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
5188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            }
5198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        }
5208d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
52150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int getStreamType() {
52250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
52350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
52450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
52550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public float getVolume() {
52650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
52750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
52850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
52950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public float getPan() {
53050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
53150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
53250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
53350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String getUtteranceId() {
53450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
53550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
53650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
53750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected String getStringParam(String key, String defaultValue) {
53850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
53950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
54050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
54150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int getIntParam(String key, int defaultValue) {
54250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
54350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
54450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
54550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected float getFloatParam(String key, float defaultValue) {
54650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
54750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
54813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
54950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
55050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
55113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    class SynthesisSpeechItem extends UtteranceSpeechItem {
55240f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath        // Never null.
55350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private final String mText;
554e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        private final SynthesisRequest mSynthesisRequest;
555e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        private final String[] mDefaultLocale;
556e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        // Non null after synthesis has started, and all accesses
557e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        // guarded by 'this'.
558e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        private AbstractSynthesisCallback mSynthesisCallback;
5596dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        private final EventLogger mEventLogger;
560653278341d76d0b23a008087ff94250ae0beb54bPrzemyslaw Szczepaniak        private final int mCallerUid;
56150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
562492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
563492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, String text) {
564492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params);
56550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mText = text;
566653278341d76d0b23a008087ff94250ae0beb54bPrzemyslaw Szczepaniak            mCallerUid = callerUid;
567e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            mSynthesisRequest = new SynthesisRequest(mText, mParams);
568e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            mDefaultLocale = getSettingsLocale();
569e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            setRequestParams(mSynthesisRequest);
570492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
571492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    mPackageName);
57250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
57350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
57450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String getText() {
57550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mText;
57650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
57750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
57850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
57950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
58040f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath            if (mText == null) {
5819c3d7a888d0c5c09f0153e81018ff68aa6e91712Narayan Kamath                Log.e(TAG, "null synthesis text");
58250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return false;
58350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
5842d940bcbd1c472f8b11ce1495354f340604b4f2cPrzemyslaw Szczepaniak            if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
58550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                Log.w(TAG, "Text too long: " + mText.length() + " chars");
58650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return false;
58750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
58850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
58950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
59050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
59150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
59250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int playImpl() {
593e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            AbstractSynthesisCallback synthesisCallback;
5946dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            mEventLogger.onRequestProcessingStart();
59550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
596a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // stop() might have been called before we enter this
597a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // synchronized block.
598a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                if (isStopped()) {
599a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                    return TextToSpeech.ERROR;
600a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                }
601e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath                mSynthesisCallback = createSynthesisCallback();
602e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath                synthesisCallback = mSynthesisCallback;
60350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
604e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
605e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
60650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
60750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
608e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        protected AbstractSynthesisCallback createSynthesisCallback() {
609e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
610492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
61150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
61250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
61350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private void setRequestParams(SynthesisRequest request) {
614c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath            request.setLanguage(getLanguage(), getCountry(), getVariant());
615c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath            request.setSpeechRate(getSpeechRate());
616653278341d76d0b23a008087ff94250ae0beb54bPrzemyslaw Szczepaniak            request.setCallerUid(mCallerUid);
61750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            request.setPitch(getPitch());
61850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
61950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
62050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
62150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void stopImpl() {
622e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            AbstractSynthesisCallback synthesisCallback;
62350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
624e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath                synthesisCallback = mSynthesisCallback;
62550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
626a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (synthesisCallback != null) {
627a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // If the synthesis callback is null, it implies that we haven't
628a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // entered the synchronized(this) block in playImpl which in
629a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // turn implies that synthesis would not have started.
630a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                synthesisCallback.stop();
631a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                TextToSpeechService.this.onStop();
632a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            }
63350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
63450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
63550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String getLanguage() {
636e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
63750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
63850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
63950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean hasLanguage() {
64050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
64150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
64250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
64350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private String getCountry() {
644e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (!hasLanguage()) return mDefaultLocale[1];
64550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
64650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
64750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
64850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private String getVariant() {
649e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (!hasLanguage()) return mDefaultLocale[2];
65050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getStringParam(Engine.KEY_PARAM_VARIANT, "");
65150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
65250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
65350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private int getSpeechRate() {
65450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
65550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
65650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
65750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private int getPitch() {
65850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
65950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
66050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
66150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
662fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak    private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
663fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak        private final FileOutputStream mFileOutputStream;
66450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
6657341786b138cb52eac053108b524ea3296d40f6dPrzemyslaw Szczepaniak        public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
666fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak                int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
667492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params, text);
668fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak            mFileOutputStream = fileOutputStream;
66950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
67050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
67150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
672e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        protected AbstractSynthesisCallback createSynthesisCallback() {
673fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak            return new FileSynthesisCallback(mFileOutputStream.getChannel());
67450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
67550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
6768d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        @Override
6778d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        protected int playImpl() {
678754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            dispatchOnStart();
6798d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            int status = super.playImpl();
6808d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            if (status == TextToSpeech.SUCCESS) {
681754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                dispatchOnDone();
682754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            } else {
683754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                dispatchOnError();
6848d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            }
685fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak            try {
686fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak              mFileOutputStream.close();
687fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak            } catch(IOException e) {
688fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak              Log.w(TAG, "Failed to close output file", e);
689fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak            }
6908d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            return status;
6918d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        }
69250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
69350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
69413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    private class AudioSpeechItem extends UtteranceSpeechItem {
695af802c6831551323126537cf8edabea97d2fc762Narayan Kamath        private final AudioPlaybackQueueItem mItem;
696492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
697492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, Uri uri) {
698492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params);
699af802c6831551323126537cf8edabea97d2fc762Narayan Kamath            mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
700af802c6831551323126537cf8edabea97d2fc762Narayan Kamath                    TextToSpeechService.this, uri, getStreamType());
70150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
70250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
70350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
70450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
70550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
70650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
70750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
70850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
70950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int playImpl() {
710af802c6831551323126537cf8edabea97d2fc762Narayan Kamath            mAudioPlaybackHandler.enqueue(mItem);
7118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            return TextToSpeech.SUCCESS;
71250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
71350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
71450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
71550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void stopImpl() {
716be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // Do nothing.
71750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
71850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
71950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
72013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    private class SilenceSpeechItem extends UtteranceSpeechItem {
72150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private final long mDuration;
72250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
723492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
724492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, long duration) {
725492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params);
72650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mDuration = duration;
72750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
72850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
72950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
73050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
73150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
73250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
73350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
73450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
73550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int playImpl() {
73667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
73767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    this, getCallerIdentity(), mDuration));
7388d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            return TextToSpeech.SUCCESS;
73950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
74050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
74150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
74250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void stopImpl() {
74367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // Do nothing, handled by AudioPlaybackHandler#stopForApp
74450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
74550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
74650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
74713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    private class LoadLanguageItem extends SpeechItem {
74813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        private final String mLanguage;
74913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        private final String mCountry;
75013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        private final String mVariant;
75113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
75213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
75313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                Bundle params, String language, String country, String variant) {
75413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            super(callerIdentity, callerUid, callerPid, params);
75513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            mLanguage = language;
75613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            mCountry = country;
75713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            mVariant = variant;
75813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
75913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
76013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        @Override
76113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        public boolean isValid() {
76213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            return true;
76313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
76413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
76513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        @Override
76613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        protected int playImpl() {
76713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
76813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            if (result == TextToSpeech.LANG_AVAILABLE ||
76913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    result == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
77013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
77113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                return TextToSpeech.SUCCESS;
77213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            }
77313896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            return TextToSpeech.ERROR;
77413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
77513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
77613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        @Override
77713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        protected void stopImpl() {
77813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            // No-op
77913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        }
78013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak    }
78113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
78250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    @Override
78350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    public IBinder onBind(Intent intent) {
78450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
78550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mBinder;
78650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
78750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        return null;
78850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
78950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
79050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
79150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
79250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * called called from several different threads.
79350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
794abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath    // NOTE: All calls that are passed in a calling app are interned so that
795abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath    // they can be used as message objects (which are tested for equality using ==).
79650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
797492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
798492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int speak(IBinder caller, String text, int queueMode, Bundle params) {
799492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, text, params)) {
800abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
801abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
802abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
803492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new SynthesisSpeechItem(caller,
804492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingUid(), Binder.getCallingPid(), params, text);
80550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(queueMode, item);
80650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
80750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
808492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
8095acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak        public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
8105acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak                fileDescriptor, Bundle params) {
8115acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak            if (!checkNonNull(caller, text, fileDescriptor, params)) {
812abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
813abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
814abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
81524943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak            // In test env, ParcelFileDescriptor instance may be EXACTLY the same
81624943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak            // one that is used by client. And it will be closed by a client, thus
81724943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak            // preventing us from writing anything to it.
81824943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak            final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
81924943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak                    fileDescriptor.detachFd());
82024943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak
821fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak            SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
822fcf671be890f9fb49067c5f86b182bf8b8e7b9c0Przemyslaw Szczepaniak                    Binder.getCallingUid(), Binder.getCallingPid(), params, text,
82324943bf19d3d000f9f5840513526b48523b62c71Przemyslaw Szczepaniak                    new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
82450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
82550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
82650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
827492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
828492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
829492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, audioUri, params)) {
830abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
831abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
832abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
833492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new AudioSpeechItem(caller,
834492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
83550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(queueMode, item);
83650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
83750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
838492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
839492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
840492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, params)) {
841abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
842abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
843abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
844492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new SilenceSpeechItem(caller,
845492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
84650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(queueMode, item);
84750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
84850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
849492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
85050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isSpeaking() {
851c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath            return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
85250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
85350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
854492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
855492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int stop(IBinder caller) {
856492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller)) {
857abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
858abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
859abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
860492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            return mSynthHandler.stopForApp(caller);
86150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
86250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
863492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
86450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String[] getLanguage() {
86550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return onGetLanguage();
86650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
86750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
868b46533732c40c6aa4d0d7357176835a33d863234Przemyslaw Szczepaniak        @Override
869b46533732c40c6aa4d0d7357176835a33d863234Przemyslaw Szczepaniak        public String[] getClientDefaultLanguage() {
870b46533732c40c6aa4d0d7357176835a33d863234Przemyslaw Szczepaniak            return getSettingsLocale();
871b46533732c40c6aa4d0d7357176835a33d863234Przemyslaw Szczepaniak        }
872b46533732c40c6aa4d0d7357176835a33d863234Przemyslaw Szczepaniak
8737a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath        /*
8747a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * If defaults are enforced, then no language is "available" except
8757a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * perhaps the default language selected by the user.
8767a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         */
877492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
87850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int isLanguageAvailable(String lang, String country, String variant) {
879abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            if (!checkNonNull(lang)) {
880abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
881abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
882abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
88350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return onIsLanguageAvailable(lang, country, variant);
88450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
88550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
886492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
887748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath        public String[] getFeaturesForLanguage(String lang, String country, String variant) {
888748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath            Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
8896c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            String[] featuresArray = null;
8906c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            if (features != null) {
8916c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath                featuresArray = new String[features.size()];
8926c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath                features.toArray(featuresArray);
8936c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            } else {
8946c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath                featuresArray = new String[0];
8956c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            }
896748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath            return featuresArray;
897748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath        }
898748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath
8997a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath        /*
9007a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * There is no point loading a non default language if defaults
9017a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * are enforced.
9027a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         */
903492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
90413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak        public int loadLanguage(IBinder caller, String lang, String country, String variant) {
905abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            if (!checkNonNull(lang)) {
906abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
907abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
90813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            int retVal = onIsLanguageAvailable(lang, country, variant);
90913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
91013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            if (retVal == TextToSpeech.LANG_AVAILABLE ||
91113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
91213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
913abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
91413896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
91513896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    Binder.getCallingPid(), null, lang, country, variant);
91613896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak
91713896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
91813896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                        TextToSpeech.SUCCESS) {
91913896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                    return TextToSpeech.ERROR;
92013896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak                }
92113896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            }
92213896b74194b07c821d5d89713e4e747b9b77d73Przemyslaw Szczepaniak            return retVal;
92350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
92450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
925492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
926492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
927abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            // Note that passing in a null callback is a valid use case.
928492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller)) {
929abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return;
930abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
931abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
932492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallbacks.setCallback(caller, cb);
93350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
9347a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath
935abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        private String intern(String in) {
936abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            // The input parameter will be non null.
937abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            return in.intern();
938abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        }
939abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
940abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        private boolean checkNonNull(Object... args) {
941abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            for (Object o : args) {
942abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                if (o == null) return false;
943abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
944abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            return true;
945abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        }
94650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    };
94750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
94850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
949492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
950492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                = new HashMap<IBinder, ITextToSpeechCallback>();
95150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
952492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
953492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
95450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                ITextToSpeechCallback old;
95550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (cb != null) {
956492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    register(cb, caller);
957492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    old = mCallerToCallback.put(caller, cb);
95850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                } else {
959492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    old = mCallerToCallback.remove(caller);
96050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
96150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (old != null && old != cb) {
96250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    unregister(old);
96350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
96450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
96550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
96650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
967492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void dispatchOnDone(Object callerIdentity, String utteranceId) {
968492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
969754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            if (cb == null) return;
970754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            try {
971754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                cb.onDone(utteranceId);
972754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            } catch (RemoteException e) {
973754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                Log.e(TAG, "Callback onDone failed: " + e);
97450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
975754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
976754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
977492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void dispatchOnStart(Object callerIdentity, String utteranceId) {
978492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
979754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            if (cb == null) return;
980754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            try {
981754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                cb.onStart(utteranceId);
982754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            } catch (RemoteException e) {
983754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                Log.e(TAG, "Callback onStart failed: " + e);
984754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
985754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
986754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
987754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
988492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void dispatchOnError(Object callerIdentity, String utteranceId) {
989492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
99050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (cb == null) return;
99150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            try {
992754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                cb.onError(utteranceId);
99350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } catch (RemoteException e) {
994754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                Log.e(TAG, "Callback onError failed: " + e);
99550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
99650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
99750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
99850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
99950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
1000492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            IBinder caller = (IBinder) cookie;
1001492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
1002492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallerToCallback.remove(caller);
100350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
1004492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mSynthHandler.stopForApp(caller);
100550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
100650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
100750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
100850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void kill() {
1009492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
1010492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallerToCallback.clear();
101150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                super.kill();
101250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
101350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
101450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
1015492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private ITextToSpeechCallback getCallbackFor(Object caller) {
1016754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            ITextToSpeechCallback cb;
1017492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            IBinder asBinder = (IBinder) caller;
1018492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
1019492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                cb = mCallerToCallback.get(asBinder);
1020754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
1021754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
1022754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            return cb;
1023754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
1024754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
102550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
102650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
102750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert}
1028