Move settings to their new location according to ux spec.

This change doesn't add or remove any functionality.

Additionally, make the settings searchable.

Test: mmma packages/apps/Settings + manual testing

Change-Id: Id3e24fd52b49b7373b4f247241cc52f61ffe169e
This commit is contained in:
Niels Egberts
2016-12-12 13:29:41 +00:00
parent 42c61c10cc
commit e5017dc6db
12 changed files with 735 additions and 814 deletions

View File

@@ -31,17 +31,4 @@
android:maxLines="2" android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem" android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/> android:ellipsize="marquee"/>
<ImageView
android:id="@+id/tts_engine_settings"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:src="@drawable/ic_settings"
android:contentDescription="@string/tts_engine_settings_button"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout> </LinearLayout>

View File

@@ -4624,9 +4624,20 @@
behalf. It comes from the <xliff:g id="voice_input_service_app_name">%s</xliff:g> behalf. It comes from the <xliff:g id="voice_input_service_app_name">%s</xliff:g>
application. Enable the use of this service?</string> application. Enable the use of this service?</string>
<!-- On main TTS Settings screen, in default settings section, reset speech rate for synthesized voice to 1x speech rate.--> <!-- [CHAR LIMIT=50] The text for the settings section that is used to set a preferred text to speech engine -->
<string name="tts_engine_preference_title">Preferred engine</string>
<!-- [CHAR LIMIT=50] The text for a settings screen of the currently set text to speech engine -->
<string name="tts_engine_settings_title">Engine settings</string>
<!-- [CHAR LIMIT=50] The text for a button that goes to the speech rate and pitch settings for text to speech. -->
<string name="tts_sliders_title">Speech rate &amp; pitch</string>
<!-- [CHAR LIMIT=50] Name for the general text to speech settings section. -->
<string name="tts_engine_section_title">Engine</string>
<!-- [CHAR LIMIT=50] Name for the button that goes to the voice selection screen. -->
<string name="tts_install_voice_title">Voices</string>
<!-- Reset speech rate for synthesized voice to 1x speech rate in the text to speech settings.-->
<string name="tts_reset_speech_rate_title">Reset speech rate</string> <string name="tts_reset_speech_rate_title">Reset speech rate</string>
<!-- On main TTS Settings screen, summary for reset speech rate for synthesized voice --> <!-- Summary for reset speech rate for synthesized voice in the text to speech settings.-->
<string name="tts_reset_speech_rate_summary">Reset the speed at which the text is spoken to normal.</string> <string name="tts_reset_speech_rate_summary">Reset the speed at which the text is spoken to normal.</string>
<!-- Power Control Widget --> <!-- Power Control Widget -->

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/tts_settings_title">
<PreferenceCategory android:key="tts_engine_preference_category"
android:title="@string/tts_engine_preference_title"/>
</PreferenceScreen>

View File

@@ -1,36 +0,0 @@
<?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" android:title="">
<ListPreference
android:key="tts_default_lang"
android:title="@string/tts_default_lang_title"
android:summary="@string/tts_default_lang_summary"
android:order="100" />
<Preference
android:key="tts_engine_settings"
android:persistent="false"
android:title="@string/tts_engine_settings_title"
android:order="200" />
<Preference
android:key="tts_install_data"
android:persistent="false"
android:title="@string/tts_install_data_title"
android:summary="@string/tts_install_data_summary"
android:order="300" />
</PreferenceScreen>

View File

@@ -17,38 +17,40 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/tts_settings_title"> android:title="@string/tts_settings_title">
<!-- The contents of this category are filled in by the Java code <PreferenceCategory android:key="tts_engine_section"
based on the list of available engines. --> android:title="@string/tts_engine_section_title">
<PreferenceCategory android:key="tts_engine_preference_section"
android:title="@string/tts_engine_preference_section_title" /> <com.android.settingslib.RestrictedPreference android:key="tts_engine_preference"
android:title="@string/tts_engine_preference_title"
android:fragment="com.android.settings.tts.TtsEnginePreferenceFragment"/>
<Preference
android:key="tts_engine_settings"
android:persistent="false"
android:title="@string/tts_engine_settings_title"
android:order="200" />
<Preference
android:key="tts_install_data"
android:persistent="false"
android:title="@string/tts_install_voice_title"
android:order="300" />
</PreferenceCategory>
<PreferenceCategory android:key="tts_general_section" <PreferenceCategory android:key="tts_general_section"
android:title="@string/tts_general_section_title"> android:title="@string/tts_general_section_title">
<!-- The max value for seek bars here should be kept in sync
with the max value specified in TextToSpeechSettings class. -->
<com.android.settings.SeekBarPreference
android:key="tts_default_rate"
android:title="@string/tts_default_rate_title"
android:summary="@string/tts_default_rate_summary"
android:defaultValue="50"
android:max="600"/>
<com.android.settings.SeekBarPreference <com.android.settingslib.RestrictedPreference
android:key="tts_default_pitch" android:key="tts_sliders"
android:title="@string/tts_default_pitch_title" android:title="@string/tts_sliders_title"
android:summary="@string/tts_default_pitch_summary" android:fragment="com.android.settings.tts.TtsSlidersFragment"/>
android:defaultValue="100"
android:max="400"/>
<Preference android:key="reset_speech_rate" <ListPreference
android:persistent="false" android:key="tts_default_lang"
android:title="@string/tts_reset_speech_rate_title" android:title="@string/tts_default_lang_title"
android:summary="@string/tts_reset_speech_rate_summary" /> android:summary="@string/tts_default_lang_summary"
android:persistent="false" />
<Preference android:key="reset_speech_pitch"
android:persistent="false"
android:title="@string/tts_reset_speech_pitch_title"
android:summary="@string/tts_reset_speech_pitch_summary" />
<Preference android:key="tts_play_example" <Preference android:key="tts_play_example"
android:persistent="false" android:persistent="false"

46
res/xml/tts_sliders.xml Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/tts_settings_title">
<!-- The max value for seek bars here should be kept in sync
with the max value specified in TextToSpeechSettings class. -->
<com.android.settings.SeekBarPreference
android:key="tts_default_rate"
android:title="@string/tts_default_rate_title"
android:summary="@string/tts_default_rate_summary"
android:defaultValue="50"
android:max="600"/>
<com.android.settings.SeekBarPreference
android:key="tts_default_pitch"
android:title="@string/tts_default_pitch_title"
android:summary="@string/tts_default_pitch_summary"
android:defaultValue="100"
android:max="400"/>
<Preference android:key="reset_speech_rate"
android:persistent="false"
android:title="@string/tts_reset_speech_rate_title"
android:summary="@string/tts_reset_speech_rate_summary" />
<Preference android:key="reset_speech_pitch"
android:persistent="false"
android:title="@string/tts_reset_speech_pitch_title"
android:summary="@string/tts_reset_speech_pitch_summary" />
</PreferenceScreen>

View File

