mirror of
https://github.com/rhasspy/espeak-ng.git
synced 2026-11-01 21:58:52 +00:00
Move the android sources to android/... to avoid polluting the APK when it is being built through eclipse
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.Engine;
|
||||
import android.util.Log;
|
||||
|
||||
import com.googlecode.eyesfree.espeak.SpeechSynthesis.SynthReadyCallback;
|
||||
import com.googlecode.eyesfree.espeak.SpeechSynthesis.Voice;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class CheckVoiceData extends Activity {
|
||||
private static final String TAG = "eSpeakTTS";
|
||||
|
||||
private static final int REQUEST_DOWNLOAD = 1;
|
||||
|
||||
/** Resources required for eSpeak to run correctly. */
|
||||
private static final String[] BASE_RESOURCES = {
|
||||
"intonations", "phondata", "phonindex", "phontab", "en_dict", "voices/en/en-us"
|
||||
};
|
||||
|
||||
public static File getDataPath(Context context) {
|
||||
return new File(context.getDir("voices", MODE_WORLD_READABLE), "espeak-data");
|
||||
}
|
||||
|
||||
public static boolean hasBaseResources(Context context) {
|
||||
final File dataPath = getDataPath(context);
|
||||
|
||||
for (String resource : BASE_RESOURCES) {
|
||||
final File resourceFile = new File(dataPath, resource);
|
||||
|
||||
if (!resourceFile.exists()) {
|
||||
Log.e(TAG, "Missing base resource: " + resourceFile.getPath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
checkForVoices(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_DOWNLOAD:
|
||||
checkForVoices(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForVoices(boolean attemptedInstall) {
|
||||
final File dataPath = getDataPath(this);
|
||||
|
||||
ArrayList<String> availableLanguages = new ArrayList<String>();
|
||||
ArrayList<String> unavailableLanguages = new ArrayList<String>();
|
||||
|
||||
if (!hasBaseResources(this)) {
|
||||
if (!attemptedInstall) {
|
||||
downloadVoiceData();
|
||||
return;
|
||||
}
|
||||
// No base resource, can't load available voices.
|
||||
unavailableLanguages.add(Locale.ENGLISH.toString());
|
||||
returnResults(Engine.CHECK_VOICE_DATA_MISSING_DATA, dataPath, availableLanguages,
|
||||
unavailableLanguages);
|
||||
return;
|
||||
}
|
||||
|
||||
final SpeechSynthesis engine = new SpeechSynthesis(this, mSynthReadyCallback);
|
||||
final List<Voice> voices = engine.getAvailableVoices();
|
||||
|
||||
for (Voice voice : voices) {
|
||||
availableLanguages.add(voice.toString());
|
||||
}
|
||||
|
||||
final ArrayList<String> checkFor = getIntent().getStringArrayListExtra(
|
||||
TextToSpeech.Engine.EXTRA_CHECK_VOICE_DATA_FOR);
|
||||
|
||||
if (checkFor != null && !checkFor.isEmpty()) {
|
||||
final Set<String> checkForSet = new HashSet<String>(checkFor);
|
||||
|
||||
availableLanguages = filter(availableLanguages, checkForSet);
|
||||
unavailableLanguages = filter(unavailableLanguages, checkForSet);
|
||||
}
|
||||
|
||||
returnResults(Engine.CHECK_VOICE_DATA_PASS, dataPath, availableLanguages,
|
||||
unavailableLanguages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the voice data installer.
|
||||
*/
|
||||
private void downloadVoiceData() {
|
||||
final Intent checkIntent = new Intent(this, DownloadVoiceData.class);
|
||||
|
||||
startActivityForResult(checkIntent, REQUEST_DOWNLOAD);
|
||||
}
|
||||
|
||||
private void returnResults(int result, File dataPath, ArrayList<String> availableLanguages,
|
||||
ArrayList<String> unavailableLanguages) {
|
||||
final Intent returnData = new Intent();
|
||||
returnData.putStringArrayListExtra(Engine.EXTRA_AVAILABLE_VOICES, availableLanguages);
|
||||
returnData.putStringArrayListExtra(Engine.EXTRA_UNAVAILABLE_VOICES, unavailableLanguages);
|
||||
|
||||
// Don't bother returning Engine.EXTRA_VOICE_DATA_FILES,
|
||||
// Engine.EXTRA_VOICE_DATA_FILES_INFO, or
|
||||
// Engine.EXTRA_VOICE_DATA_ROOT_DIRECTORY
|
||||
// because they're don't seem necessary.
|
||||
|
||||
setResult(result, returnData);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a given array list, maintaining only elements that are in the
|
||||
* constraint. Returns a new list containing only the filtered elements.
|
||||
*/
|
||||
private ArrayList<String> filter(ArrayList<String> in, Set<String> constraint) {
|
||||
final ArrayList<String> out = new ArrayList<String>(constraint.size());
|
||||
|
||||
for (String s : in) {
|
||||
if (constraint.contains(s)) {
|
||||
out.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private final SynthReadyCallback mSynthReadyCallback = new SynthReadyCallback() {
|
||||
@Override
|
||||
public void onSynthDataReady(byte[] audioData) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSynthDataComplete() {
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class DownloadVoiceData extends Activity {
|
||||
public static final String BROADCAST_LANGUAGES_UPDATED = "com.googlecode.eyesfree.espeak.LANGUAGES_UPDATED";
|
||||
|
||||
private static final File LEGACY_DATA = new File(
|
||||
Environment.getExternalStorageDirectory(), "espeak-data");
|
||||
|
||||
private AsyncExtract mAsyncExtract;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.download_voice_data);
|
||||
|
||||
final File dataPath = CheckVoiceData.getDataPath(this).getParentFile();
|
||||
|
||||
mAsyncExtract = new AsyncExtract(this, R.raw.espeakdata, dataPath) {
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
onLanguagesInstalled(result);
|
||||
}
|
||||
};
|
||||
|
||||
mAsyncExtract.execute();
|
||||
|
||||
// Send a fake accessibility event so the user knows what's going on.
|
||||
findViewById(R.id.installing_voice_data)
|
||||
.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mAsyncExtract.cancel(true);
|
||||
}
|
||||
|
||||
private void onLanguagesInstalled(int resultCode) {
|
||||
switch (resultCode) {
|
||||
case RESULT_OK:
|
||||
final Intent intent = new Intent(BROADCAST_LANGUAGES_UPDATED);
|
||||
sendBroadcast(intent);
|
||||
new AsyncCleanup().execute(LEGACY_DATA);
|
||||
break;
|
||||
case RESULT_CANCELED:
|
||||
// Do nothing?
|
||||
break;
|
||||
}
|
||||
|
||||
setResult(resultCode);
|
||||
finish();
|
||||
}
|
||||
|
||||
private static void clearContents(File directory) {
|
||||
if (!directory.exists() || !directory.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final File[] children = directory.listFiles();
|
||||
|
||||
for (File child : children) {
|
||||
if (child.isDirectory()) {
|
||||
clearContents(child);
|
||||
}
|
||||
|
||||
child.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AsyncCleanup extends AsyncTask<File, Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground(File... params) {
|
||||
for (File directory : params) {
|
||||
clearContents(directory);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AsyncExtract extends AsyncTask<Void, Void, Integer> {
|
||||
private final LinkedList<File> mExtractedFiles = new LinkedList<File>();
|
||||
|
||||
private final Context mContext;
|
||||
private final int mRawResId;
|
||||
private final File mOutput;
|
||||
|
||||
public AsyncExtract(Context context, int rawResId, File output) {
|
||||
mContext = context;
|
||||
mRawResId = rawResId;
|
||||
mOutput = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
clearContents(CheckVoiceData.getDataPath(mContext));
|
||||
|
||||
final InputStream stream = mContext.getResources().openRawResource(mRawResId);
|
||||
final ZipInputStream zipStream = new ZipInputStream(new BufferedInputStream(stream));
|
||||
|
||||
boolean successful = false;
|
||||
|
||||
try {
|
||||
extractEntries(zipStream);
|
||||
successful = true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
zipStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (isCancelled() || !successful) {
|
||||
removeExtractedFiles();
|
||||
return RESULT_CANCELED;
|
||||
}
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
private void extractEntries(ZipInputStream zipStream) throws IOException {
|
||||
final byte[] buffer = new byte[10240];
|
||||
|
||||
int bytesRead;
|
||||
ZipEntry entry;
|
||||
|
||||
while (!isCancelled() && ((entry = zipStream.getNextEntry()) != null)) {
|
||||
final File outputFile = new File(mOutput, entry.getName());
|
||||
|
||||
mExtractedFiles.add(outputFile);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
outputFile.mkdirs();
|
||||
doChmod(outputFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure the target path exists.
|
||||
outputFile.getParentFile().mkdirs();
|
||||
|
||||
final FileOutputStream outputStream = new FileOutputStream(outputFile);
|
||||
|
||||
while (!isCancelled() && ((bytesRead = zipStream.read(buffer)) != -1)) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
zipStream.closeEntry();
|
||||
|
||||
// Make sure the output file is readable.
|
||||
doChmod(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeExtractedFiles() {
|
||||
for (File extractedFile : mExtractedFiles) {
|
||||
if (!extractedFile.isDirectory()) {
|
||||
extractedFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
mExtractedFiles.clear();
|
||||
}
|
||||
|
||||
private static void doChmod(File file) {
|
||||
try {
|
||||
Runtime.getRuntime().exec("/system/bin/chmod 755 " + file.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/*
|
||||
* Returns the sample text string for the language requested
|
||||
*/
|
||||
public class GetSampleText extends Activity {
|
||||
private static final String TAG = GetSampleText.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Locale locale = getLocaleFromIntent(getIntent());
|
||||
final Resources res = getResourcesForLocale(this, locale);
|
||||
|
||||
String text = null;
|
||||
|
||||
try {
|
||||
text = res.getString(R.string.sample_text, locale.getDisplayName(locale));
|
||||
} catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
final String language = (locale == null) ? "eng" : locale.getISO3Language();
|
||||
|
||||
if (text != null) {
|
||||
// Do nothing.
|
||||
} else if (language.equals("afr")) {
|
||||
text = getString(R.string.afr);
|
||||
} else if (language.equals("bos")) {
|
||||
text = getString(R.string.bos);
|
||||
} else if (language.equals("zho")) {
|
||||
text = getString(R.string.zho);
|
||||
} else if (language.equals("hrv")) {
|
||||
text = getString(R.string.hrv);
|
||||
} else if (language.equals("ces")) {
|
||||
text = getString(R.string.ces);
|
||||
} else if (language.equals("nld")) {
|
||||
text = getString(R.string.nld);
|
||||
} else if (language.equals("eng")) {
|
||||
text = getString(R.string.eng);
|
||||
} else if (language.equals("epo")) {
|
||||
text = getString(R.string.epo);
|
||||
} else if (language.equals("fin")) {
|
||||
text = getString(R.string.fin);
|
||||
} else if (language.equals("fra")) {
|
||||
text = getString(R.string.fra);
|
||||
} else if (language.equals("deu")) {
|
||||
text = getString(R.string.deu);
|
||||
} else if (language.equals("ell")) {
|
||||
text = getString(R.string.ell);
|
||||
} else if (language.equals("hin")) {
|
||||
text = getString(R.string.hin);
|
||||
} else if (language.equals("hun")) {
|
||||
text = getString(R.string.hun);
|
||||
} else if (language.equals("isl")) {
|
||||
text = getString(R.string.isl);
|
||||
} else if (language.equals("ind")) {
|
||||
text = getString(R.string.ind);
|
||||
} else if (language.equals("ita")) {
|
||||
text = getString(R.string.ita);
|
||||
} else if (language.equals("kur")) {
|
||||
text = getString(R.string.kur);
|
||||
} else if (language.equals("lat")) {
|
||||
text = getString(R.string.lat);
|
||||
} else if (language.equals("mkd")) {
|
||||
text = getString(R.string.mkd);
|
||||
} else if (language.equals("nor")) {
|
||||
text = getString(R.string.nor);
|
||||
} else if (language.equals("pol")) {
|
||||
text = getString(R.string.pol);
|
||||
} else if (language.equals("por")) {
|
||||
text = getString(R.string.por);
|
||||
} else if (language.equals("ron")) {
|
||||
text = getString(R.string.ron);
|
||||
} else if (language.equals("rus")) {
|
||||
text = getString(R.string.rus);
|
||||
} else if (language.equals("srp")) {
|
||||
text = getString(R.string.srp);
|
||||
} else if (language.equals("slk")) {
|
||||
text = getString(R.string.slk);
|
||||
} else if (language.equals("spa")) {
|
||||
text = getString(R.string.spa);
|
||||
} else if (language.equals("swa")) {
|
||||
text = getString(R.string.swa);
|
||||
} else if (language.equals("swe")) {
|
||||
text = getString(R.string.swe);
|
||||
} else if (language.equals("tam")) {
|
||||
text = getString(R.string.tam);
|
||||
} else if (language.equals("tur")) {
|
||||
text = getString(R.string.tur);
|
||||
} else if (language.equals("vie")) {
|
||||
text = getString(R.string.vie);
|
||||
} else if (language.equals("cym")) {
|
||||
text = getString(R.string.cym);
|
||||
} else {
|
||||
Log.e(TAG, "Missing sample text for " + language);
|
||||
text = getString(R.string.eng);
|
||||
}
|
||||
|
||||
final int result = TextToSpeech.LANG_AVAILABLE;
|
||||
final Intent returnData = new Intent();
|
||||
returnData.putExtra("sampleText", text);
|
||||
setResult(result, returnData);
|
||||
finish();
|
||||
}
|
||||
|
||||
private static Locale getLocaleFromIntent(Intent intent) {
|
||||
if (intent != null) {
|
||||
final String language = intent.getStringExtra("language");
|
||||
|
||||
if (language != null) {
|
||||
return new Locale(language);
|
||||
}
|
||||
}
|
||||
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
private static Resources getResourcesForLocale(Activity activity, Locale locale) {
|
||||
final Configuration config = activity.getResources().getConfiguration();
|
||||
config.locale = locale;
|
||||
|
||||
final DisplayMetrics metrics = new DisplayMetrics();
|
||||
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
|
||||
return new Resources(activity.getAssets(), metrics, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file implements the Java API to eSpeak using the JNI bindings.
|
||||
*
|
||||
* Android Version: 4.0 (Ice Cream Sandwich)
|
||||
* API Version: 14
|
||||
*/
|
||||
|
||||
package com.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.content.Context;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SpeechSynthesis {
|
||||
private static final String TAG = SpeechSynthesis.class.getSimpleName();
|
||||
|
||||
public static final int GENDER_MALE = 1;
|
||||
public static final int GENDER_FEMALE = 2;
|
||||
|
||||
static {
|
||||
System.loadLibrary("ttsespeak");
|
||||
|
||||
nativeClassInit();
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final SynthReadyCallback mCallback;
|
||||
private final String mDatapath;
|
||||
|
||||
private boolean mInitialized = false;
|
||||
|
||||
public SpeechSynthesis(Context context, SynthReadyCallback callback) {
|
||||
// First, ensure the data directory exists, otherwise init will crash.
|
||||
final File dataPath = CheckVoiceData.getDataPath(context);
|
||||
|
||||
if (!dataPath.exists()) {
|
||||
Log.e(TAG, "Missing voice data");
|
||||
dataPath.mkdirs();
|
||||
}
|
||||
|
||||
mContext = context;
|
||||
mCallback = callback;
|
||||
mDatapath = dataPath.getParentFile().getPath();
|
||||
|
||||
attemptInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() {
|
||||
nativeDestroy();
|
||||
}
|
||||
|
||||
public int getSampleRate() {
|
||||
return nativeGetSampleRate();
|
||||
}
|
||||
|
||||
public int getChannelCount() {
|
||||
return nativeGetChannelCount();
|
||||
}
|
||||
|
||||
public int getAudioFormat() {
|
||||
return nativeGetAudioFormat();
|
||||
}
|
||||
|
||||
public int getBufferSizeInBytes() {
|
||||
final int bufferSizeInMillis = nativeGetBufferSizeInMillis();
|
||||
final int sampleRate = nativeGetSampleRate();
|
||||
return (bufferSizeInMillis * sampleRate) / 1000;
|
||||
}
|
||||
|
||||
public List<Voice> getAvailableVoices() {
|
||||
final List<Voice> voices = new LinkedList<Voice>();
|
||||
final String[] results = nativeGetAvailableVoices();
|
||||
|
||||
for (int i = 0; i < results.length; i += 4) {
|
||||
final String name = results[i];
|
||||
final String identifier = results[i + 1];
|
||||
final int gender = Integer.parseInt(results[i + 2]);
|
||||
final int age = Integer.parseInt(results[i + 3]);
|
||||
final Voice voice = new Voice(name, identifier, gender, age);
|
||||
|
||||
voices.add(voice);
|
||||
}
|
||||
|
||||
return voices;
|
||||
}
|
||||
|
||||
public void setVoiceByProperties(
|
||||
String name, String languages, int gender, int age, int variant) {
|
||||
nativeSetVoiceByProperties(name, languages, gender, age, variant);
|
||||
}
|
||||
|
||||
public void setRate(int rate) {
|
||||
nativeSetRate(rate);
|
||||
}
|
||||
|
||||
public void setPitch(int pitch) {
|
||||
nativeSetPitch(pitch);
|
||||
}
|
||||
|
||||
public void synthesize(String text) {
|
||||
nativeSynthesize(text);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
nativeStop();
|
||||
}
|
||||
|
||||
private void nativeSynthCallback(byte[] audioData) {
|
||||
if (mCallback == null)
|
||||
return;
|
||||
|
||||
if (audioData == null) {
|
||||
mCallback.onSynthDataComplete();
|
||||
} else {
|
||||
mCallback.onSynthDataReady(audioData);
|
||||
}
|
||||
}
|
||||
|
||||
private void attemptInit() {
|
||||
if (mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckVoiceData.hasBaseResources(mContext)) {
|
||||
Log.e(TAG, "Missing base resources");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nativeCreate(mDatapath)) {
|
||||
Log.e(TAG, "Failed to initialize speech synthesis library");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Initialized synthesis library with sample rate = " + getSampleRate());
|
||||
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
private int mNativeData;
|
||||
|
||||
private static native final boolean nativeClassInit();
|
||||
|
||||
private native final boolean nativeCreate(String path);
|
||||
|
||||
private native final boolean nativeDestroy();
|
||||
|
||||
private native final int nativeGetSampleRate();
|
||||
|
||||
private native final int nativeGetChannelCount();
|
||||
|
||||
private native final int nativeGetAudioFormat();
|
||||
|
||||
private native final int nativeGetBufferSizeInMillis();
|
||||
|
||||
private native final String[] nativeGetAvailableVoices();
|
||||
|
||||
private native final boolean nativeSetVoiceByProperties(
|
||||
String name, String languages, int gender, int age, int variant);
|
||||
|
||||
private native final boolean nativeSetRate(int rate);
|
||||
|
||||
private native final boolean nativeSetPitch(int pitch);
|
||||
|
||||
private native final boolean nativeSynthesize(String text);
|
||||
|
||||
private native final boolean nativeStop();
|
||||
|
||||
public interface SynthReadyCallback {
|
||||
void onSynthDataReady(byte[] audioData);
|
||||
|
||||
void onSynthDataComplete();
|
||||
}
|
||||
|
||||
public class Voice {
|
||||
public final String name;
|
||||
public final String identifier;
|
||||
public final int gender;
|
||||
public final int age;
|
||||
public final Locale locale;
|
||||
|
||||
public Voice(String name, String identifier, int gender, int age) {
|
||||
this.name = name;
|
||||
this.identifier = identifier;
|
||||
this.gender = gender;
|
||||
this.age = age;
|
||||
|
||||
locale = new Locale(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts a partial match against a query locale.
|
||||
*
|
||||
* @param query The locale to match.
|
||||
* @return A text-to-speech availability code. One of:
|
||||
* <ul>
|
||||
* <li>{@link TextToSpeech#LANG_NOT_SUPPORTED}
|
||||
* <li>{@link TextToSpeech#LANG_AVAILABLE}
|
||||
* <li>{@link TextToSpeech#LANG_COUNTRY_AVAILABLE}
|
||||
* <li>{@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}
|
||||
* </ul>
|
||||
*/
|
||||
public int match(Locale query) {
|
||||
if (!locale.getISO3Language().equals(query.getISO3Language())) {
|
||||
return TextToSpeech.LANG_NOT_SUPPORTED;
|
||||
} else if (!locale.getISO3Country().equals(query.getISO3Country())) {
|
||||
return TextToSpeech.LANG_AVAILABLE;
|
||||
} else if (!locale.getVariant().equals(query.getVariant())) {
|
||||
return TextToSpeech.LANG_COUNTRY_AVAILABLE;
|
||||
} else {
|
||||
return TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file implements the Android Text-to-Speech engine for eSpeak.
|
||||
*
|
||||
* Android Version: 4.0 (Ice Cream Sandwich)
|
||||
* API Version: 14
|
||||
*/
|
||||
|
||||
package com.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioTrack;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
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 com.googlecode.eyesfree.espeak.SpeechSynthesis.SynthReadyCallback;
|
||||
import com.googlecode.eyesfree.espeak.SpeechSynthesis.Voice;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Implements the eSpeak engine as a {@link TextToSpeechService}.
|
||||
*
|
||||
* @author alanv@google.com (Alan Viverette)
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public class TtsService extends TextToSpeechService {
|
||||
private static final String TAG = TtsService.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String DEFAULT_LANGUAGE = "en";
|
||||
private static final String DEFAULT_COUNTRY = "uk";
|
||||
private static final String DEFAULT_VARIANT = "";
|
||||
|
||||
private SpeechSynthesis mEngine;
|
||||
private SynthesisCallback mCallback;
|
||||
|
||||
private List<Voice> mAvailableVoices;
|
||||
private Voice mMatchingVoice = null;
|
||||
|
||||
private String mLanguage = DEFAULT_LANGUAGE;
|
||||
private String mCountry = DEFAULT_COUNTRY;
|
||||
private String mVariant = DEFAULT_VARIANT;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (!CheckVoiceData.hasBaseResources(this)) {
|
||||
final IntentFilter filter =
|
||||
new IntentFilter(DownloadVoiceData.BROADCAST_LANGUAGES_UPDATED);
|
||||
registerReceiver(mBroadcastReceiver, filter);
|
||||
|
||||
final Intent intent = new Intent(this, DownloadVoiceData.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
initializeTtsEngine();
|
||||
|
||||
// This calls onIsLanguageAvailable() and must run AFTER initialization!
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the native eSpeak engine.
|
||||
*/
|
||||
private void initializeTtsEngine() {
|
||||
if (mEngine != null) {
|
||||
mEngine.stop();
|
||||
mEngine = null;
|
||||
}
|
||||
|
||||
mEngine = new SpeechSynthesis(this, mSynthCallback);
|
||||
mAvailableVoices = mEngine.getAvailableVoices();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] onGetLanguage() {
|
||||
// This is used to specify the language requested from GetSampleText.
|
||||
return new String[] {
|
||||
mLanguage, mCountry, mVariant
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int onIsLanguageAvailable(String language, String country, String variant) {
|
||||
if (mAvailableVoices == null) {
|
||||
Log.e(TAG, "Attempted to check language availability before loading voices!");
|
||||
return TextToSpeech.LANG_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
final Locale query = new Locale(language, country, variant);
|
||||
|
||||
Voice languageVoice = null;
|
||||
Voice countryVoice = null;
|
||||
|
||||
synchronized (mAvailableVoices) {
|
||||
for (Voice voice : mAvailableVoices) {
|
||||
switch (voice.match(query)) {
|
||||
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
|
||||
mMatchingVoice = voice;
|
||||
return TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
|
||||
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
|
||||
countryVoice = voice;
|
||||
case TextToSpeech.LANG_AVAILABLE:
|
||||
languageVoice = voice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (languageVoice == null) {
|
||||
mMatchingVoice = null;
|
||||
return TextToSpeech.LANG_NOT_SUPPORTED;
|
||||
} else if (countryVoice == null) {
|
||||
mMatchingVoice = languageVoice;
|
||||
return TextToSpeech.LANG_AVAILABLE;
|
||||
} else {
|
||||
mMatchingVoice = countryVoice;
|
||||
return TextToSpeech.LANG_COUNTRY_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int onLoadLanguage(String language, String country, String variant) {
|
||||
final int result = onIsLanguageAvailable(language, country, variant);
|
||||
|
||||
// Return immediately if the language is not available.
|
||||
if (result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
Log.e(TAG, "Failed to load language {language='" + language + "', country='" + country
|
||||
+ "', variant='" + variant + "'");
|
||||
return result;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
mLanguage = language;
|
||||
mCountry = ((country == null) ? "" : country);
|
||||
mVariant = ((variant == null) ? "" : variant);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Log.i(TAG, "Received stop request.");
|
||||
|
||||
mEngine.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onSynthesizeText(
|
||||
SynthesisRequest request, SynthesisCallback callback) {
|
||||
final int result = onLoadLanguage(request.getLanguage(), request.getCountry(), request.getVariant());
|
||||
|
||||
// Return immediately if the language is not available.
|
||||
if (result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = request.getText();
|
||||
final int gender = getDefaultGender();
|
||||
final int rate = scaleRate(request.getSpeechRate());
|
||||
final int pitch = scalePitch(request.getPitch());
|
||||
final Bundle params = request.getParams();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Received synthesis request: {language=\"" + mMatchingVoice.name + "\"}");
|
||||
|
||||
for (String key : params.keySet()) {
|
||||
Log.v(TAG,
|
||||
"Synthesis request contained param {" + key + ", " + params.get(key) + "}");
|
||||
}
|
||||
}
|
||||
|
||||
mCallback = callback;
|
||||
mCallback.start(mEngine.getSampleRate(), mEngine.getAudioFormat(),
|
||||
mEngine.getChannelCount());
|
||||
|
||||
mEngine.setVoiceByProperties(null, mMatchingVoice.name, gender, 0, 0);
|
||||
mEngine.setRate(rate);
|
||||
mEngine.setPitch(pitch);
|
||||
mEngine.synthesize(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the pitch by the user-specified value.
|
||||
*
|
||||
* @param pitch A pitch value.
|
||||
* @return A scaled pitch value.
|
||||
*/
|
||||
private int scalePitch(int pitch) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final String defaultPitchString = prefs.getString("default_pitch", "100");
|
||||
final int defaultPitch = Integer.parseInt(defaultPitchString);
|
||||
|
||||
return (pitch * defaultPitch / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user-specified gender.
|
||||
*
|
||||
* @return A gender value.
|
||||
*/
|
||||
private int getDefaultGender() {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final String defaultGenderString = prefs.getString("default_gender", "0");
|
||||
final int defaultGender = Integer.parseInt(defaultGenderString);
|
||||
|
||||
return defaultGender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the rate by the user-specified value.
|
||||
*
|
||||
* @param rate A rate value.
|
||||
* @return A scaled rate value.
|
||||
*/
|
||||
private int scaleRate(int rate) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final String defaultRateString = prefs.getString("default_rate", "100");
|
||||
final int defaultRate = Integer.parseInt(defaultRateString);
|
||||
|
||||
return (rate * defaultRate / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipes synthesizer output from native eSpeak to an {@link AudioTrack}.
|
||||
*/
|
||||
private final SpeechSynthesis.SynthReadyCallback mSynthCallback = new SynthReadyCallback() {
|
||||
@Override
|
||||
public void onSynthDataReady(byte[] audioData) {
|
||||
if ((audioData == null) || (audioData.length == 0)) {
|
||||
onSynthDataComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
final int maxBytesToCopy = mCallback.getMaxBufferSize();
|
||||
|
||||
int offset = 0;
|
||||
|
||||
while (offset < audioData.length) {
|
||||
final int bytesToWrite = Math.min(maxBytesToCopy, (audioData.length - offset));
|
||||
mCallback.audioAvailable(audioData, offset, bytesToWrite);
|
||||
offset += bytesToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSynthDataComplete() {
|
||||
mCallback.done();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listens for language update broadcasts and initializes the eSpeak engine.
|
||||
*/
|
||||
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
initializeTtsEngine();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceGroup;
|
||||
|
||||
public class TtsSettingsActivity extends PreferenceActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
fixListSummaries(getPreferenceScreen());
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the "%s" summary is currently broken, this sets the preference
|
||||
* change listener for all {@link ListPreference} views to fill in the
|
||||
* summary with the current entry value.
|
||||
*/
|
||||
private void fixListSummaries(PreferenceGroup group) {
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int count = group.getPreferenceCount();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Preference preference = group.getPreference(i);
|
||||
|
||||
if (preference instanceof PreferenceGroup) {
|
||||
fixListSummaries((PreferenceGroup) preference);
|
||||
} else if (preference instanceof ListPreference) {
|
||||
preference.setOnPreferenceChangeListener(mPreferenceChangeListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for preference changes and updates the summary to reflect the
|
||||
* current setting. This shouldn't be necessary, since preferences are
|
||||
* supposed to automatically do this when the summary is set to "%s".
|
||||
*/
|
||||
private final OnPreferenceChangeListener mPreferenceChangeListener =
|
||||
new OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (preference instanceof ListPreference && newValue instanceof String) {
|
||||
final ListPreference listPreference = (ListPreference) preference;
|
||||
final int index = listPreference.findIndexOfValue((String) newValue);
|
||||
final CharSequence[] entries = listPreference.getEntries();
|
||||
|
||||
if (index >= 0 && index < entries.length) {
|
||||
preference.setSummary(entries[index].toString().replaceAll("%", "%%"));
|
||||
} else {
|
||||
preference.setSummary("");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/*
|
||||
* The Java portion of this TTS plugin engine app does nothing.
|
||||
* This activity is only here so that the native code can be
|
||||
* wrapped up inside an apk file.
|
||||
*
|
||||
* The file path structure convention is that the native library
|
||||
* implementing TTS must be a file placed here:
|
||||
* /data/data/<PACKAGE_NAME>/lib/libtts<ACTIVITY_NAME_LOWERCASED>.so
|
||||
* Example:
|
||||
* /data/data/com.googlecode.eyesfree.espeak/lib/libttsespeak.so
|
||||
*/
|
||||
|
||||
public class eSpeak extends Activity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// The Java portion of this does nothing.
|
||||
// This activity is only here so that everything
|
||||
// can be wrapped up inside an apk file.
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
* 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.googlecode.eyesfree.espeak;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class eSpeakActivity extends Activity {
|
||||
private static final String ACTION_TTS_SETTINGS = "com.android.settings.TTS_SETTINGS";
|
||||
|
||||
/** Handler code for TTS initialization hand-off. */
|
||||
private static final int TTS_INITIALIZED = 1;
|
||||
|
||||
private static final int REQUEST_CHECK = 1;
|
||||
private static final int REQUEST_DOWNLOAD = 2;
|
||||
private static final int REQUEST_DEFAULT = 3;
|
||||
|
||||
private static final int DIALOG_SET_DEFAULT = 1;
|
||||
private static final int DIALOG_DOWNLOAD_FAILED = 2;
|
||||
private static final int DIALOG_ERROR = 3;
|
||||
|
||||
private enum State {
|
||||
LOADING,
|
||||
FAILURE,
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
private boolean mDownloadedVoiceData;
|
||||
private ArrayList<String> mVoices;
|
||||
private TextToSpeech mTts;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.main);
|
||||
|
||||
setState(State.LOADING);
|
||||
manageSettingVisibility();
|
||||
checkVoiceData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (mTts != null) {
|
||||
mTts.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UI state.
|
||||
*
|
||||
* @param state The current state.
|
||||
*/
|
||||
private void setState(State state) {
|
||||
findViewById(R.id.loading).setVisibility((state == State.LOADING) ? View.VISIBLE
|
||||
: View.GONE);
|
||||
findViewById(R.id.success).setVisibility((state == State.SUCCESS) ? View.VISIBLE
|
||||
: View.GONE);
|
||||
findViewById(R.id.failure).setVisibility((state == State.FAILURE) ? View.VISIBLE
|
||||
: View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launcher the voice data verifier.
|
||||
*/
|
||||
private void checkVoiceData() {
|
||||
final Intent checkIntent = new Intent(this, CheckVoiceData.class);
|
||||
|
||||
startActivityForResult(checkIntent, REQUEST_CHECK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the voice data installer.
|
||||
*/
|
||||
private void downloadVoiceData() {
|
||||
final Intent checkIntent = new Intent(this, DownloadVoiceData.class);
|
||||
|
||||
startActivityForResult(checkIntent, REQUEST_DOWNLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the TTS engine.
|
||||
*/
|
||||
private void initializeEngine() {
|
||||
mTts = new TextToSpeech(this, mInitListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides preferences according to SDK level.
|
||||
*/
|
||||
private void manageSettingVisibility() {
|
||||
if (Build.VERSION.SDK_INT < 14) {
|
||||
// Hide the eSpeak setting button on pre-ICS.
|
||||
findViewById(R.id.engineSettingsLayout).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of voice data verification. If verification fails
|
||||
* following a successful installation, displays an error dialog. Otherwise,
|
||||
* either launches the installer or attempts to initialize the TTS engine.
|
||||
*
|
||||
* @param resultCode The result of voice data verification.
|
||||
* @param data The intent containing available voices.
|
||||
*/
|
||||
private void onDataChecked(int resultCode, Intent data) {
|
||||
if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
|
||||
if (mDownloadedVoiceData) {
|
||||
setState(State.FAILURE);
|
||||
showDialog(DIALOG_ERROR);
|
||||
} else {
|
||||
downloadVoiceData();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mVoices = data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
|
||||
|
||||
initializeEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of voice data installation. Either shows a failure
|
||||
* dialog or launches the voice data verifier.
|
||||
*
|
||||
* @param resultCode
|
||||
*/
|
||||
private void onDataDownloaded(int resultCode) {
|
||||
if (resultCode != RESULT_OK) {
|
||||
setState(State.FAILURE);
|
||||
showDialog(DIALOG_DOWNLOAD_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
mDownloadedVoiceData = true;
|
||||
|
||||
checkVoiceData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of TTS engine initialization. Either displays an error
|
||||
* dialog or populates the activity's UI.
|
||||
*
|
||||
* @param status The TTS engine initialization status.
|
||||
*/
|
||||
private void onInitialized(int status) {
|
||||
if (!getPackageName().equals(mTts.getDefaultEngine())) {
|
||||
showDialog(DIALOG_SET_DEFAULT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == TextToSpeech.ERROR || mVoices == null) {
|
||||
setState(State.FAILURE);
|
||||
showDialog(DIALOG_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
final TextView currentLocale = (TextView) findViewById(R.id.currentLocale);
|
||||
currentLocale.setText(mTts.getLanguage().getDisplayName());
|
||||
|
||||
final TextView availableVoices = (TextView) findViewById(R.id.availableVoices);
|
||||
availableVoices.setText(Integer.toString(mVoices.size()));
|
||||
|
||||
findViewById(R.id.updateVoices).setOnClickListener(mOnClickListener);
|
||||
findViewById(R.id.ttsSettings).setOnClickListener(mOnClickListener);
|
||||
findViewById(R.id.engineSettings).setOnClickListener(mOnClickListener);
|
||||
|
||||
setState(State.SUCCESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CHECK:
|
||||
onDataChecked(resultCode, data);
|
||||
break;
|
||||
case REQUEST_DOWNLOAD:
|
||||
onDataDownloaded(resultCode);
|
||||
break;
|
||||
case REQUEST_DEFAULT:
|
||||
initializeEngine();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_SET_DEFAULT:
|
||||
return new AlertDialog.Builder(this).setTitle(R.string.app_name)
|
||||
.setMessage(R.string.set_default_message)
|
||||
.setNegativeButton(android.R.string.no, mFinishClickListener)
|
||||
.setPositiveButton(android.R.string.ok, mDialogClickListener).create();
|
||||
case DIALOG_DOWNLOAD_FAILED:
|
||||
return new AlertDialog.Builder(this).setTitle(R.string.app_name)
|
||||
.setMessage(R.string.voice_data_failed_message)
|
||||
.setNegativeButton(android.R.string.ok, mFinishClickListener)
|
||||
.setOnCancelListener(mFinishCancelListener).create();
|
||||
case DIALOG_ERROR:
|
||||
return new AlertDialog.Builder(this).setTitle(R.string.app_name)
|
||||
.setMessage(R.string.error_message)
|
||||
.setNegativeButton(android.R.string.no, mFinishClickListener)
|
||||
.setNegativeButton(android.R.string.ok, mReportClickListener)
|
||||
.setOnCancelListener(mFinishCancelListener).create();
|
||||
}
|
||||
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
private final DialogInterface.OnClickListener mDialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
startActivityForResult(
|
||||
new Intent(ACTION_TTS_SETTINGS), REQUEST_DEFAULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final DialogInterface.OnClickListener mReportClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// TODO: Send a crash report.
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
private final DialogInterface.OnClickListener mFinishClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
private final DialogInterface.OnCancelListener mFinishCancelListener = new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
mHandler.obtainMessage(TTS_INITIALIZED, status, 0).sendToTarget();
|
||||
}
|
||||
};
|
||||
|
||||
private static class EspeakHandler extends Handler {
|
||||
private WeakReference<eSpeakActivity> mActivity;
|
||||
|
||||
public EspeakHandler(eSpeakActivity activity)
|
||||
{
|
||||
mActivity = new WeakReference<eSpeakActivity>(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case TTS_INITIALIZED:
|
||||
mActivity.get().onInitialized(msg.arg1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private final Handler mHandler = new EspeakHandler(this);
|
||||
|
||||
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.updateVoices:
|
||||
downloadVoiceData();
|
||||
break;
|
||||
case R.id.engineSettings:
|
||||
startActivityForResult(
|
||||
new Intent(eSpeakActivity.this, TtsSettingsActivity.class),
|
||||
REQUEST_DEFAULT);
|
||||
break;
|
||||
case R.id.ttsSettings:
|
||||
startActivityForResult(new Intent(ACTION_TTS_SETTINGS), REQUEST_DEFAULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
*
|
||||
* 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.googlecode.eyesfree.espeak.providers;
|
||||
|
||||
import com.googlecode.eyesfree.espeak.CheckVoiceData;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Provides the "engineConfig" parameter for the legacy (pre-ICS) TTS API.
|
||||
*
|
||||
* @author alanv@google.com (Alan Viverette)
|
||||
*/
|
||||
public class SettingsProvider extends ContentProvider {
|
||||
private class SettingsCursor extends MatrixCursor {
|
||||
private String settings;
|
||||
|
||||
public SettingsCursor(String[] columnNames) {
|
||||
super(columnNames);
|
||||
}
|
||||
|
||||
public void putSettings(String settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int column) {
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
final File dataPath = CheckVoiceData.getDataPath(getContext());
|
||||
final String[] dummyColumns = {
|
||||
"", ""
|
||||
};
|
||||
final SettingsCursor cursor = new SettingsCursor(dummyColumns);
|
||||
cursor.putSettings(dataPath.getParent());
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user