Create an App Widget in Android with Text-to-Speech (TTS)
DownloadKeywords: 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?
5. The Widget Provider class
The Widget Provider class is basically a BroadcastReceiver but the method we need to override is onUpdate(). Additionally, we can override onDeleted(), onEnabled(), and onDisabled() for receiving various callbacks.You can learn more about using AppWidgetProvider class from the official docs.
We have modified the generated code (by ADT) as follows.public class VocabWidget extends AppWidgetProvider { public static final String ACTION_UPDATE = "com.appsrox.dailyvocab.action.UPDATE"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // There may be multiple widgets active, so update all of them final int N = appWidgetIds.length; for (int i = 0; i < N; i++) { updateAppWidget(context, appWidgetManager, appWidgetIds[i]); } } private void onUpdate(Context context) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); ComponentName thisAppWidgetComponentName = new ComponentName(context.getPackageName(),getClass().getName()); int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName); onUpdate(context, appWidgetManager, appWidgetIds); } @Override public void onDeleted(Context context, int[] appWidgetIds) { // When the user deletes the widget } @Override public void onEnabled(Context context) { // Enter relevant functionality for when the first widget is created Util.scheduleUpdate(context); } @Override public void onDisabled(Context context) { // Enter relevant functionality for when the last widget is disabled Util.clearUpdate(context); } static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { String[] content = getContent(context); // Construct the RemoteViews object RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.vocab_widget); views.setTextViewText(R.id.txtWord, content[0]); views.setTextViewText(R.id.txtMeaning, content[1]); views.setOnClickPendingIntent(R.id.btnNext, getPendingSelfIntent(context, ACTION_UPDATE)); // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, views); } private static PendingIntent getPendingSelfIntent(Context context, String action, String... content) { Intent intent = new Intent(context, VocabWidget.class); intent.setAction(action); return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } @Override public void onReceive(Context context, Intent intent) { if (ACTION_UPDATE.equals(intent.getAction())) { onUpdate(context); } else super.onReceive(context, intent); } }Essentially, we have taken control of widget update by scheduling an alarm when the widget gets enabled. And we have overridden onReceive() to handle the UPDATE broadcast triggered by the alarm.
The other modification we did is in updateAppWidget() method which is responsible for providing UI to the widget. Whenever the widget receives an update then onUpdate() is invoked which in turn calls updateAppWidget().
A widget runs as part of the host app process so we need RemoteViews for updating the UI and PendingIntent for performing any action.
6. The Widget layout
Now, let's take a look at the layout of the widget. Apart from text views, it contains buttons for speech functionality and for refreshing the widget manually.Modify vocab_widget.xml in res/layout as follows.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/widget_margin" > <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/scroll" android:padding="10dp" > <TextView android:id="@+id/txtWord" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:clickable="true" android:textSize="18sp" android:typeface="serif" android:textStyle="bold|italic" android:textColor="@android:color/black" /> <ImageButton android:id="@+id/btnSpeaker" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/txtWord" android:layout_alignTop="@id/txtWord" android:paddingRight="2dp" android:paddingLeft="10dp" android:paddingTop="2dp" android:paddingBottom="10dp" android:background="@null" android:src="@drawable/speaker" android:visibility="gone" /> <TextView android:id="@+id/txtMeaning" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/txtWord" android:layout_alignLeft="@id/txtWord" android:layout_marginRight="10dp" android:textSize="12sp" android:typeface="sans" android:textColor="@android:color/black" /> <ImageButton android:id="@+id/btnNext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="2dp" android:paddingLeft="10dp" android:paddingTop="10dp" android:paddingBottom="2dp" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:background="@null" android:src="@drawable/next" /> </RelativeLayout> </FrameLayout>
We can only use a limited set of widgets and layouts for the widget UI as listed here.
7. Using AlarmManager to update App Widget
Recall that in widget provider class we used onEnabled() and onDisabled() callback methods for scheduling and clearing the alarm through utility methods. Here is the code for Util class.public class Util { public static void scheduleUpdate(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String interval = prefs.getString(SettingsActivity.INTERVAL_PREF, null); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); long intervalMillis = Integer.parseInt(interval)*60*1000; PendingIntent pi = getAlarmIntent(context); am.cancel(pi); am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), intervalMillis, pi); } private static PendingIntent getAlarmIntent(Context context) { Intent intent = new Intent(context, VocabWidget.class); intent.setAction(VocabWidget.ACTION_UPDATE); PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0); return pi; } public static void clearUpdate(Context context) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.cancel(getAlarmIntent(context)); } }We get the refresh interval provided by the user from shared preferences and use it for creating inexact repeating alarms using AlarmManager.
Also we need to reschedule the alarms since they are lost on reboot. So we create a broadcast receiver for boot completed and register it in the manifest.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <application> <!-- ... --> <receiver android:name=".BootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> </application>The BootReceiver simply reschedules the alarms if the widget was added to the Home screen.
public class BootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] ids = appWidgetManager.getAppWidgetIds(new ComponentName(context, VocabWidget.class)); if (ids.length > 0) { Util.scheduleUpdate(context); } } }