@@ -72,6 +72,8 @@ import com.android.settings.notification.ZenModeVisualInterruptionSettings;
import com.android.settings.print.PrintSettingsFragment; import com.android.settings.print.PrintSettingsFragment;
import com.android.settings.sim.SimSettings; import com.android.settings.sim.SimSettings;
import com.android.settings.system.SystemDashboardFragment; import com.android.settings.system.SystemDashboardFragment;
import com.android.settings.tts.TtsEnginePreferenceFragment;
import com.android.settings.tts.TtsSlidersFragment;
import com.android.settings.users.UserSettings; import com.android.settings.users.UserSettings;
import com.android.settings.wifi.AdvancedWifiSettings; import com.android.settings.wifi.AdvancedWifiSettings;
import com.android.settings.wifi.ConfigureWifiSettings; import com.android.settings.wifi.ConfigureWifiSettings;
@@ -167,6 +169,9 @@ public final class SearchIndexableResources {
addIndex(ConnectedDeviceDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_bt_laptop); addIndex(ConnectedDeviceDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_bt_laptop);
addIndex(EnterprisePrivacySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_about); addIndex(EnterprisePrivacySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_about);
addIndex(PaymentSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_nfc_payment); addIndex(PaymentSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_nfc_payment);
addIndex(
TtsEnginePreferenceFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
addIndex(TtsSlidersFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
} }
private SearchIndexableResources() { private SearchIndexableResources() {

View File

@@ -16,29 +16,27 @@
package com.android.settings.tts; package com.android.settings.tts;
import android.support.v7.preference.ListPreference;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings.SettingNotFoundException;
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.EngineInfo; import android.speech.tts.TextToSpeech.EngineInfo;
import android.speech.tts.TtsEngines; import android.speech.tts.TtsEngines;
import android.speech.tts.UtteranceProgressListener; import android.speech.tts.UtteranceProgressListener;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Checkable; import android.util.Pair;
import java.util.Comparator;
import java.util.Collections;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SeekBarPreference;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -48,13 +46,15 @@ import java.util.MissingResourceException;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH; import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
public class TextToSpeechSettings extends SettingsPreferenceFragment implements public class TextToSpeechSettings extends SettingsPreferenceFragment
Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
RadioButtonGroupState {
private static final String STATE_KEY_LOCALE_ENTRIES = "locale_entries";
private static final String STATE_KEY_LOCALE_ENTRY_VALUES = "locale_entry_values";
private static final String STATE_KEY_LOCALE_VALUE = "locale_value";
private static final String TAG = "TextToSpeechSettings"; private static final String TAG = "TextToSpeechSettings";
private static final boolean DBG = false; private static final boolean DBG = false;
@@ -62,84 +62,37 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
/** Preference key for the "play TTS example" preference. */ /** Preference key for the "play TTS example" preference. */
private static final String KEY_PLAY_EXAMPLE = "tts_play_example";; private static final String KEY_PLAY_EXAMPLE = "tts_play_example";;
/** Preference key for the TTS pitch selection slider. */
private static final String KEY_DEFAULT_PITCH = "tts_default_pitch";
/** Preference key for the TTS rate selection slider. */
private static final String KEY_DEFAULT_RATE = "tts_default_rate";
/** Preference key for the TTS reset speech rate preference. */
private static final String KEY_RESET_SPEECH_RATE = "reset_speech_rate";
/** Preference key for the TTS reset speech pitch preference. */
private static final String KEY_RESET_SPEECH_PITCH = "reset_speech_pitch";
/** Preference key for the TTS status field. */ /** Preference key for the TTS status field. */
private static final String KEY_STATUS = "tts_status"; private static final String KEY_STATUS = "tts_status";
/**
* Preference key for the engine selection preference.
*/
private static final String KEY_ENGINE_PREFERENCE_SECTION =
"tts_engine_preference_section";
/** /**
* These look like birth years, but they aren't mine. I'm much younger than this. * These look like birth years, but they aren't mine. I'm much younger than this.
*/ */
private static final int GET_SAMPLE_TEXT = 1983; private static final int GET_SAMPLE_TEXT = 1983;
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
/**
* Speech rate value.
* This value should be kept in sync with the max value set in tts_settings xml.
*/
private static final int MAX_SPEECH_RATE = 600;
private static final int MIN_SPEECH_RATE = 10;
/**
* Speech pitch value.
* TTS pitch value varies from 25 to 400, where 100 is the value
* for normal pitch. The max pitch value is set to 400, based on feedback from users
* and the GoogleTTS pitch variation range. The range for pitch is not set in stone
* and should be readjusted based on user need.
* This value should be kept in sync with the max value set in tts_settings xml.
*/
private static final int MAX_SPEECH_PITCH = 400;
private static final int MIN_SPEECH_PITCH = 25;
private PreferenceCategory mEnginePreferenceCategory;
private SeekBarPreference mDefaultPitchPref;
private SeekBarPreference mDefaultRatePref;
private Preference mResetSpeechRate;
private Preference mResetSpeechPitch;
private Preference mPlayExample; private Preference mPlayExample;
private Preference mEngineStatus; private Preference mEngineStatus;
private int mDefaultPitch = TextToSpeech.Engine.DEFAULT_PITCH;
private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
/** private static final String KEY_ENGINE_LOCALE = "tts_default_lang";
* The currently selected engine. private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings";
*/ private static final String KEY_INSTALL_DATA = "tts_install_data";
private int mSelectedLocaleIndex = -1;
/** The currently selected engine. */
private String mCurrentEngine; private String mCurrentEngine;
/**
* The engine checkbox that is currently checked. Saves us a bit of effort
* in deducing the right one from the currently selected engine.
*/
private Checkable mCurrentChecked;
/**
* The previously selected TTS engine. Useful for rollbacks if the users
* choice is not loaded or fails a voice integrity check.
*/
private String mPreviousEngine;
private TextToSpeech mTts = null; private TextToSpeech mTts = null;
private TtsEngines mEnginesHelper = null; private TtsEngines mEnginesHelper = null;
private String mSampleText = null; private String mSampleText = null;
private ListPreference mLocalePreference;
private Preference mEngineSettingsPreference;
private Preference mInstallVoicesPreference;
/** /**
* Default locale used by selected TTS engine, null if not connected to any engine. * Default locale used by selected TTS engine, null if not connected to any engine.
*/ */
@@ -164,18 +117,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
} }
}; };
/**
* The initialization listener used when the user changes his choice of
* engine (as opposed to when then screen is being initialized for the first
* time).
*/
private final TextToSpeech.OnInitListener mUpdateListener = new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
onUpdateEngine(status);
}
};
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
return MetricsEvent.TTS_TEXT_TO_SPEECH; return MetricsEvent.TTS_TEXT_TO_SPEECH;
@@ -192,21 +133,40 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
mPlayExample.setOnPreferenceClickListener(this); mPlayExample.setOnPreferenceClickListener(this);
mPlayExample.setEnabled(false); mPlayExample.setEnabled(false);
mResetSpeechRate = findPreference(KEY_RESET_SPEECH_RATE); mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
mResetSpeechRate.setOnPreferenceClickListener(this);
mResetSpeechPitch = findPreference(KEY_RESET_SPEECH_PITCH);
mResetSpeechPitch.setOnPreferenceClickListener(this);
mEnginePreferenceCategory = (PreferenceCategory) findPreference( mLocalePreference = (ListPreference) findPreference(KEY_ENGINE_LOCALE);
KEY_ENGINE_PREFERENCE_SECTION); mLocalePreference.setOnPreferenceChangeListener(this);
mDefaultPitchPref = (SeekBarPreference) findPreference(KEY_DEFAULT_PITCH);
mDefaultRatePref = (SeekBarPreference) findPreference(KEY_DEFAULT_RATE); if (savedInstanceState == null) {
mLocalePreference.setEnabled(false);
mLocalePreference.setEntries(new CharSequence[0]);
mLocalePreference.setEntryValues(new CharSequence[0]);
} else {
// Repopulate mLocalePreference with saved state. Will be updated later with
// up-to-date values when checkTtsData() calls back with results.
final CharSequence[] entries =
savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRIES);
final CharSequence[] entryValues =
savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES);
final CharSequence value = savedInstanceState.getCharSequence(STATE_KEY_LOCALE_VALUE);
mLocalePreference.setEntries(entries);
mLocalePreference.setEntryValues(entryValues);
mLocalePreference.setValue(value != null ? value.toString() : null);
mLocalePreference.setEnabled(entries.length > 0);
}
mEngineSettingsPreference = findPreference(KEY_ENGINE_SETTINGS);
mEngineSettingsPreference.setOnPreferenceClickListener(this);
mInstallVoicesPreference = findPreference(KEY_INSTALL_DATA);
mInstallVoicesPreference.setOnPreferenceClickListener(this);
mInstallVoicesPreference.setEnabled(false);
mEngineStatus = findPreference(KEY_STATUS); mEngineStatus = findPreference(KEY_STATUS);
updateEngineStatus(R.string.tts_status_checking); updateEngineStatus(R.string.tts_status_checking);
mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener); mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
setTtsUtteranceProgressListener(); setTtsUtteranceProgressListener();
initSettings(); initSettings();
@@ -222,6 +182,23 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
if (mTts == null || mCurrentDefaultLocale == null) { if (mTts == null || mCurrentDefaultLocale == null) {
return; return;
} }
if (!mTts.getDefaultEngine().equals(mTts.getCurrentEngine())) {
try {
mTts.shutdown();
mTts = null;
} catch (Exception e) {
Log.e(TAG, "Error shutting down TTS engine" + e);
}
mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
setTtsUtteranceProgressListener();
initSettings();
} else {
// Do set pitch correctly after it may have changed, and unlike speed, it doesn't change
// immediately.
final ContentResolver resolver = getContentResolver();
mTts.setPitch(android.provider.Settings.Secure.getInt(resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)/100.0f);
}
Locale ttsDefaultLocale = mTts.getDefaultLanguage(); Locale ttsDefaultLocale = mTts.getDefaultLanguage();
if (mCurrentDefaultLocale != null && !mCurrentDefaultLocale.equals(ttsDefaultLocale)) { if (mCurrentDefaultLocale != null && !mCurrentDefaultLocale.equals(ttsDefaultLocale)) {
updateWidgetState(false); updateWidgetState(false);
@@ -259,26 +236,9 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private void initSettings() { private void initSettings() {
final ContentResolver resolver = getContentResolver(); final ContentResolver resolver = getContentResolver();
// Set up the default rate and pitch.
mDefaultRate = android.provider.Settings.Secure.getInt(
resolver, TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
mDefaultPitch = android.provider.Settings.Secure.getInt(
resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate));
mDefaultRatePref.setOnPreferenceChangeListener(this);
mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE));
mDefaultPitchPref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH,
mDefaultPitch));
mDefaultPitchPref.setOnPreferenceChangeListener(this);
mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH,
MAX_SPEECH_PITCH));
if (mTts != null) { if (mTts != null) {
mCurrentEngine = mTts.getCurrentEngine(); mCurrentEngine = mTts.getCurrentEngine();
mTts.setSpeechRate(mDefaultRate/100.0f); mTts.setPitch(android.provider.Settings.Secure.getInt(resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)/100.0f);
mTts.setPitch(mDefaultPitch/100.0f);
} }
SettingsActivity activity = null; SettingsActivity activity = null;
@@ -289,46 +249,22 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
"Settings"); "Settings");
} }
mEnginePreferenceCategory.removeAll(); if (mCurrentEngine != null) {
EngineInfo info = mEnginesHelper.getEngineInfo(mCurrentEngine);
mEngineSettingsPreference.setSummary(info.label);
final Intent settingsIntent = mEnginesHelper.getSettingsIntent(info.name);
mEngineSettingsPreference.setIntent(settingsIntent);
if (settingsIntent == null) {
mEngineSettingsPreference.setEnabled(false);
}
List<EngineInfo> engines = mEnginesHelper.getEngines(); Preference mEnginePreference = findPreference("tts_engine_preference");
for (EngineInfo engine : engines) { mEnginePreference.setSummary(info.label);
TtsEnginePreference enginePref = new TtsEnginePreference(getPrefContext(), engine,
this, activity);
mEnginePreferenceCategory.addPreference(enginePref);
} }
checkVoiceData(mCurrentEngine); checkVoiceData(mCurrentEngine);
} }
/**
* The minimum speech pitch/rate value should be > 0 but the minimum value of a seekbar in
* android is fixed at 0. Therefore, we increment the seekbar progress with MIN_SPEECH_VALUE
* so that the minimum seekbar progress value is MIN_SPEECH_PITCH/RATE.
* SPEECH_VALUE = MIN_SPEECH_VALUE + SEEKBAR_PROGRESS
*/
private int getValueFromSeekBarProgress(String preferenceKey, int progress) {
if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
return MIN_SPEECH_RATE + progress;
} else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
return MIN_SPEECH_PITCH + progress;
}
return progress;
}
/**
* Since we are appending the MIN_SPEECH value to the speech seekbar progress, the
* speech seekbar progress should be set to (speechValue - MIN_SPEECH value).
*/
private int getSeekBarProgressFromValue(String preferenceKey, int value) {
if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
return value - MIN_SPEECH_RATE;
} else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
return value - MIN_SPEECH_PITCH;
}
return value;
}
/** /**
* Called when the TTS engine is initialized. * Called when the TTS engine is initialized.
*/ */
@@ -336,6 +272,14 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
if (status == TextToSpeech.SUCCESS) { if (status == TextToSpeech.SUCCESS) {
if (DBG) Log.d(TAG, "TTS engine for settings screen initialized."); if (DBG) Log.d(TAG, "TTS engine for settings screen initialized.");
checkDefaultLocale(); checkDefaultLocale();
getActivity()
.runOnUiThread(
new Runnable() {
@Override
public void run() {
mLocalePreference.setEnabled(true);
}
});
} else { } else {
if (DBG) Log.d(TAG, "TTS engine for settings screen failed to initialize successfully."); if (DBG) Log.d(TAG, "TTS engine for settings screen failed to initialize successfully.");
updateWidgetState(false); updateWidgetState(false);
@@ -452,8 +396,87 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
onSampleTextReceived(resultCode, data); onSampleTextReceived(resultCode, data);
} else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { } else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
onVoiceDataIntegrityCheckDone(data); onVoiceDataIntegrityCheckDone(data);
if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) {
updateDefaultLocalePref(data);
} }
} }
}
private void updateDefaultLocalePref(Intent data) {
final ArrayList<String> availableLangs =
data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
final ArrayList<String> unavailableLangs =
data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
if (availableLangs != null && unavailableLangs.size() > 0) {
mInstallVoicesPreference.setEnabled(true);
} else {
mInstallVoicesPreference.setEnabled(false);
}
if (availableLangs == null || availableLangs.size() == 0) {
mLocalePreference.setEnabled(false);
return;
}
Locale currentLocale = null;
if (!mEnginesHelper.isLocaleSetToDefaultForEngine(mTts.getCurrentEngine())) {
currentLocale = mEnginesHelper.getLocalePrefForEngine(mTts.getCurrentEngine());
}
ArrayList<Pair<String, Locale>> entryPairs =
new ArrayList<Pair<String, Locale>>(availableLangs.size());
for (int i = 0; i < availableLangs.size(); i++) {
Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i));
if (locale != null) {
entryPairs.add(new Pair<String, Locale>(locale.getDisplayName(), locale));
}
}
// Sort it
Collections.sort(
entryPairs,
new Comparator<Pair<String, Locale>>() {
@Override
public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) {
return lhs.first.compareToIgnoreCase(rhs.first);
}
});
// Get two arrays out of one of pairs
mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value
CharSequence[] entries = new CharSequence[availableLangs.size() + 1];
CharSequence[] entryValues = new CharSequence[availableLangs.size() + 1];
entries[0] = getActivity().getString(R.string.tts_lang_use_system);
entryValues[0] = "";
int i = 1;
for (Pair<String, Locale> entry : entryPairs) {
if (entry.second.equals(currentLocale)) {
mSelectedLocaleIndex = i;
}
entries[i] = entry.first;
entryValues[i++] = entry.second.toString();
}
mLocalePreference.setEntries(entries);
mLocalePreference.setEntryValues(entryValues);
mLocalePreference.setEnabled(true);
setLocalePreference(mSelectedLocaleIndex);
}
/** Set entry from entry table in mLocalePreference */
private void setLocalePreference(int index) {
if (index < 0) {
mLocalePreference.setValue("");
mLocalePreference.setSummary(R.string.tts_lang_not_selected);
} else {
mLocalePreference.setValueIndex(index);
mLocalePreference.setSummary(mLocalePreference.getEntries()[index]);
}
}
private String getDefaultSampleString() { private String getDefaultSampleString() {
if (mTts != null && mTts.getLanguage() != null) { if (mTts != null && mTts.getLanguage() != null) {
@@ -522,17 +545,59 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
@Override @Override
public boolean onPreferenceChange(Preference preference, Object objValue) { public boolean onPreferenceChange(Preference preference, Object objValue) {
if (KEY_DEFAULT_RATE.equals(preference.getKey())) { if (preference == mLocalePreference) {
updateSpeechRate((Integer) objValue); String localeString = (String) objValue;
} else if (KEY_DEFAULT_PITCH.equals(preference.getKey())) { updateLanguageTo(
updateSpeechPitchValue((Integer) objValue); (!TextUtils.isEmpty(localeString)
? mEnginesHelper.parseLocaleString(localeString)
: null));
checkDefaultLocale();
return true;
} }
return true; return true;
} }
private void updateLanguageTo(Locale locale) {
int selectedLocaleIndex = -1;
String localeString = (locale != null) ? locale.toString() : "";
for (int i = 0; i < mLocalePreference.getEntryValues().length; i++) {
if (localeString.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) {
selectedLocaleIndex = i;
break;
}
}
if (selectedLocaleIndex == -1) {
Log.w(TAG, "updateLanguageTo called with unknown locale argument");
return;
}
mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]);
mSelectedLocaleIndex = selectedLocaleIndex;
mEnginesHelper.updateLocalePrefForEngine(mTts.getCurrentEngine(), locale);
// Null locale means "use system default"
mTts.setLanguage((locale != null) ? locale : Locale.getDefault());
}
/** /**
* Called when mPlayExample, mResetSpeechRate or mResetSpeechPitch is * Ask the current default engine to launch the matching INSTALL_TTS_DATA activity so the
* clicked. * required TTS files are properly installed.
*/
private void installVoiceData() {
if (TextUtils.isEmpty(mCurrentEngine)) return;
Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
intent.setPackage(mCurrentEngine);
try {
Log.v(TAG, "Installing voice data: " + intent.toUri(0));
startActivity(intent);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")");
}
}
/**
* Called when mPlayExample, mInstallVoicesPreference is clicked.
*/ */
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
@@ -541,57 +606,15 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
// the actual speaking // the actual speaking
speakSampleText(); speakSampleText();
return true; return true;
} else if (preference == mResetSpeechRate) { } else if (preference == mInstallVoicesPreference) {
int speechRateSeekbarProgress = getSeekBarProgressFromValue( installVoiceData();
KEY_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
mDefaultRatePref.setProgress(speechRateSeekbarProgress);
updateSpeechRate(speechRateSeekbarProgress);
return true;
} else if (preference == mResetSpeechPitch) {
int pitchSeekbarProgress = getSeekBarProgressFromValue(
KEY_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
mDefaultPitchPref.setProgress(pitchSeekbarProgress);
updateSpeechPitchValue(pitchSeekbarProgress);
return true; return true;
} }
return false; return false;
} }
private void updateSpeechRate(int speechRateSeekBarProgress) {
mDefaultRate = getValueFromSeekBarProgress(KEY_DEFAULT_RATE,
speechRateSeekBarProgress);
try {
android.provider.Settings.Secure.putInt(getContentResolver(),
TTS_DEFAULT_RATE, mDefaultRate);
if (mTts != null) {
mTts.setSpeechRate(mDefaultRate / 100.0f);
}
if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist default TTS rate setting", e);
}
return;
}
private void updateSpeechPitchValue(int speechPitchSeekBarProgress) {
mDefaultPitch = getValueFromSeekBarProgress(KEY_DEFAULT_PITCH,
speechPitchSeekBarProgress);
try {
android.provider.Settings.Secure.putInt(getContentResolver(),
TTS_DEFAULT_PITCH, mDefaultPitch);
if (mTts != null) {
mTts.setPitch(mDefaultPitch / 100.0f);
}
if (DBG) Log.d(TAG, "TTS default pitch changed, now" + mDefaultPitch);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist default TTS pitch setting", e);
}
return;
}
private void updateWidgetState(boolean enable) { private void updateWidgetState(boolean enable) {
mPlayExample.setEnabled(enable); mPlayExample.setEnabled(enable);
mDefaultRatePref.setEnabled(enable);
mEngineStatus.setEnabled(enable); mEngineStatus.setEnabled(enable);
} }
@@ -614,67 +637,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
dialog.show(); dialog.show();
} }
private void updateDefaultEngine(String engine) { /** Check whether the voice data for the engine is ok. */
if (DBG) Log.d(TAG, "Updating default synth to : " + engine);
// Disable the "play sample text" preference and the speech
// rate preference while the engine is being swapped.
updateWidgetState(false);
updateEngineStatus(R.string.tts_status_checking);
// Keep track of the previous engine that was being used. So that
// we can reuse the previous engine.
//
// Note that if TextToSpeech#getCurrentEngine is not null, it means at
// the very least that we successfully bound to the engine service.
mPreviousEngine = mTts.getCurrentEngine();
// Step 1: Shut down the existing TTS engine.
if (mTts != null) {
try {
mTts.shutdown();
mTts = null;
} catch (Exception e) {
Log.e(TAG, "Error shutting down TTS engine" + e);
}
}
// Step 2: Connect to the new TTS engine.
// Step 3 is continued on #onUpdateEngine (below) which is called when
// the app binds successfully to the engine.
if (DBG) Log.d(TAG, "Updating engine : Attempting to connect to engine: " + engine);
mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine);
setTtsUtteranceProgressListener();
}
/*
* Step 3: We have now bound to the TTS engine the user requested. We will
* attempt to check voice data for the engine if we successfully bound to it,
* or revert to the previous engine if we didn't.
*/
public void onUpdateEngine(int status) {
if (status == TextToSpeech.SUCCESS) {
if (DBG) {
Log.d(TAG, "Updating engine: Successfully bound to the engine: " +
mTts.getCurrentEngine());
}
checkVoiceData(mTts.getCurrentEngine());
} else {
if (DBG) Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
if (mPreviousEngine != null) {
// This is guaranteed to at least bind, since mPreviousEngine would be
// null if the previous bind to this engine failed.
mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener,
mPreviousEngine);
setTtsUtteranceProgressListener();
}
mPreviousEngine = null;
}
}
/*
* Step 4: Check whether the voice data for the engine is ok.
*/
private void checkVoiceData(String engine) { private void checkVoiceData(String engine) {
Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
intent.setPackage(engine); intent.setPackage(engine);
@@ -686,9 +649,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
} }
} }
/* /** The voice data check is complete. */
* Step 5: The voice data check is complete.
*/
private void onVoiceDataIntegrityCheckDone(Intent data) { private void onVoiceDataIntegrityCheckDone(Intent data) {
final String engine = mTts.getCurrentEngine(); final String engine = mTts.getCurrentEngine();
@@ -715,39 +676,5 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
if (evaluateDefaultLocale()) { if (evaluateDefaultLocale()) {
getSampleText(); getSampleText();
} }
final int engineCount = mEnginePreferenceCategory.getPreferenceCount();
for (int i = 0; i < engineCount; ++i) {
final Preference p = mEnginePreferenceCategory.getPreference(i);
if (p instanceof TtsEnginePreference) {
TtsEnginePreference enginePref = (TtsEnginePreference) p;
if (enginePref.getKey().equals(engine)) {
enginePref.setVoiceDataDetails(data);
break;
} }
}
}
}
@Override
public Checkable getCurrentChecked() {
return mCurrentChecked;
}
@Override
public String getCurrentKey() {
return mCurrentEngine;
}
@Override
public void setCurrentChecked(Checkable current) {
mCurrentChecked = current;
}
@Override
public void setCurrentKey(String key) {
mCurrentEngine = key;
updateDefaultEngine(mCurrentEngine);
}
} }

