Lecture 8 Intents
This lecture discusses how to use Intents to communicate between different Activities and Applications. The Intent system allows Activities to communicate, even though they don’t have references to each other (and thus we can’t just call a method on them).
This lecture references code found at https://github.com/info448-s17/lecture08-intents. Note that you will need to have a working camera on your device. To enable the camera in the emulator, use the Tools > Android > AVD
menu to modify the emulator, and select “webcam” for the front camera option. Confirm that it is enabled by launching the Camera app.
An Intent is a message that is sent between app components, allowing them to communicate!
Most object communication we do is via direct method call; you have a reference to an Object and then you call a method on it. We’ve also seen event callbacks, where on an event one of our callbacks gets executed by the system (really just a wrapper around direct method call via the Observer pattern)
Intents step outside of this a little bit: they allow us to create objects that can be “given” to another component (read: Activity), who can then respond upon receiving that. Similar to an event callback, but working at a slightly higher system level.
You can think of Intents as like letters you’d send through the mail: they are addressed to a particular target (e.g., another Activity—more properly a Context
), and have room for some data called extras to go inside (held in a Bundle
). When the envelope arrives, the recipient can get that data out and do something with it… and possibly sending a response back.
Note that there are couple of different kinds of Intents; we’ll go through examples of each.
8.1 Intents for Another Activity (Explicit)
The most basic kind of Intent is an Intent sent to a specific Activity/Context, such as for telling that Activity to open.
An Intent
26 is an object we can instantiate: for example, we can create a new Intent
in the event handler for when we click the button on MainActivity
. The Intent
class has a number of different constructors, but the one we’ll start with looks like:
// context, target
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
The first parameter refers to the current Context in which the message should be delivered. The second parameter to this constructor is the class we want to send the Intent to (the
.class
property fetches a reference to the class type; this is metaprogramming!). Effectively, it is the “address” on the envelop for the message we’re sending.- We’re using
MainActivity.this
as the context, because thethis
would refer to the anonymous listener class (for methods inMain
, we can just usethis
).
- We’re using
After having instantiated the new Intent
, we can use that message to start an Activity by calling the startActivity()
method (inherited from Activity
), passing it the Intent
:
startActivity(intent);
This method will “send” the message to the operating system, which will deliver the Intent to the appropriate Activity, telling that Activity to start as soon as it receives the message.
- And we can use the back button to go backwards! See the Activities lecture for details.
This is called an Explicit Intent because we’re explicit about what target we want to receive it. It’s a letter to a specific Activity.
8.1.1 Extras
We can also specify some extra data inside our envelope. These data are referred to as Extras. This is a Bundle
(so a set of primitive key-value pairs) that we can use to pass limited information around!
intent.putExtra("package.name.key","value");
- Docs say that best practice is to include the full package name on keys, so avoid any collisions or misreading of data. There are also some pre-defined values (constants) that you can use in the
Intent
class.
We can then get the extras from the Intent in the Activity that receives it:
//in onCreate();
Bundle extras = getIntent().getExtras(); //All activities are started with an Intent!
String value = extras.getString("key");
So we can have Activities communicate, and even share information between them! Yay!
8.2 Intents for Another App (Implicit)
We can send Intents to our own Activities, but we can even address them to other Apps. When calling on other apps, we usually use Implicit Intents.
- This is a little bit like letters that have weird addresses27, but still get delivered. “For that guy at the end of the block with the red mailbox.”
An Implicit Intent includes an Action and some Data. The Action says what the target should do upon receiving the intent (a Command), and the Data gives more detail about what to run that action on.
Actions can be things like
ACTION_VIEW
to view some data, orACTION_PICK
to choose an item from a list. See a full list under “Standard Action Activities”.ACTION_MAIN
is the most common (just start the Activity as if it were a “main” launching point). So when we don’t specify anything else, this is used!Data gives detail about what to do with the action (e.g., the Uri to
VIEW
or the Contact toDIAL
).Extras then support this data!
For example, if we specify a DIAL
action, then we’re saying that we want our Intent to be delivered to an App that is capable of dialing a telephone number. - If there is more than one app that supports this action, the user will pick one! This is key: we’re not saying exactly what app to use, just what kind of functionality we need to be supported! It’s a kind of abstraction!
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:206-685-1622"));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
Here we’ve specified the Action (ACTION_DIAL
) for our Intent, as well as some Data (a phone number, converted into a Uri). The resolveActivity()
method looks up what Activity is going to receive our action–we check that it’s not null before trying to start it up.
- This should allow us to “dial out” !
Note that we can open up all sorts of apps. See Common Intents28 for a list of common implicit events (with examples!).
8.3 Intents for a Response
We’ve been using intents to start Activities, but what if we’d like to get a result back from the Activity? That is, what if we want to look up a Contact or take a Picture, and then be able to use the Contact or show the Picture?
To do this, we’re going to create Intents in the same way, but use a different method to launch them: startActivityForResult()
. This will launch the resolved Activity. But once that Action is finished, the launched Activity will send another Intent back to us, which we can then react to in order to handle the result.
- This is a bit like including an “RSVP” note in a letter!
For fun, let’s do it with the Camera–we’ll launch the Camera to take a picture, and then get the picture and show it in an ImageView
we have.
Note that your Emulator will need to have Camera emulation on!
See Taking Photos Simply for walkthrough.
In the activity, we can specify an intent that uses the MediaStore.ACTION_IMAGE_CAPTURE
action (the action for “take a still picture and return it”).
The “request code” is used to distinguish this intent from others we may send (kind of like a “tag”).
Note that we could pass an Extra for where we want to save the large picture file to. However, we’re going to leave that off and just work with the thumbnail for this demonstration. See the guide29 for details; if time we can walk through it!
static final int REQUEST_IMAGE_CAPTURE = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
In order to handle the “response” Intent, we need to provide a callback that will get executed when that Intent arrives. Called onActivityResult()
.
We can get information about the Intent we’re receiving from the params. And we can get access to the returned data (e.g., the image) by getting the
"data"
field from the extras.Note that this is a
Bitmap
, which is the Android class representing a raster image. We’ll play with Bitmaps more in a couple weeks, because I like graphics.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);
}
}
8.4 Listening for Intents
We’re able to send implicit Intents that can be heard by other Apps, but what if we wanted to receive implicit Intents ourselves? What if we want to be able to handle phone dialing?!
In order to receive an implicit Intent, we need to declare that our Activity is able to handle that request. Since we’re specifying an aspect of our application, we’ll do this in the Manifest
using what is called an <intent-filter>
.
- The idea is that we’re “hearing” all the intents, and we’re “filtering” for the ones that are relevant to us. Like sorting out the junk mail.
An <intent-filter>
tag is nested inside the element that it applies to (e.g., the <activity>
). In fact, you can see there is already one there: that responds to the MAIN
action sent with the LAUNCHER
category (meaning that it responds to intents from the app launcher).
Similarly, we can specify three “parts” of the filter:
a
<action android:name="action">
filter, which describes the Action we can respond to.a
<data ...>
filter, which specifies aspects of the data we accept (e.g., only respond to Uri’s that look like telephone numbers)a
<category android:name="category">
filter, which is basically a “more information” piece. You can see the “Standard Categories” in the documentation.Note that you must include the
DEFAULT
category to receive implicit intents. This is the category used bystartActivity()
andstartActivityForResult
.
Note that you can include multiple actions, data, and category tags. You just need to make sure that you can handle all possible combinations selected from each type (they are “or” not “and” filters!)
Responding to that dial command:
<activity android:name="SecondActivity">
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
</activity>
You can see many more examples in the Intent
documentation.
8.5 Broadcasts and Receivers
There is one other kind of Intent I want to talk about: Broadcasts. A broadcast is a message that any app can receive. Unlike Explicit and Implicit Intents, broadcasts are heard by the entire system–anything you “shout” with a broadcast is publicly available (security concerns!)
- Mass mailings question mark?
Other than who receives them, broadcasts work the same as normal implicit intents! We create an Intent
with an Action and Data (and Category and Extras…). But instead of using the startActivity()
method, we use the sendBroadcast()
method. That intent can now be heard by all Activities
on the phone,
- We’ll skip a demo for time and motivation… we’ll generate broadcasts later in the course.
But more common than sending broadcasts will be receiving broadcasts; that is, we want to listen and respond to System broadcasts that are produced (things like power events, wifi status, etc). Or more germane to this week’s homework–to incoming text messages!!
We can receive broadcasts by using a BroadcastReceiver
. This is a base class that is used by an class that can receive broadcast Intents. We subclass it and implement the onReceive(Context, Intent)
callback in order to handle when broadcasts are received.
public void onReceive(Context context, Intent intent)
{
Log.v("TAG", "received! "+intent.toString());
else if(intent.getAction() == Intent.ACTION_BATTERY_LOW){
Toast.makeText(context, "Battery is low!", Toast.LENGTH_SHORT).show();
}
}
But in order to register our receiver (so that intents go past its desk), we also need to specify it in the Manifest
. We do this by including a <receiver>
attribute inside our <application>
. Note that this is not an Activity, but a separate component! We can put an <intent-filter>
inside of this to filter for broadcasts we care about.
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<action android:name="android.intent.action.BATTERY_CHANGED" />
<action android:name="android.intent.action.BATTERY_OKAY" />
<!-- no category because not for an activity! -->
</intent-filter>
</receiver>
We can test these power events easily using the latest version of the emulator. In the “extra options” button (the three dots at the bottom) in the emulator’s toolbar, we can get to the Battery
tab where we can effectively change the battery status of the device (which our app can respond to!)
- Note that there is a
Phone
tab where you can send Text Messages to the emulator… you’ll need this for your homework this week.
We can also register these receivers in code (rather than in the manifest). This is good for if we only want to temporarily listen for some kind of events, or if we want to determine the intent-filter
on the fly.
IntentFilter batteryFilter = new IntentFilter();
batteryFilter.addAction(Intent.ACTION_BATTERY_LOW);
batteryFilter.addAction(Intent.ACTION_BATTERY_OKAY);
batteryFilter.addAction(Intent.ACTION_POWER_CONNECTED);
batteryFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
this.registerReceiver(new MyReceiver(), batteryFilter);
- We’re dynamically declaring an intent-filter as well! This can be used not just for
BroadcastReceivers
, butActivities
too.
8.6 An Example: SMS
One specific use of Intents is when working with text messages (SMS, Short Messaging Service, the most popular form of data communication in the world). While it is possile to fetch a list of messages usin a ContentProvider
, it is also possible to send SMS as well. This will also let us show off one more type of Intent
.
- Important note: the SMS APIs changed drastically in KitKat (API 19). So we’re going to make sure that is our minimum so we can get all the helpful methods and support newer stuff (check gradle to confirm!).
The main thing to note about sending SMS is that as of KitKat, each system has a default messaging client—who is the only one who can actually send messages. Luckily, the API lets you get access to that messaging client’s services in order to send a message through it:
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage("5554", null, "This is a test message!", null, null);
// target, message
We will need permission: <uses-permission android:name="android.permission.SEND_SMS" />
If we look at the documentation for this method30, you can see that this works by looking at the inbox in the Messages app… but there is another way as well. Those last two parameters are for PendingIntents
: one for when messages are sent and one for when messages are delivered.
- What’s a
PendingIntent
? The details are not super readable… It’s basically a wrapper around anIntent
that we give to another class. Then when that class receives ourPendingIntent
and reacts to it, it can run theIntent
(command) we sent it with as if thatActivity
was us (whew). - Basically we’re saying “when I call you, you can come pick me up using my car” kind of thing.
Or like if you gave a stamped envelope to someone to put your letter or recommendation inside (do this!)
So the idea is we specify what
Intent
should be delivered when the message is finished being sent (thatIntent
becomes “pending”). Effectively, this let’s us send Intents in response to some other kind of event.
Let’s go ahead and set one up:
public static final String ACTION_SMS_STATUS = "edu.uw.intentdemo.ACTION_SMS_STATUS";
...
Intent intent = new Intent(ACTION_SMS_STATUS);
PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);
smsManager.sendTextMessage("5554", null, "This is a test message!", pendingIntent, null);
We’re doing a couple of steps here:
- We’re defining out own custom Action. It’s just a
String
, but name-spaced to avoid conflicts - We then create an implicit intent for this action
- And then create a
PendingIntent
. We’re using thegetBroadcast()
method to specify that the intent should be sent via a Broadcast (c.f.getActivity()
forstartActivity()
). - First param is
content
that should send the intent, then a request code (e.g., for result callbacks if we wanted), then theIntent
, and finally any extra flags (none for now).
We can then have our BroadcastReceiver
respond to this Intent
just like any other one!
if(intent.getAction() == MainActivity.ACTION_SMS_STATUS) {
if (getResultCode() == Activity.RESULT_OK) {
Toast.makeText(context, "Message sent!", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(context, "Error sending message", Toast.LENGTH_SHORT).show();
}
}
- Don’t forget to add our custom intent to the
<intent-filter>
!
We’ll see more with PendingIntents
in the next chapter when we talk about notifications.
http://developer.android.com/reference/android/content/Intent.html↩
http://www.theguardian.com/world/2015/jul/18/postman-turns-detective-to-deliver-letter-with-cryptic-address-in-ireland↩
http://developer.android.com/guide/components/intents-common.html↩
http://developer.android.com/training/camera/photobasics.html#TaskPath↩
https://developer.android.com/reference/android/telephony/SmsManager.html↩