Create an App Widget in Android with Text-to-Speech (TTS)

Keywords: AppWidgetProvider RemoteViews AppWidgetManager BroadcastReceiver Widget Configuration Activity AlarmManager TextToSpeech Service PreferenceActivity OnPreferenceChangeListener
Contents

- Overview
- Create a new Android project
- Create Vocab App Widget
- Android manifest
- The Widget Provider class
- The Widget layout
- Using AlarmManager to update App Widget
- The Widget Configuration activity
- The Widget Configuration layout
- Android Text-to-Speech (TTS) using Service
- Create a Settings screen
- What's next?
11. Create a Settings screen

Go to File > New > Other... and select Android Object within Android folder in the wizard. Click Next and select Settings Activity in listed templates.

All the necessary code will get generated in the project. Let's take a look at the modifications we've done to the SettingsActivity class.
public class SettingsActivity extends PreferenceActivity { public static String TTS_PREF = "tts_pref"; public static String ALPHABET_PREF = "alphabet_pref"; public static String INTERVAL_PREF = "interval_pref"; public static String DEFAULT_INTERVAL = "60"; public static String ALL_ALPHABET = "*"; private static final boolean ALWAYS_SIMPLE_PREFS = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(getString(R.string.title_activity_settings)); setupActionBar(); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void setupActionBar() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { getActionBar().setDisplayHomeAsUpEnabled(false); getActionBar().setDisplayUseLogoEnabled(true); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { getActionBar().setHomeButtonEnabled(false); } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); setupSimplePreferencesScreen(); } private void setupSimplePreferencesScreen() { if (!isSimplePreferences(this)) { return; } // In the simplified UI, fragments are not used at all and we instead // use the older PreferenceActivity APIs. // Add 'general' preferences. addPreferencesFromResource(R.xml.pref_general); // Bind the summaries of EditText/List/Dialog/Ringtone preferences // to their values. When their values change, their summaries are // updated to reflect the new value, per the Android Design // guidelines. bindPreferenceSummaryToValue(findPreference(TTS_PREF)); bindPreferenceSummaryToValue(findPreference(ALPHABET_PREF)); bindPreferenceSummaryToValue(findPreference(INTERVAL_PREF)); } @Override public boolean onIsMultiPane() { return isXLargeTablet(this) && !isSimplePreferences(this); } private static boolean isXLargeTablet(Context context) { return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; } private static boolean isSimplePreferences(Context context) { return ALWAYS_SIMPLE_PREFS || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || !isXLargeTablet(context); } @Override @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void onBuildHeaders(List<Header> target) { if (!isSimplePreferences(this)) { loadHeadersFromResource(R.xml.pref_headers, target); } } private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object value) { String stringValue = value.toString(); if (preference instanceof ListPreference) { // For list preferences, look up the correct display value in // the preference's 'entries' list. ListPreference listPreference = (ListPreference) preference; int index = listPreference.findIndexOfValue(stringValue); // Set the summary to reflect the new value. preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null); } else { // For all other preferences, set the summary to the value's // simple string representation. preference.setSummary(stringValue); } return true; } }; private static void bindPreferenceSummaryToValue(Preference preference) { // Set the listener to watch for value changes. preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); if (TTS_PREF.equals(preference.getKey())) return; // Trigger the listener immediately with the preference's // current value. sBindPreferenceSummaryToValueListener.onPreferenceChange( preference, PreferenceManager.getDefaultSharedPreferences(preference.getContext()).getString(preference.getKey(), "")); } //------------------------------------------------------------------------ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static class GeneralPreferenceFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.pref_general); // Bind the summaries of EditText/List/Dialog/Ringtone preferences // to their values. When their values change, their summaries are // updated to reflect the new value, per the Android Design // guidelines. bindPreferenceSummaryToValue(findPreference(TTS_PREF)); bindPreferenceSummaryToValue(findPreference(ALPHABET_PREF)); bindPreferenceSummaryToValue(findPreference(INTERVAL_PREF)); } } }We have removed unnecessary preferences and have kept only General preferences for our simple requirement. There is a corresponding pref_general.xml file in res/xml directory that we've also modified.
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <Preference android:summary="@string/app_desc" /> <ListPreference android:defaultValue="*" android:entries="@array/alphabets" android:entryValues="@array/alphabets" android:key="alphabet_pref" android:negativeButtonText="@null" android:positiveButtonText="@null" android:title="Show words beginning with" /> <ListPreference android:defaultValue="60" android:entries="@array/refresh_interval_titles" android:entryValues="@array/refresh_interval_values" android:key="interval_pref" android:negativeButtonText="@null" android:positiveButtonText="@null" android:title="Refresh interval" /> <CheckBoxPreference android:defaultValue="false" android:key="tts_pref" android:summary="Listen to words and their meanings" android:title="Enable Text-to-speech" /> </PreferenceScreen>
Modify SettingsActivity class as follows.
private static final int TTS_CHECK_CODE = 101; private static WeakReference<Activity> weakActivity; @Override protected void onCreate(Bundle savedInstanceState) { //... weakActivity = new WeakReference<Activity>(this); } private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object value) { String stringValue = value.toString(); if (TTS_PREF.equals(preference.getKey())){ if ("true".equalsIgnoreCase(value.toString())) { Intent checkIntent = new Intent(); checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); Activity activity = weakActivity != null ? weakActivity.get() : null; if (activity != null) { activity.startActivityForResult(checkIntent, TTS_CHECK_CODE); } return false; } } //... return true; } }; protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == TTS_CHECK_CODE) { if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { // success, create the TTS instance CheckBoxPreference cbPreference = (CheckBoxPreference) findPreference(TTS_PREF); cbPreference.setChecked(true); } else { Toast.makeText(this, getString(R.string.setup_tts), Toast.LENGTH_LONG).show(); // missing data, install it Intent installIntent = new Intent(); installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); startActivity(installIntent); } } }Basically, we allow user to enable TTS only if its supported on the device, otherwise direct him to the Play Store.