View File

@@ -20,50 +20,22 @@ import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech.EngineInfo; import android.speech.tts.TextToSpeech.EngineInfo;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder; import android.support.v7.preference.PreferenceViewHolder;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.Checkable; import android.widget.Checkable;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.RadioButton; import android.widget.RadioButton;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
public class TtsEnginePreference extends Preference { public class TtsEnginePreference extends Preference {
private static final String TAG = "TtsEnginePreference"; private static final String TAG = "TtsEnginePreference";
/**
* Key for the name of the TTS engine passed in to the engine
* settings fragment {@link TtsEngineSettingsFragment}.
*/
static final String FRAGMENT_ARGS_NAME = "name";
/**
* Key for the label of the TTS engine passed in to the engine
* settings fragment. This is used as the title of the fragment
* {@link TtsEngineSettingsFragment}.
*/
static final String FRAGMENT_ARGS_LABEL = "label";
/**
* Key for the voice data data passed in to the engine settings
* fragmetn {@link TtsEngineSettingsFragment}.
*/
static final String FRAGMENT_ARGS_VOICES = "voices";
/**
* The preference activity that owns this preference. Required
* for instantiating the engine specific settings screen.
*/
private final SettingsActivity mSettingsActivity;
/** /**
* The engine information for the engine this preference represents. * The engine information for the engine this preference represents.
* Contains it's name, label etc. which are used for display. * Contains it's name, label etc. which are used for display.
@@ -81,7 +53,6 @@ public class TtsEnginePreference extends Preference {
*/ */
private volatile boolean mPreventRadioButtonCallbacks; private volatile boolean mPreventRadioButtonCallbacks;
private View mSettingsIcon;
private RadioButton mRadioButton; private RadioButton mRadioButton;
private Intent mVoiceCheckData; private Intent mVoiceCheckData;
@@ -99,7 +70,6 @@ public class TtsEnginePreference extends Preference {
setLayoutResource(R.layout.preference_tts_engine); setLayoutResource(R.layout.preference_tts_engine);
mSharedState = state; mSharedState = state;
mSettingsActivity = prefActivity;
mEngineInfo = info; mEngineInfo = info;
mPreventRadioButtonCallbacks = false; mPreventRadioButtonCallbacks = false;
@@ -130,52 +100,10 @@ public class TtsEnginePreference extends Preference {
mPreventRadioButtonCallbacks = false; mPreventRadioButtonCallbacks = false;
mRadioButton = rb; mRadioButton = rb;
mSettingsIcon = view.findViewById(R.id.tts_engine_settings);
// Will be enabled only the engine has passed the voice check, and
// is currently enabled.
mSettingsIcon.setEnabled(isChecked && mVoiceCheckData != null);
if (!isChecked) {
mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA);
}
mSettingsIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle args = new Bundle();
args.putString(FRAGMENT_ARGS_NAME, mEngineInfo.name);
args.putString(FRAGMENT_ARGS_LABEL, mEngineInfo.label);
if (mVoiceCheckData != null) {
args.putParcelable(FRAGMENT_ARGS_VOICES, mVoiceCheckData);
}
// Note that we use this instead of the (easier to use)
// SettingsActivity.startPreferenceFragment because the
// title will not be updated correctly in the fragment
// breadcrumb since it isn't inflated from the XML layout.
mSettingsActivity.startPreferencePanel(
TtsEngineSettingsFragment.class.getName(),
args, 0, mEngineInfo.label, null, 0);
}
});
if (mVoiceCheckData != null) {
mSettingsIcon.setEnabled(mRadioButton.isChecked());
}
} }
public void setVoiceDataDetails(Intent data) { public void setVoiceDataDetails(Intent data) {
mVoiceCheckData = data; mVoiceCheckData = data;
// This might end up running before getView aboive, in which
// case mSettingsIcon && mRadioButton will be null. In this case
// getView will set the right values.
if (mSettingsIcon != null && mRadioButton != null) {
if (mRadioButton.isChecked()) {
mSettingsIcon.setEnabled(true);
} else {
mSettingsIcon.setEnabled(false);
mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA);
}
}
} }
private boolean shouldDisplayDataAlert() { private boolean shouldDisplayDataAlert() {
@@ -227,8 +155,6 @@ public class TtsEnginePreference extends Preference {
// Privileged engine, set it current // Privileged engine, set it current
makeCurrentEngine(buttonView); makeCurrentEngine(buttonView);
} }
} else {
mSettingsIcon.setEnabled(false);
} }
} }
@@ -239,7 +165,6 @@ public class TtsEnginePreference extends Preference {
mSharedState.setCurrentChecked(current); mSharedState.setCurrentChecked(current);
mSharedState.setCurrentKey(getKey()); mSharedState.setCurrentKey(getKey());
callChangeListener(mSharedState.getCurrentKey()); callChangeListener(mSharedState.getCurrentKey());
mSettingsIcon.setEnabled(true);
} }

