Add sample TTS engine

Bug: 4149751
Change-Id: Id8e96d4ec442db46cb97edaeab3ef7c13321ba1f
diff --git a/samples/TtsEngine/Android.mk b/samples/TtsEngine/Android.mk
new file mode 100644
index 0000000..30569ac
--- /dev/null
+++ b/samples/TtsEngine/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := TtsEngine
+
+include $(BUILD_PACKAGE)
+
diff --git a/samples/TtsEngine/AndroidManifest.xml b/samples/TtsEngine/AndroidManifest.xml
new file mode 100644
index 0000000..268ac43
--- /dev/null
+++ b/samples/TtsEngine/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.ttsengine">
+
+    <!-- TODO: Fix this when the API level for ICS is finalized. -->
+    <uses-sdk android:minSdkVersion="IceCreamSandwich"
+        android:targetSdkVersion="IceCreamSandwich" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:label="@string/app_name">
+        <service android:name=".RobotSpeakTtsService"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.TTS_SERVICE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <!-- Note that this meta data element must be inside the service
+                 definition of the service that handles the TTS_SERVICE intent
+                 -->
+            <meta-data android:name="android.speech.tts" android:resource="@xml/tts_engine" />
+        </service>
+
+        <activity android:name=".CheckVoiceData"
+                android:theme="@android:style/Theme.NoDisplay">
+            <intent-filter>
+                <action android:name="android.speech.tts.engine.CHECK_TTS_DATA" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".RobotSpeakSettings"
+                android:label="@string/general_settings"  />
+
+    </application>
+
+</manifest>
+
diff --git a/samples/TtsEngine/_index.html b/samples/TtsEngine/_index.html
new file mode 100644
index 0000000..07a54a9
--- /dev/null
+++ b/samples/TtsEngine/_index.html
@@ -0,0 +1,30 @@
+<p>This sample demonstrates how to create a text to speech engine
+that users can install on their devices.</p>
+
+<p>The application includes a service and two activities:</p>
+<ul>
+  <li><a
+    href="src/com/example/android/ttsengine/RobotSpeakEngine.html"><code>RobotSpeakEngine</code></a>,
+    a simple text to speech engine that converts sentences into audio by
+    generating a square wave of a given frequency for each alphabet of a
+    given language. Though this doesn't qualify as speech (except for robots)
+    it exercises all aspects of the new text to speech API
+    by subclassing the
+    <a href="../../../reference/android/speech/tts/TextToSpeechService.html"><code>TextToSpeechService</code></a>
+    framework class.
+  </li>
+  <li><a
+    href="src/com/example/android/ttsengine/CheckVoiceData.html"><code>CheckVoiceData</code></a>,
+  an activity that checks that all voice related data is installed and
+  available.</li>
+  <li><a
+    href="src/com/example/android/ttsengine/RobotSpeakSettings.html"><code>RobotSpeakSettings</code></a>,
+    a settings screen for users to set various engine parameters. This is
+    usually accessed by users from the system wide settings app. This must be
+    declared in the <code>AndroidManifest.xml</code> file as a
+    <code>meta-data</code> element.</li>
+
+<!-- TODO: Fix this when the API level for ICS is finalized -->
+<p>Note that this API is supported only on Android 4.0 (API level 13)
+and higher versions of the platform.</p>
+
diff --git a/samples/TtsEngine/assets/eng-GBR.freq b/samples/TtsEngine/assets/eng-GBR.freq
new file mode 100644
index 0000000..61382ac
--- /dev/null
+++ b/samples/TtsEngine/assets/eng-GBR.freq
@@ -0,0 +1,26 @@
+z:2
+y:4
+x:8
+w:16
+v:32
+u:64
+t:128
+s:160
+r:320
+q:400
+p:640
+o:800
+n:1600
+m:3200
+l:4000
+k:8000
+j:20
+i:40
+h:80
+g:125
+f:250
+e:500
+d:1000
+c:2000
+b:5
+a:10
diff --git a/samples/TtsEngine/assets/eng-USA.freq b/samples/TtsEngine/assets/eng-USA.freq
new file mode 100644
index 0000000..9dfc147
--- /dev/null
+++ b/samples/TtsEngine/assets/eng-USA.freq
@@ -0,0 +1,26 @@
+a:2
+b:4
+c:8
+d:16
+e:32
+f:64
+g:128
+h:160
+i:320
+j:400
+k:640
+l:800
+m:1600
+n:3200
+o:4000
+p:8000
+q:20
+r:40
+s:80
+t:125
+u:250
+v:500
+w:1000
+x:2000
+y:5
+z:10
diff --git a/samples/TtsEngine/res/values/strings.xml b/samples/TtsEngine/res/values/strings.xml
new file mode 100644
index 0000000..70ad3fb
--- /dev/null
+++ b/samples/TtsEngine/res/values/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+
+    <!-- The name of the application. -->
+    <string name="app_name">Example TTS Engine</string>
+    <!-- The heading of the first (and only) preference fragment -->
+    <string name="general_settings">General settings</string>
+
+    <string name="whisper_title">Whisper utterances</string>
+    <string name="whisper_summary">Say stuff really softly, only the keenest
+      eared robots can understand.</string>
+
+</resources>
diff --git a/samples/TtsEngine/res/xml/general_settings.xml b/samples/TtsEngine/res/xml/general_settings.xml
new file mode 100644
index 0000000..1cd6fd9
--- /dev/null
+++ b/samples/TtsEngine/res/xml/general_settings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <CheckboxPreference
+        android:key="robot_speak_whisper"
+        android:title="@string/whisper_title"
+        android:summary="@string/whisper_summary"
+        android:defaultValue="true"
+        android:persistent="true"
+        />
+
+</PreferenceScreen>
diff --git a/samples/TtsEngine/res/xml/preferences_headers.xml b/samples/TtsEngine/res/xml/preferences_headers.xml
new file mode 100644
index 0000000..53d0047
--- /dev/null
+++ b/samples/TtsEngine/res/xml/preferences_headers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header android:fragment="com.example.android.ttsengine.GeneralSettingsFragment"
+        android:title="@string/general_settings"
+    />
+</preference-headers>
diff --git a/samples/TtsEngine/res/xml/tts_engine.xml b/samples/TtsEngine/res/xml/tts_engine.xml
new file mode 100644
index 0000000..fb6a823
--- /dev/null
+++ b/samples/TtsEngine/res/xml/tts_engine.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<tts-engine xmlns:android="http://schemas.android.com/apk/res/android"
+  android:settingsActivity="com.example.android.ttsengine.RobotSpeakSettings" />
diff --git a/samples/TtsEngine/src/com/example/android/ttsengine/CheckVoiceData.java b/samples/TtsEngine/src/com/example/android/ttsengine/CheckVoiceData.java
new file mode 100755
index 0000000..954dc80
--- /dev/null
+++ b/samples/TtsEngine/src/com/example/android/ttsengine/CheckVoiceData.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.ttsengine;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/*
+ * Checks if the voice data is present.
+ */
+public class CheckVoiceData extends Activity {
+    private static final String TAG = "CheckVoiceData";
+
+    private static final String[] SUPPORTED_LANGUAGES = { "eng-GBR", "eng-USA" };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        List<String> checkLanguages = getCheckVoiceDataFor(intent);
+
+        // If the call didn't specify which languages to check, check
+        // for all the supported ones.
+        if (checkLanguages.isEmpty()) {
+            checkLanguages = Arrays.asList(SUPPORTED_LANGUAGES);
+        }
+
+        ArrayList<String> available = new ArrayList<String>();
+        ArrayList<String> unavailable = new ArrayList<String>();
+
+        for (String lang : checkLanguages) {
+            // This check is required because checkLanguages might contain
+            // an arbitrary list of languages if the intent specified them
+            // {@link #getCheckVoiceDataFor}.
+            if (isLanguageSupported(lang)) {
+                if (isDataInstalled(lang)) {
+                    available.add(lang);
+                } else {
+                    unavailable.add(lang);
+                }
+            }
+        }
+
+        int result;
+        if (!checkLanguages.isEmpty() && available.isEmpty()) {
+            // No voices available at all.
+            result = TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL;
+        } else if (!unavailable.isEmpty()) {
+            // Some voices are available, but some have missing
+            // data.
+            result = TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA;
+        } else {
+            // All voices are available.
+            result = TextToSpeech.Engine.CHECK_VOICE_DATA_PASS;
+        }
+
+        // We now return the list of available and unavailable voices
+        // as well as the return code.
+        Intent returnData = new Intent();
+        returnData.putStringArrayListExtra(
+                TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES, available);
+        returnData.putStringArrayListExtra(
+                TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES, unavailable);
+        setResult(result, returnData);
+        finish();
+    }
+
+    /**
+     * The intent that launches this activity can contain an intent extra
+     * {@link TextToSpeech.Engine.EXTRA_CHECK_VOICE_DATA_FOR} that might specify
+     * a given language to check voice data for. If the intent does not contain
+     * this extra, we assume that a voice check for all supported languages
+     * was requested.
+     */
+    private List<String> getCheckVoiceDataFor(Intent intent) {
+        ArrayList<String> list = intent.getStringArrayListExtra(
+                TextToSpeech.Engine.EXTRA_CHECK_VOICE_DATA_FOR);
+        ArrayList<String> ret = new ArrayList<String>();
+        if (list != null) {
+            for (String lang : list) {
+                if (!TextUtils.isEmpty(lang)) {
+                    ret.add(lang);
+                }
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Checks whether a given language is in the list of supported languages.
+     */
+    private boolean isLanguageSupported(String input) {
+        for (String lang : SUPPORTED_LANGUAGES) {
+            if (lang.equals(input)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /*
+     * Note that in our example, all data is packaged in our APK as
+     * assets (it could be a raw resource as well). This check is unnecessary
+     * because it will always succeed.
+     *
+     * If for example, engine data was downloaded or installed on external storage,
+     * this check would make much more sense.
+     */
+    private boolean isDataInstalled(String lang) {
+        try {
+            InputStream is = getAssets().open(lang + ".freq");
+
+            if (is != null) {
+                is.close();
+            } else {
+                return false;
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to find data for: " + lang + ", exception: " + e);
+            return false;
+        }
+
+        // The asset InputStream was non null, and therefore this
+        // data file is available.
+        return true;
+    }
+}
diff --git a/samples/TtsEngine/src/com/example/android/ttsengine/GeneralSettingsFragment.java b/samples/TtsEngine/src/com/example/android/ttsengine/GeneralSettingsFragment.java
new file mode 100644
index 0000000..ca7353b
--- /dev/null
+++ b/samples/TtsEngine/src/com/example/android/ttsengine/GeneralSettingsFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.ttsengine;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+public class GeneralSettingsFragment extends PreferenceFragment {
+    static final String SHARED_PREFS_NAME = "RobotSpeakSettings";
+    static final String WHISPER_KEY = "robot_speak_whisper";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getPreferenceManager().setSharedPreferencesName(SHARED_PREFS_NAME);
+        addPreferencesFromResource(R.xml.general_settings);
+    }
+}
diff --git a/samples/TtsEngine/src/com/example/android/ttsengine/RobotSpeakSettings.java b/samples/TtsEngine/src/com/example/android/ttsengine/RobotSpeakSettings.java
new file mode 100644
index 0000000..d4713d2
--- /dev/null
+++ b/samples/TtsEngine/src/com/example/android/ttsengine/RobotSpeakSettings.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.ttsengine;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+import java.util.List;
+
+/*
+ * This class is referenced via a meta data section in the manifest.
+ * A settings screen is optional, and if a given engine has no settings,
+ * there is no need to implement such a class.
+ */
+public class RobotSpeakSettings extends PreferenceActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preferences_headers, target);
+    }
+}
diff --git a/samples/TtsEngine/src/com/example/android/ttsengine/RobotSpeakTtsService.java b/samples/TtsEngine/src/com/example/android/ttsengine/RobotSpeakTtsService.java
new file mode 100644
index 0000000..81f9f2b
--- /dev/null
+++ b/samples/TtsEngine/src/com/example/android/ttsengine/RobotSpeakTtsService.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.ttsengine;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.AudioFormat;
+import android.speech.tts.SynthesisCallback;
+import android.speech.tts.SynthesisRequest;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeechService;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A text to speech engine that generates "speech" that a robot might understand.
+ * The engine supports two different "languages", each with their own frequency
+ * mappings.
+ *
+ * It exercises all aspects of the Text to speech engine API
+ * {@link android.speech.tts.TextToSpeechService}.
+ */
+public class RobotSpeakTtsService extends TextToSpeechService {
+    private static final String TAG = "ExampleTtsService";
+
+    /*
+     * This is the sampling rate of our output audio. This engine outputs
+     * audio at 16khz 16bits per sample PCM audio.
+     */
+    private static final int SAMPLING_RATE_HZ = 16000;
+
+    /*
+     * We multiply by a factor of two since each sample contains 16 bits (2 bytes).
+     */
+    private final byte[] mAudioBuffer = new byte[SAMPLING_RATE_HZ * 2];
+
+    private Map<Character, Integer> mFrequenciesMap;
+    private volatile String[] mCurrentLanguage = null;
+    private volatile boolean mStopRequested = false;
+    private SharedPreferences mSharedPrefs = null;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mSharedPrefs = getSharedPreferences(GeneralSettingsFragment.SHARED_PREFS_NAME,
+                Context.MODE_PRIVATE);
+        // We load the default language when we start up. This isn't strictly
+        // required though, it can always be loaded lazily on the first call to
+        // onLoadLanguage or onSynthesizeText. This a tradeoff between memory usage
+        // and the latency of the first call.
+        onLoadLanguage("eng", "usa", "");
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    protected String[] onGetLanguage() {
+        // Note that mCurrentLanguage is volatile because this can be called from
+        // multiple threads.
+        return mCurrentLanguage;
+    }
+
+    @Override
+    protected int onIsLanguageAvailable(String lang, String country, String variant) {
+        // The robot speak synthesizer supports only english.
+        if ("eng".equals(lang)) {
+            // We support two specific robot languages, the british robot language
+            // and the american robot language.
+            if ("USA".equals(country) || "GBR".equals(country)) {
+                // If the engine supported a specific variant, we would have
+                // something like.
+                //
+                // if ("android".equals(variant)) {
+                //     return TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
+                // }
+                return TextToSpeech.LANG_COUNTRY_AVAILABLE;
+            }
+
+            // We support the language, but not the country.
+            return TextToSpeech.LANG_AVAILABLE;
+        }
+
+        return TextToSpeech.LANG_NOT_SUPPORTED;
+    }
+
+    /*
+     * Note that this method is synchronized, as is onSynthesizeText because
+     * onLoadLanguage can be called from multiple threads (while onSynthesizeText
+     * is always called from a single thread only).
+     */
+    @Override
+    protected synchronized int onLoadLanguage(String lang, String country, String variant) {
+        final int isLanguageAvailable = onIsLanguageAvailable(lang, country, variant);
+
+        if (isLanguageAvailable == TextToSpeech.LANG_NOT_SUPPORTED) {
+            return isLanguageAvailable;
+        }
+
+        String loadCountry = country;
+        if (isLanguageAvailable == TextToSpeech.LANG_AVAILABLE) {
+            loadCountry = "USA";
+        }
+
+        // If we've already loaded the requested language, we can return early.
+        if (mCurrentLanguage != null) {
+            if (mCurrentLanguage[0].equals(lang) && mCurrentLanguage[1].equals(country)) {
+                return isLanguageAvailable;
+            }
+        }
+
+        Map<Character, Integer> newFrequenciesMap = null;
+        try {
+            InputStream file = getAssets().open(lang + "-" + loadCountry + ".freq");
+            newFrequenciesMap = buildFrequencyMap(file);
+            file.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Error loading data for : " + lang + "-" + country);
+        }
+
+        mFrequenciesMap = newFrequenciesMap;
+        mCurrentLanguage = new String[] { lang, loadCountry, ""};
+
+        return isLanguageAvailable;
+    }
+
+    @Override
+    protected void onStop() {
+        mStopRequested = true;
+    }
+
+    @Override
+    protected synchronized void onSynthesizeText(SynthesisRequest request,
+            SynthesisCallback callback) {
+        // Note that we call onLoadLanguage here since there is no guarantee
+        // that there would have been a prior call to this function.
+        int load = onLoadLanguage(request.getLanguage(), request.getCountry(),
+                request.getVariant());
+
+        // We might get requests for a language we don't support - in which case
+        // we error out early before wasting too much time.
+        if (load == TextToSpeech.LANG_NOT_SUPPORTED) {
+            callback.error();
+            return;
+        }
+
+        // At this point, we have loaded the language we need for synthesis and
+        // it is guaranteed that we support it so we proceed with synthesis.
+
+        // We denote that we are ready to start sending audio across to the
+        // framework. We use a fixed sampling rate (16khz), and send data across
+        // in 16bit PCM mono.
+        callback.start(SAMPLING_RATE_HZ,
+                AudioFormat.ENCODING_PCM_16BIT, 1 /* Number of channels. */);
+
+        // We then scan through each character of the request string and
+        // generate audio for it.
+        final String text = request.getText().toLowerCase();
+        for (int i = 0; i < text.length(); ++i) {
+            char value = normalize(text.charAt(i));
+            // It is crucial to call either of callback.error() or callback.done() to ensure
+            // that audio / other resources are released as soon as possible.
+            if (!generateOneSecondOfAudio(value, callback)) {
+                callback.error();
+                return;
+            }
+        }
+
+        // Alright, we're done with our synthesis - yay!
+        callback.done();
+    }
+
+    /*
+     * Normalizes a given character to the range 'a' - 'z' (inclusive). Our
+     * frequency mappings contain frequencies for each of these characters.
+     */
+    private static char normalize(char input) {
+        if (input == ' ') {
+            return input;
+        }
+
+        if (input < 'a') {
+            return 'a';
+        }
+        if (input > 'z') {
+            return 'z';
+        }
+
+        return input;
+    }
+
+    private Map<Character, Integer> buildFrequencyMap(InputStream is) throws IOException {
+        BufferedReader br = new BufferedReader(new InputStreamReader(is));
+        String line = null;
+        Map<Character, Integer> map = new HashMap<Character, Integer>();
+        try {
+            while ((line = br.readLine()) != null) {
+                String[] parts = line.split(":");
+                if (parts.length != 2) {
+                    throw new IOException("Invalid line encountered: " + line);
+                }
+                map.put(parts[0].charAt(0), Integer.parseInt(parts[1]));
+            }
+            map.put(' ', 0);
+            return map;
+        } finally {
+            is.close();
+        }
+    }
+
+    private boolean generateOneSecondOfAudio(char alphabet, SynthesisCallback cb) {
+        ByteBuffer buffer = ByteBuffer.wrap(mAudioBuffer).order(ByteOrder.LITTLE_ENDIAN);
+
+        // Someone called onStop, end the current synthesis and return.
+        // The mStopRequested variable will be reset at the beginning of the
+        // next synthesis.
+        //
+        // In general, a call to onStop( ) should make a best effort attempt
+        // to stop all processing for the *current* onSynthesizeText request (if
+        // one is active).
+        if (mStopRequested) {
+            return false;
+        }
+
+
+        if (mFrequenciesMap == null || !mFrequenciesMap.containsKey(alphabet)) {
+            return false;
+        }
+
+        final int frequency = mFrequenciesMap.get(alphabet);
+
+        if (frequency > 0) {
+            // This is the wavelength in samples. The frequency is chosen so that the
+            // waveLength is always a multiple of two and frequency divides the
+            // SAMPLING_RATE exactly.
+            final int waveLength = SAMPLING_RATE_HZ / frequency;
+            final int times = SAMPLING_RATE_HZ / waveLength;
+
+            for (int j = 0; j < times; ++j) {
+                // For a square curve, half of the values will be at Short.MIN_VALUE
+                // and the other half will be Short.MAX_VALUE.
+                for (int i = 0; i < waveLength / 2; ++i) {
+                    buffer.putShort((short)(getAmplitude() * -1));
+                }
+                for (int i = 0; i < waveLength / 2; ++i) {
+                    buffer.putShort(getAmplitude());
+                }
+            }
+        } else {
+            // Play a second of silence.
+            for (int i = 0; i < mAudioBuffer.length / 2; ++i) {
+                buffer.putShort((short) 0);
+            }
+        }
+
+        // Get the maximum allowed size of data we can send across in audioAvailable.
+        final int maxBufferSize = cb.getMaxBufferSize();
+        int offset = 0;
+        while (offset < mAudioBuffer.length) {
+            int bytesToWrite = Math.min(maxBufferSize, mAudioBuffer.length - offset);
+            cb.audioAvailable(mAudioBuffer, offset, bytesToWrite);
+            offset += bytesToWrite;
+        }
+        return true;
+    }
+
+    private short getAmplitude() {
+        boolean whisper = mSharedPrefs.getBoolean(GeneralSettingsFragment.WHISPER_KEY, false);
+        return (short) (whisper ? 2048 : 8192);
+    }
+}