Understanding Intents and Intent Filters
This chapter will delve into intents, which are messaging objects that carry communications between the major components of your application—your activities, services, and broadcast receivers, which handle Android messaging. We have seen that Android development is highly modularized, and intents provide a way to wire these modules together to form a cohesive yet flexible application with secure, fluid communication among all of its components.
This is a fairly complex and important topic, and we are going to cover intents as they pertain to activities, services, and broadcast providers in detail. In fact, by the time we get to the end of the chapter, we will have an application that has four different XML files and four different Java files open in the Eclipse IDE. Lucky we are close to the end of the book because for a book on Android for absolute beginners, this chapter is going to seem a bit advanced. We’ll chalk it up to a rapid learning process and dive right in.
An intent is represented by the android.content.Intent class. It is in the content package because intents can be used to quickly access content providers, as we will see in this chapter. But its use is much broader than that; in fact, the Android Developer Reference says, “An intent is an abstract description of an operation to be performed,” so intents can be used to quickly accomplish many tasks that would otherwise take more programming code. An intent is sort of a programming shortcut that’s built into the Android OS and programming environment.
An Intent object is basically a passive data object (a bundle of instructions, if you will) that both provides a description of some sort of standard operating system or developer created “action” that needs to be performed and passes the data which that action needs to operate on to the code receiving the intent.
In addition to a specified action, the Intent object also can contain relevant data needed to complete that action, as well as data type specifications, constants, flags, and even extra data related to the data needed by the action.
Because intents provide a detailed data and process communication structure among Android application components, they also can be rather complex data structures (objects). We’ll see the various parts of an intent’s structure in the next section.
There are three types of Intent objects that can be used inside the Android OS to communicate with activities, services, and broadcast receivers. In fact, there is one intent type for each of these. None of these types of Intent objects are allowed to intersect with (i.e., interfere with, collide with, or mistakenly be used with or by) any of the other types of Intent objects. For this reason, we will cover each type of Intent object separately, so we can see how intent-based communication with activities, services, and broadcast messages differ from each other.
Android Intent Messaging via Intent Objects
Essentially, intents carry messages from one module of your application to another (activity to activity, activity to service, broadcast to activity, etc.). Intents can be sent to and from background processing services or intra-application activities or even inter-application broadcast messages. Intents are similar to the events that are found in other programming languages, except that intents can reach outside your application, whereas events can’t. Events are used to process user interface elements, as we have seen in Chapter 9, and are internal to the blocks of programming logic you write. Intents can be passed to other applications written by other programmers, allowing them to be connected as modules of each other, if needed.
Intent object-based messages can contain up to seven different kinds of informational parts, as shown in Figure 11-1:
Figure 11-1 . The anatomy of an Intent object
With these seven different types of information, the messaging construct that an Intent object communicates can become quite an intricate data structure, if you need it to be, it can also be quite simple, depending on the application use that is involved.
The first thing an Intent object usually specifies is the name of the application component you are targeting (usually a class you create); this is specified via the package and class name, like so:
ComponentName(string package, string class)
The component name is optional. If it is not specified, the Android OS will utilize all of the other information contained within the Intent object to infer what component of the application or Android OS the Intent object should be passed to for further processing. It is safer to always specify this information. On the other hand, intents are intended to be used as programming shortcuts, and for many standard or common instances, Android is designed to properly infer how to process them.
The most important part of the Intent object is the action specification. The action defines the type of operation that the intent is requesting to be performed. Some of the common action constants are listed in Table 11-1, along with their primary functions, so you can get an idea of where these intents might be utilized in the Android OS.
Table 11-1. Examples of Action Constants and Their Primary Functions
Action constant | Target activity | Function |
---|---|---|
ACTION_DIAL | Activity | Displays the phone dialer |
ACTION_CALL | Activity | Initiates a phone call |
ACTION_EDIT | Activity | Display data for user to edit |
ACTION_MAIN | Activity | Start up an initial task activity |
ACTION_BATTERY_LOW | Broadcast Receiver | Battery low warning message |
ACTION_HEADSET_PLUG | Broadcast Receiver | Headset plug/remove message |
ACTION_SCREEN_ON | Broadcast Receiver | The screen turned on message |
ACTION_TIMEZONE_CHANGED | Broadcast Receiver | Time zone has changed |
It is important to note that in many cases the action constant that is specified determines the type and structure of the data of the Intent object. The data parameter is as important to the overall result of the intent resolution as the specified action to be performed. Without providing the data for the action to operate on, the action is as useless as the data would be without any action to be performed on it!
The ACTION_DIAL action constant is a good example; it targets an activity and displays the smartphone dialing utility with the phone number (the data passed to it) to be dialed. The data is the phone number the user entered into the user interface, and because the action constant is ACTION_DIAL, Android can infer that the data passed to it is the phone number to be dialed.
Thus, the next most important part of the Intent object is the data component, which contains the data that is to be operated on. This is usually done via a URI object that contains information about where the data can be found.
As we learned in the previous chapter, this often turns out to be a database content provider; for instance, a SQLite database can be the target of an ACTION_VIEW or ACTION_EDIT intent action. So, to edit database record information about a person in your contacts list with the database ID of 1, we would use the following intent data structure:
ACTION_EDIT content://contacts/people/1
A closely related part of the Intent object specification is the data’s MIME type, which explicitly tells Android what type of data the intent should be working with so that, for example, audio data doesn’t encounter an image processing routine.
The type part of the Intent object allows you to specify an explicit MIME data definition or data type that, if present, overrides any inference of the data type by the Android OS. You may already be familiar with the MIME data type declarations, as they are quite common on web servers and other types of data servers.
MIME stands for “Multipurpose Internet Mail Extensions” and was originally designed for e-mail servers to define their support for different types of data. It has since been extended to other server definitions of supported data and content types, and to communication protocols (such as HTTP) data type definitions, and now to Android OS to define content data types as well. Suffice it to say that MIME has become a standard for defining content data types in a myriad of computing environments. Examples of MIME definition include the following:
Another important parameter of an Intent object is the category, which is meant to give additional or more fine-tuned information about the action that is specified to execute. This is more useful with some actions than with others.
A good example of how categories help define what to do with a given action is launching the home screen on a user’s Android phone via an Intent object. You use the Action constant ACTION_MAIN with a category constant CATEGORY_HOME and voila! Android launches the phone’s Home screen and shows it on the display.
Finally, the extras parameter allows additional data fields to be passed with the Intent object to the activity, service, or broadcast receiver. This parameter uses a Bundle object to pass a collection of data objects.
This is a slick way to allow you to piggyback any additional data or more complex data structure you wish to pass along with the Action request/message.
Intent Resolution: Implicit Intents and Explicit Intents
Intents, like events, need to be resolved so that they can be processed properly. Resolution in this case means ascertaining the appropriate component to handle the intent and its data structure.
There are two broad categories of intents—explicit intents and implicit intents. We will look at explicit intent resolution first, as it is much more straightforward. Then, we’ll cover implicit Intents and see how they need to be filtered so that Android knows how to handle them properly.
Explicit intents use the component portion of the Intent object via the ComponentName data field. You’ll generally use these when working with applications you have developed, as you’ll know which packages and classes are appropriate for the Intent object to send an action message and data to be acted on. Because the component is specified explicitly, this type of intent is also safer, as there is zero room for error in interpretation. Best programming practices dictate that you thoroughly document your code and thereby give other programmers using your intent code the proper component name information. However in the real world, this best case does not always happen, and thus Android also has implicit intents and intent filters to handle other scenarios.
Other developers may not know what components to explicitly declare when working with your application, and thus explicit intents are a better fit for private interapplication communication. In fact, developing your application so that other developers can use the intents is what implicit intents and intent filters are all about. As noted earlier, if there is a component name specified, it will override all of the other parts of the Intent object as far as determining which code will handle the intent resolution.
There are two ways to specify a component. One way is via the setComponent() method, which uses the ComponentName object:
.setComponent(ComponentName);
The other way is using the setClass(Context, Class) method to provide the exact class to use to process the intent. Sometimes this is the only information in the intent, especially if the desired result from using the intent is simply to launch parallel activities that are internal to the application when they are needed by the user.
Implicit intents are those that don’t specify the component within the intent object. This means that Android has to infer from the other parameters in the intent object what code it needs to pass the intent message to for successful processing.
Android does this inference based on a comparison of the various actions, data, and categories defined in the intent object with the code components that are available to process the intent. This is usually done via intent filters that are defined in the AndroidManifest.xml file.
Although designing classes that utilize implicit intents and intent filters is beyond the scope of an introductory book on Android programming, we will go over the concept here just to give you an idea of what can be done in Android and in which situations you would use implicit intents and intent filters. You can find more information at:
developer.android.com/reference/android/content/IntentFilter.html
Intent filters are declared in AndroidManifest.xml using the <intent-filter> tag, and they filter based on three of the seven attributes of the Intent object—action, data, and category.
Intent filters provide a description of intent object structures that need to be matched as well as a priority attribute to be used if more than one match is encountered. If no action filters are specified, the action parameter of the intent will not be tested at all, moving the testing on to the data parameter of the intent. If no data filters are specified, then only intents that contain no data will be matched. Here is an example intent-filter definition from an AndroidManifest.xml file that specifies that video MPEG4 and audio MPEG3 can be retrieved from the internet via HTTP:
<intent-filter>
<data android:mimeType="video/mp4" android:scheme="http" />
<data android:mimeType="audio/mp3" android:scheme="http" />
</intent-filter>
For Intent filtering based on data characteristics, the data parameter gets broken down into four subcategories:
Any of these you specify will be matched precisely to the content of the intent itself, for example:
content://com.apress.project:300/datafolder/files/file1
In this example, the scheme is content, the host is com.apress.project, the port is 300, and the path is datafolder/files/file1.
Because we can specify intents explicitly, we can use intent objects productively via the methodologies outlined in the rest of this chapter without having to learn the convoluted hierarchy of how to match unspecified intents. If you wish to delve into the complexities of how to set up levels of intent filters for implicit intent matching, visit the Android developer site and get ready to wrap your mind around some intense global logic structures.
Using Intents with Activities
Enough theory, let’s write an Android application that uses intents to switch back and forth between two different activities—an analog clock activity and a digital clock activity—so you can see how intents are sent back and forth between more than one Activity class.
Figure 11-2 . Creating our IntentFilters project in Eclipse New Android Application dialog series
Figure 11-3 . Creating new Java AlternateActivity class by right-click on IntentFilters project folder
Now let’s create our user interface for our first activity, which we will leave in its default activity_main.xml file container (shown in Figure 11-4 on page 315).
a. Create a main_activity_text in your strings.xml file, which text value is set to: “You Are Currently in the MainActivity”. Or you can edit the hello world string tag to serve this purpose, so that you don’t have to delete it, as it will be unused in this exercise.
b. Use the android:text attribute to set the pointer to the strings.xml file value via the “@string/main_activity_button” setting.
c. Let’s use the android:textSize attribute to increase the text size to 18 device-independent pixels, so it’s large and readable. You can do this via the Properties Tab in Eclipse to the right of the bold TextSize parameter.
d. Finally, let’s use the android:centerHorizontal="true" attribute to center the text heading at the top of the screen. Note that the GLE and default XML TextView settings have done this for you, all you have to do is delete the android:centerVertical parameter to place the TextView at the top of the UI screen where it belongs.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:centerHorizontal="true"
android:text="@string/main_activity_text"
android:textSize="18dp"
tools:context=".MainActivity" />
a. Create a main_activity_button in your strings.xml file, and set it’s text value to: “Go To the Alternate Activity to use a Digital Clock” so we can point our text attribute to “@string/main_activity_button”
b. Set the layout_below attribute to “@+id/textView1” so we have our button defined as being underneath our TextView in our RelativeLayout container.
c. Now let’s make sure to center our button using the android:centerHorizontal="true".
d. Finally, we’ll space our UI button down a little bit with the familiar android:layout_marginTop="30dp", and we are done creating the button UI attributes as shown below.
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="@string/main_activity_button" />
Figure 11-4 . Using the Eclipse Graphical Layout Editor to add and configure AnalogClock
Note If Eclipse is not showing the Properties tab at the bottom, simply go into the Window menu and select Show View Other . . . and select Properties and then OK.
<AnalogClock
android:id="@+id/analogClock1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button1"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dip"
android:background="@drawable/image1" />
And here is the final code, which is also shown in Figure 11-5 for context:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:centerHorizontal="true"
android:text="@string/main_activity_text"
android:textSize="18dp"
tools:context=".MainActivity" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="@string/main_activity_button" />
<AnalogClock
android:id="@+id/analogClock1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button1"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dip"
android:background="@drawable/image1" />
</RelativeLayout>
Figure 11-5 . Adding user interface elements to our activity_main.xml file and image1 file to drawable-xhdpi folder
Writing a Digital Clock Alternate Activity
Now let’s copy the user interface we have just developed in our activity_main.xml to use for our second activity, for which we’ve already created an AlternateActivity.java class.
Figure 11-6 . Specifying activity_alternate.xml as the new copy name for activity_main.xml
a. Change it to a DigitalClock tag.
b. Remove the background image reference to image1.png.
c. Change the id to digitalClock1.
d. Add a textSize attribute of 40dp.
e. Add a textColor attribute of #7AC to add some nice blue coloring. Note that #7AC is the same as (shorthand for) #77AACC.
f. Finally, add an android:typeface="monospace" attribute for readability, and we’re ready to change our TextView and Button UI objects.
<DigitalClock
android:id="@+id/digitalClock1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button1"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dip"
android:textSize="40dp"
android:textColor="#7AC"
android:typeface="monospace"/>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="@string/alt_activity_button" />
<TextView android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:centerHorizontal="true"
android:text="@string/alt_activity_text"
android:textSize="17dp"
tools:context=".AlternateActivity" />
When you’re done, the whole UI layout should look like this (also shown in Figure 11-7):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:centerHorizontal="true"
android:text="@string/alt_activity_text"
android:textSize="17dp"
tools:context=".AlternateActivity" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="@string/alt_activity_button" />
<DigitalClock
android:id="@+id/digitalClock1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button1"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dip" />
</RelativeLayout>
Figure 11-7 . XML mark-up for activity_alternate.xml our second user interface activity shown in Eclipse
Wiring Up the Application
While we’re working with XML files, let’s add an activity tag in our AndroidManifest.xml file so that our second activity can be recognized by Android, before finishing off the configuration.
Right-click on the AndroidManifest.xml file name under your IntentFilters folder (at the bottom of the list), and select Open, or simply select the file name and hit F3. Add a second activity tag after the first tag (which our New Android Application Project series of dialogs has already created) that points to the new AlternateActivity.java class that we created earlier (see Figure 11-8 ). Here is the code:
<activity
android:name=".AlternateActivity"
android:label="@string/title_activity_alternate" >
</activity>
Figure 11-8 . Adding the .AlternateActivity tag to our IntentFilters Project’s AndroidManifest.xml file
Make sure to add the <string> tag to the strings.xml file by copying the MainActivity string tag and changing the “Main” to “Alternate” (and “main” to “alternate”).
Now let’s make sure both Activities have user interfaces. Thanks to our handy New Android Application Project dialog, our MainActivity class is ready and pointing to the activity_main.xml file so that the MainActivity side of the equation is already taken care of for us. So now, all we have to worry about is the AlternateActivity class.
Copy the three import statements and the onCreate() method over to the AlternateActivity.java class and then change the R.layout specification to point to the activity_alternate XML user interface specification. Now we’ve implemented our user interface logic for each of our two Activity classes as follows:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alternate_activity);
}
Now we need to add in our Button object and Intent object code to the onClick() event handler in each activity so each can send an intent to the other activity, allowing us to switch between the two activities using the button.
So let’s get started with the primary MainActivity.java activity class first, and add in our familiar Button object instantiation and an onClick() event handler that will contain the code that creates our Intent object. Remember to also add in (or have Eclipse add in for you, using the hover-over-redline method we learned about earlier) the import statements for the android.view.View and android.widget.Button packages, as well as a new import we haven’t used before called android.contact.Intent, which defines our Intent object class.
Because we’ve already covered adding a button and attaching it to an onClick() event handler routine, we’ll get right into the two lines of code that create our Intent object and send it over to the second alternate activity. Here is the code, which you’ll also see in Figure 11-9. The screenshot shows what your Eclipse editor pane for MainActivity.java will look like when we are finished.
Button activity1 = (Button) findViewById(R.id.button1);
activity1.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
Intent myIntent =
new Intent(arg0.getContext(), AlternateActivity.class);
startActivityForResult(myIntent, 0);
}
});
Figure 11-9 . Adding the MainActivity Java code for the UI button, event listener, and Intent object
To create an Intent object, we use the now familiar structure where we declare the object type (Intent) and our name for it (myIntent). We set it equal to a new Intent object using the new keyword, along with a call to the Intent class’s constructor. The constructor takes the context this intent is created in (in this case, from a button obtained from the View via a arg0.getContext() method call) and the activity class (AlternateActivity.class) into which we want to pass our Intent object.
We then use the startActivityForResult() method (part of the android.app.Activity class we imported) to pass the intent object we just created, myIntent, and a parameter of zero; this is what we are sending to the other activity to be acted on. This would typically consist of data that your application wants to pass from one activity class to another via the Intent object for further processing. In this case, we are simply calling (switching to) the other user interface activity.
Now let’s look at the code in our AlternateActivity class and see how the second activity talks back to the first activity. We will skip over the Button instantiation and onClick() event handling explanations, and get right into the intent declaration and the Java code that returns a result to the calling activity class via yet another Intent object. Here’s the code (also shown in Figure 11-10).
Button activity2 = (Button) findViewById(R.id.button1);
activity2.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
Intent replyIntent = new Intent();
setResult(RESULT_OK, replyIntent);
finish();
}
});
Figure 11-10 . Java Code for the AlternateActivity, event listener, and intent object
In this activity class, in the onClick() listener we create a new (empty) intent object called replyIntent, then load it with the setResult() method, which loads a constant called RESULT_OK. When we have finished handling the intent (in this case by loading a new intent with the reply data), we call the finish() method to send the intent back, after the button in the second activity is clicked on to send us back to the first activity.
Now let’s right-click on our IntentFilters project folder in the Package Explorer pane of Eclipse and then Run As Android Application, so we can see that we can now switch back and forth between the two activities that contain the two different time utilities we created in one application. As you can see in Figure 11-11, we can switch between the two activities by clicking on the respective buttons, and we can do this as many times as we like, and the application performs as expected and does not crash. That is important, by the way, that the app does not crash under repeated use.
Figure 11-11 . Running our app in the Android 4.1 emulator and switching back and forth between activities
Next we will use an intent object to call a service, the MediaPlayer, to play some audio in the background, and allow us to start the audio and stop the audio.
To do this we first must learn what services are, and how they can help us do things in the background without affecting our application’s user interface functionality. We will then get into an example that uses intents with both services and activities.
Android Services: Data Processing in Its Own Class
Android has an entire Service class dedicated to enabling developers to create services that run apart from the main user interface program logic. These services can either run in a separate process (known as a thread in Java programming, as well as in other programming languages) or in the same process as the user interface activities. With new Android devices featuring multiple processors, the preference would be to run processing-intensive services (such as 3D rendering via OpenGL ES or audio or video playback via the MediaPlayer class) in their own thread.
A thread is an area of the operating system’s memory where a program function has its own resources and can run in parallel with other applications (termed: multitasking) or in parallel with other application components or processor-intensive functions. For instance, a video player can run in a different thread from the rest of the application, so that it doesn’t hog all of the main application thread resources. If an Android device has a dual-core or quad-core CPU, it is also conceivable that a processing-intensive thread could even get its own processor allocated to it. Pretty cool!
Threads and Processes were originally devised for multi-tasking operating systems such as Mac, Linux, and Windows, so that if a program or task crashed, it would not bring down the entire operating system. Instead, just that thread or process could crash or lock-up, and the others that were running wouldn’t be affected adversely. Because Android OS runs on top of a full version of the Linux OS kernel, it simply passes that capability through (up) to the Android OS.
A service is a type of Android application component that needs to run asynchronously (not in step with the usual flow of the user interface). For example, if you have some processing that takes a bit longer than the user is willing to wait for, you can set off that processing task asynchronously in the background, while the main program continues to respond to the user. When the processing has finished, the results can be delivered to the main program and then dealt with appropriately. A service can also be used by other Android applications, so it is more extensible (widely usable) than an activity.
To create your own service class to offload programming tasks like calculating things or playing media such as audio or video in real-time, you need to subclass the Service class and implement at least its onCreate(), onStart(), and onDestroy() methods with your own custom programming logic. You also must declare the Service class in your AndroidManifest.xml file using the <service> tag, all of which we’ll get into in detail a bit later on.
Using Intents with Services
To see how to control a service class via intent objects, we will need to add some user interface elements to our IntentFilters project’s MainActivity activity, namely two button objects that will start and stop our service. In this case, the service is the Android MediaPlayer, which needs to run in the background, independently of our user interface elements.
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="analogClock1"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="@string/start_media_button" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="button2"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="@string/stop_media_button" />
Figure 11-12 . Designing a media player service user interface in the Eclipse Graphical Layout Editor
Figure 11-13 . Adding the start and stop buttons for our media player service in activity_main.xml
<AnalogClock
android:id="@+id/analogClock1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button1"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dip"
android:background="@drawable/image1" />
Next we need to let our Android application know that we are going to be calling a service, and this is done via the AndroidManifest.xml file. We will edit that file next to add the service tag that points to our MediaPlayerService class, which we are going to code next. We will add this service tag right after the second activity tag that we added in the previous example (see Figure 11-14 for context). This is how the service tag is structured:
<service android:enabled="true" android:name=".MediaPlayerService" />
Figure 11-14 . Adding a .MediaPlayerService <Service> Tag to the AndroidManifest.xml file in Eclipse
The first attribute of the service tag android:enabled indicates that the service is enabled for use. If you set this attribute to false, the service is still declared to Android for the application and can later be enabled via Java code. As we have seen, everything that can be done in XML can also be later accessed and changed inside of our Java code.
The second attribute, android:name, specifies the name of the service class that we will code. We are going to name it MediaPlayerService.java so we specify that in XML with a leading period, as .MediaPlayerService. Now we are ready to start coding the service that will play media files without interfering with the user interface code in our activity class.
If you haven’t done so already, be sure to add the <string> tags for the Start Button and Stop Button labels as shown in Figure 11-15. We now have ten <string> tag entries in our strings.xml file and all of the UI and app labeling of activities and UI elements can be modified from one central location. The best part of all is that we have a “clean” IDE where our XML code is concerned, as Android does not like “hard coded” text values, and flags them in the IDE UI, which can be disconcerting, so we are doing things using the proper work process here for our string values.
Figure 11-15 . Adding the two new button labels to the strings.xml file in the XML Editor
Now that we’ve added the XML markup, let’s create the MediaPlayerService.java class, extending the Android Service class to create our own custom service class that we’ll call from our IntentFilters Project’s MainActivity.java Activity class.
Creating a Service
To do this, we will use the same work process as before:
The completed dialog is shown in Figure 11-16. It will create an empty class where we can put our media player logic for creating, starting, and stopping the media player.
Figure 11-16 . Creating the MediaPlayerService.java class via the New Java Class dialog in Eclipse
Here (and in Figure 11-17) is the empty class that the New Java Class dialog created for us, complete with the import statements that let us use the Service class, the Intent class, and the IBinder interface. We won’t actually be using the IBinder interface in this example, but will leave it in the code. This won’t affect how the app runs because it is used by a null method, onBind(). We need to keep this method here because Android expects it to be implemented when we extend the Service class, so we’ll just leave it as is.
Figure 11-17 . Eclipse-created MediaPlayerService base service class created by New Class
package seventh.example.intentfilters;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MediaPlayerService extends Service {
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}
Note Binding is a concept in services where your activity can talk with the Service class while it is running and more than one time; but our example simply needs to start and stop the service, so the complexity of binding is not needed.
Here is the code that lets our MediaPlayerService class do things. I’ll show you each of the sections in turn as I describe what they do, so you can type them in as we go:
package seventh.example.intentfilters;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.media.MediaPlayer;
public class MediaPlayerService extends Service {
MediaPlayer myMediaPlayer;
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
myMediaPlayer = MediaPlayer.create(this, R.raw.mindtaffy);
myMediaPlayer.setLooping(true);
}
@Override
public void onStart(Intent intent, int startid) {
myMediaPlayer.start();
}
@Override
public void onDestroy() {
myMediaPlayer.stop();
}
}
The results are shown in Figure 11-18.
Figure 11-18 . Adding our MediaPlayerService Class onCreate( ), onStart( ), and onStop( ) methods
The first code structure we always add to a new class is the onCreate() method, which tells the class what to do to set itself up when it is called the first time (i.e., created). This is where you would put initialization variables, for instance, if you were coding a game level. Then we’ll add an onStart( ) method and finally an onDestroy( ) method. Here’s a simple diagram of what happens when the startService(MediaPlayer) code we’ll write later is called:
startService.Media.PlayerObject > onCreate() (Initialize) > onStart() (Do) > onDestroy (Clean Up)
We will use the onCreate() method to instantiate and configure our MediaPlayer object, and load it with our audio file. Because the audio file is an MP3 file and already optimized for compression, we will put it in the /res/raw folder. Files in the /raw folder are left alone by the Android app compression process and are simply put into the .apk file as is. As you can see in Figure 11-18 Eclipse will tell you if it cannot find the /raw/mindtaffy.m4a file referenced in your code. We’ll add that file now before explaining the code in detail.
Implementing Our MediaPlayer Functions
Now it’s time to go into the code so you can add the media player functionality to your own app:
MediaPlayer myMediaPlayer;
@Override
public void onCreate() {
myMediaPlayer = MediaPlayer.create(this, R.raw.mindtaffy);
myMediaPlayer.setLooping(true);
}
@Override
public void onStart(Intent intent, int startid) {
myMediaPlayer.start();
}
@Override
public void onDestroy() {
myMediaPlayer.stop();
}
Wiring the Buttons to the Service
Now let’s go back into our MainActivity.java class using the Eclipse Editor tab and add our Button objects, associated onClick() event handlers for each button, and the necessary calls to our Service class onStart() and onDestroy() methods, as shown in Figure 11-19.
Figure 11-19 . Implementing the start and stop buttons to control the media player
First we have the start button.
Button startButton = (Button) findViewById(R.id.button2);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
startService(new Intent(getBaseContext(), MediaPlayerService.class));
}
});
As usual, we declare our startButton Button object with the button2 ID reference, then use the setOnClickListener() method to add onClick() event handling to the startButton. We are now ready to call the startService() method inside of the onClick() programming construct.
The startService() calls the onStart() method of the Service class we just wrote, and requires an intent object; this intent object tells Android what service to start and call the onStart() method on. We will get a little tricky here and create a new intent object inside of the startService() call using the following code structure:
startService(new Intent(getBaseContext(), MediaPlayerService.class));
To create a new intent object, we need to declare the current context as the first parameter and then pass the name of the service class we wish to call as the second parameter. In a third level of nesting (inside the new intent creation), we use another method called getBaseContext() to obtain the current context for the new intent object. As the second parameter, we will declare the name of the MediaPlayerService.class to complete the creation of a valid intent object.
Now let’s go through the same procedure with the stopButton Button object, inserting the button3 ID reference and then using the trusty setOnClickListener() method to add onClick() event handling to our new stopButton. Now we’re ready to call the stopService() method in our newest onClick() programming routine.
Button stopButton = (Button) findViewById(R.id.button3);
stopButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
stopService(new Intent(getBaseContext(), MediaPlayerService.class));
}
});
The stopService() method calls the onDestroy() method of our Service class and requires an intent object that tells Android what service to stop (destroy) and call the onDestroy() method on.
We will create yet another new intent object in the stopService() call, using the following code structure:
stopService(new Intent(getBaseContext(), MediaPlayerService.class));
To create our final intent object, we will declare the current context as our first parameter and then pass the name of our MediaPlayerService()class as our second parameter.
Running the Application
Now we are ready to right-click our IntentFilters folder and Run As Android Application to run our app. You’ll find when you test the application that everything works perfectly with everything else; you can start and stop the media player as many times as you like, the audio plays smoothly in the background without faltering, and you can switch back and forth between the two activities as many times as you want without affecting the media playback of the audio file. See Figure 11-20.
Figure 11-20 . Running our media player service inside the Android 4.1 emulator
Next we are going to take a look at using Intent objects with broadcast receivers, and then we will have covered all three areas of Intent use within Android.
Using Intents with Broadcast Receivers
The final type of intent object we will look at in this chapter is the broadcast receiver, which is used for communication between different applications or different areas of Android, such as the MediaPlayer or Alarm functions. These intents send, listen to, or receive messages, sort of like a head’s up notification system, to let your application know what’s going on around it during the ongoing operation of the Android smartphone, tablet, e-reader, or iTV, whether that’s a phone call coming in, an alarm going off, or a media player finishing a file playback.
Because we already have an analog watch and a digital clock, let’s add a timer and alarm function to finish off our suite. Because our analog clock user interface screen is full of UI elements, let’s add the alarm functions to our digital clock user interface screen in our AlternateActivity Class, as that’s the most logical place to add an alarm anyway. Figure 11-21 shows a basic diagram of what we will do in XML and then Java to create the intent and implement the alarm in our next application segment.
Figure 11-21 . What we have to do in XML and then in Java to create the intent and alarm
Creating the Timer User Interface via XML
So, first, let’s add an EditText tag via the Eclipse Graphical Layout Editor so that we can let users enter their own custom timer duration. Open the text field area of the GLE palette and find the numeric decimal text field (represented in the GLE UI as 42.0) and drag and drop it onto the existing UI, so that it centers both horizontally and vertically on the UI screen, as shown in Figure 11-22 below.
Figure 11-22 . Using the Eclipse Graphical Layout Editor to add our EditText widget using the tool-tip guide
Now open up the strings.xml file and add a <string> tag with an edit_text_hint variable that points to the text value “Enter Number of Seconds” so we can add a “Hint” to our EditText field. Then use the properties editor as shown in Figure 11-22 (on the right side) to add a hint that points to the @string/edit_text_hint string (you can use the three . . . ellipses to choose this variable once you add it to the strings.xml file and save it so that the entry is permanent).
This should place the following markup in your activity_alternate.xml file after the DigitalClock tag:
<EditText
android:id="@+id/editText1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:ems="10"
android:hint="@string/edit_text_hint"
android:inputType="numberDecimal" >
<requestFocus />
<EditText/>
The EditText tag has an ID of editText1 and a layout_center in both vertical and horizontal planes for consistency with our prior UI design. Because EditText is a new user interface object for us, we added an android:hint attribute that says “Enter Number of Seconds” so that you would have experience with that important EditText parameter.
Note The hint attribute is text you enter to appear in the text field when it is created by Android and placed on the screen in the layout container. This hint tells the user what to type in the field, which in this case, is the number of seconds the timer should count down.
Next we have an important android:inputType attribute, which tells us what data type the field will contain, in this case a real number that is represented by the numberDecimal constant. The timer uses milliseconds, so if we want the timer to count 1,534 milliseconds, we can type 1.534 in this field and achieve this precise result.
We’ll also add a Button that we will call startTimer in our Java code to, well, start the timer. Let’s use the GLE to drag out a second button and center it and position it below EditText with a marginTop value of 30dp, all of which you will be able to see via the real-time tool-tip as you position the button via the GLE. We’ll accept the GLE assigned ID of button2 and edit the android:text value to read: “@string/timer_button_label” so be sure and add a <string> variable and value of “Start Timer Countdown” to the strings.xml file so we can prompt the user to action via the button label. Our strings.xml file now has a dozen variables defining all of our UI text elements as shown in Figure 11-23.
Figure 11-23 . Adding string tags for our Timer Utility in the strings.xml file in Eclipse
As usual, the GLE will use a now familiar android:layout_centerHorizontal="true" to center our button, and android:layout_below=“editText1” so that the UI remains consistent and well defined. The Button tag and parameters should look like the XML code below:
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/editText1"
android:layout_centerHorizontal="true"
android:marginTop="30dp"
android:text="@string/timer_button_label" />
Figure 11-24 shows how our activity_alternate.xml file should look in the Eclipse IDE.
Figure 11-24 . Adding our timer user interface elements to the activity_alternate.xml file
Creating a Timer Broadcast Receiver
Now let’s create our TimerBroadcastReceiver class, which is a subclass of the BroadcastReceiver class.
Figure 11-25 shows what your New Java Class dialog should look like when you’ve entered all of the relevant new Java class information.
Figure 11-25 . Creating a new TimerBroadcastReceiver class via the New Java Class dialog in Eclipse
Now we have an empty class shell with all of the import statements that we need for our BroadcastReceiver class and an empty onReceive() method for us to fill out with our programming logic. The onReceive() method will receive our intent and broadcast a message via the Toast class, which will notify us regarding the alarm status.
Let’s add an import statement for the Toast widget, so that we can use it to broadcast a message to the user when the broadcast receiver is used. It is important to note that this Toast message could be replaced by anything we want to happen when our timer goes off, including but not limited to playing a song, playing a ringtone, vibrating the phone, playing a video, or anything else you can think of. As you know by now, you can add the Import statement below or let Eclipse do it for you when you code the Toast.makeText statement that follows.
import android.widget.Toast;
The Toast widget’s makeText() method can be coded as follows:
public void onReceive(Context arg0, Intent arg1) {
Toast.makeText(arg0, "Alarm Notification", Toast.LENGTH_LONG).show();
}
We first pass the Toast widget the context parameter received along with the onReceive() call and then tell it what to write to the screen (our “Alarm Notification” string) and how long to show it on the screen (the LENGTH_LONG constant). We then append the show() method to makeText() to draw the message on the screen.
Figure 11-26 shows how all of this should look on the TimerBroadcastReceiver tab in the Eclipse IDE.
Figure 11-26 . Our TimerBroadcastReceiver class
Configuring the AndroidManifest.xml file <receiver> Tag
Now we need to declare our broadcast receiver using the receiver tag in our AndroidManifest.xml file so it is registered for use with Android. We will do this right after the service tag that we added in the previous section, entering the following line of XML markup code:
<receiver
android:name=".TimerBroadcastReceiver"
android:enabled="true" >
</receiver>
This is fairly straightforward. We use the name attribute to assign our .T imerBroadcastReceiver class name to the receiver declaration tag and then enable it for use in our application by setting the android:enabled attribute to true so that the broadcast receiver is live (Figure 11-27).
Figure 11-27 . Adding a <receiver> tag to our AndroidManifest.xml file for .TimerBroadcastReceiver
Now our broadcast receiver is set up to notify users via a Toast message when the broadcast receiver is utilized. The next thing we need to do is add the code to our AlternateActivity class to implement an alarm clock function that triggers this Broadcast Receiver class via an intent object, so we can see how all this works together.
Implementing our Intent
The modifications to our AlternateActivity class will be done via several new import statements, an event handler for a click on our startTimer countdown button, and the timerAlert() method that we will write to do all the heavy lifting to implement the new timer functionality to our application and to trigger our broadcast receiver class using intent objects.
Let’s start with the onCreate() method:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alternate);
Button activity2 = (Button) findViewById(R.id.button1);
activity2.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
Intent replyIntent = new Intent();
setResult(RESULT_OK, replyIntent);
finish();
}
});
Button startTimer = (Button) findViewById(R.id.button2);
startTimer.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
timerAlert(arg0);
}
});
}
Now let’s see how to add the highlighted code, starting with the import statements needed.
1. We need to import the two new widgets that we are going to use to implement editable text and a toast notification message, or have the IDE do it for us, either way. Both of these classes are from the android.widget package:
import android.widget.EditText;
import android.widget.Toast;
2. Next let’s create our startTimer Button object for the start timer countdown button and use the findViewById() method to set it to the new button2 button tag we previously added to our activity_alternate.xml file. Place the following in the onCreate() method after the existing button code:
Button startTimer = (Button) findViewById(R.id.button2);
3. Now we’ll add a setOnClickListener() method to handle events generated by the startTimer button. Inside of that construct we will create an onClick() method that calls a timerAlert() method, which holds all of the relevant program logic to set up intents and construct the alarm feature for our digital clock activity:
startTimer.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
timerAlert(arg0);
}
});
We will pass the arg0 variable (the onClick View or Button that was passed from the onClick() method) to the timerAlert() method so that it has the context needed for the PendingIntent. Here is the code for the timerAlert() method, which we will go over line by line:
public void timerAlert(View view) {
EditText textField = (EditText) findViewById(R.id.editText1);
int i = Integer.parseInt(textField.getText().toString());
Intent timerIntent = new Intent(this, TimerBroadcastReceiver.class);
PendingIntent myPendingIntent =
PendingIntent.getBroadcast(this.getApplicationContext(), 0, timerIntent, 0);
AlarmManager myAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
myAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() +
(i * 1000), myPendingIntent);
Toast.makeText(this, "Alarm is set for " + i + " seconds!",
Toast.LENGTH_LONG).show();
}
4. First, we need two import statements for two new classes from the android.app package.
import android.app.AlarmManager;
import android.app.PendingIntent;
5. The timerAlert() method is void because it just performs some tasks relating to setting up intents and alarm functions. It takes a View object named view as its passed parameter.
public void timerAlert(View view) {
6. The first thing we do in this method’s code block is to declare the EditText object, name it textField, and locate it in our activity_alternate.xml layout definition via its editText1 ID parameter.
EditText textField = (EditText) findViewById(R.id.editText1);
7. Once we have the textField object we can use the getText() method along with the toString() method to get the string that the user types into the field. We then use the parseInt() method to convert that string value into an integer value and store it in the i variable of type int (or integer). Later we will use this integer value with our set() method to set the alarm duration.
int i = Integer.parseInt(textField.getText().toString());
8. In the third line of code we declare an Intent object that we name timerIntent and set it to a new Intent object with the context of this and the class of TimerBroadcastReceiver.class as we have done in the previous sections of this chapter. We will use this timerIntent object in the PendingIntent object.
Intent timerIntent = new Intent(this, TimerBroadcastReceiver.class);
9. Now let’s create a PendingIntent object called myPendingIntent and set it to the result of the getBroadcast() method call; this takes four parameters:
Note In this case we need no code or constants, so we use zeroes in those slots, and simply pass the current context, which we get using the getApplicationContext() method, and the timerIntent object we created just prior in the previous line of code.
PendingIntent myPendingIntent =
PendingIntent.getBroadcast(this.getApplicationContext(), 0, timerIntent, 0);
10. Now we are ready to create our alarm using the AlarmManager class. To do this we declare an AlarmManager object named myAlarmManager and call the getSystemService() method with the ALARM_SERVICE constant to specify that we want to get the alarm system service and set it to the myAlarmManager object. Once we have defined myAlarmManager as an alarm service object we can use the set() method to configure it for our use in the application.
AlarmManager myAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
11. The next line in the code block is the one that ties everything else together. The set() method we will use on our myAlarmManager object has three parameters:
myAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() +
(i * 1000), myPendingIntent);
In our incarnation of the set() method on the myAlarmManager object, we first specify the AlarmManager.RTC_WAKEUP, which uses the Real Time Clock (RTC) method and wake-up constant to specify that we want to wake up the phone (if it is asleep) to deliver the alarm. The RTC method uses the system clock in milliseconds as its time reference.
Using RTC only (without the _WAKEUP) will not wake the phone up if it triggers while the phone is asleep, and thus will be delivered only when the phone wakes up again. This makes it not nearly as accurate as the RTC_WAKEUP constant. You can imagine how handy it is to be able to wake up your phone at a certain discreet time even if it is asleep, so it’s a good thing we are exposing you to this handy constant here.
The next parameter we need to specify is the precise system time, in milliseconds, to trigger the alarm. We will wax a bit tricky here, and we will specify this middle parameter using a bit of inline programming logic.
We call the currentTimeMillis() method on the Android System object to get the current system time in milliseconds, then we add to the system time the number of seconds specified by our user in milliseconds, by multiplying the number of seconds in variable i by 1,000 because there are 1,000 milliseconds in one second. The system time is calculated in milliseconds since the calendar year 1970, so it is a discrete number that we can simply add our timer milliseconds value to.
This numeric result gives us the exact system time in milliseconds when the alarm needs to be triggered, and puts it into the set() method’s second parameter, when our inline code is evaluated at runtime. As we have seen, Java allows some fairly powerful programming constructs to be created using just a single line of programming code.
Finally, we will specify the myPendingIntent object as our third parameter. This object, created earlier with two lines of code, was loaded with the current context and the timerIntent object that we created earlier with three lines of code. The timerIntent object references our TimerBroadcastReceiver class, which will ultimately be called when the alarm is triggered, and will send a Toast to the screen to tell our end user that the time is up.
The final line of code sends a familiar Toast message to the end user, confirming that the alarm has been set for the proper number of seconds. This is done by passing the Toast makeText() method the current context (this) along with the Toast.LENGTH_LONG constant and two strings with the i variable between them like this:
Toast.makeText(this, "Alarm is set for " + i + " seconds!", Toast.LENGTH_LONG).show();
As we’ve seen here, Java is very flexible in how it allows us to mix different data types. Figure 11-28 shows our newly enhanced AlternateActivity class with the new import statements, onCreate() method and timerAlert() method modifications shown. Notice in the Package Explorer pane that we now have seven actively used project files with XML and Java code that we have either modified or written. Actually, if you include the AndroidManifest we have eight files, four Java and four XML that we have customized for this project! This is the most robust application we’ve written so far! Now we will run and test our new app in the Android 4.1 emulator.
Figure 11-28 . Adding the startTimer button UI code and timerAlert() method
Running the Timer Application via the Android 4.1 Emulator
Let’s right-click on our IntentFilters folder, and select Run As Android Application, and get right into our final IntentFilters project application. You’ll find that when you run this application that all three sections we’ve added work perfectly together.
This shows us that all of the different types of intents can work seamlessly together in Android, and that they don’t interfere with each other, as we noted at the beginning of the chapter.
We can now go back and forth between the analog (main) and digital (alternate) activities using the intents we created; turn on the audio and go back and forth while it is playing; and use the timer function while the digital audio is playing back as a service.
In the digital clock alternate activity, we can use the editable text field to set our timer value and the start timer countdown button to trigger the broadcast intent, which broadcasts a Toast to the screen when the specified number of seconds has passed.
Figure 11-29 shows the application running in the Android 4.1 emulator displaying the digital clock, the timer function, and the button that allows us to switch back and forth between our two different activities and their user interfaces.
Figure 11-29 . Running our timerAlert() method in the Android 4.1 emulator to show broadcast receiver intents
Summary
In this chapter, we’ve seen how different parts of the Android OS and the developer’s application components communicate to form a cohesive and seamless application. From user interface activities to background processing services and systems utilities, intent objects are used in integral ways to pass requests for processing actions on data structures between different types of application components.
This serves to enforce a modular, logical programming work process on the Android applications developer, which ultimately increases security, decreases bugs and poorly constructed code, and attempts to facilitate the kind of optimization that will be needed in the mobile embedded environment of smartphones, tablets, e-readers, and iTV sets.
Ultimately, the proper use of intents and the creative structuring of application components is what set the successful Android developer apart from the crowd, so be sure to practice using intents and delve deeper into this area of the Android developer documentation whenever you get a chance.
98.82.120.188