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 Intent26 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 the this would refer to the anonymous listener class (for methods in Main, we can just use this).

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, or ACTION_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 to DIAL).

  • 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 by startActivity() and startActivityForResult.

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, but Activities 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 an Intent that we give to another class. Then when that class receives our PendingIntent and reacts to it, it can run the Intent (command) we sent it with as if that Activity 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 (that Intent 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 the getBroadcast() method to specify that the intent should be sent via a Broadcast (c.f. getActivity() for startActivity()).
  • First param is content that should send the intent, then a request code (e.g., for result callbacks if we wanted), then the Intent, 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.

8.7 ShareActionProvider

But wait there’s more we can do with Intents One of the other things we can add to menus are Action Views that are expandable widgets in the action bar (e.g., search example). Or, to play around with Intents more, we can add an Action Provider (like ShareActionProvider), which gives us a bunch of interaction built into the menu! This is the “quick share with these social media sites” button that we see commonly.

  • We’d want to look at class documentation for how to set this up (it’s much clearer than the training docs).

How to use it:

  • We’re going to add another item to our menu’s XML. This will look like most items, except it will have an extra field app:actionProviderClass

    <item
        android:id="@+id/menu_item_share"
        android:title="Share"
        app:showAsAction="ifRoom"
        app:actionProviderClass="android.support.v7.widget.ShareActionProvider"
        />
  • We’ll then add the item to our menu in onCreateOptionsMenu()

    MenuItem item = menu.findItem(R.id.menu_item_share);
    mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
    
    Intent intent = new Intent(Intent.ACTION_DIAL);
    intent.setData(Uri.parse("tel:206-685-1622"));
    
    mShareActionProvider.setShareIntent(intent);
  • We get access to the item using findItem(), and then cast it to a ShareActonProvider (make sure you’re using the support version!)

  • We can then specify an implicit Intent that we want that “Share Button” to be able to perform. This would commonly use the ACTION_SEND action (like for sharing a picture or text), but we’ll use the DIAL action because we have a couple of dialers but don’t actually have many SEND responders on the emulator.

The Menu item will then list a dropdown with all of the different Activities that resolve to handling that implicit intent!