From 9c00360347684496b03cbeff2d160cac6eaa8d9f Mon Sep 17 00:00:00 2001 From: "Reece H. Dunn" Date: Tue, 17 Sep 2013 13:41:01 +0100 Subject: [PATCH] Add the ability to import an eSpeak dictionary file from the SD card. --- .../res/layout/import_voice_preference.xml | 14 +++ android/res/values/strings.xml | 2 + .../com/reecedunn/espeak/FileListAdapter.java | 69 +++++++++++ .../src/com/reecedunn/espeak/FileUtils.java | 28 ++++- .../reecedunn/espeak/TtsSettingsActivity.java | 13 ++ .../preference/ImportVoicePreference.java | 117 ++++++++++++++++++ 6 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 android/res/layout/import_voice_preference.xml create mode 100644 android/src/com/reecedunn/espeak/FileListAdapter.java create mode 100644 android/src/com/reecedunn/espeak/preference/ImportVoicePreference.java diff --git a/android/res/layout/import_voice_preference.xml b/android/res/layout/import_voice_preference.xml new file mode 100644 index 00000000..dbd1daab --- /dev/null +++ b/android/res/layout/import_voice_preference.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index f84d1fca..4e2cbaf9 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -92,4 +92,6 @@ Old Croak Whisper + Import eSpeak dictionary + Import an eSpeak voice dictionary file from the SD card. diff --git a/android/src/com/reecedunn/espeak/FileListAdapter.java b/android/src/com/reecedunn/espeak/FileListAdapter.java new file mode 100644 index 00000000..e83b7009 --- /dev/null +++ b/android/src/com/reecedunn/espeak/FileListAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 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; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import java.io.File; + +public class FileListAdapter extends ArrayAdapter +{ + private final LayoutInflater mInflater; + + static class ViewHolder + { + public TextView text; + } + + public FileListAdapter(Activity context, File[] resources) + { + super(context, android.R.layout.simple_list_item_1, resources); + mInflater = context.getLayoutInflater(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + ViewHolder holder; + if (convertView == null) + { + convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); + holder = new ViewHolder(); + holder.text = (TextView)convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } + else + { + holder = (ViewHolder)convertView.getTag(); + } + + File file = getItem(position); + holder.text.setText(file.getName()); + return convertView; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) + { + return getView(position, convertView, parent); + } +} diff --git a/android/src/com/reecedunn/espeak/FileUtils.java b/android/src/com/reecedunn/espeak/FileUtils.java index 31fb6fed..1f93821d 100644 --- a/android/src/com/reecedunn/espeak/FileUtils.java +++ b/android/src/com/reecedunn/espeak/FileUtils.java @@ -26,14 +26,30 @@ import java.io.InputStream; public class FileUtils { public static String read(File file) throws IOException { - return read(new FileInputStream(file), (int)file.length()); + return readByteArray(new FileInputStream(file), (int)file.length()).toString(); } public static String read(InputStream stream) throws IOException { - return read(stream, stream.available()); + return readByteArray(stream, stream.available()).toString(); } public static String read(InputStream stream, int length) throws IOException { + return readByteArray(stream, length).toString(); + } + + public static byte[] readBinary(File file) throws IOException { + return readByteArray(new FileInputStream(file), (int)file.length()).toByteArray(); + } + + public static byte[] readBinary(InputStream stream) throws IOException { + return readByteArray(stream, stream.available()).toByteArray(); + } + + public static byte[] readBinary(InputStream stream, int length) throws IOException { + return readByteArray(stream, length).toByteArray(); + } + + private static ByteArrayOutputStream readByteArray(InputStream stream, int length) throws IOException { ByteArrayOutputStream content = new ByteArrayOutputStream(length); int c = stream.read(); while (c != -1) @@ -41,13 +57,17 @@ public class FileUtils { content.write((byte)c); c = stream.read(); } - return content.toString(); + return content; } public static void write(File outputFile, String contents) throws IOException { + write(outputFile, contents.getBytes()); + } + + public static void write(File outputFile, byte[] contents) throws IOException { FileOutputStream outputStream = new FileOutputStream(outputFile); try { - outputStream.write(contents.getBytes(), 0, contents.length()); + outputStream.write(contents, 0, contents.length); } finally { outputStream.close(); } diff --git a/android/src/com/reecedunn/espeak/TtsSettingsActivity.java b/android/src/com/reecedunn/espeak/TtsSettingsActivity.java index af2de603..1691faac 100644 --- a/android/src/com/reecedunn/espeak/TtsSettingsActivity.java +++ b/android/src/com/reecedunn/espeak/TtsSettingsActivity.java @@ -29,6 +29,7 @@ import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceManager; +import com.reecedunn.espeak.preference.ImportVoicePreference; import com.reecedunn.espeak.preference.SeekBarPreference; import com.reecedunn.espeak.preference.SpeakPunctuationPreference; import com.reecedunn.espeak.preference.VoiceVariantPreference; @@ -101,6 +102,17 @@ public class TtsSettingsActivity extends PreferenceActivity { } } + private static Preference createImportVoicePreference(Context context) { + final String title = context.getString(R.string.import_voice_title); + + final ImportVoicePreference pref = new ImportVoicePreference(context); + pref.setTitle(title); + pref.setDialogTitle(title); + pref.setOnPreferenceChangeListener(mOnPreferenceChanged); + pref.setDescription(R.string.import_voice_description); + return pref; + } + private static Preference createVoiceVariantPreference(Context context, VoiceSettings settings, int titleRes) { final String title = context.getString(titleRes); @@ -172,6 +184,7 @@ public class TtsSettingsActivity extends PreferenceActivity { SpeechSynthesis engine = new SpeechSynthesis(context, null); VoiceSettings settings = new VoiceSettings(PreferenceManager.getDefaultSharedPreferences(context), engine); + group.addPreference(createImportVoicePreference(context)); group.addPreference(createVoiceVariantPreference(context, settings, R.string.espeak_variant)); group.addPreference(createSpeakPunctuationPreference(context, settings, R.string.espeak_speak_punctuation)); group.addPreference(createSeekBarPreference(context, engine.Rate, VoiceSettings.PREF_RATE, R.string.setting_default_rate)); diff --git a/android/src/com/reecedunn/espeak/preference/ImportVoicePreference.java b/android/src/com/reecedunn/espeak/preference/ImportVoicePreference.java new file mode 100644 index 00000000..ed73ae01 --- /dev/null +++ b/android/src/com/reecedunn/espeak/preference/ImportVoicePreference.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 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.preference; + +import android.app.Activity; +import android.app.DownloadManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Environment; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Spinner; + +import com.reecedunn.espeak.CheckVoiceData; +import com.reecedunn.espeak.DownloadVoiceData; +import com.reecedunn.espeak.FileListAdapter; +import com.reecedunn.espeak.FileUtils; +import com.reecedunn.espeak.R; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; + +public class ImportVoicePreference extends DialogPreference { + private File mRoot; + private Spinner mDictionaries; + + public ImportVoicePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setDialogLayoutResource(R.layout.import_voice_preference); + setLayoutResource(R.layout.information_view); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + + mRoot = Environment.getExternalStorageDirectory(); + } + + public ImportVoicePreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ImportVoicePreference(Context context) { + this(context, null); + } + + public void setDescription(int resId) { + callChangeListener(getContext().getString(resId)); + } + + @Override + protected View onCreateDialogView() { + View root = super.onCreateDialogView(); + mDictionaries = (Spinner)root.findViewById(R.id.dictionaries); + return root; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + File[] dictionaries = mRoot.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return !file.isDirectory() && file.getName().endsWith("_dict"); + } + }); + Arrays.sort(dictionaries); + mDictionaries.setAdapter(new FileListAdapter((Activity)getContext(), dictionaries)); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + new AsyncTask() { + @Override + protected Object doInBackground(Object... objects) { + File source = (File)mDictionaries.getSelectedItem(); + File destination = new File(CheckVoiceData.getDataPath(getContext()), source.getName()); + try { + byte[] data = FileUtils.readBinary(source); + FileUtils.write(destination, data); + } catch (IOException e) { + } + return null; + } + + @Override + protected void onPostExecute(Object object) { + final Intent intent = new Intent(DownloadVoiceData.BROADCAST_LANGUAGES_UPDATED); + getContext().sendBroadcast(intent); + } + }.execute(); + break; + } + super.onClick(dialog, which); + } +}