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 three 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 a 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 can also 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 can also 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, or 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.
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 previous chapters, 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:
Component name. The name of the class that the intent and its action are targeting, specified by using the package name and the class name.
Action. A predefined type of action that is to be performed, such as ACTION_DIAL
to initiate a phone dialing sequence or ACTION_VIEW
to view records in a database.
Data. The actual data to be acted upon, such as the address of the database records to view or the phone number to dial.
Category. Android has predefined intents that are part of the OS that are divided into various types or categories for easy access and use. The category name tells what area of the OS the action that follows it is going to affect. For instance, CATEGORY_HOME
deals with the Android Home screen. An ACTION_MAIN
following a CATEGORY_HOME
would cause the Home screen to be launched in the smatphone.
Type. This attribute specifies the type of the data using a MIME format. It's often left out as Android is usually able to infer the data type from analyzing the data itself.
Flags. This allows on/off flags to be sent with the intent. Flags are not used for typical intents, but allow more complicated intents to be crafted if needed by advanced developers.
Extras. This parameter allows any extra information that is not covered in the above fields to be included in the intent. This allows very complex intents to be created.
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 |
---|---|---|
| Activity | Displays the phone dialer |
| Activity | Initiates a phone call |
| Activity | Display data for user to edit |
| Activity | Start up an initial task activity |
| Broadcast Receiver | Battery low warning message |
| Broadcast Receiver | Headset plug/remove message |
| Broadcast Receiver | The screen turned on message |
| 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 since 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.
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.
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 non-public inter-application 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 what 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 what 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:
Data type; This is the MIME data type, for instance, image/jpeg
or audio/mp3
,
Data scheme: This is written as scheme://host:port/path
Data authority: This is the server host and the server port (see the data scheme format above) specified together.
Data path: A data path is an address to the location of the data, for instance, http://www.apress.com/datafolder/file1.jpg
.
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
.
Since 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.
Enough theory, let's write an Android application that uses intents to switch back and forth between two different activities— an analog watch activity and a digital clock activity—so you can see how intents are sent back and forth between more than one Activity
class.
First, let's close our Chapter10
project folder (via a right-click and Close Project) and create a new Chapter11
Android project with the following parameters, as shown in Figure 11-1:
Now we are going to create a second Activity
class, so we can switch back and forth between the two activities using an intent. To do this, we need to right-click on the Chapter11 folder, and select New
If you right-clicked on the Chapter11 folder to do the New
Next we need to fill out the Name
field, which will name our class. Let's use DigitalClockActivity
since that's one of the activities we'll use in this exercise.
Leave the Modifiers as set. Since we are creating an Activity
class, we need to extend the superclass android.app.Activity
. This is the full pathname to the Activity
class, which is part of the app
package in Android OS.
Now let's create our user interface for our first activity, which we will leave in its default main.xml
file container (shown in Figure 11-3).
Let's expand our TextView
tag with some new attributes:
Start with text that reads "You Are Currently in: Activity #1"
Use the android:textColor
attribute to set the color to #FEA, which is the equivalent to hexadecimal #FFEEAA, a light orange-gold color.
Let's use the android:textSize
attribute to increase the text size to 22 device-independent pixels, so it's large and readable.
Finally, let's use the android:paddingBottom="20dip"
attribute to push the button user interface object down and away from the text title a little bit.
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content"android:text="You Are Currently in: Activity #1"
android:textColor="#fea"
android:textSize="22dip"
android:paddingBottom="20dip"/>
Next, let's edit the Button
tag attributes:
Change its text
label to "Go To Digital Clock: Activity #2"
Set the textSize
attribute to 18 pixels so we have readable text on our button.
Now let's define our button size in pixels: android:layout_width="280px"
and android:layout_height="60px"
.
Finally, we'll center our UI button with the familiar android:layout_gravity="center"
and we are done creating the button UI attributes.
<Button android:id="@+id/Button01"android:text="Go To Digital Clock: Activity #2"
android:textSize="18px"
android:layout_width="280px"
android:layout_height="60px"
android:layout_gravity="center"/>
Now we'll add an AnalogClock
tag, so we can create a cool watch. Use the Layout
tab at the bottom of the Eclipse editor (circled in Figure 11-3) and drag the AnalogClock View element icon out of the Views List on the left, and drop it under the Button
in the UI layout view.
Then, either go into the Properties
tab at the bottom of Eclipse, find the Misc
section, and add in Layout gravity
and Layout margin top
values of center and 30dip, respectively, or click the main.xml
tab at the bottom of the editor, and add in the tags yourself by hand.
If Eclipse is not showing the Properties
tab at the bottom, simply go into the Window
menu and select Show View
Next, copy the image1.png
file from our earlier Chapter7/res/drawable
folder to your Chapter11/res/drawable
folder, then right-click on the Chapter11
folder and use the Refresh
option so that Android can see this image file inside our project.
Go into the Properties tab again and find the file using the Background option, then click on the search ellipses ...
to open a dialog where you can select image1.png
in the drawable folder to use as a background. Here's the final AnalogClock
tag:
<AnalogClock android:id="@+id/AnalogClock01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="30dip" android:background="@drawable/image1" />
And here is the final code, which is also shown in Figure 11-3 for context:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="You Are Currently in: Activity #1" android:textColor="#fea" android:textSize="22dip" android:paddingBottom="20dip"/> <Button android:id="@+id/Button01" android:text="Go To Digital Clock: Activity #2" android:textSize="18px" android:layout_width="280px" android:layout_height="60px" android:layout_gravity="center"/> <AnalogClock android:id="@+id/AnalogClock01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="30dip" android:background="@drawable/image1" /> </LinearLayout>
Now let's copy the user interface we just developed in our main.xml
to use for our second activity, which we've already created a DigitalClockActivity.java
class for.
The easiest way to do this is to right-click on the main.xml
file under the /res/layout
folder and select Copy from the pop-up context menu, then right-click on the /res/layout
folder in the Package Explorer pane (right above the file name) and select Paste, which will paste a copy of main.xml
right alongside main.xml
in the same folder. When you do this you will get a Name Conflict dialog like the one in Figure 4.
Eclipse sees the duplicate file names and automatically provides a simple dialog box that allows you to change the name. Change it to digital_clock.xml
and click OK.
We are ready to right-click on digital_clock.xml
and select Open
, or hit the F3 key to open the copied file in its own editor pane, so we can change some of the key tag attributes and quickly craft a user interface for our second (digital clock) activity. Do this now.
Edit the AnalogClock
tag as follows:
Change it to a DigitalClock
tag.
Remove the background image reference to image1.png
.
Change the id
to DigitalClock01
.
Add a textSize
attribute of 32dip
.
Add a textColor
attribute of #ADF to add some nice blue sky coloring.
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/DigitalClock01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="30dip" android:textSize="32dip" android:textColor="#adf" android:typeface="monospace"/>
Change the button text to "Go to Analog Watch: Activity #1" and leave the ID at Button01
. Why? Because these two different XML files are going to be called by two different Activity classes, and thus the ID does not conflict. If one Activity class referenced both these XML files, we might have a naming conflict.
<Button android:id="@+id/Button01"
android:text="Go to Analog Watch: Activity #1"
android:textSize="18px"
android:layout_width="280px"
android:layout_height="60px"
android:layout_gravity="center"/>
Finally, we change the TextView
object text to read "You are Currently in: Activity #2" and change the android:textColor
to the #ADF
value we are using with the digital clock tag.
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content"android:text="You Are Currently in: Activity #2"
android:textColor="#adf"
android:textSize="22dip" android:paddingBottom="30dip"/>
When you're done, the whole UI layout should look like this (also shown in Figure 11-5):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="You Are Currently in: Activity #2" android:textColor="#adf" android:textSize="22dip" android:paddingBottom="30dip"/> <Button android:id="@+id/Button01" android:text="Go to Analog Watch: Activity #1" android:textSize="18px" android:layout_width="280px" android:layout_height="60px" android:layout_gravity="center"/> <DigitalClock android:id="@+id/DigitalClock01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="30dip" android:textSize="32dip" android:textColor="#adf" android:typeface="monospace"/> </LinearLayout>
While we're working with XML files, let's add an activity
tag in our AndroidManifest.xml
file so our second activity can be recognized by Android, before finishing off the configuration.
Right-click on the AndroidManifest.xml
file name under your Chapter11 folder (at the bottom of the list), and select Open
or hit F3. Add a second activity
tag after the first tag (which our New Android Project dialog has already created) that points to the new DigitalClockActivity.java
class we created earlier (see Figure 11-6). Here is the code:
<activity android:name=".DigitalClockActivity"></activity>
Now let's make sure both Activities have user interfaces. Thanks to our handy New Android Project dialog, our IntentExamples
class is ready and pointing to the main.xml
file so that the Activity #1 side of the equation is already taken care of. So all we have to worry about is the DigitalClockActivity
class.
Copy the import android.os.Bundle
statement and the onCreate()
method over to the DigitalClockActivity.java
class and change the R.layout
specification to point to the digital_clock
XML user interface specification. Now we've implemented our user interface logic for each of our two Activity classes as follows (and as shown in Figure 11-7):
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.digital_clock); }
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 main IntentExamples
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 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.
Since 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 activity. Here is the code, which you'll also see in Figure 11-8. The screenshot shows what your Eclipse editor pane for IntentExamples.java
will look like when we are finished.
Button Activity1 = (Button) findViewById(R.id.Button01); Activity1.setOnClickListener(new View.OnClickListener() { public void onClick(View view) {Intent myIntent =
new Intent(view.getContext(), DigitalClockActivity.class);
startActivityForResult(myIntent, 0);
} });
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, a button obtained from the View via a view.getContext()
method call) and the activity class (DigitalClockActivity.class
) into which we want to pass our Intent object.
We then use the startActivityForResult()
method (part of the android.content.Intent
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 DigitalClockActivity
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-9).
Button activity2 = (Button) findViewById(R.id.Button01); activity2.setOnClickListener(new View.OnClickListener() { public void onClick(View view) {Intent replyIntent = new Intent();
setResult(RESULT_OK, replyIntent);
finish();
} });
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 Chapter11 folder and then Run As
Figure 11.10. Running our app in the Android 1.5 emulator and switching back and forth between activities
Next we will use an intent object to call a service, the MediaPlayer
, to play some music in the background and allow us to start the music and stop the music.
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 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 the same process as the user interface activities.
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 or other application components or 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.
Threads were originally devised for multitasking operating systems like 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.
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 the processing asynchronously in the background while the main program continues. When the processing has finished, the results can be delivered to the main program and dealt with appropriately. A service can also be used by other Android applications, so it is more extensible 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, which we'll get into a bit later on.
To see how to control a service class via intent objects, we will need to add some user interface elements to our Chapter11 IntentExamples
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.
First, let's add a Button
tag to our main.xml
file by copying the Button01
tag and pasting it underneath the AnalogClock
tag.
Change the id
to startButton
and the android:text
to "Start the Media Player Service" and leave the other attributes in the tag the same.
<Button android:id="@+id/startButton"
android:text="Start the Media Player Service"
android:textSize="18px" android:layout_width="280px" android:layout_height="60px"
android:layout_gravity="center"/>
Next, copy this startButton
tag and paste the copy immediately below the startButton
tag.
Change the id
of the copy to stopButton
and the android:text
to read "Stop the Media Player Service" so that we now have a stop button and a start button. Figure 11-11 shows both buttons in place in the Layout
tab of Eclipse and Figure 11-12 shows the code in context.
<Button android:id="@+id/stopButton"
android:text="Stop the Media Player Service"
android:textSize="18px" android:layout_width="280px" android:layout_height="60px" android:layout_gravity="center"/>
Now let's change the AnalogClock
tag attribute android:layout_marginTop
to use 20dip
rather than 30dip
. Copy the attribute to the line below and change it to android:layout_marginBottom
so that we have an even 20 pixels of spacing around the analog watch.
<AnalogClock android:id="@+id/AnalogClock01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"android:layout_marginTop="20dip"
android:layout_marginBottom="20dip"
android:background="@drawable/image1" />
Click the Layout
tab at the bottom of the main.xml
pane to make sure the user interface layout looks good and check Figure 11-12 to see that your XML looks right.
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 a 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 we added in the previous example (see Figure 11-13 for context). This is how the service
tag is structured:
<service android:enabled="true" android:name=".MediaPlayerService" />
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 accessed and changed in 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 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.
Note that if you haven't created the MediaPlayerService.java
class before you add the service
tag, Eclipse may highlight this fact with a red X in the margin to the left of the service
tag, as shown circled in Figure 11-13.
Now that we've added the XML mark-up, 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 IntentExamples
Activity class.
To do this, we will use the same work process as before:
Right-click on your Chapter11 folder in the Eclipse Package Explorer pane on the left and select New
Fill out the New Java Class
dialog as follows:
When everything is filled out, select Finish.
The completed dialog is shown in Figure 11-14. It will create an empty class where we can put our media player logic for creating, starting, and stopping the media player.
Below (and in Figure 11-15) is the empty class that the NewJavaClass 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 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, but we'll just leave it as is.
package intent.filters; import android.app.Service; import android.content.Intent; import android.os.IBinder;
public class MediaPlayerService extends Service { @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }
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 bold sections in turn as I describe what they do, so you can type them in as we go:
package intent.filters; 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 intent) { // 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-16.
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).
We will use the onCreate()
method to instantiate and configure our MediaPlayer
object and load it with our audio file. Since 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. We'll do that now before explaining the code in detail.
Let's create a Chapter11/res/raw
folder to hold the mindtaffy.m4a
file that we will call in our MediaPlayerService
class. You can either create the new /raw
folder under the /Chapter11/res
folder using your operating system's file manager or you can right-click on the /res
folder in the Eclipse Package Explorer pane and select New
Right-click on the Chapter11
folder and select Refresh and, if necessary, Validate to remove any error flags you might get in Eclipse. Usually if Refresh does not make something visible to Android and get rid of error flags in Eclipse, the Validate procedure will. If it doesn't, you may have a problem and need to examine your overall application structure.
Now it's time to go into the code so you can add the media player functionality to your own app:
First, at the top of the MediaPlayerService
class, declare a public global variable called myMediaPlayer
of object type MediaPlayer
. This will be accessed in one way or another by each of the methods that we'll be coding here, so we declare it at the top of the class to make it visible to the whole class.
MediaPlayer myMediaPlayer;
In the onCreate()
code block, let's set the myMediaPlayer
object to contain the results of a create()
method call with the mindtaffy.m4a
file passed as a parameter using the R.raw.mindtaffy
reference. The create()
method call creates an instance of the media player and loads it with the audio file that we are going to play.
Next we call the setLooping()
method on the myMediaPlayer
object and set a true
parameter so that the audio file loops while we are testing the rest of the code.
@Override public void onCreate() { myMediaPlayer = MediaPlayer.create(this, R.raw.mindtaffy); myMediaPlayer.setLooping(true); }
Now that our myMediaPlayer
object has been declared, loaded with MP3 data, and set to loop when started, we can trigger it with the start()
method, which we will code next in the onStart()
method (onStart()
is called when the service is started by our activity).
@Override public void onStart(Intent intent, int startid) { myMediaPlayer.start(); }
In the onDestroy()
method we use the stop()
method to stop the myMediaPlayer
object. onDestroy()
is called when the service is closed and disposed of by Android, so we release memory containing the media player and the audio file when we exit the application.
@Override public void onDestroy() { myMediaPlayer.stop(); }
Now let's go back into our IntentExamples.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-17.
First we have the start button.
Button startButton = (Button) findViewById(R.id.startButton); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startService(new Intent(getBaseContext(), MediaPlayerService.class)); } });
As usual, we declare our startButtonButton
object with the startButton
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 the onClick()
programming construct.
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 stopButtonButton
object, inserting the stopButton
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.stopButton); stopButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { 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.
Now we are ready to right-click our Chapter11 folder and Run As
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.
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 smartphone, whether that's a phone call coming in, an alarm going off, or a media player finishing a file playback.
Since we already have an analog watch and a digital clock, let's add a timer and alarm function to finish off our suite. Since our analog clock user interface screen is full of UI elements, let's add the alarm functions to our digital clock user interface, as that's the most logical place to add an alarm anyway. Figure 11-19 shows a basic diagram of what we will do in XML and Java to create the intent and alarm in our next application segment.
So, first, let's add an EditText
tag so we can let users enter their own custom timer duration. Place the following markup in your digital_clock.xml
file after the DigitalClock
tag:
<EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/timeInSeconds" android:layout_gravity="center" android:hint="Enter Number of Seconds Here!" android:inputType="numberDecimal" android:layout_marginTop="30dip" android:layout_marginBottom="30dip" />
The EditText
tag has an ID of timeInSeconds
and a layout gravity of center
for consistency with our prior UI design. Since EditText
is a new user interface object for us, we will add an android:hint
attribute that says "Enter Number of Seconds Here!"
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 1534 milliseconds, we can type 1.534 in this field and achieve this precise result. Finally, we add two margin attributes (top and bottom) of 30dip
each to space the user interface out and to make it more visually attractive to the end user.
We'll also add a Button
tag called startTimer
to, well, start the timer. Let's use an ID of startTimer
and an android:text
value of "Start Timer Countdown" to prompt the user. And we'll also use our familiar android:layout_gravity="center"
to center our button, so that the UI remains consistent.
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:id="@+id/startTimer" android:text="Start Timer Countdown" />
Figure 11-20 shows how our digital_clock.xml
file should look in the Eclipse IDE.
Now let's create our TimerBroadcastReceiver
class, which is a subclass of the BroadcastReceiver
class.
As we are now used to doing, let's create a new class using New
When everything is filled out, select Finish.
Figure 11-21 shows what your New Java Class dialog should look like when you've entered all of the relevant new Java class information.
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 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.
import android.widget.Toast;
The Toast widget's makeText()
method can be coded as follows:
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "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 to the screen.
Figure 11-22 shows how all of this should look on the TimerBroadcastReceiver
tab in the Eclipse IDE.
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 mark-up code:
<receiver android:name=".TimerBroadcastReceiver" android:enabled="true" />
This is fairly straightforward. We use the name
attribute to assign our TimerBroadcastReceiver
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-23).
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 DigitalClockActivity
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.
The modifications to our DigitalClockActivity
class will be done via several new import
statements, an event handler for a click on our start timer 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.digital_clock); Button activity2 = (Button) findViewById(R.id.Button01); activity2.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Intent replyIntent = new Intent(); setResult(RESULT_OK, replyIntent); finish(); } });Button startTimer = (Button) findViewById(R.id.startTimer);
startTimer.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
timerAlert(view);
}
});
}
Now let's see how to add the highlighted code, starting with the import
statements needed.
We need to import the two new widgets that we are going to use to implement editable text and a toast notification message. Both of these classes are from the android.widget
package:
import android.widget.EditText; import android.widget.Toast;
Next let's create our startTimerButton
object for the start timer countdown button and use the findViewById()
method to set it to the new startTimerbutton
tag we previously added to our digital_clock.xml
file. Place the following in the onCreate()
method after the existing button code:
Button startTimer = (Button) findViewById(R.id.startTimer);
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 view) { timerAlert(view); } });
We will pass the view
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.timeInSeconds); 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(); }
First, we need two import
statements for two new classes from the android.app
package.
android.app.AlarmManager
is a class that manages alarm functions for the Android OS.
android.app.PendingIntent
is a class that allows intents to be pending, which means they can be scheduled. This means they can be handled by classes in Android even if the calling class is paused, missing, asleep, stopped, or destroyed before the called intent has been processed. This is important for an alarm, especially if the timer is set to hours rather than minutes or seconds, because the phone could run out of charge before the Intent was ever satisfied.
import android.app.AlarmManager; import android.app.PendingIntent;
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) {
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 digital_clock.xml
layout definition via its timeInSeconds
ID parameter.
EditText textField = (EditText) findViewById(R.id.timeInSeconds);
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.
int i = Integer.parseInt(textField.getText().toString());
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);
Now let's create a PendingIntent
object called myPendingIntent
and set it to the result of the getBroadcast()
method call; this takes four parameters:
The context
Code
The intent object we want to use as a PendingIntent
Any constants
In this case we need no code or constants so we 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);
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);
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:
TYPE: The type of alarm trigger we wish to set.
TRIGGER TIME: The alarm will trigger when it reaches this system time.
OPERATION: The PendingIntent
object containing the context and target intent code we wrote in TimerBroadcastReceiver.java
, as specified using the getBroadcast()
method.
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 wakeup 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 class 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 1000, since there are 1000 milliseconds in one second. The system time is calculated in milliseconds since 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-24 shows our newly enhanced DigitalClockActivity
class with the new import
statements, onCreate()
method and timerAlert()
method modifications shown. Notice along the top of the IDE that we now have seven open tabs with XML and Java code that we have either modified or written. This is the most robust application we've written so far! Now we will run and test our new app.
Let's right-click on our Chapter11 folder and select Run As
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 and digital activities using the intents we created; turn on the music 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 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-25 shows the application running in the Android 1.5 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.
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.
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