View File

@@ -0,0 +1,194 @@
package com.android.settings.tts;
import android.speech.tts.TextToSpeech;
import com.android.settings.R;
import android.os.Bundle;
import com.android.settings.SettingsPreferenceFragment;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import android.support.v7.preference.PreferenceCategory;
import android.speech.tts.TtsEngines;
import android.speech.tts.TextToSpeech.EngineInfo;
import com.android.settings.SettingsActivity;
import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
import android.widget.Checkable;
import android.util.Log;
import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
import com.android.settings.search.Indexable;
import com.android.settings.search.BaseSearchIndexProvider;
import android.content.Context;
import android.provider.SearchIndexableResource;
import java.util.List;
import java.util.Arrays;
public class TtsEnginePreferenceFragment extends SettingsPreferenceFragment //implements
implements RadioButtonGroupState, Indexable {
private static final String TAG = "TtsEnginePreferenceFragment";
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
/** The currently selected engine. */
private String mCurrentEngine;
/**
* The engine checkbox that is currently checked. Saves us a bit of effort in deducing the right
* one from the currently selected engine.
*/
private Checkable mCurrentChecked;
/**
* The previously selected TTS engine. Useful for rollbacks if the users choice is not loaded or
* fails a voice integrity check.
*/
private String mPreviousEngine;
private PreferenceCategory mEnginePreferenceCategory;
private TextToSpeech mTts = null;
private TtsEngines mEnginesHelper = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.tts_engine_picker);
mEnginePreferenceCategory =
(PreferenceCategory) findPreference("tts_engine_preference_category");
mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
mTts = new TextToSpeech(getActivity().getApplicationContext(), null);
initSettings();
}
@Override
public int getMetricsCategory() {
return MetricsEvent.TTS_ENGINE_SETTINGS;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mTts != null) {
mTts.shutdown();
mTts = null;
}
}
private void initSettings() {
if (mTts != null) {
mCurrentEngine = mTts.getCurrentEngine();
}
mEnginePreferenceCategory.removeAll();
SettingsActivity activity = (SettingsActivity) getActivity();
List<EngineInfo> engines = mEnginesHelper.getEngines();
for (EngineInfo engine : engines) {
TtsEnginePreference enginePref =
new TtsEnginePreference(getPrefContext(), engine, this, activity);
mEnginePreferenceCategory.addPreference(enginePref);
}
}
@Override
public Checkable getCurrentChecked() {
return mCurrentChecked;
}
@Override
public String getCurrentKey() {
return mCurrentEngine;
}
@Override
public void setCurrentChecked(Checkable current) {
mCurrentChecked = current;
}
/**
* The initialization listener used when the user changes his choice of engine (as opposed to
* when then screen is being initialized for the first time).
*/
private final TextToSpeech.OnInitListener mUpdateListener =
new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
onUpdateEngine(status);
}
};
private void updateDefaultEngine(String engine) {
Log.d(TAG, "Updating default synth to : " + engine);
// Keep track of the previous engine that was being used. So that
// we can reuse the previous engine.
//
// Note that if TextToSpeech#getCurrentEngine is not null, it means at
// the very least that we successfully bound to the engine service.
mPreviousEngine = mTts.getCurrentEngine();
// Step 1: Shut down the existing TTS engine.
Log.i(TAG, "Shutting down current tts engine");
if (mTts != null) {
try {
mTts.shutdown();
mTts = null;
} catch (Exception e) {
Log.e(TAG, "Error shutting down TTS engine" + e);
}
}
// Step 2: Connect to the new TTS engine.
// Step 3 is continued on #onUpdateEngine (below) which is called when
// the app binds successfully to the engine.
Log.i(TAG, "Updating engine : Attempting to connect to engine: " + engine);
mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine);
Log.i(TAG, "Success");
}
/**
* Step 3: We have now bound to the TTS engine the user requested. We will attempt to check
* voice data for the engine if we successfully bound to it, or revert to the previous engine if
* we didn't.
*/
public void onUpdateEngine(int status) {
if (status == TextToSpeech.SUCCESS) {
Log.d(
TAG,
"Updating engine: Successfully bound to the engine: "
+ mTts.getCurrentEngine());
android.provider.Settings.Secure.putString(
getContentResolver(), TTS_DEFAULT_SYNTH, mTts.getCurrentEngine());
} else {
Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
if (mPreviousEngine != null) {
// This is guaranteed to at least bind, since mPreviousEngine would be
// null if the previous bind to this engine failed.
mTts =
new TextToSpeech(
getActivity().getApplicationContext(), null, mPreviousEngine);
}
mPreviousEngine = null;
}
}
@Override
public void setCurrentKey(String key) {
mCurrentEngine = key;
updateDefaultEngine(mCurrentEngine);
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
Log.i(TAG, "Indexing");
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.tts_engine_picker;
return Arrays.asList(sir);
}
};
}

