blob: 12f06dd93a1aef659ab9febc1bbb5184e8d7625b [file] [log] [blame]
/*
* Copyright (C) 2014 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.tv.settings.system;
import com.android.internal.view.RotationPolicy;
import com.android.tv.settings.ActionBehavior;
import com.android.tv.settings.BaseSettingsActivity;
import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
import com.android.tv.settings.ActionKey;
import com.android.tv.settings.R;
import com.android.tv.settings.system.DeveloperOptionsActivity.MyApplicationInfo;
import com.android.tv.settings.util.SettingsHelper;
import com.android.tv.settings.dialog.old.Action;
import com.android.tv.settings.dialog.old.ActionAdapter;
import com.android.tv.settings.dialog.old.ContentFragment;
import com.android.tv.settings.util.SettingsHelper;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils.SimpleStringSplitter;
import android.speech.tts.TtsEngines;
import android.util.Log;
import android.util.Pair;
import android.speech.tts.TextToSpeech.EngineInfo;
import android.speech.tts.UtteranceProgressListener;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
public class AccessibilityActivity extends BaseSettingsActivity implements ActionAdapter.Listener {
private static final String TAG = "AccessibilityActivity";
private static final boolean DEBUG = false;
private static final int GET_SAMPLE_TEXT = 1983;
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
private SettingsHelper mHelper;
private boolean mTtsSettingsEnabled;
private TextToSpeech mTts = null;
private TtsEngines mEnginesHelper = null;
private String mCurrentEngine;
private String mPreviousEngine;
private Intent mVoiceCheckData;
private String mServiceSettingTitle;
private String mSelectedServiceComponent;
private String mSelectedServiceSettings;
private boolean mSelectedServiceEnabled;
/**
* The initialization listener used when we are initalizing the settings
* screen for the first time (as opposed to when a user changes his choice
* of engine).
*/
private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
onInitEngine(status);
}
};
/**
* 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
public void onCreate(Bundle savedInstanceState) {
mResources = getResources();
mHelper = new SettingsHelper(this);
mActions = new ArrayList<Action>();
mTts = new TextToSpeech(getApplicationContext(), mInitListener);
mEnginesHelper = new TtsEngines(getApplicationContext());
initSettings();
super.onCreate(savedInstanceState);
}
@Override
protected void refreshActionList() {
mActions.clear();
switch ((ActionType) mState) {
case ACCESSIBILITY_OVERVIEW:
mActions.add(ActionType.ACCESSIBILITY_CAPTIONS.toAction(mResources));
mActions.add(ActionType.ACCESSIBILITY_SERVICES.toAction(mResources));
// TODO b/18007521
// uncomment when Talkback is able to support not speaking passwords aloud
//mActions.add(ActionType.ACCESSIBILITY_SPEAK_PASSWORDS.toAction(mResources,
// mHelper.getSecureStatusIntSetting(
// Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD)));
mActions.add(ActionType.ACCESSIBILITY_TTS_OUTPUT.toAction(mResources,
getDisplayNameForEngine(mTts.getCurrentEngine())));
break;
case ACCESSIBILITY_SERVICES:
mActions = getInstalledServicesActions();
break;
case ACCESSIBILITY_SERVICES_SETTINGS:
mActions.add(ActionType.ACCESSIBILITY_SERVICES_STATUS.toAction(mResources,
mHelper.getStatusStringFromBoolean(mSelectedServiceEnabled)));
if (mSelectedServiceSettings != null) {
mActions.add(ActionType.ACCESSIBILITY_SERVICE_CONFIG.toAction(mResources));
}
break;
case ACCESSIBILITY_SERVICES_STATUS:
mActions = getEnableActions(mServiceSettingTitle, mSelectedServiceEnabled);
break;
case ACCESSIBILITY_SERVICES_CONFIRM_ON:
mActions.add(ActionType.AGREE.toAction(mResources));
mActions.add(ActionType.DISAGREE.toAction(mResources));
break;
case ACCESSIBILITY_SERVICES_CONFIRM_OFF:
mActions.add(ActionType.OK.toAction(mResources));
mActions.add(ActionType.CANCEL.toAction(mResources));
break;
case ACCESSIBILITY_SPEAK_PASSWORDS:
mActions = getEnableActions(((ActionType) mState).name(), getProperty());
break;
case ACCESSIBILITY_TTS_OUTPUT:
mActions.add(ActionType.ACCESSIBILITY_PREFERRED_ENGINE.toAction(
mResources, getDisplayNameForEngine(mTts.getCurrentEngine())));
if (mTtsSettingsEnabled) {
if (mTts.getLanguage() != null) {
mActions.add(ActionType.ACCESSIBILITY_LANGUAGE.toAction(
mResources, mTts.getLanguage().getDisplayName()));
} else {
mActions.add(ActionType.ACCESSIBILITY_LANGUAGE.toAction(
mResources, " "));
}
}
mActions.add(ActionType.ACCESSIBILITY_INSTALL_VOICE_DATA.toAction(mResources));
mActions.add(ActionType.ACCESSIBILITY_SPEECH_RATE.toAction(
mResources, getTtsRate(mHelper.getSecureIntSetting(
TTS_DEFAULT_RATE, getString(R.string.tts_rate_default_value)))));
if (mTtsSettingsEnabled) {
mActions.add(ActionType.ACCESSIBILITY_PLAY_SAMPLE.toAction(mResources));
}
break;
case ACCESSIBILITY_PREFERRED_ENGINE:
mActions = getEngines();
break;
case ACCESSIBILITY_LANGUAGE:
final ArrayList<String> available = mVoiceCheckData.getStringArrayListExtra(
TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
if (available != null && available.size() > 0) {
mActions = updateDefaultLocalePref(available);
if (mTts.getLanguage() != null) {
String currLang = getLanguageString(mTts.getLanguage());
checkSelectedAction(mActions, currLang);
}
}
break;
case ACCESSIBILITY_SPEECH_RATE:
mActions = Action.createActionsFromArrays(
mResources.getStringArray(R.array.tts_rate_values),
mResources.getStringArray(R.array.tts_rate_entries));
checkSelectedAction(mActions, mHelper.getSecureIntSetting(
TTS_DEFAULT_RATE, getString(R.string.tts_rate_default_value)));
break;
default:
break;
}
}
@Override
protected void updateView() {
refreshActionList();
switch ((ActionType) mState) {
case ACCESSIBILITY_SERVICES_SETTINGS:
setView(mServiceSettingTitle,
((ActionType) getPrevState()).getTitle(mResources),
null, R.drawable.ic_settings_accessibility);
break;
case ACCESSIBILITY_SERVICES_STATUS:
setView(((ActionType) mState).getTitle(mResources), mServiceSettingTitle,
null,
R.drawable.ic_settings_accessibility);
return;
case ACCESSIBILITY_SERVICES_CONFIRM_ON:
String onTitle = getString(
R.string.system_accessibility_service_on_confirm_title,
mServiceSettingTitle);
setView(onTitle, mServiceSettingTitle,
getString(R.string.system_accessibility_service_on_confirm_desc,
mServiceSettingTitle), R.drawable.ic_settings_accessibility);
return;
case ACCESSIBILITY_SERVICES_CONFIRM_OFF:
String offTitle = getString(
R.string.system_accessibility_service_off_confirm_title,
mServiceSettingTitle);
setView(offTitle, mServiceSettingTitle,
getString(R.string.system_accessibility_service_off_confirm_desc,
mServiceSettingTitle), R.drawable.ic_settings_accessibility);
return;
default:
setView(((ActionType) mState).getTitle(mResources),
getPrevState() != null ?
((ActionType) getPrevState()).getTitle(mResources) :
getString(R.string.settings_app_name),
((ActionType) mState).getDescription(mResources),
R.drawable.ic_settings_accessibility);
}
}
private String getTtsRate(String value) {
String[] values = mResources.getStringArray(R.array.tts_rate_values);
String[] entries = mResources.getStringArray(R.array.tts_rate_entries);
for (int index = 0; index < values.length; ++index) {
if (values[index].equals(value)) {
return entries[index];
}
}
return "";
}
private ArrayList<Action> getInstalledServicesActions() {
ArrayList<Action> actions = new ArrayList<Action>();
final List<AccessibilityServiceInfo> installedServiceInfos = AccessibilityManager
.getInstance(this).getInstalledAccessibilityServiceList();
Set<ComponentName> enabledServices = getEnabledServicesFromSettings(this);
final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
for (AccessibilityServiceInfo accInfo : installedServiceInfos) {
ServiceInfo serviceInfo = accInfo.getResolveInfo().serviceInfo;
ComponentName componentName = new ComponentName(serviceInfo.packageName,
serviceInfo.name);
final boolean serviceEnabled = accessibilityEnabled
&& enabledServices.contains(componentName);
String title = accInfo.getResolveInfo().loadLabel(getPackageManager()).toString();
actions.add(new Action.Builder()
.key(componentName.flattenToString())
.title(title)
.description(mHelper.getStatusStringFromBoolean(serviceEnabled))
.build());
}
return actions;
}
private String getSettingsForService(String serviceComponentName) {
final List<AccessibilityServiceInfo> installedServiceInfos = AccessibilityManager
.getInstance(this).getInstalledAccessibilityServiceList();
ComponentName comp = ComponentName.unflattenFromString(serviceComponentName);
if (comp != null) {
for (AccessibilityServiceInfo accInfo : installedServiceInfos) {
ServiceInfo serviceInfo = accInfo.getResolveInfo().serviceInfo;
if (serviceInfo.packageName.equals(comp.getPackageName()) &&
serviceInfo.name.equals(comp.getClassName())) {
String settingsClassName = accInfo.getSettingsActivityName();
if (!TextUtils.isEmpty(settingsClassName)) {
ComponentName settingsComponent =
new ComponentName(comp.getPackageName(), settingsClassName);
return settingsComponent.flattenToString();
} else {
return null;
}
}
}
}
return null;
}
private static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServicesSetting == null) {
enabledServicesSetting = "";
}
Set<ComponentName> enabledServices = new HashSet<ComponentName>();
SimpleStringSplitter colonSplitter = new SimpleStringSplitter(
ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
colonSplitter.setString(enabledServicesSetting);
while (colonSplitter.hasNext()) {
String componentNameString = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(
componentNameString);
if (enabledService != null) {
enabledServices.add(enabledService);
}
}
return enabledServices;
}
private ArrayList<Action> getEnableActions(String type, boolean enabled) {
ArrayList<Action> actions = new ArrayList<Action>();
actions.add(ActionBehavior.ON.toAction(ActionBehavior.getOnKey(type), mResources, enabled));
actions.add(ActionBehavior.OFF.toAction(ActionBehavior.getOffKey(type), mResources,
!enabled));
return actions;
}
private void checkSelectedAction(ArrayList<Action> actions, String selectedKey) {
for (Action action : actions) {
if (action.getKey().equalsIgnoreCase(selectedKey)) {
action.setChecked(true);
break;
}
}
}
private String getLanguageString(Locale lang) {
if (lang.getLanguage().isEmpty())
return "";
StringBuilder builder = new StringBuilder();
builder.append(lang.getLanguage());
if (!lang.getCountry().isEmpty()) {
builder.append('-');
builder.append(lang.getCountry());
}
if (!lang.getVariant().isEmpty()) {
builder.append('-');
builder.append(lang.getVariant());
}
return builder.toString();
}
private void updateDefaultEngine(String engine) {
if (DEBUG) {
Log.d(TAG, "Updating default synth to : " + engine);
}
// TODO Disable the "play sample text" preference and the speech
// rate preference while the engine is being swapped.
// 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 (DEBUG) {
Log.d(TAG, "Updating engine : Attempting to connect to engine: " + engine);
}
mTts = new TextToSpeech(getApplicationContext(), mUpdateListener, engine);
setTtsUtteranceProgressListener();
}
@Override
public void onActionClicked(Action action) {
/*
* For list preferences
*/
final String key = action.getKey();
final String title = action.getTitle();
switch((ActionType)mState){
case ACCESSIBILITY_SERVICES:
mServiceSettingTitle = action.getTitle();
mSelectedServiceComponent = action.getKey();
mSelectedServiceEnabled = mHelper.getStatusFromString(action.getDescription());
mSelectedServiceSettings = getSettingsForService(mSelectedServiceComponent);
if (mSelectedServiceSettings != null) {
// Service provides a settings component, so go to the Status/Settings screen
setState(ActionType.ACCESSIBILITY_SERVICES_SETTINGS, true);
} else {
// Service does not provide Settings, so go straight to Enable/Disable
setState(ActionType.ACCESSIBILITY_SERVICES_STATUS, true);
}
return;
case ACCESSIBILITY_PREFERRED_ENGINE:
mCurrentEngine = key;
updateDefaultEngine(mCurrentEngine);
// Delay the goBack here until we are done binding to the service, so we have
// the Language data available for the previous screen.
return;
case ACCESSIBILITY_LANGUAGE:
updateLanguageTo(
!TextUtils.isEmpty(key) ? mEnginesHelper.parseLocaleString(key) : null);
goBack();
return;
case ACCESSIBILITY_SPEECH_RATE:
mHelper.setSecureIntValueSetting(TTS_DEFAULT_RATE, key);
goBack();
return;
}
/*
* For regular states
*/
ActionKey<ActionType, ActionBehavior> actionKey = new ActionKey<ActionType, ActionBehavior>(
ActionType.class, ActionBehavior.class, action.getKey());
final ActionType type = actionKey.getType();
if (type != null) {
switch (type) {
case ACCESSIBILITY_PLAY_SAMPLE:
getSampleText();
return;
case ACCESSIBILITY_INSTALL_VOICE_DATA:
installVoiceData();
return;
case ACCESSIBILITY_SERVICE_CONFIG: {
ComponentName comp = ComponentName.unflattenFromString(
mSelectedServiceSettings);
Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(comp);
startActivity(settingsIntent);
return;
}
case ACCESSIBILITY_CAPTIONS: {
ComponentName comp = new ComponentName(this, CaptionSetupActivity.class);
Intent captionsIntent = new Intent(Intent.ACTION_MAIN).setComponent(comp);
startActivity(captionsIntent);
return;
}
case AGREE:
setProperty(true); // Agreed to turn ON service
return;
case DISAGREE:
setProperty(false); // Disagreed to turn service ON
return;
case OK:
setProperty(false); // ok to STOP Service
return;
case CANCEL:
goBack(); // Cancelled request to STOP service
return;
default:
}
}
final ActionBehavior behavior = actionKey.getBehavior();
switch (behavior) {
case ON:
setProperty(true);
return;
case OFF:
setProperty(false);
return;
default:
}
setState(type, true);
}
@Override
protected void setProperty(boolean enable) {
switch ((ActionType) mState) {
case ACCESSIBILITY_SPEAK_PASSWORDS:
mHelper.setSecureIntSetting(Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, enable);
break;
case ACCESSIBILITY_SERVICES_STATUS:
// Accessibility Service ON/OFF requires an extra confirmation screen.
if (enable) {
setState(ActionType.ACCESSIBILITY_SERVICES_CONFIRM_ON, true);
} else {
if (mSelectedServiceEnabled) {
setState(ActionType.ACCESSIBILITY_SERVICES_CONFIRM_OFF, true);
} else {
goBack();
}
}
return;
case ACCESSIBILITY_SERVICES_CONFIRM_ON:
setAccessibilityServiceState(mSelectedServiceComponent, enable);
mSelectedServiceEnabled = enable;
// go back twice: Remove the ON/OFF screen from the stack
goBack();
break;
case ACCESSIBILITY_SERVICES_CONFIRM_OFF:
setAccessibilityServiceState(mSelectedServiceComponent, enable);
mSelectedServiceEnabled = enable;
// go back twice: Remove the ON/OFF screen from the stack
goBack();
break;
}
goBack();
}
private boolean getProperty() {
if ((ActionType) mState == ActionType.ACCESSIBILITY_SPEAK_PASSWORDS) {
return mHelper.getSecureIntValueSettingToBoolean(
Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD);
}
return false;
}
private Set<ComponentName> getInstalledServices() {
Set<ComponentName> installedServices = new HashSet<ComponentName>();
installedServices.clear();
List<AccessibilityServiceInfo> installedServiceInfos =
AccessibilityManager.getInstance(this)
.getInstalledAccessibilityServiceList();
if (installedServiceInfos == null) {
return installedServices;
}
final int installedServiceInfoCount = installedServiceInfos.size();
for (int i = 0; i < installedServiceInfoCount; i++) {
ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
ComponentName installedService = new ComponentName(
resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name);
installedServices.add(installedService);
}
return installedServices;
}
public void setAccessibilityServiceState(String preferenceKey, boolean enabled) {
// Parse the enabled services.
Set<ComponentName> enabledServices = getEnabledServicesFromSettings(this);
// Determine enabled services and accessibility state.
ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
boolean accessibilityEnabled = false;
if (enabled) {
enabledServices.add(toggledService);
// Enabling at least one service enables accessibility.
accessibilityEnabled = true;
} else {
enabledServices.remove(toggledService);
// Check how many enabled and installed services are present.
Set<ComponentName> installedServices = getInstalledServices();
for (ComponentName enabledService : enabledServices) {
if (installedServices.contains(enabledService)) {
// Disabling the last service disables accessibility.
accessibilityEnabled = true;
break;
}
}
}
// Update the enabled services setting.
StringBuilder enabledServicesBuilder = new StringBuilder();
// Keep the enabled services even if they are not installed since we
// have no way to know whether the application restore process has
// completed. In general the system should be responsible for the
// clean up not settings.
for (ComponentName enabledService : enabledServices) {
enabledServicesBuilder.append(enabledService.flattenToString());
enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
}
final int enabledServicesBuilderLength = enabledServicesBuilder.length();
if (enabledServicesBuilderLength > 0) {
enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
}
Settings.Secure.putString(getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServicesBuilder.toString());
// Update accessibility enabled.
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
}
private ArrayList<Action> getEngines() {
ArrayList<Action> actions = new ArrayList<Action>();
List<EngineInfo> engines = mEnginesHelper.getEngines();
int totalEngine = engines.size();
for (int i = 0; i < totalEngine; i++) {
Action action = new Action.Builder()
.key(engines.get(i).name)
.title(engines.get(i).label)
.build();
actions.add(action);
}
mCurrentEngine = mTts.getCurrentEngine();
checkVoiceData(mCurrentEngine);
return actions;
}
private String getDisplayNameForEngine(String enginePackageName) {
List<EngineInfo> engines = mEnginesHelper.getEngines();
int totalEngine = engines.size();
for (int i = 0; i < totalEngine; i++) {
if (engines.get(i).name.equals(enginePackageName)) {
return engines.get(i).label;
}
}
// Not found, return package name then
return enginePackageName;
}
/*
* Check whether the voice data for the engine is ok.
*/
private void checkVoiceData(String engine) {
Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
intent.setPackage(engine);
try {
if (DEBUG) {
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 + ")");
}
}
/**
* Called when the TTS engine is initialized.
*/
public void onInitEngine(int status) {
if (status == TextToSpeech.SUCCESS) {
if (DEBUG) {
Log.d(TAG, "TTS engine for settings screen initialized.");
}
} else {
if (DEBUG) {
Log.d(TAG, "TTS engine for settings screen failed to initialize successfully.");
}
}
}
private void initSettings() {
mCurrentEngine = mTts.getCurrentEngine();
checkVoiceData(mCurrentEngine);
}
/*
* 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 (DEBUG) {
Log.d(TAG, "Updating engine: Successfully bound to the engine: " +
mTts.getCurrentEngine());
}
checkVoiceData(mTts.getCurrentEngine());
} else {
if (DEBUG) {
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(getApplicationContext(), mInitListener,
mPreviousEngine);
setTtsUtteranceProgressListener();
}
mPreviousEngine = null;
}
goBack();
}
private void setTtsUtteranceProgressListener() {
if (mTts == null) {
return;
}
mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
}
@Override
public void onDone(String utteranceId) {
}
@Override
public void onError(String utteranceId) {
Log.e(TAG, "Error while trying to synthesize sample text");
}
});
}
private ArrayList<Action> updateDefaultLocalePref(ArrayList<String> availableLangs) {
ArrayList<Action> actions = new ArrayList<Action>();
Locale currentLocale = mEnginesHelper.getLocalePrefForEngine(mCurrentEngine);
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
int selectedLanguageIndex = -1;
int i = 0;
for (Pair<String, Locale> entry : entryPairs) {
if (entry.second.equals(currentLocale)) {
selectedLanguageIndex = i;
}
Action action = new Action.Builder()
.key(entry.second.toString())
.title(entry.first).build();
actions.add(action);
}
return actions;
}
private void updateLanguageTo(Locale locale) {
mEnginesHelper.updateLocalePrefForEngine(mCurrentEngine, locale);
if (mCurrentEngine.equals(mTts.getCurrentEngine())) {
// Null locale means "use system default"
mTts.setLanguage((locale != null) ? locale : Locale.getDefault());
}
}
/**
* Called when voice data integrity check returns
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == GET_SAMPLE_TEXT) {
onSampleTextReceived(resultCode, data);
} else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
onVoiceDataIntegrityCheckDone(data);
}
}
/**
* Ask the current default engine to return a string of sample text to be
* spoken to the user.
*/
private void getSampleText() {
String currentEngine = mTts.getCurrentEngine();
if (TextUtils.isEmpty(currentEngine))
currentEngine = mTts.getDefaultEngine();
Locale defaultLocale = mTts.getDefaultLanguage();
if (defaultLocale == null) {
Log.e(TAG, "Failed to get default language from engine " + currentEngine);
return;
}
mTts.setLanguage(defaultLocale);
// TODO: This is currently a hidden private API. The intent extras
// and the intent action should be made public if we intend to make this
// a public API. We fall back to using a canned set of strings if this
// doesn't work.
Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT);
intent.putExtra("language", defaultLocale.getLanguage());
intent.putExtra("country", defaultLocale.getCountry());
intent.putExtra("variant", defaultLocale.getVariant());
intent.setPackage(currentEngine);
try {
if (DEBUG) {
Log.d(TAG, "Getting sample text: " + intent.toUri(0));
}
startActivityForResult(intent, GET_SAMPLE_TEXT);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "Failed to get sample text, no activity found for " + intent + ")");
}
}
private String getDefaultSampleString() {
if (mTts != null && mTts.getLanguage() != null) {
final String currentLang = mTts.getLanguage().getISO3Language();
String[] strings = mResources.getStringArray(R.array.tts_demo_strings);
String[] langs = mResources.getStringArray(R.array.tts_demo_string_langs);
for (int i = 0; i < strings.length; ++i) {
if (langs[i].equals(currentLang)) {
return strings[i];
}
}
}
return null;
}
private void onSampleTextReceived(int resultCode, Intent data) {
String sample = getDefaultSampleString();
if (resultCode == TextToSpeech.LANG_AVAILABLE && data != null) {
if (data != null && data.getStringExtra("sampleText") != null) {
sample = data.getStringExtra("sampleText");
}
if (DEBUG) {
Log.d(TAG, "Got sample text: " + sample);
}
} else {
if (DEBUG) {
Log.d(TAG, "Using default sample text :" + sample);
}
}
if (sample != null && mTts != null) {
// The engine is guaranteed to have been initialized here
// because this preference is not enabled otherwise.
final boolean networkRequired = isNetworkRequiredForSynthesis();
if (!networkRequired || networkRequired &&
(mTts.isLanguageAvailable(mTts.getLanguage()) >= TextToSpeech.LANG_AVAILABLE)) {
HashMap<String, String> params = new HashMap<String, String>();
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "Sample");
mTts.speak(sample, TextToSpeech.QUEUE_FLUSH, params);
} else {
Log.w(TAG, "Network required for sample synthesis for requested language");
// TODO displayNetworkAlert();
}
} else {
// TODO: Display an error here to the user.
Log.e(TAG, "Did not have a sample string for the requested language");
}
}
private boolean isNetworkRequiredForSynthesis() {
Set<String> features = mTts.getFeatures(mTts.getLanguage());
return features.contains(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS) &&
!features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
}
/*
* Step 5: The voice data check is complete.
*/
private void onVoiceDataIntegrityCheckDone(Intent data) {
final String engine = mTts.getCurrentEngine();
if (engine == null) {
Log.e(TAG, "Voice data check complete, but no engine bound");
return;
}
if (data == null) {
Log.e(TAG, "Engine failed voice data integrity check (null return)" +
mTts.getCurrentEngine());
return;
}
Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine);
setVoiceDataDetails(data);
}
public void setVoiceDataDetails(Intent data) {
mVoiceCheckData = data;
// This might end up running before getView above, in which
// case mSettingsIcon && mRadioButton will be null. In this case
// getView will set the right values.
if (mVoiceCheckData != null) {
mTtsSettingsEnabled = true;
} else {
mTtsSettingsEnabled = false;
}
}
/**
* 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(mCurrentEngine)) {
return;
}
Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
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 + ")");
}
}
@Override
protected Object getInitialState() {
return ActionType.ACCESSIBILITY_OVERVIEW;
}
}