diff --git a/android/eSpeakTests/.classpath b/android/eSpeakTests/.classpath
new file mode 100644
index 00000000..8c0d9401
--- /dev/null
+++ b/android/eSpeakTests/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/android/eSpeakTests/.project b/android/eSpeakTests/.project
new file mode 100644
index 00000000..a3134b73
--- /dev/null
+++ b/android/eSpeakTests/.project
@@ -0,0 +1,34 @@
+
+
+ eSpeakTests
+
+
+ eSpeakActivity
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/android/eSpeakTests/AndroidManifest.xml b/android/eSpeakTests/AndroidManifest.xml
new file mode 100644
index 00000000..b4a65001
--- /dev/null
+++ b/android/eSpeakTests/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/eSpeakTests/proguard-project.txt b/android/eSpeakTests/proguard-project.txt
new file mode 100644
index 00000000..f2fe1559
--- /dev/null
+++ b/android/eSpeakTests/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/android/eSpeakTests/project.properties b/android/eSpeakTests/project.properties
new file mode 100644
index 00000000..a3ee5ab6
--- /dev/null
+++ b/android/eSpeakTests/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
diff --git a/android/eSpeakTests/res/drawable-hdpi/ic_launcher.png b/android/eSpeakTests/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 00000000..96a442e5
Binary files /dev/null and b/android/eSpeakTests/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android/eSpeakTests/res/drawable-ldpi/ic_launcher.png b/android/eSpeakTests/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 00000000..99238729
Binary files /dev/null and b/android/eSpeakTests/res/drawable-ldpi/ic_launcher.png differ
diff --git a/android/eSpeakTests/res/drawable-mdpi/ic_launcher.png b/android/eSpeakTests/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 00000000..359047df
Binary files /dev/null and b/android/eSpeakTests/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android/eSpeakTests/res/drawable-xhdpi/ic_launcher.png b/android/eSpeakTests/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..71c6d760
Binary files /dev/null and b/android/eSpeakTests/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/android/eSpeakTests/res/values/strings.xml b/android/eSpeakTests/res/values/strings.xml
new file mode 100644
index 00000000..2f2fb5b3
--- /dev/null
+++ b/android/eSpeakTests/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ ESpeakTestsTest
+
+
\ No newline at end of file
diff --git a/android/eSpeakTests/src/com/reecedunn/espeak/test/SpeechSynthesisTest.java b/android/eSpeakTests/src/com/reecedunn/espeak/test/SpeechSynthesisTest.java
new file mode 100644
index 00000000..91bb59e6
--- /dev/null
+++ b/android/eSpeakTests/src/com/reecedunn/espeak/test/SpeechSynthesisTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2012 Reece H. Dunn
+ *
+ * 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.reecedunn.espeak.test;
+
+import java.util.List;
+
+import com.reecedunn.espeak.SpeechSynthesis;
+import com.reecedunn.espeak.SpeechSynthesis.Voice;
+
+import android.media.AudioFormat;
+import android.test.AndroidTestCase;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+public class SpeechSynthesisTest extends AndroidTestCase
+{
+ private SpeechSynthesis.SynthReadyCallback mCallback = new SpeechSynthesis.SynthReadyCallback()
+ {
+ @Override
+ public void onSynthDataReady(byte[] audioData)
+ {
+ }
+
+ @Override
+ public void onSynthDataComplete()
+ {
+ }
+ };
+
+ private List mVoices = null;
+
+ public List getVoices()
+ {
+ if (mVoices == null)
+ {
+ SpeechSynthesis synth = new SpeechSynthesis(getContext(), mCallback);
+ mVoices = synth.getAvailableVoices();
+ assertThat(mVoices, is(notNullValue()));
+ assertThat(mVoices.size(), is(78));
+ }
+ return mVoices;
+ }
+
+ public Voice getVoice(String name)
+ {
+ for (Voice voice : getVoices())
+ {
+ if (voice.name.equals(name))
+ {
+ return voice;
+ }
+ }
+ return null;
+ }
+
+ public void checkVoice(String name, String identifier, String language, String iso3Language, String country, String iso3Country, String variant, int gender)
+ {
+ Voice voice = getVoice(name);
+ assertThat(voice, is(notNullValue()));
+ assertThat(voice.name, is(name));
+ assertThat(voice.identifier, is(identifier));
+ assertThat(voice.age, is(0));
+ assertThat(voice.gender, is(gender));
+ assertThat(voice.locale.getLanguage(), is(language));
+ assertThat(voice.locale.getISO3Language(), is(iso3Language));
+ assertThat(voice.locale.getCountry(), is(country));
+ assertThat(voice.locale.getISO3Country(), is(iso3Country));
+ assertThat(voice.locale.getVariant(), is(variant));
+ }
+
+ public void testConstruction()
+ {
+ SpeechSynthesis synth = new SpeechSynthesis(getContext(), mCallback);
+ assertThat(synth.getSampleRate(), is(22050));
+ assertThat(synth.getChannelCount(), is(1));
+ assertThat(synth.getAudioFormat(), is(AudioFormat.ENCODING_PCM_16BIT));
+ assertThat(synth.getBufferSizeInBytes(), is(22050));
+ }
+
+ public void testAvailableVoices()
+ {
+ // : : : language : country : :
+ // : name : identifier : 639-1 : 639-2/T : 2 : 3 : variant : gender
+ checkVoice("af", "af", "af", "afr", "", "", "", SpeechSynthesis.GENDER_MALE); // Afrikaans
+ checkVoice("ak", "test/ak", "ak", "aka", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Akan
+ checkVoice("am", "test/am", "am", "amh", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Amharic
+ checkVoice("az", "test/az", "az", "aze", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Azerbaijani
+ checkVoice("bg", "test/bg", "bg", "bul", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Bulgarian
+ checkVoice("bs", "bs", "bs", "bos", "", "", "", SpeechSynthesis.GENDER_MALE); // Bosnian
+ checkVoice("ca", "ca", "ca", "cat", "", "", "", SpeechSynthesis.GENDER_MALE); // Catalan
+ checkVoice("cs", "cs", "cs", "ces", "", "", "", SpeechSynthesis.GENDER_MALE); // Czech
+ checkVoice("cy", "cy", "cy", "cym", "", "", "", SpeechSynthesis.GENDER_MALE); // Welsh
+ checkVoice("da", "da", "da", "dan", "", "", "", SpeechSynthesis.GENDER_MALE); // Danish
+ checkVoice("de", "de", "de", "deu", "", "", "", SpeechSynthesis.GENDER_MALE); // German
+ checkVoice("dv", "test/dv", "dv", "div", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Divehi
+ checkVoice("el", "el", "el", "ell", "", "", "", SpeechSynthesis.GENDER_MALE); // Greek (Modern)
+ checkVoice("en", "default", "en", "eng", "", "", "", SpeechSynthesis.GENDER_MALE); // German
+ checkVoice("en-sc", "en/en-sc", "en", "eng", "GB", "GBR", "scotland", SpeechSynthesis.GENDER_MALE); // English (Scotland)
+ checkVoice("en-uk", "en/en", "en", "eng", "GB", "GBR", "", SpeechSynthesis.GENDER_MALE); // English (UK)
+ checkVoice("en-uk-north", "en/en-n", "en", "eng", "GB", "GBR", "north", SpeechSynthesis.GENDER_MALE); // English (Lancashire)
+ checkVoice("en-uk-rp", "en/en-rp", "en", "eng", "GB", "GBR", "rp", SpeechSynthesis.GENDER_MALE); // English (Received Pronunciation)
+ checkVoice("en-uk-wmids", "en/en-wm", "en", "eng", "GB", "GBR", "wmids", SpeechSynthesis.GENDER_MALE); // English (West Midlands)
+ checkVoice("en-us", "en/en-us", "en", "eng", "US", "USA", "", SpeechSynthesis.GENDER_MALE); // English (US)
+ checkVoice("en-wi", "en/en-wi", "en", "eng", "029", "", "", SpeechSynthesis.GENDER_MALE); // English (Caribbean)
+ checkVoice("eo", "eo", "eo", "epo", "", "", "", SpeechSynthesis.GENDER_MALE); // Esperanto
+ checkVoice("es-la", "es-la", "es", "spa", "419", "", "", SpeechSynthesis.GENDER_MALE); // Spanish (Latin America & Caribbean)
+ checkVoice("et", "et", "et", "est", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Estonian
+ checkVoice("fi", "fi", "fi", "fin", "", "", "", SpeechSynthesis.GENDER_MALE); // Finnish
+ checkVoice("fr-fr", "fr", "fr", "fra", "FR", "FRA", "", SpeechSynthesis.GENDER_MALE); // French (France)
+ checkVoice("ga", "test/ga", "ga", "gle", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Irish
+ checkVoice("grc", "test/grc", "grc", "grc", "", "", "", SpeechSynthesis.GENDER_MALE); // Greek (Ancient)
+ checkVoice("hi", "hi", "hi", "hin", "", "", "", SpeechSynthesis.GENDER_MALE); // Hindi
+ checkVoice("hr", "hr", "hr", "hrv", "", "", "", SpeechSynthesis.GENDER_MALE); // Croatian
+ checkVoice("ht", "test/ht", "ht", "hat", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Haitian Creole
+ checkVoice("hu", "hu", "hu", "hun", "", "", "", SpeechSynthesis.GENDER_MALE); // Hungarian
+ checkVoice("hy", "hy", "hy", "hye", "", "", "", SpeechSynthesis.GENDER_MALE); // Armenian
+ checkVoice("hy-west", "hy-west", "hy", "hye", "", "", "arevmda", SpeechSynthesis.GENDER_MALE); // Armenian (West)
+ checkVoice("id", "id", "in", "ind", "", "", "", SpeechSynthesis.GENDER_MALE); // Indonesia
+ checkVoice("is", "is", "is", "isl", "", "", "", SpeechSynthesis.GENDER_MALE); // Icelandic
+ checkVoice("it", "it", "it", "ita", "", "", "", SpeechSynthesis.GENDER_MALE); // Italian
+ checkVoice("jbo", "test/jbo", "jbo", "jbo", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Lojban
+ checkVoice("ka", "ka", "ka", "kat", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Georgian
+ checkVoice("kk", "test/kk", "kk", "kaz", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Kazakh
+ checkVoice("kl", "test/kl", "kl", "kal", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Greenlandic
+ checkVoice("kn", "kn", "kn", "kan", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Kannada
+ checkVoice("ko", "test/ko", "ko", "kor", "", "", "", SpeechSynthesis.GENDER_MALE); // Korean
+ checkVoice("ku", "ku", "ku", "kur", "", "", "", SpeechSynthesis.GENDER_MALE); // Kurdish
+ checkVoice("la", "la", "la", "lat", "", "", "", SpeechSynthesis.GENDER_MALE); // Latin
+ checkVoice("lt", "test/lt", "lt", "lit", "", "", "", SpeechSynthesis.GENDER_MALE); // Lithuanian
+ checkVoice("lv", "lv", "lv", "lav", "", "", "", SpeechSynthesis.GENDER_MALE); // Latvian
+ checkVoice("ml", "ml", "ml", "mal", "", "", "", SpeechSynthesis.GENDER_MALE); // Malayalam
+ checkVoice("mt", "test/mt", "mt", "mlt", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Maltese
+ checkVoice("nci", "test/nci", "nci", "", "", "", "", SpeechSynthesis.GENDER_MALE); // Classical Nahuatl
+ checkVoice("ne", "test/ne", "ne", "nep", "", "", "", SpeechSynthesis.GENDER_MALE); // Nepali
+ checkVoice("nl", "nl", "nl", "nld", "", "", "", SpeechSynthesis.GENDER_MALE); // Dutch
+ checkVoice("no", "no", "no", "nor", "", "", "", SpeechSynthesis.GENDER_MALE); // Norwegian
+ checkVoice("nso", "test/nso", "nso", "nso", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Sotho (Northern)
+ checkVoice("pa", "test/pa", "pa", "pan", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Panjabi
+ checkVoice("pap", "test/pap", "pap", "pap", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Papiamento
+ checkVoice("prs", "test/prs", "prs", "", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Dari (Afghan Persian)
+ checkVoice("pt-br", "pt", "pt", "por", "BR", "BRA", "", SpeechSynthesis.GENDER_MALE); // Portuguese (Brazil)
+ checkVoice("pt-pt", "pt-pt", "pt", "por", "PT", "PRT", "", SpeechSynthesis.GENDER_MALE); // Portuguese (Portugal)
+ checkVoice("ro", "ro", "ro", "ron", "", "", "", SpeechSynthesis.GENDER_MALE); // Romanian
+ checkVoice("ru", "ru", "ru", "rus", "", "", "", SpeechSynthesis.GENDER_MALE); // Russian
+ checkVoice("rw", "test/rw", "rw", "kin", "", "", "", SpeechSynthesis.GENDER_MALE); // Kinyarwanda
+ checkVoice("si", "test/si", "si", "sin", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Sinhalese
+ checkVoice("sk", "sk", "sk", "slk", "", "", "", SpeechSynthesis.GENDER_MALE); // Slovak
+ checkVoice("sl", "test/sl", "sl", "slv", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Slovenian
+ checkVoice("sq", "sq", "sq", "sqi", "", "", "", SpeechSynthesis.GENDER_MALE); // Albanian
+ checkVoice("sr", "sr", "sr", "srp", "", "", "", SpeechSynthesis.GENDER_MALE); // Serbian
+ checkVoice("sv", "sv", "sv", "swe", "", "", "", SpeechSynthesis.GENDER_MALE); // Swedish
+ checkVoice("ta", "ta", "ta", "tam", "", "", "", SpeechSynthesis.GENDER_MALE); // Tamil
+ checkVoice("te", "test/te", "te", "tel", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Telugu
+ checkVoice("tn", "test/tn", "tn", "tsn", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Setswana
+ checkVoice("tr", "tr", "tr", "tur", "", "", "", SpeechSynthesis.GENDER_MALE); // Turkish
+ checkVoice("tt", "test/tt", "tt", "tat", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Tatar
+ checkVoice("ur", "test/ur", "ur", "urd", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Urdu
+ checkVoice("vi", "vi", "vi", "vie", "", "", "", SpeechSynthesis.GENDER_MALE); // Vietnamese
+ checkVoice("wo", "test/wo", "wo", "wol", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Wolof
+ checkVoice("zh", "zh", "zh", "zho", "", "", "", SpeechSynthesis.GENDER_MALE); // Chinese (Mandarin)
+ checkVoice("zh-yue", "zh-yue", "yue", "", "", "", "", SpeechSynthesis.GENDER_MALE); // Chinese (Cantonese)
+ }
+}
diff --git a/android/src/com/reecedunn/espeak/SpeechSynthesis.java b/android/src/com/reecedunn/espeak/SpeechSynthesis.java
index ced93dc7..3cd8d042 100644
--- a/android/src/com/reecedunn/espeak/SpeechSynthesis.java
+++ b/android/src/com/reecedunn/espeak/SpeechSynthesis.java
@@ -35,6 +35,7 @@ import java.util.Locale;
public class SpeechSynthesis {
private static final String TAG = SpeechSynthesis.class.getSimpleName();
+ public static final int GENDER_UNSPECIFIED = 0;
public static final int GENDER_MALE = 1;
public static final int GENDER_FEMALE = 2;
@@ -206,7 +207,45 @@ public class SpeechSynthesis {
this.gender = gender;
this.age = age;
- locale = new Locale(name);
+ if (name.equals("en-sc")) {
+ // 'SC' is not a country code.
+ locale = new Locale("en", "GB", "scotland");
+ } else if (name.equals("en-wi")) {
+ // 'WI' is not a country code.
+ locale = new Locale("en", "029");
+ } else if (name.equals("es-la")) {
+ // 'LA' is the country code for Laos, not Latin America.
+ locale = new Locale("es", "419");
+ } else if (name.equals("hy-west")) {
+ // 'west' is not a country code.
+ locale = new Locale("hy", "", "arevmda");
+ } else if (name.equals("zh-yue")) {
+ // Android/Java does not support macrolanguages.
+ locale = new Locale("yue");
+ } else {
+ String[] parts = name.split("-");
+ switch (parts.length) {
+ case 1: // language
+ locale = new Locale(parts[0]);
+ break;
+ case 2: // language-country
+ if (parts[1].equals("uk")) {
+ // 'uk' is the language code for Ukranian, not Great Britain.
+ parts[1] = "GB";
+ }
+ locale = new Locale(parts[0], parts[1]);
+ break;
+ case 3: // language-country-variant
+ if (parts[1].equals("uk")) {
+ // 'uk' is the language code for Ukranian, not Great Britain.
+ parts[1] = "GB";
+ }
+ locale = new Locale(parts[0], parts[1], parts[2]);
+ break;
+ default:
+ locale = null;
+ }
+ }
}
/**