Chapter 7
In This Chapter
Seeing how app widgets work in Android
Understanding remote views and pending intents
Building an AppWidgetProvider
Putting your widget on the Home screen
Usability is the name of the game in regard to all disciplines of application development: If your application isn’t easy to use, users simply won’t use it.
If you’ve followed the first six chapters of this book to build the Silent Mode Toggle application, it undoubtedly works well. But it still requires launching an app to use. To make this application even easier to use, simply turn it into a Home screen widget. A Home screen widget allows you to place a view on the user’s Home screen, which they can use to interact with your app without having to open the app.
In this chapter, you build an app widget for your application. An app widget normally is a small icon or tiny view on the Home screen. Users can interact with your application by simply tapping this widget to toggle their phone’s Silent mode. This chapter introduces you to these classes:
Each of these classes plays a vital role in Android as well as in the app widget framework.
An app widget in Android is a special kind of view that can be embedded on your device’s Home screen. An app widget can accept user input via click events, and it can update itself regularly. A user can add an app widget to the Home screen by tapping the Applications button and then selecting Widgets. The result is shown in Figure 7-1.
To make the Silent Mode Toggle application more usable, build an app widget for it so that users can add the widget to the Home screen. Tapping the widget changes the phone’s ringer mode without having to open the application. The widget also updates its layout to indicate what state the phone is in, as shown in Figure 7-2.
When you develop apps in Android, remember that it’s based on the Linux kernel. Linux comes supplied with its own idioms about security, and the Android platform inherits them. For example, the Android security model is heavily based around the Linux user, file, and process security model.
Because the Home screen (also known as the Launcher) is its own application and thus has its own unique user, developers such as yourself aren’t allowed to directly run your application code on the Home screen for security reasons. To provide a way to access the Home screen and modify the contents of a particular area on it from an application, Android provides the RemoteViews architecture: It lets you run code inside your application, in a separate process from the Home screen application, but it still allows a widget’s view to be updated on the Home screen. This architecture protects the Home screen app from buggy or malicious apps, because no third‐party app code needs to run in the Home screen app.
Suppose that a user taps the Home screen app widget (in this case, an icon she added to the Home screen). This action sends a request — addressed to your application — to change the ringer mode. Android routes the request to your application, and the application processes the request, instructing the Android platform to change the ringer mode and update the app widget on the Home screen with a new image. None of this code is run in the Home screen application — it’s all run remotely in your application, with Android routing the message to the appropriate application. These messages are called intents in Android.
A remote view combines a little magic with innovative engineering. Known as the RemoteViews class on the Android platform, it allows your application to programmatically supply a remote user interface to the Home screen in another process. The app widget code isn’t an activity (as in earlier chapters), but is an implementation of an AppWidgetProvider. When Android routes an intent to your application from the Home screen, the message is handled in your implementation of the AppWidgetProvider class.
The AppWidgetProvider class allows the developer to programmatically interact with the app widget on the Home screen. When this interaction takes place, messages are sent from the Home screen app widget to your application via broadcast events. Using these broadcast events, you can respond when the app widget is updated, enabled, disabled, or deleted. You can also update the look and feel of the app widget on the Home screen by providing a new view. Because this view is located on the Home screen and not within your running application, you use RemoteViews to update the app widget layout. All the logic that determines what should happen is contained in an implementation of AppWidgetProvider and initiated by an intent.
Picture the app widget framework (AppWidgetManager) as the translator of a conversation between two entities. If you need to speak to someone who knows Italian, but you don’t know how to speak Italian, you would find a translator who would accept your input, translate it into Italian, and relay the message to the native Italian speaker. The same process applies to the app widget framework: This framework is your translator.
When the Italian native (AppWidgetHost, which is the Home screen, in this case) needs to let you know that something has happened (such as a user tapping a button), the translator (the app widget framework in the Android system) translates the action into a message (intent) that you can understand (tapping a particular button). At that time, you can respond with the action you want to take (such as change the app widget background color to lime green), and the translator (the app widget framework) relays the message to the native Italian speaker (AppWidgetHost; that is, the Home screen via the Android system). The Home screen then updates the background color of the view.
When the user needs to interact with your application, she communicates by tapping the app widget using the Android messaging architecture (as described earlier), and you aren’t immediately notified. However, this doesn’t mean you can’t be notified about a click event on your app widget — it’s just done a little differently than regular views.
App widget click events contain instructions for what to do when a click event happens via the Intent class in the Android framework.
An Intent object in Android is a message telling Android to make something happen. When you turn on a light using a wall switch, the action of your intent is to turn on the light, so you flip the switch to the On position. In Android, this action correlates to creating an instance of the Intent class with an action in it specifying that the light is to be turned on:
Intent turnLightOn = new Intent("TURN_LIGHT_ON");
This intent is fired off using startActivity() in the Android messaging system (as described in Chapter 1), and the appropriate activity handles the Intent. (If multiple activities respond, Android lets the user choose one to do the work.) However, in the physical world, an electrical connection is made by positioning the switch to the On position, resulting in illuminating the light. In Android, you have to provide code, in the form of an activity, to make this happen. This activity (which could hypothetically be named TurnLightOnActivity) responds to the turnLightOn intent. If you’re working with an app widget, you must handle the intent in a BroadcastReceiver rather than in an activity. AppWidgetProvider is a subclass of a BroadcastReceiver with a few extra bells and whistles that configure a lot of the app widget framework for you. A BroadcastReceiver is responsible for receiving broadcast intents.
The AppWidgetProvider (a BroadcastReceiver) handles the intent from the Home screen and responds with the appropriate result that you determined, using your code, inside your custom AppWidgetProvider.
An intent is a message that can carry a wide variety of data describing an operation that needs to be performed. An intent can be addressed to a specific activity or broadcast to a generic category of receivers known as BroadcastReceivers (which includes AppWidgetProvider). The Intent, Activity, and BroadcastReceiver system is reminiscent of the message bus architecture, where a message is placed on a message bus and any of the endpoints on the bus respond to the message if (and only if) they know how. If no endpoint knows how to respond to the message, or if the message wasn’t addressed to the endpoint, the app will crash.
An intent can be launched into the message bus system in a couple of ways:
An intent is the glue that binds together the various components of the application. It provides a mechanism that allows you to communicate within your app, as well as communicate with other apps.
An intent’s data consists of these elements:
Table 7-1 demonstrates a few action and data parameters for Intent objects and their simple data structure.
Table 7‐1 Intent Data Examples
Action |
Data |
Result |
ACTION_VIEW |
tel:123 |
Display the dialer with the given number (123) filled in. |
ACTION_DIAL |
content://contacts/people/1 |
Display the dialer showing the phone number from the contact with the ID of 1. |
ACTION_EDIT |
content://contacts/people/1 |
Edit the information about the person whose given identifier is 1. |
ACTION_VIEW |
Display the web page of the given intent. |
|
ACTION_VIEW |
content://contacts/people |
Display a list of all people in the Contacts system. |
Intents can also carry an array of other data that include these elements:
In the Android system, intents are evaluated either explicitly or implicitly.
The intent has specified an explicit component or the exact class that will execute the data in the intent. (Again, this is likely the most common way to address intents.) This type of intent often contains no other data because it’s a means to start other activities within an application. You find out later in this chapter how to use an explicit intent in an application.
An example of an explicit intent would be new Intent( ... , MainActivity.class) to create an intent that would explicitly launch your MainActivity.
The intent hasn’t specified a component or class. Instead, the intent must provide enough information about the action that needs to be performed with the given data for the Android system to determine which available components can handle the intent — sometimes referred to as an address and a payload.
An example is setting up an email intent that contains email fields (To, CC, Subject, and Body) and an email MIME type. Android interprets it as an email and gives the user of the device the opportunity to choose which application should handle the intent. Possibilities include Gmail or Exchange or a POP email account. The user determines which email program to use. The Android capability to identify possible matches for the given intent is known as intent resolution.
To create an implicit email intent, you would do something like the following:
new Intent(Intent.ACTION_SENDTO,
Uri.parse("mailto:[email protected]"));
A PendingIntent is used for something different than regular intents: A PendingIntent is created by your application and given to another, completely different application. By giving another application a PendingIntent, you’re granting the other application the right to perform the operation you have specified as though the application were your own application. When the other application deems that the given work needs to take place, it executes the PendingIntent, which is sent back to your application to perform the necessary work.
For the purpose of the Silent Mode Toggle application, you use the PendingIntent.getBroadcast() call to create a PendingIntent. This call returns a PendingIntent that you can use to wrap a regular intent that instructs the Silent Mode Toggle app to toggle Silent mode. The call takes these four parameters:
The Intent object is wrapped inside a PendingIntent because a PendingIntent is used for inter‐process communication. When the PendingIntent is fired off, the real work that needs to be done is wrapped up in the broadcast Intent object.
That’s a lot of information! Now that you understand the basics of the Android intent system, it’s time to implement the guts of the application inside this app widget.
The process of sending messages between the Home screen app widget and your application is handled via the Android messaging system, the PendingIntent class, and the AppWidgetProvider. In this section, you build each component to get your first app widget up and running on the Home screen.
Implementing the AppWidgetProvider is fairly straightforward: Open Android Studio and open the Silent Mode Toggle application.
To add a new class to the com.dummies.silentmodetoggle package and provide a name, such as AppWidget.java, follow these steps:
Name the class AppWidget and click Finish.
The new class is added to the selected package.
The AppWidget class has no code in it at first — it’s an empty shell. In the code file you just created, type the code shown in Listing 7‐1 into the editor.
Listing 7‐1: The Initial Setup of the App Widget
/**
* The main class that represents our app's widget.
* Dispatches to a service to do all of the heavy lifting.
*/
public class AppWidget extends AppWidgetProvider { →5
@Override
public void onUpdate(Context context, AppWidgetManager →8
appWidgetManager, int[] appWidgetIds) {
context.startService(new Intent(context, AppWidgetService.class)); →11
}
}
This list briefly describes the numbered lines:
→ 5 The AppWidget class extends from AppWidgetProvider. Remember that AppWidgetProvider is a BroadcastReceiver, so it can receive broadcasted intents.
→ 8 Overrides the onUpdate method in AppWidgetProvider. onUpdate is called when the widget is first created. It is also called periodically at a set interval that you will define later in widget_provider.xml.
→ 11 Starts a service so the service can take on the responsibility of updating the widget without you having to worry about how long the responses take to generate. This is necessary for any widgets that do any sort of I/O (network, disk, and so on). Our widget doesn’t do I/O, so using a service is not strictly speaking necessary, but it’s a very common pattern and it’s important to know.
The AppWidgetProvider does all the work of responding to events from the RemoteViews, but how so? Recall that AppWidgetProvider is a subclass of BroadcastReceiver. At a high level, a BroadcastReceiver is a component that can receive broadcast messages from the Android system. When a user taps a clickable view in the RemoteViews on the Home screen (such as a button), the Android system broadcasts a message informing the receiver that the view was clicked. After the message is broadcast, the AppWidgetProvider can handle that message.
The app widget needs to have a layout for Android to know what to display on the Home screen. The widget layout file defines what the widget will look like while on the Home screen. Earlier in this chapter, Figure 7-2 showed the app widget running in the emulator.
To create the widget layout, create an XML layout file in the res/layout directory. Create one now and name it app_widget.xml.
The contents of app_widget.xml are shown in Listing 7‐2.
Listing 7‐2: The Contents of app_widget.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/phone_state" →3
android:layout_height="wrap_content" →4
android:layout_width="wrap_content"
android:src="@drawable/icon_ringer_on" →6
android:contentDescription="@string/toggle_silent_mode"/> →7
This layout should be nothing new. It’s simply a single ImageView with an image that will represent whether the phone is in Silent mode or not.
Here’s what the code is doing:
→ 3 Sets the ID of the image to phone_state so that it can be referenced later in the Java code. The + tells Android that this is a new ID and to create it.
→ 4 The height and width of this view is set to wrap the image.
→ 6 Sets the initial icon for the ImageView. You will write the code to update this icon based on the ringer state of the phone later. You can download icon_ringer_on from the sample source code on the book’s website.
→ 7 A simple text string that describes what the image is. This is provided for accessibility. It’s a good practice to include contentDescription text for any images you supply in your app. Go ahead and create a string resource for this string in your res/values/strings.xml file. Name it toggle_silent_mode and set its value to something like "Toggle Silent Mode".
After the PendingIntent has started your AppWidgetProvider, you perform some work on behalf of the calling application (in this case, the Home screen application). In the following sections, you perform time‐sensitive work on behalf of the caller.
The work that your app does to update the widget is divided into two parts: The AppWidgetProvider, which must finish processing its work quickly, and the IntentService, which can take as long as it wants to finish.
Most widgets will have an AppWidgetProvider that does a little bit of light work, but passes all the heavier work to an IntentService to execute in the background. Anything that involves I/O (such as reading or writing from a network, database, or disk) or a lot of CPU processing, should be done on a background thread in something like an IntentService.
Any code that executes for too long without responding to the Android system is subject to the Application Not Responding (ANR) error. App widgets are especially vulnerable to ANR errors because they’re executing code in a remote process, and because app widgets execute across process boundaries that can take time to set up, execute, and tear. The Android system watches app widgets to ensure that they don’t take too long to execute. When they do, the calling application (the Home screen) locks up and the device is unusable. Therefore, the Android platform wants to ensure that you’re never capable of making the device unresponsive.
Because app widgets are expensive in regard to CPU and memory, judging whether an app widget will cause an ANR error is difficult. If the device isn’t doing any other expensive tasks, the app widget would probably work just fine. However, if the device is in the middle of expensive CPU or I/O operations, the app widget can take too long to respond — causing an ANR error. To work around this problem, move any CPU‐ or I/O‐intensive work of the app widget into an IntentService that can take as long as it needs to complete — which in turn doesn’t affect the Home screen application.
Unlike most background services, which are long‐running, an IntentService uses the work queue processor pattern, which handles each intent in turn using a worker thread, and it stops when it runs out of work. In layman’s terms, the IntentService simply runs the work given to it as a background service, and then stops the background service when no more work needs to be done.
The AppWidget in this example isn’t doing any I/O and isn’t CPU intensive, so technically it probably doesn’t need to use an IntentService. But it’s more common that your widgets will be doing some amount of I/O, so it’s an important design pattern for you to understand.
Create a new class called AppWidgetService in com.dummies.silentmodetoggle.widget, then type the code in Listing 7‐3 into your code editor.
Listing 7‐3: The AppWidgetService
public class AppWidgetService extends IntentService { →1
private static String ACTION_DO_TOGGLE = "actionDoToggle"; →3
AudioManager audioManager;
public AppWidgetService() {
super("AppWidgetService"); →8
}
@Override
public void onCreate() { →12
// Always call super.onCreate
super.onCreate();
audioManager = (AudioManager) getSystemService( →16
Context.AUDIO_SERVICE);
}
@Override
protected void onHandleIntent(Intent intent){ →21
if( intent!=null && intent.getBooleanExtra( →23
ACTION_DO_TOGGLE,false)) {
RingerHelper.performToggle(audioManager);
}
AppWidgetManager mgr = AppWidgetManager.getInstance(this); →28
ComponentName name = new ComponentName(this, AppWidget.class); →30
mgr.updateAppWidget(name, updateUi());
}
private RemoteViews updateUi() { →35
RemoteViews remoteViews = new RemoteViews(getPackageName(), →36
R.layout.app_widget);
int phoneImage = RingerHelper.isPhoneSilent(audioManager) →39
? R.drawable.icon_ringer_off
: R.drawable.icon_ringer_on;
remoteViews.setImageViewResource(R.id.phone_state, phoneImage); →42
Intent intent = new Intent(this, AppWidgetService.class) →44
.putExtra(ACTION_DO_TOGGLE,true);
PendingIntent pendingIntent = →47
PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
remoteViews.setOnClickPendingIntent(R.id.widget, pendingIntent); →51
return remoteViews;
}
}
The following list briefly explains the purpose of the major sections of code:
→ 1 The service that handles all your widget’s operations. The intent sent to the service will tell it what you it want to do.
This service is an instance of IntentService. An IntentService is a convenient way to handle things that need to be done on background threads. Whenever a new intent is received, onHandleIntent executes in a background thread. This allows you to perform whatever operations you want to in the background — no matter how long they might take — without blocking the foreground UI thread (which would cause the app to hang).
→ 3 A flag that you set in your intent whenever you want to indicate that you want to toggle the phone’s silent setting.
→ 8 All IntentServices need to have a name. Ours is called AppWidgetService.
→ 12 onCreate is called when the service is initialized, after the object’s Java constructor.
→ 16 Just like in the activity, you’ll get a reference to Android’s AudioManager so you can use it to toggle our ringer.
→ 21 onHandleIntent is called on a background thread. This is where all your heavy processing happens. All IntentServices must override onHandleIntent.
→ 23 Checks the intent. If it says ACTION_DO_TOGGLE, then it toggles the phone’s Silent mode. If it doesn’t say ACTION_DO_TOGGLE, then this is just an update request, so it updates the UI.
→ 28 Gets a reference to Android’s AppWidgetManager, which is used to update the widget’s state.
→ 30 Updates the widget’s UI. First, find the name for your widget, then ask the AppWidgetManager to update it using the views that you’ll construct in updateUi() in line 35.
→ 35 Returns the RemoteViews that is used to update the widget. Similar to updateUi() in MainActivity, but appropriate for use with widgets.
→ 36 Inflates the res/layout/app_widget.xml layout file into a RemoteViews object, which communicates with the widget.
→ 39 Determines which image to use in the widget.
→ 42 Sets the appropriate image.
→ 44 Creates an intent to toggle the phone’s state. This intent specifies ACTION_DO_TOGGLE=true, which you look for in onHandleIntent on line 23.
→ 47 Wraps the intent in a PendingIntent, which gives someone in another process permission to send you an intent. In this case, the widget is actually running in another process (the device’s launcher process), so it must have a pending intent to communicate back into your service.
You should specify FLAG_ONE_SHOT to this intent to ensure it is used only once. There are some situations where a PendingIntent can be automatically retried on your behalf, and you want to ensure that you don’t accidentally do a few extra toggles. See
for more information about pending intents.http://d.android.com/reference/android/app/PendingIntent.html
→ 51 Gets the layout for the app widget and attaches an on‐click listener to the button.
After you’ve written the code to handle the updating of the app widget, you might wonder how to list it on the Widgets menu. This fairly simple process requires you to add a single XML file to your project. This file describes basic metadata about the app widget so that the Android platform can determine how to lay out the app widget on the Home screen. Follow these steps:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="1800000"
android:initialLayout="@layout/app_widget"/>
The minWidth and minHeight properties are used for setting the minimum amount of space that the view needs on the Home screen. 40dp represents the size of one widget “cell,” which is all we need for this widget.
The updatePeriodMillis property defines how often the app widget should attempt to update itself. You will want the widget to update itself periodically in case the user changes the state of the ringer using some other mechanism. Therefore, this value is set to 1800000 milliseconds — 30 minutes. Every 30 minutes, the app attempts to update itself by sending an intent that executes the onUpdate() method call in the AppWidgetProvider.
The initialLayout property identifies what the app widget looks like when the app widget is first added to the Home screen, before any work takes place. The initial layout is shown until the widget finishes updating itself.
An example of a longer delay is an app widget that checks Twitter for status updates. The initialLayout is shown until updates are received from Twitter. Inform the user in the initialLayout that information is loading to keep him aware of what’s happening when the app widget is initially loaded on the Home screen. You can do this by providing a TextView with the contents of "Loading . . ." while the AppWidgetProvider does its work.
Anytime you add an activity, a service, or a broadcast receiver (or certain other items) to your application, you need to declare them in the application manifest file. The application manifest presents vital information to the Android platform — namely, the components of the application. The system doesn’t recognize the Activity, Service, and BroadcastReceiver objects that aren’t declared in the application manifest.
To add your AppWidgetProvider and IntentService to your application manifest file, open the AndroidManifest.xml file and type the code shown in Listing 7‐4 into the already existing file. Bolded lines are newly added lines for the new components.
Listing 7‐4: An Updated AndroidManifest.xml File with New Components Registered
<<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dummies.silentmodetoggle">
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:allowBackup="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver
android:name="com.dummies.silentmodetoggle.widget.AppWidget"
android:label="@string/app_name"> →21
<intent-filter>
<action android:name= →24
"android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider"/> →29
</receiver>
<service android:name= →32
"com.dummies.silentmodetoggle.widget.AppWidgetService"/>
</application>
</manifest>
The following list briefly describes each section:
→ 21 The opening element registers a BroadcastReceiver as part of this application. The name property identifies the name of the receiver — in this case, .widget.AppWidget, which correlates to the AppWidget.java file in the application. The name and label help identify the receiver.
→ 24 Identifies what kind of intent (based on the action of the intent in the intent filter) the app widget automatically responds to when the particular intent is broadcast. Known as an IntentFilter, it helps the Android system understand what kind of events your app should be notified of. In this case, your application is concerned about the APPWIDGET_UPDATE action of the broadcast intent. This event fires after the updatePeriodMillis property has elapsed, which is defined in the widget_provider.xml file. Other actions include enabled, deleted, and disabled.
→ 29 Identifies the location of the widget_provider.xml file that you recently built into your application. Android uses the widget_provider to help determine defaults and to lay out parameters for your app widget.
→ 32 The <service> element registers the AppWidgetService with your application. This is the background service that does most of the work for your widget.
At this point, your application is ready to be installed and tested. To install the application, choose Run⇒Run ‘Silent Mode Toggle’. It should show up on the emulator. Return to the Home screen by pressing the Home key. You can now add to the Home screen the app widget that you recently created.
Adding a widget to your Home screen is easy — follow these steps:
You have now added the Silent Mode Toggle widget to the Home screen. You can tap the icon to change the ringer mode and the icon changes accordingly. (Refer to Figure 7-2.)
44.220.184.63