Android provides the user with several ways of starting phone
calls: from the contact list, from the call history, using a dialer that
displays a 12-key dialpad on the screen, etc. All of these software
modules use the same application to start a phone call. Your program can
initiate a phone call in the same way: by using an Intent
object to ask Android’s specialized
telephony application to make the call. We’ll cover that technique in
this chapter, and take a look behind the scenes at how the process
works.
In Chapter 15, we’ll introduce Android classes that give you more information about telephony, such as tracking the state of the call you made.
Android includes an application called PhoneApp that embodies
the functions of a mobile phone. Through the use of Intent
objects, Android enables applications
to tell other applications to perform certain operations, such as
initiating a phone call. To enable your application to initiate a
phone call, a method like the one in Example 14-1 will do the job.
private void call() { try { Intent callIntent = new Intent(Intent.ACTION_CALL); callIntent.setData(Uri.parse("tel:9785551212")); startActivity(callIntent); } catch (ActivityNotFoundException activityException) { Log.e("dialing-example", "Call failed", activityException); } }
What happens when you start a phone call depends, in part, on
the telephone network. The number may be incorrect. The network may be
busy or otherwise unavailable. The call can be interrupted. Here,
however, you see no error-handling logic, except for catching and
logging exceptions that can be thrown if Android’s system encounters a
problem when finding applications that can process Intent
objects. Instead, the PhoneApp application, which already has
code for interpreting and remediating errors, handles the job from the
time the phone call is started.
When an application just wants to start phone calls, making it handle all these contingencies is a large burden. Systems that provide a telephony API place that burden on application authors when, in most cases, all an application needs is to start a phone call—not to manage the lifecycle of a phone call.
Starting a phone call is a multistep operation. Here we’ll take
a detailed look at each step in the execution of the call
method shown in Example 14-1. Along the way, we’ll see
how it uses Android’s system of Intent
objects and Intent filters.
To test the method in Example 14-1, create a new Android project in Eclipse by selecting File → New → Project → Other.... When the “Select a Wizard” dialog appears, select Android → Android Project. When you see the new project dialog, fill it in as shown in Figure 14-1.
Press Finish to create a project named dialing-example
in your Eclipse workspace.
(The complete code for this example is also on the book’s website.)
You will see this project in the Package Explorer pane of your
Eclipse IDE. Expand the project to see a set of folders, including
one named src. Expand this
folder to see a package named example.dialing. Expand that package
and you will see two Java source files, one of which is named
dialing.java. This file
contains the code in Example 14-2.
package example.dialing; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; public class dialing extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
This is where you will put the code that invokes our call
method.
Now that you have created a simple Android application, you can use it to isolate and observe operations, such as starting a phone call.
Copy the method we created in Creating an Example Application to Run the call
Method to the dialing
class in the dialing.java file. Then, add a line to
the onCreate
method that calls
the call
method. The results
should look something like Example 14-3.
package example.dialing; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; public class dialing extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); call(); } private void call() { try { Intent callIntent = new Intent(Intent.ACTION_CALL); callIntent.setData(Uri.parse("tel:9785551212")); startActivity(callIntent); } catch (ActivityNotFoundException activityException) { Log.e("dialing-example", "Call failed", activityException); } }
Make sure your program compiles and runs. To run the program, select the Run → Run command. When the Run As dialog appears, select Android Application. If you have followed the steps in this chapter, the result should be displayed in the Android emulator window (Figure 14-2).
You can use the red “end” button on the phone depicted in the emulator to let the user end the simulated phone call.
We will use the Eclipse debugger, set breakpoints, and inspect class members in the running application to observe what is going on inside this example. The use of the debugger with Android is described in Chapter 5. If you have not used a debugger before, don’t worry: we will use a limited set of debugging capabilities here to observe a program that works correctly. Just follow the steps in this section and let the debugger show you what is happening.
First, we will set a breakpoint where we want to start observing what is happening inside the application. To set a breakpoint, double-click on the left margin of the view that shows the program code in Eclipse. A blue dot will appear. If you change your mind and decide not to insert a breakpoint, double-click again on the blue dot, and it will disappear.
All we want is to stop execution of the program at the point
where we want to start inspecting what happens to the members of this
instance of the dialing
class. To
do this, set a breakpoint on line 21 of the program. You can tell
which line you are on by clicking a line in the program. In the status
bar at the bottom of the Eclipse window, you will see two numbers
separated by a colon. The first number is the line number where you
just clicked, and the second number is the character position on that
line where the insertion point is right now.
Start the application with the debugger by selecting Run → Debug, and when the “Debug as” dialog appears, select Android Application.
The program will stop at the breakpoint, after the Android emulator appears on your screen but before the appearance of the dialer shown in Figure 14-2. Eclipse will switch to a debug perspective: a set of views configured for debugging a program instead of editing it. Eclipse will ask if you want to switch perspectives the first time you run the debugger; you can save your answer if you want Eclipse to do the same thing each time you start debugging.
In the debug perspective, the view displaying the program code will show a small arrow overlapping the blue dot in the left margin. This line of code will be highlighted. The program has stopped before executing the Java bytecodes corresponding to the Java source code on this line of the program.
Figure 14-3 shows the Eclipse window in debug perspective with contents similar to those that should appear on your screen. The main information to look for is that the program stopped executing on the line where you set the breakpoint.
The line of code where the program stopped in the previous section looks like this:
Intent callIntent = new Intent(Intent.ACTION_CALL);
This creates an instance of the Intent
class. Use the “step over” command
to execute this line, by selecting the Run → Step Over option from
the menu or any of the shortcuts available in Eclipse.
“Step over” does not mean “skip.” Instead, it tells the debugger to run the entire line of code and all the method calls it contains (the Intent constructor, in this case) instead of entering the method calls and going through them line by line. It isn’t useful to see the internals of the Intent constructor. So “step over” creates the Intent and presents you with the next line of your own code.
The debugger also has commands for “stepping into” methods and “stepping out” of the method currently being executed. These commands are more convenient than setting more breakpoints and using the Resume command.
Now that we have used the new
operator and the Intent constructor
with an argument that specifies we want to initialize the Intent
with the Intent.ACTION_CALL
constant, we have an
instance of the Intent
class. The
action we use, ACTION_CALL
, will
enable Android to find PhoneApp or any other program the user may
install that offers the ACTION_CALL
action.
Let’s take a look inside by entering the Variables view in Eclipse. You will see two columns in this view. The first column shows the names of the variables, and the second column shows their values. In our case, the names refer to instances of classes, and the values consist of the class name and the ID of the instance.
That’s not very informative! Let’s look inside these instances
and see what they contain. Click on the triangle icon in the left
margin next to the variable named callIntent
. Now you see all the members of
the Intent
class and the values
for this instance of the Intent
class. The only member that has a nondefault value is mAction
. Its value is the string "android.intent.action.CALL"
. This is the
result of calling the Intent
class’s constructor with the argument we used.
So far, our instance of the Intent
class has enough information to
tell the Android system we want to start a phone call, but not
enough to tell it what number to call.
After creating the Intent instance with the information that means “we want to call a number,” in the next line we will add to it the number to call:
callIntent.setData(Uri.parse("tel:9785551212"));
Two things happen on this line of code: an instance of a
Uri
is created, and we use that
instance as an argument to the setData
method of the Intent
class. Step over this line of code,
and then let’s see what happens to the variables we are
inspecting.
Look at the Variable view in Eclipse and you will see that the
mData member of this instance of the Intent
now refers to the instance of
Uri
that was returned from the
parse method of the Uri
class.
And if you click on the triangle icon next to “mData”, you will see
the members of the Uri
class,
including the uriString
member
that refers to the string tel:9785551212
. Now our instance of the
Intent
class contains all the
information we need to start a phone call.
Why use a URI? All mobile numbers conform to the E.164
standard, so why not use a String
object containing a valid E.164 number? A URI has the advantage of
generality. All parts of Android are replaceable and the components
of Android that handle this particular Intent
object could be augmented or
replaced by a module that can also connect VoIP calls with SIP URIs
or Gmail addresses.
The next line in our program looks like this:
startActivity(callIntent);
This looks like we want to start an Activity, using the
Intent
object we created. But why
don’t we need to specify an instance, or even a class, when we call
startActivity
? Because our
program is an instance of the Activity
class. We are calling a method of
the class this object is an instance of. We could have used the
following instead:
this.startActivity(callIntent);
Our program is already an Activity, but we now want to start a
new instance of the Activity
class—one that can handle the Intent
instance we created. The Android
framework handles the call by searching for an Intent that matches
our request for ACTION_CALL
.
Let’s step over this line and see what happens.
Now the arrow in the left margin of the code view points to the last line of the call method, just before the method returns. The emulator window shows the Android call status application displaying the number we specified. It should look like Figure 14-2, shown earlier in this chapter.
The fact that we stepped over this line of code and can now continue executing our program means that making a phone call this way is asynchronous: it allows our program to continue running while the dialer program makes the phone call.
Android is a collection of applications, and the application you are debugging places no restrictions on other applications that can be running at the same time.
What if something goes wrong? The code in the call method
that starts the dialer is wrapped in a try/catch block. The catch
statement contains a line of code that
logs an error if the startActivity
method throws an exception of the type ActivityNotFoundException
. If a method can
throw an exception that indicates an error, the call to that method
should be in a try/catch block that catches that type of exception. In
this case, we use Android’s logging facility to record the
error.
We do not catch all exceptions, because unexpected exceptions indicate failures a program cannot, in general, recover from.
Let’s make an exception happen. We can do this by removing part
of the data needed to have the startActivity
method call work correctly.
Comment out line 22 of the code, as shown:
// callIntent.setData(Uri.parse("tel:9785551212"));
Now make some changes to breakpoints. Clear the breakpoint on
line 21, and set a breakpoint on line 25, where, in the catch clause,
the method of the Log
class is
called to log the caught exception. Use the Run → Debug command again
to start the program.
This time you will see execution stop at the new breakpoint you
set. You will also see that an exception has been thrown. The Debug
view in Eclipse shows a stack backtrace, a list of all
the methods called when the exception was thrown. The Variables view
shows that activityException
now
refers to the exception that was thrown. Look at the members of the
exception to see the information this exception provides.
If you examine the exception that was thrown (you can do this by
hovering your mouse over activityException
) you will see that the
explanation for the exception reads “No activity found to handle
intent.” That is, in the case of an Intent
object created with Intent.ACTION_CALL
as the argument to the
constructor, it also needs the data of the Intent to be set correctly
in order to find an activity to process that Intent.
Getting modularity right is difficult. In the case of Android, the problem is especially difficult: mobile phones were not designed to have replaceable software components, but Android is all about replaceable, modular parts. Every part of the Android application environment, even core components that handle phone calls and talk to the mobile radio, can be replaced by code you can write.
How do you avoid perplexing program authors with too much complexity managing the interfaces and the versions of the interfaces between modules? The mobile radio in a handset has a particularly complex interface. In addition to the obvious functionality for starting and ending phone calls and reporting state and error conditions, it also encompasses critical functions such as emergency calls, and obscure functions such as “MMI codes” that enable users to access features of the phone and mobile network through special dialing strings.
Android provides a practical, usable, and flexible system for
modularity for telephony applications. It uses the Android system of
Intent
objects and activities that
listen for Intent
objects that
indicate they should handle a particular request. In this case, we see
that the Intent
class and the
activities and data you need to specify when making a phone call are
easy to use. We also see that application-level modularity is a boon
to practicality: because you don’t need to track the inner workings of
a phone call—PhoneApp does it for you.
Android does all of this without replacing, modifying, or adding requirements to the modularity tools provided by Java. You still have class libraries, reflection, and other tools for making and using existing Java software modules.
In the next chapter, you will see what happens inside of Android’s telephony software, all the way down to how the mobile radio is commanded to start a phone call.
3.238.227.73