View File

@@ -1,370 +0,0 @@
/*
* 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.android.settings.tts;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TtsEngines;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implements
OnPreferenceClickListener, OnPreferenceChangeListener {
private static final String TAG = "TtsEngineSettings";
private static final boolean DBG = false;
private static final String KEY_ENGINE_LOCALE = "tts_default_lang";
private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings";
private static final String KEY_INSTALL_DATA = "tts_install_data";
private static final String STATE_KEY_LOCALE_ENTRIES = "locale_entries";
private static final String STATE_KEY_LOCALE_ENTRY_VALUES= "locale_entry_values";
private static final String STATE_KEY_LOCALE_VALUE = "locale_value";
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
private TtsEngines mEnginesHelper;
private ListPreference mLocalePreference;
private Preference mEngineSettingsPreference;
private Preference mInstallVoicesPreference;
private Intent mEngineSettingsIntent;
private Intent mVoiceDataDetails;
private TextToSpeech mTts;
private int mSelectedLocaleIndex = -1;
private final TextToSpeech.OnInitListener mTtsInitListener = new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status != TextToSpeech.SUCCESS) {
finishFragment();
} else {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mLocalePreference.setEnabled(true);
}
});
}
}
};
private final BroadcastReceiver mLanguagesChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Installed or uninstalled some data packs
if (TextToSpeech.Engine.ACTION_TTS_DATA_INSTALLED.equals(intent.getAction())) {
checkTtsData();
}
}
};
public TtsEngineSettingsFragment() {
super();
}
@Override
public int getMetricsCategory() {
return MetricsEvent.TTS_ENGINE_SETTINGS;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.tts_engine_settings);
mEnginesHelper = new TtsEngines(getActivity());
final PreferenceScreen root = getPreferenceScreen();
mLocalePreference = (ListPreference) root.findPreference(KEY_ENGINE_LOCALE);
mLocalePreference.setOnPreferenceChangeListener(this);
mEngineSettingsPreference = root.findPreference(KEY_ENGINE_SETTINGS);
mEngineSettingsPreference.setOnPreferenceClickListener(this);
mInstallVoicesPreference = root.findPreference(KEY_INSTALL_DATA);
mInstallVoicesPreference.setOnPreferenceClickListener(this);
root.setTitle(getEngineLabel());
root.setKey(getEngineName());
mEngineSettingsPreference.setTitle(getResources().getString(
R.string.tts_engine_settings_title, getEngineLabel()));
mEngineSettingsIntent = mEnginesHelper.getSettingsIntent(getEngineName());
if (mEngineSettingsIntent == null) {
mEngineSettingsPreference.setEnabled(false);
}
mInstallVoicesPreference.setEnabled(false);
if (savedInstanceState == null) {
mLocalePreference.setEnabled(false);
mLocalePreference.setEntries(new CharSequence[0]);
mLocalePreference.setEntryValues(new CharSequence[0]);
} else {
// Repopulate mLocalePreference with saved state. Will be updated later with
// up-to-date values when checkTtsData() calls back with results.
final CharSequence[] entries =
savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRIES);
final CharSequence[] entryValues =
savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES);
final CharSequence value =
savedInstanceState.getCharSequence(STATE_KEY_LOCALE_VALUE);
mLocalePreference.setEntries(entries);
mLocalePreference.setEntryValues(entryValues);
mLocalePreference.setValue(value != null ? value.toString() : null);
mLocalePreference.setEnabled(entries.length > 0);
}
mVoiceDataDetails = getArguments().getParcelable(TtsEnginePreference.FRAGMENT_ARGS_VOICES);
mTts = new TextToSpeech(getActivity().getApplicationContext(), mTtsInitListener,
getEngineName());
// Check if data packs changed
checkTtsData();
getActivity().registerReceiver(mLanguagesChangedReceiver,
new IntentFilter(TextToSpeech.Engine.ACTION_TTS_DATA_INSTALLED));
}
@Override
public void onDestroy() {
getActivity().unregisterReceiver(mLanguagesChangedReceiver);
mTts.shutdown();
super.onDestroy();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the mLocalePreference values, so we can repopulate it with entries.
outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRIES,
mLocalePreference.getEntries());
outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES,
mLocalePreference.getEntryValues());
outState.putCharSequence(STATE_KEY_LOCALE_VALUE,
mLocalePreference.getValue());
}
private final void checkTtsData() {
Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
intent.setPackage(getEngineName());
try {
if (DBG) Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0));
startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) {
updateVoiceDetails(data);
} else {
Log.e(TAG, "CheckVoiceData activity failed");
}
}
}
private void updateVoiceDetails(Intent data) {
if (data == null){
Log.e(TAG, "Engine failed voice data integrity check (null return)" +
mTts.getCurrentEngine());
return;
}
mVoiceDataDetails = data;
if (DBG) Log.d(TAG, "Parsing voice data details, data: " + mVoiceDataDetails.toUri(0));
final ArrayList<String> available = mVoiceDataDetails.getStringArrayListExtra(
TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
final ArrayList<String> unavailable = mVoiceDataDetails.getStringArrayListExtra(
TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
if (unavailable != null && unavailable.size() > 0) {
mInstallVoicesPreference.setEnabled(true);
} else {
mInstallVoicesPreference.setEnabled(false);
}
if (available == null){
Log.e(TAG, "TTS data check failed (available == null).");
mLocalePreference.setEnabled(false);
return;
} else {
updateDefaultLocalePref(available);
}
}
private void updateDefaultLocalePref(ArrayList<String> availableLangs) {
if (availableLangs == null || availableLangs.size() == 0) {
mLocalePreference.setEnabled(false);
return;
}
Locale currentLocale = null;
if (!mEnginesHelper.isLocaleSetToDefaultForEngine(getEngineName())) {
currentLocale = mEnginesHelper.getLocalePrefForEngine(getEngineName());
}
ArrayList<Pair<String, Locale>> entryPairs =
new ArrayList<Pair<String, Locale>>(availableLangs.size());
for (int i = 0; i < availableLangs.size(); i++) {
Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i));
if (locale != null){
entryPairs.add(new Pair<String, Locale>(
locale.getDisplayName(), locale));
}
}
// Sort it
Collections.sort(entryPairs, new Comparator<Pair<String, Locale>>() {
@Override
public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) {
return lhs.first.compareToIgnoreCase(rhs.first);
}
});
// Get two arrays out of one of pairs
mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value
CharSequence[] entries = new CharSequence[availableLangs.size()+1];
CharSequence[] entryValues = new CharSequence[availableLangs.size()+1];
entries[0] = getActivity().getString(R.string.tts_lang_use_system);
entryValues[0] = "";
int i = 1;
for (Pair<String, Locale> entry : entryPairs) {
if (entry.second.equals(currentLocale)) {
mSelectedLocaleIndex = i;
}
entries[i] = entry.first;
entryValues[i++] = entry.second.toString();
}
mLocalePreference.setEntries(entries);
mLocalePreference.setEntryValues(entryValues);
mLocalePreference.setEnabled(true);
setLocalePreference(mSelectedLocaleIndex);
}
/** Set entry from entry table in mLocalePreference */
private void setLocalePreference(int index) {
if (index < 0) {
mLocalePreference.setValue("");
mLocalePreference.setSummary(R.string.tts_lang_not_selected);
} else {
mLocalePreference.setValueIndex(index);
mLocalePreference.setSummary(mLocalePreference.getEntries()[index]);
}
}
/**
* Ask the current default engine to launch the matching INSTALL_TTS_DATA activity
* so the required TTS files are properly installed.
*/
private void installVoiceData() {
if (TextUtils.isEmpty(getEngineName())) return;
Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
intent.setPackage(getEngineName());
try {
Log.v(TAG, "Installing voice data: " + intent.toUri(0));
startActivity(intent);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")");
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mInstallVoicesPreference) {
installVoiceData();
return true;
} else if (preference == mEngineSettingsPreference) {
startActivity(mEngineSettingsIntent);
return true;
}
return false;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mLocalePreference) {
String localeString = (String) newValue;
updateLanguageTo((!TextUtils.isEmpty(localeString) ?
mEnginesHelper.parseLocaleString(localeString) : null));
return true;
}
return false;
}
private void updateLanguageTo(Locale locale) {
int selectedLocaleIndex = -1;
String localeString = (locale != null) ? locale.toString() : "";
for (int i=0; i < mLocalePreference.getEntryValues().length; i++) {
if (localeString.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) {
selectedLocaleIndex = i;
break;
}
}
if (selectedLocaleIndex == -1) {
Log.w(TAG, "updateLanguageTo called with unknown locale argument");
return;
}
mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]);
mSelectedLocaleIndex = selectedLocaleIndex;
mEnginesHelper.updateLocalePrefForEngine(getEngineName(), locale);
if (getEngineName().equals(mTts.getCurrentEngine())) {
// Null locale means "use system default"
mTts.setLanguage((locale != null) ? locale : Locale.getDefault());
}
}
private String getEngineName() {
return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_NAME);
}
private String getEngineLabel() {
return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_LABEL);
}
}

