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;
2950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.RemoteCallbackList;
3050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.os.RemoteException;
3150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.provider.Settings;
3250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.speech.tts.TextToSpeech.Engine;
3350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.text.TextUtils;
3450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.util.Log;
3550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
3650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.io.File;
3750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.io.IOException;
3850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.util.HashMap;
3950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.util.Locale;
40748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamathimport java.util.Set;
4150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
4250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
4350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert/**
44e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * Abstract base class for TTS engine implementations. The following methods
45e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * need to be implemented.
46e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
47e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * <ul>
48e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onIsLanguageAvailable}</li>
49e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onLoadLanguage}</li>
50e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onGetLanguage}</li>
51e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onSynthesizeText}</li>
52e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *   <li>{@link #onStop}</li>
53e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * </ul>
54e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
55e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * The first three deal primarily with language management, and are used to
56e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * query the engine for it's support for a given language and indicate to it
57e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * that requests in a given language are imminent.
58e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
59e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * {@link #onSynthesizeText} is central to the engine implementation. The
60e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * implementation should synthesize text as per the request parameters and
61e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * return synthesized data via the supplied callback. This class and its helpers
62e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * will then consume that data, which might mean queueing it for playback or writing
63e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * it to a file or similar. All calls to this method will be on a single
64e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * thread, which will be different from the main thread of the service. Synthesis
65e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * must be synchronous which means the engine must NOT hold on the callback or call
66e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * any methods on it after the method returns
67e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath *
68e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
69e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath * any. Any pending data from the current synthesis will be discarded.
70e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath *
7150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */
7250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertpublic abstract class TextToSpeechService extends Service {
7350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
7450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final boolean DBG = false;
7550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final String TAG = "TextToSpeechService";
7650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
7750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
7850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private static final String SYNTH_THREAD_NAME = "SynthThread";
7950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
8050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private SynthHandler mSynthHandler;
818d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    // A thread and it's associated handler for playing back any audio
828d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    // associated with this TTS engine. Will handle all requests except synthesis
838d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    // to file requests, which occur on the synthesis thread.
848d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    private AudioPlaybackHandler mAudioPlaybackHandler;
85e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private TtsEngines mEngineHelper;
8650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
8750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private CallbackMap mCallbacks;
886dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private String mPackageName;
897a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath
9050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    @Override
9150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    public void onCreate() {
9250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        if (DBG) Log.d(TAG, "onCreate()");
9350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        super.onCreate();
9450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
9550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        SynthThread synthThread = new SynthThread();
9650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        synthThread.start();
9750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mSynthHandler = new SynthHandler(synthThread.getLooper());
9850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mAudioPlaybackHandler = new AudioPlaybackHandler();
1004924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mAudioPlaybackHandler.start();
101c90f1c815dc06d5bb563474a340f7bb158fab2e2Narayan Kamath
102e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        mEngineHelper = new TtsEngines(this);
103e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
10450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mCallbacks = new CallbackMap();
10550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
1066dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mPackageName = getApplicationInfo().packageName;
1076dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
108e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String[] defaultLocale = getSettingsLocale();
10950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        // Load default language
110e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
11150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
11250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
11350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    @Override
11450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    public void onDestroy() {
11550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        if (DBG) Log.d(TAG, "onDestroy()");
11650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
11750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        // Tell the synthesizer to stop
11850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mSynthHandler.quit();
1198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        // Tell the audio playback thread to stop.
1208d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        mAudioPlaybackHandler.quit();
12150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        // Unregister all callbacks.
12250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        mCallbacks.kill();
12350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
12450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        super.onDestroy();
12550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
12650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
12750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
12850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Checks whether the engine supports a given language.
12950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
13050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads.
13150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
13250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param lang ISO-3 language code.
13350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param country ISO-3 country code. May be empty or null.
13450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param variant Language variant. May be empty or null.
13550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @return Code indicating the support status for the locale.
13650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         One of {@link TextToSpeech#LANG_AVAILABLE},
13750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
13850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
13950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_MISSING_DATA}
14050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
14150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
14250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
14350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
14450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
14550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Returns the language, country and variant currently being used by the TTS engine.
14650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
14750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads.
14850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
14950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @return A 3-element array, containing language (ISO 3-letter code),
15050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         country (ISO 3-letter code) and variant used by the engine.
15150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         The country and variant may be {@code ""}. If country is empty, then variant must
15250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         be empty too.
15350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @see Locale#getISO3Language()
15450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @see Locale#getISO3Country()
15550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @see Locale#getVariant()
15650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
15750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract String[] onGetLanguage();
15850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
15950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
16050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Notifies the engine that it should load a speech synthesis language. There is no guarantee
16150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * that this method is always called before the language is used for synthesis. It is merely
16250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * a hint to the engine that it will probably get some synthesis requests for this language
16350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * at some point in the future.
16450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
16550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads.
16650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
16750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param lang ISO-3 language code.
16850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param country ISO-3 country code. May be empty or null.
16950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @param variant Language variant. May be empty or null.
17050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * @return Code indicating the support status for the locale.
17150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         One of {@link TextToSpeech#LANG_AVAILABLE},
17250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
17350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
17450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_MISSING_DATA}
17550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
17650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
17750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract int onLoadLanguage(String lang, String country, String variant);
17850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
17950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
18050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Notifies the service that it should stop any in-progress speech synthesis.
18150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * This method can be called even if no speech synthesis is currently in progress.
18250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
18350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Can be called on multiple threads, but not on the synthesis thread.
18450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
18550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    protected abstract void onStop();
18650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
18750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
18850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Tells the service to synthesize speech from the given text. This method should
18950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * block until the synthesis is finished.
19050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
19150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Called on the synthesis thread.
19250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     *
193e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath     * @param request The synthesis request.
194e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath     * @param callback The callback the the engine must use to make data available for
195e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath     *         playback or for writing to a file.
19650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
197e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath    protected abstract void onSynthesizeText(SynthesisRequest request,
198e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            SynthesisCallback callback);
19950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
200748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath    /**
201748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * Queries the service for a set of features supported for a given language.
202748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     *
203748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @param lang ISO-3 language code.
204748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @param country ISO-3 country code. May be empty or null.
205748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @param variant Language variant. May be empty or null.
206748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     * @return A list of features supported for the given language.
207748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath     */
208748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath    protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
209748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath        return null;
210748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath    }
211748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath
21250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private int getDefaultSpeechRate() {
21350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
21450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
21550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
216e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String[] getSettingsLocale() {
217e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
218e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return TtsEngines.parseLocalePref(locale);
21950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
22050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
22150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private int getSecureSettingInt(String name, int defaultValue) {
22250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
22350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
22450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
22550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
22650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Synthesizer thread. This thread is used to run {@link SynthHandler}.
22750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
22850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
22950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
23050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean mFirstIdle = true;
23150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
23250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public SynthThread() {
23384deb60cf1055385b9922811e5942076dd72e315Narayan Kamath            super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
23450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
23550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
23650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
23750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void onLooperPrepared() {
23850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            getLooper().getQueue().addIdleHandler(this);
23950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
24050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
24150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
24250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean queueIdle() {
24350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (mFirstIdle) {
24450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                mFirstIdle = false;
24550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } else {
24650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                broadcastTtsQueueProcessingCompleted();
24750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
24850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
24950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
25050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
25150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private void broadcastTtsQueueProcessingCompleted() {
25250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
25350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (DBG) Log.d(TAG, "Broadcasting: " + i);
25450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            sendBroadcast(i);
25550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
25650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
25750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
25850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class SynthHandler extends Handler {
25950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
26050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private SpeechItem mCurrentSpeechItem = null;
26150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
26250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public SynthHandler(Looper looper) {
26350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            super(looper);
26450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
26550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
26650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private synchronized SpeechItem getCurrentSpeechItem() {
26750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mCurrentSpeechItem;
26850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
26950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
27050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
27150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            SpeechItem old = mCurrentSpeechItem;
27250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mCurrentSpeechItem = speechItem;
27350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return old;
27450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
27550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
276492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
277a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (mCurrentSpeechItem != null &&
278492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    mCurrentSpeechItem.getCallerIdentity() == callerIdentity) {
279a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                SpeechItem current = mCurrentSpeechItem;
280a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                mCurrentSpeechItem = null;
281a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                return current;
282a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            }
283a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
284a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            return null;
285a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        }
286a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
28750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isSpeaking() {
28850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getCurrentSpeechItem() != null;
28950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
29050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
29150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void quit() {
29250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            // Don't process any more speech items
29350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            getLooper().quit();
29450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            // Stop the current speech item
29550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            SpeechItem current = setCurrentSpeechItem(null);
29650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (current != null) {
29750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                current.stop();
29850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
299be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath
300be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // The AudioPlaybackHandler will be destroyed by the caller.
30150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
30250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
30350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
30450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Adds a speech item to the queue.
30550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
30650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Called on a service binder thread.
30750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
30850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
30950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (!speechItem.isValid()) {
310754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                speechItem.dispatchOnError();
31150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.ERROR;
31250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
3134924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
31450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (queueMode == TextToSpeech.QUEUE_FLUSH) {
315492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                stopForApp(speechItem.getCallerIdentity());
316abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
317a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                stopAll();
31850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
31950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            Runnable runnable = new Runnable() {
32050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                @Override
32150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                public void run() {
32250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    setCurrentSpeechItem(speechItem);
3238d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath                    speechItem.play();
32450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    setCurrentSpeechItem(null);
32550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
32650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            };
32750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            Message msg = Message.obtain(this, runnable);
328a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // The obj is used to remove all callbacks from the given app in
329a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // stopForApp(String).
330abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            //
331abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            // Note that this string is interned, so the == comparison works.
332492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            msg.obj = speechItem.getCallerIdentity();
33350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (sendMessage(msg)) {
33450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.SUCCESS;
33550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } else {
33650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                Log.w(TAG, "SynthThread has quit");
337754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                speechItem.dispatchOnError();
33850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.ERROR;
33950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
34050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
34150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
34250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
34350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Stops all speech output and removes any utterances still in the queue for
34450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * the calling app.
34550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
34650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Called on a service binder thread.
34750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
348492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int stopForApp(Object callerIdentity) {
349492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (callerIdentity == null) {
35050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return TextToSpeech.ERROR;
35150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
3524924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
353492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            removeCallbacksAndMessages(callerIdentity);
354be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // This stops writing data to the file / or publishing
355be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // items to the audio playback handler.
356a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            //
357a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Note that the current speech item must be removed only if it
358a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // belongs to the callingApp, else the item will be "orphaned" and
359a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // not stopped correctly if a stop request comes along for the item
360a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // from the app it belongs to.
361492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
362a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (current != null) {
36350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                current.stop();
36450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
3658d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
3664924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath            // Remove any enqueued audio too.
36767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mAudioPlaybackHandler.stopForApp(callerIdentity);
3684924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
36950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return TextToSpeech.SUCCESS;
37050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
371a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
372a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        public int stopAll() {
373a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Stop the current speech item unconditionally.
374a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            SpeechItem current = setCurrentSpeechItem(null);
375a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (current != null) {
376a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                current.stop();
377a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            }
378a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Remove all other items from the queue.
379a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            removeCallbacksAndMessages(null);
380a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            // Remove all pending playback as well.
38167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mAudioPlaybackHandler.stop();
382a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
383a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            return TextToSpeech.SUCCESS;
384a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        }
38550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
38650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
387754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath    interface UtteranceProgressDispatcher {
388754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnDone();
389754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnStart();
390754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnError();
3918d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    }
3928d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
39350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
39450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * An item in the synth thread queue.
39550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
396754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath    private abstract class SpeechItem implements UtteranceProgressDispatcher {
397492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final Object mCallerIdentity;
398b956f37e375bb2588208d4b5e8a40fae6fae5f86Narayan Kamath        protected final Bundle mParams;
399492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final int mCallerUid;
400492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final int mCallerPid;
40150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean mStarted = false;
40250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean mStopped = false;
40350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
404492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
405492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallerIdentity = caller;
40650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mParams = params;
407492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallerUid = callerUid;
408492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallerPid = callerPid;
40950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
41050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
411492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public Object getCallerIdentity() {
412492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            return mCallerIdentity;
41350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
41450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
41550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
41650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Checker whether the item is valid. If this method returns false, the item should not
41750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * be played.
41850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
41950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public abstract boolean isValid();
42050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
42150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
42250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Plays the speech item. Blocks until playback is finished.
42350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Must not be called more than once.
42450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
42550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Only called on the synthesis thread.
42650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
42750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
42850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
42950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int play() {
43050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
43150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (mStarted) {
43250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    throw new IllegalStateException("play() called twice");
43350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
43450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                mStarted = true;
43550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
43650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return playImpl();
43750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
43850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
43950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
44050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Stops the speech item.
44150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Must not be called more than once.
44250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         *
44350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Can be called on multiple threads,  but not on the synthesis thread.
44450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
44550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void stop() {
44650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
44750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (mStopped) {
44850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    throw new IllegalStateException("stop() called twice");
44950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
45050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                mStopped = true;
45150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
45250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            stopImpl();
45350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
45450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
455754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        @Override
456754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnDone() {
457754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            final String utteranceId = getUtteranceId();
45868e2af55d65d2e61fbf8096eccaa2e4ca02b6c5aNarayan Kamath            if (utteranceId != null) {
459492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
460754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
461754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
462754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
463754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        @Override
464754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnStart() {
465754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            final String utteranceId = getUtteranceId();
46668e2af55d65d2e61fbf8096eccaa2e4ca02b6c5aNarayan Kamath            if (utteranceId != null) {
467492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
468754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
469754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
470754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
471754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        @Override
472754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        public void dispatchOnError() {
4738d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            final String utteranceId = getUtteranceId();
47468e2af55d65d2e61fbf8096eccaa2e4ca02b6c5aNarayan Kamath            if (utteranceId != null) {
475492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
4768d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            }
4778d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        }
4788d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
479492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int getCallerUid() {
480492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            return mCallerUid;
481492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        }
482492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath
483492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int getCallerPid() {
484492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            return mCallerPid;
485492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        }
486492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath
487a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        protected synchronized boolean isStopped() {
488a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath             return mStopped;
489a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath        }
490a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath
49150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected abstract int playImpl();
49250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
49350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected abstract void stopImpl();
49450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
49550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int getStreamType() {
49650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
49750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
49850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
49950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public float getVolume() {
50050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
50150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
50250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
50350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public float getPan() {
50450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
50550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
50650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
50750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String getUtteranceId() {
50850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
50950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
51050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
51150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected String getStringParam(String key, String defaultValue) {
51250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
51350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
51450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
51550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int getIntParam(String key, int defaultValue) {
51650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
51750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
51850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
51950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected float getFloatParam(String key, float defaultValue) {
52050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
52150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
52250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
52350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
5248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    class SynthesisSpeechItem extends SpeechItem {
52540f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath        // Never null.
52650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private final String mText;
527e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        private final SynthesisRequest mSynthesisRequest;
528e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        private final String[] mDefaultLocale;
529e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        // Non null after synthesis has started, and all accesses
530e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        // guarded by 'this'.
531e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        private AbstractSynthesisCallback mSynthesisCallback;
5326dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        private final EventLogger mEventLogger;
53350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
534492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
535492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, String text) {
536492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params);
53750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mText = text;
538e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            mSynthesisRequest = new SynthesisRequest(mText, mParams);
539e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            mDefaultLocale = getSettingsLocale();
540e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            setRequestParams(mSynthesisRequest);
541492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
542492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    mPackageName);
54350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
54450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
54550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String getText() {
54650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mText;
54750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
54850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
54950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
55050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
55140f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath            if (mText == null) {
55240f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath                Log.wtf(TAG, "Got null text");
55350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return false;
55450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
555a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
55650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                Log.w(TAG, "Text too long: " + mText.length() + " chars");
55750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return false;
55850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
55950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
56050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
56150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
56250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
56350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int playImpl() {
564e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            AbstractSynthesisCallback synthesisCallback;
5656dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            mEventLogger.onRequestProcessingStart();
56650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
567a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // stop() might have been called before we enter this
568a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // synchronized block.
569a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                if (isStopped()) {
570a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                    return TextToSpeech.ERROR;
571a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                }
572e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath                mSynthesisCallback = createSynthesisCallback();
573e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath                synthesisCallback = mSynthesisCallback;
57450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
575e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
576e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
57750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
57850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
579e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        protected AbstractSynthesisCallback createSynthesisCallback() {
580e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
581492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
58250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
58350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
58450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private void setRequestParams(SynthesisRequest request) {
585c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath            request.setLanguage(getLanguage(), getCountry(), getVariant());
586c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath            request.setSpeechRate(getSpeechRate());
587c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath
58850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            request.setPitch(getPitch());
58950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
59050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
59150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
59250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void stopImpl() {
593e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            AbstractSynthesisCallback synthesisCallback;
59450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            synchronized (this) {
595e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath                synthesisCallback = mSynthesisCallback;
59650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
597a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            if (synthesisCallback != null) {
598a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // If the synthesis callback is null, it implies that we haven't
599a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // entered the synchronized(this) block in playImpl which in
600a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                // turn implies that synthesis would not have started.
601a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                synthesisCallback.stop();
602a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath                TextToSpeechService.this.onStop();
603a65c62acabd204a0a1eb5504160238b288bee52bNarayan Kamath            }
60450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
60550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
60650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String getLanguage() {
607e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
60850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
60950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
61050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean hasLanguage() {
61150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
61250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
61350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
61450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private String getCountry() {
615e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (!hasLanguage()) return mDefaultLocale[1];
61650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
61750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
61850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
61950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private String getVariant() {
620e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (!hasLanguage()) return mDefaultLocale[2];
62150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getStringParam(Engine.KEY_PARAM_VARIANT, "");
62250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
62350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
62450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private int getSpeechRate() {
62550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
62650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
62750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
62850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private int getPitch() {
62950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
63050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
63150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
63250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
63350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
63450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private final File mFile;
63550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
636492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid,
637492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, String text,
63850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                File file) {
639492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params, text);
64050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mFile = file;
64150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
64250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
64350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
64450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
64550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (!super.isValid()) {
64650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return false;
64750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
64850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return checkFile(mFile);
64950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
65050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
65150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
652e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath        protected AbstractSynthesisCallback createSynthesisCallback() {
653e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamath            return new FileSynthesisCallback(mFile);
65450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
65550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
6568d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        @Override
6578d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        protected int playImpl() {
658754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            dispatchOnStart();
6598d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            int status = super.playImpl();
6608d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            if (status == TextToSpeech.SUCCESS) {
661754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                dispatchOnDone();
662754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            } else {
663754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                dispatchOnError();
6648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            }
6658d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            return status;
6668d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        }
6678d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
66850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        /**
66950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         * Checks that the given file can be used for synthesis output.
67050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert         */
67150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private boolean checkFile(File file) {
67250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            try {
67350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (file.exists()) {
67450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    Log.v(TAG, "File " + file + " exists, deleting.");
67550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    if (!file.delete()) {
67650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                        Log.e(TAG, "Failed to delete " + file);
67750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                        return false;
67850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    }
67950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
68050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (!file.createNewFile()) {
68150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    Log.e(TAG, "Can't create file " + file);
68250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    return false;
68350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
68450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (!file.delete()) {
68550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    Log.e(TAG, "Failed to delete " + file);
68650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    return false;
68750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
68850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return true;
68950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } catch (IOException e) {
69050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                Log.e(TAG, "Can't use " + file + " due to exception " + e);
69150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                return false;
69250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
69350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
69450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
69550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
69650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class AudioSpeechItem extends SpeechItem {
697af802c6831551323126537cf8edabea97d2fc762Narayan Kamath        private final AudioPlaybackQueueItem mItem;
698492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
699492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, Uri uri) {
700492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params);
701af802c6831551323126537cf8edabea97d2fc762Narayan Kamath            mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
702af802c6831551323126537cf8edabea97d2fc762Narayan Kamath                    TextToSpeechService.this, uri, getStreamType());
70350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
70450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
70550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
70650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
70750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
70850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
70950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
71050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
71150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int playImpl() {
712af802c6831551323126537cf8edabea97d2fc762Narayan Kamath            mAudioPlaybackHandler.enqueue(mItem);
7138d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            return TextToSpeech.SUCCESS;
71450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
71550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
71650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
71750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void stopImpl() {
718be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            // Do nothing.
71950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
72050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
72150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
72250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class SilenceSpeechItem extends SpeechItem {
72350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        private final long mDuration;
72450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
725492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
726492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                Bundle params, long duration) {
727492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            super(callerIdentity, callerUid, callerPid, params);
72850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            mDuration = duration;
72950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
73050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
73150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
73250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isValid() {
73350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return true;
73450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
73550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
73650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
73750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected int playImpl() {
73867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
73967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    this, getCallerIdentity(), mDuration));
7408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath            return TextToSpeech.SUCCESS;
74150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
74250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
74350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
74450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        protected void stopImpl() {
74567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // Do nothing, handled by AudioPlaybackHandler#stopForApp
74650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
74750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
74850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
74950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    @Override
75050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    public IBinder onBind(Intent intent) {
75150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
75250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mBinder;
75350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
75450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        return null;
75550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
75650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
75750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    /**
75850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
75950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     * called called from several different threads.
76050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert     */
761abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath    // NOTE: All calls that are passed in a calling app are interned so that
762abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath    // they can be used as message objects (which are tested for equality using ==).
76350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
764492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
765492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int speak(IBinder caller, String text, int queueMode, Bundle params) {
766492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, text, params)) {
767abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
768abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
769abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
770492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new SynthesisSpeechItem(caller,
771492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingUid(), Binder.getCallingPid(), params, text);
77250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(queueMode, item);
77350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
77450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
775492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
776492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int synthesizeToFile(IBinder caller, String text, String filename,
77750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                Bundle params) {
778492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, text, filename, params)) {
779abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
780abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
781abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
78250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            File file = new File(filename);
783492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(),
784492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingPid(), params, text, file);
78550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
78650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
78750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
788492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
789492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
790492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, audioUri, params)) {
791abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
792abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
793abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
794492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new AudioSpeechItem(caller,
795492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
79650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(queueMode, item);
79750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
79850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
799492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
800492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
801492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller, params)) {
802abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
803abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
804abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
805492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            SpeechItem item = new SilenceSpeechItem(caller,
806492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
80750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return mSynthHandler.enqueueSpeechItem(queueMode, item);
80850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
80950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
810492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
81150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public boolean isSpeaking() {
812c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath            return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
81350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
81450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
815492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
816492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public int stop(IBinder caller) {
817492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller)) {
818abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
819abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
820abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
821492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            return mSynthHandler.stopForApp(caller);
82250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
82350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
824492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
82550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public String[] getLanguage() {
82650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return onGetLanguage();
82750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
82850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
8297a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath        /*
8307a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * If defaults are enforced, then no language is "available" except
8317a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * perhaps the default language selected by the user.
8327a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         */
833492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
83450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int isLanguageAvailable(String lang, String country, String variant) {
835abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            if (!checkNonNull(lang)) {
836abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
837abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
838abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
83950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return onIsLanguageAvailable(lang, country, variant);
84050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
84150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
842492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
843748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath        public String[] getFeaturesForLanguage(String lang, String country, String variant) {
844748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath            Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
8456c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            String[] featuresArray = null;
8466c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            if (features != null) {
8476c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath                featuresArray = new String[features.size()];
8486c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath                features.toArray(featuresArray);
8496c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            } else {
8506c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath                featuresArray = new String[0];
8516c07a2028505e28abd601870f05d4752e92a4b7bNarayan Kamath            }
852748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath            return featuresArray;
853748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath        }
854748af66ca27d3afe2e16ccc80b147d447635292aNarayan Kamath
8557a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath        /*
8567a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * There is no point loading a non default language if defaults
8577a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         * are enforced.
8587a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath         */
859492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
86050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public int loadLanguage(String lang, String country, String variant) {
861abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            if (!checkNonNull(lang)) {
862abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return TextToSpeech.ERROR;
863abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
864abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
86550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            return onLoadLanguage(lang, country, variant);
86650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
86750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
868492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        @Override
869492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
870abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            // Note that passing in a null callback is a valid use case.
871492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            if (!checkNonNull(caller)) {
872abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                return;
873abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
874abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
875492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mCallbacks.setCallback(caller, cb);
87650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
8777a3af86dc03a45280ceca6154040577f614c571eNarayan Kamath
878abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        private String intern(String in) {
879abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            // The input parameter will be non null.
880abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            return in.intern();
881abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        }
882abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath
883abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        private boolean checkNonNull(Object... args) {
884abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            for (Object o : args) {
885abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath                if (o == null) return false;
886abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            }
887abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath            return true;
888abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath        }
88950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    };
89050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
89150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
892492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
893492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                = new HashMap<IBinder, ITextToSpeechCallback>();
89450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
895492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
896492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
89750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                ITextToSpeechCallback old;
89850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (cb != null) {
899492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    register(cb, caller);
900492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    old = mCallerToCallback.put(caller, cb);
90150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                } else {
902492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                    old = mCallerToCallback.remove(caller);
90350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
90450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                if (old != null && old != cb) {
90550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                    unregister(old);
90650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                }
90750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
90850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
90950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
910492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void dispatchOnDone(Object callerIdentity, String utteranceId) {
911492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
912754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            if (cb == null) return;
913754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            try {
914754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                cb.onDone(utteranceId);
915754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            } catch (RemoteException e) {
916754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                Log.e(TAG, "Callback onDone failed: " + e);
91750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
918754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
919754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
920492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void dispatchOnStart(Object callerIdentity, String utteranceId) {
921492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
922754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            if (cb == null) return;
923754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            try {
924754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                cb.onStart(utteranceId);
925754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            } catch (RemoteException e) {
926754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                Log.e(TAG, "Callback onStart failed: " + e);
927754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
928754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
929754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
930754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
931492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        public void dispatchOnError(Object callerIdentity, String utteranceId) {
932492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
93350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            if (cb == null) return;
93450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            try {
935754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                cb.onError(utteranceId);
93650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            } catch (RemoteException e) {
937754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath                Log.e(TAG, "Callback onError failed: " + e);
93850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
93950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
94050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
94150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
94250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
943492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            IBinder caller = (IBinder) cookie;
944492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
945492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallerToCallback.remove(caller);
94650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
947492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            mSynthHandler.stopForApp(caller);
94850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
94950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
95050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        @Override
95150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        public void kill() {
952492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
953492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                mCallerToCallback.clear();
95450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert                super.kill();
95550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert            }
95650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert        }
95750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
958492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        private ITextToSpeechCallback getCallbackFor(Object caller) {
959754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            ITextToSpeechCallback cb;
960492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            IBinder asBinder = (IBinder) caller;
961492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            synchronized (mCallerToCallback) {
962492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath                cb = mCallerToCallback.get(asBinder);
963754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            }
964754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
965754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath            return cb;
966754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath        }
967754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath
96850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert    }
96950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert
97050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert}
971