View File

@@ -0,0 +1,207 @@
package com.android.settings.tts;
import android.speech.tts.TextToSpeech;
import com.android.settings.R;
import android.os.Bundle;
import com.android.settings.SettingsPreferenceFragment;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import android.util.Log;
import com.android.settings.SeekBarPreference;
import android.support.v7.preference.Preference;
import android.content.ContentResolver;
import com.android.settings.search.Indexable;
import com.android.settings.search.BaseSearchIndexProvider;
import android.content.Context;
import android.provider.SearchIndexableResource;
import java.util.List;
import java.util.Arrays;
import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
public class TtsSlidersFragment extends SettingsPreferenceFragment
implements Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener,
Indexable {
private static final String TAG = TtsSlidersFragment.class.getSimpleName();
private static final boolean DBG = false;
/** Preference key for the TTS pitch selection slider. */
private static final String KEY_DEFAULT_PITCH = "tts_default_pitch";
/** Preference key for the TTS rate selection slider. */
private static final String KEY_DEFAULT_RATE = "tts_default_rate";
/** Preference key for the TTS reset speech rate preference. */
private static final String KEY_RESET_SPEECH_RATE = "reset_speech_rate";
/** Preference key for the TTS reset speech pitch preference. */
private static final String KEY_RESET_SPEECH_PITCH = "reset_speech_pitch";
/**
* Speech rate value. This value should be kept in sync with the max value set in tts_settings
* xml.
*/
private static final int MAX_SPEECH_RATE = 600;
private static final int MIN_SPEECH_RATE = 10;
/**
* Speech pitch value. TTS pitch value varies from 25 to 400, where 100 is the value for normal
* pitch. The max pitch value is set to 400, based on feedback from users and the GoogleTTS
* pitch variation range. The range for pitch is not set in stone and should be readjusted based
* on user need. This value should be kept in sync with the max value set in tts_settings xml.
*/
private static final int MAX_SPEECH_PITCH = 400;
private static final int MIN_SPEECH_PITCH = 25;
private int mDefaultPitch = TextToSpeech.Engine.DEFAULT_PITCH;
private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
private SeekBarPreference mDefaultPitchPref;
private SeekBarPreference mDefaultRatePref;
private Preference mResetSpeechRate;
private Preference mResetSpeechPitch;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.tts_sliders);
mResetSpeechRate = findPreference(KEY_RESET_SPEECH_RATE);
mResetSpeechRate.setOnPreferenceClickListener(this);
mResetSpeechPitch = findPreference(KEY_RESET_SPEECH_PITCH);
mResetSpeechPitch.setOnPreferenceClickListener(this);
mDefaultPitchPref = (SeekBarPreference) findPreference(KEY_DEFAULT_PITCH);
mDefaultRatePref = (SeekBarPreference) findPreference(KEY_DEFAULT_RATE);
initSettings();
}
private void initSettings() {
final ContentResolver resolver = getContentResolver();
// Set up the default rate and pitch.
mDefaultRate =
android.provider.Settings.Secure.getInt(
resolver, TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
mDefaultPitch =
android.provider.Settings.Secure.getInt(
resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate));
mDefaultRatePref.setOnPreferenceChangeListener(this);
mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE));
mDefaultPitchPref.setProgress(
getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, mDefaultPitch));
mDefaultPitchPref.setOnPreferenceChangeListener(this);
mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, MAX_SPEECH_PITCH));
}
/**
* The minimum speech pitch/rate value should be > 0 but the minimum value of a seekbar in
* android is fixed at 0. Therefore, we increment the seekbar progress with MIN_SPEECH_VALUE so
* that the minimum seekbar progress value is MIN_SPEECH_PITCH/RATE. SPEECH_VALUE =
* MIN_SPEECH_VALUE + SEEKBAR_PROGRESS
*/
private int getValueFromSeekBarProgress(String preferenceKey, int progress) {
if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
return MIN_SPEECH_RATE + progress;
} else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
return MIN_SPEECH_PITCH + progress;
}
return progress;
}
/**
* Since we are appending the MIN_SPEECH value to the speech seekbar progress, the speech
* seekbar progress should be set to (speechValue - MIN_SPEECH value).
*/
private int getSeekBarProgressFromValue(String preferenceKey, int value) {
if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
return value - MIN_SPEECH_RATE;
} else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
return value - MIN_SPEECH_PITCH;
}
return value;
}
@Override
public boolean onPreferenceChange(Preference preference, Object objValue) {
if (KEY_DEFAULT_RATE.equals(preference.getKey())) {
updateSpeechRate((Integer) objValue);
} else if (KEY_DEFAULT_PITCH.equals(preference.getKey())) {
updateSpeechPitchValue((Integer) objValue);
}
return true;
}
/** Called when mPlayExample, mResetSpeechRate or mResetSpeechPitch is clicked. */
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mResetSpeechRate) {
int speechRateSeekbarProgress =
getSeekBarProgressFromValue(KEY_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
mDefaultRatePref.setProgress(speechRateSeekbarProgress);
updateSpeechRate(speechRateSeekbarProgress);
return true;
} else if (preference == mResetSpeechPitch) {
int pitchSeekbarProgress =
getSeekBarProgressFromValue(
KEY_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
mDefaultPitchPref.setProgress(pitchSeekbarProgress);
updateSpeechPitchValue(pitchSeekbarProgress);
return true;
}
return false;
}
private void updateSpeechRate(int speechRateSeekBarProgress) {
mDefaultRate = getValueFromSeekBarProgress(KEY_DEFAULT_RATE, speechRateSeekBarProgress);
try {
android.provider.Settings.Secure.putInt(
getContentResolver(), TTS_DEFAULT_RATE, mDefaultRate);
if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist default TTS rate setting", e);
}
return;
}
private void updateSpeechPitchValue(int speechPitchSeekBarProgress) {
mDefaultPitch = getValueFromSeekBarProgress(KEY_DEFAULT_PITCH, speechPitchSeekBarProgress);
try {
android.provider.Settings.Secure.putInt(
getContentResolver(), TTS_DEFAULT_PITCH, mDefaultPitch);
if (DBG) Log.d(TAG, "TTS default pitch changed, now" + mDefaultPitch);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist default TTS pitch setting", e);
}
return;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int getMetricsCategory() {
return MetricsEvent.TTS_SLIDERS;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
Log.i(TAG, "Indexing");
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.tts_sliders;
return Arrays.asList(sir);
}
};
}