In this codelab, you'll learn how to use intents to make your application a better "citizen" of the device, that can perform functions for other applications and respond to system alerts. You'll do this by taking a basic media player application, and gradually making it better.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building Android apps?

Novice Intermediate Proficient

You can either download all the sample code to your computer...

Download Zip

...or clone the GitHub repository from the command line.

$ git clone https://github.com/googlecodelabs/app-citizenship-with-intents.git

First, let's see what the finished sample app looks like. With the code downloaded, the following instructions describe how to open the completed sample app in Android Studio.

  1. Select the begin directory from the sample code folder (Quickstart > Import Project... > begin).
  2. Click the Gradle sync button.
  3. Enable USB debugging on your Android device.
  4. Plug in your Android device and click the Run button. You should see the app's home screen appear after a few seconds.

Frequently Asked Questions

Imagine: There you are, walking down the street, music from your swanky new Android phone pumping through through your earbuds, experiencing sheer sonic euphoria as the world around you seems to thump and clang in time to the beat. You pass by a library and remember those books you need to drop off. As you step inside, even through the music, even through your headphones, you can hear the stillness of the place. The silence is a powerful thing, in a place where knowledge is revered. You tread carefully.

Well, you try to tread carefully. As you round a corner, a pencil sharpener rebelliously hooks the cable of your earbuds and yanks them out of your phone. Sound leaks out, BLASTS out, and that beautiful silent stillness is washed away in the cacophony of the latest Skrillex/Celine Dion mashup to top the charts. Embarrassment turns terror as your eyes meet those of The Librarian, who, at this point, is singular in both rage and purpose. Children cry out in fear at that look of fury (or perhaps your ludicrous taste in music). Helpless, ashamed, and afraid, you run, vowing never to return.

The problem? Your media player is a bad app, and it has failed you. What it should have done is recognize when a headset was unplugged, and pause the music before it blasted out your speakers at a much higher and less considerate volume. Alas! It doesn't know how.

Let's fix that.

The first thing your app needs to do is listen for an "Audio becoming Noisy" event. In the Android Framework, these are sent by a Broadcast Intent.

A Broadcast Intent is just a message that any app on the system can pick up. Broadcast intents are used by the system to alert apps to system-level events like the device running low on memory or being plugged into a charger.

They're different than implicit intents because multiple apps can respond to the same event. While you don't want 8 camera apps opening every time you fire an intent to take a picture, you do want 8 currently running apps to quiet down when you receive a call, or shrink their memory footprint when the device is low on memory.

In this case, the media player needs to listen for an AudioManager.ACTION_AUDIO_BECOMING_NOISY intent, which indicates that things are about to get loud thanks to a change in audio output.

The first thing to do is write a BroadcastReceiver, which can listen for this intent.

Add a Broadcast Receiver

Since this BroadcastReceiver will only be interacting with the ArtistActivity class, open that up and let's add an inner class near the bottom.

/**
* Listens for situations where the audio is about to become suddenly noisy, like headphones
* being unplugged from the device.
*/
public class AudioEventReceiver extends BroadcastReceiver {
   public AudioEventReceiver() {
   }

   @Override
   public void onReceive(Context context, Intent intent) {
       // Something happened out in the system! But what?  Shake this broadcast intent by
       // the shoulders and demand answers!  What's happening out there? WHAT DID YOU SEE?!
       String action = intent.getAction();
       if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {

           // Audio is about to become "noisy" due to a change in audio outputs.
           // It's a good time to pause.
           ArtistActivity.this.onReallyGoodReasonToPauseMusic();
       }
   }
}

Excellent! Now let's fill in that "onReceive" method. This method is where you analyze the broadcast intent and react to it in some useful way.

To inspect the intent, first access the "action" of that intent. This receiver should react to a AudioManager.ACTION_AUDIO_BECOMING_NOISY action, so compare the action you extract from the intent against that.

@Override
public void onReceive(Context context, Intent intent) {
   // Something happened out in the system! But what?  Shake this broadcast intent by
   // the shoulders and demand answers!  What's happening out there? WHAT DID YOU SEE?!
   String action = intent.getAction();
   if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {

       // Audio is about to become "noisy" due to a change in audio outputs.
       // It's a good time to pause.
       ArtistActivity.this.onReallyGoodReasonToPauseMusic();
   }
}

That onReallyGoodReasonToPauseMusic method, brilliantly named as it is, doesn't actually exist yet in the parent Activity. We need to add a quick method to the ArtistActivity so that it can look up the fragment that handles playback, and send the pause command along to it. Add the following method to your Activity, that will fix our little plumbing issue.

/**
* Callback for the broadcast receiver to pause music when necessary.  The Activity should know
* to punt this responsibility to a specific fragment, but the broadcast receiver shouldn't
* have to worry about such details.
*/
public void onReallyGoodReasonToPauseMusic() {
   // A really good reason to pause music has occurred.  How should we react?
   // Impromptu dance party?  Pause the music?  Heckle innocent codelab authors?  Wait, let's
   // go back one.  Pause the music! Yeah!  First get a reference the fragment that manages
   // playback.
   PlaylistFragment playlistFragment = (PlaylistFragment) getSupportFragmentManager()
           .findFragmentById(R.id.playlist_fragment);

   // Now... what to do? WHAT TO DO?
   playlistFragment.pause();
}

At this point, you're probably itching to try out all this awesome new code you just wrote. Right there with you, reader. I admire your attitude. If you go ahead and open the app now, and plug/unplug your earbuds from the device, you'll notice that IT DOES NOTHING.

Why? Why does it do nothing?

Because we haven't registered the BroadcastReceiver yet. The system has to be aware that there's a BroadcastReceiver in place, and what kind of broadcast intents should go to it. Otherwise it's a lump of dead code sitting in your application waiting for a purpose that will never come. Depressing, right?

Let's fix that!

In ArtistActivity's onResume method, add a couple lines that create a new AudioEventReceiver and register it with the system. The registering method also takes an IntentFilter as a parameter, which lets us specify what KIND of events we want to receive. Which is great, because it means the code doesn't have to fire up every time we get a low battery warning, receive an SMS message, or anything else happens that we have absolutely no interest in. In onResume, add this:

mReceiver = new CallEventReceiver();
registerReceiver(mReceiver, new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED));

Correspondingly we want to unregister this receiver when ArtistActivity is no longer active, because the way this media player is set up, that means music isn't playing anymore. You'll want to unregister by overriding onPause and calling unregister, like so:

@Override
protected void onPause() {
   unregisterReceiver(mReceiver);
   super.onPause();
}

Now compile your app and run it. Plug some headphones into your app. When you unplug them, the music should pause. Vicious library scenario averted! Hooray!

So now you have a media player that not only knows how to perform its basic purpose in life, but also how to not be totally selfish and irritating. Congratulations, your app is... adequate. A solid 3 stars out of 5. If you were to publish at this point, you would get such glowing reviews as "did not offend me with its painful mediocrity." or "I didn't uninstall it until a couple days after I found something better." Deep down you'd know it could be better. Deep down, you'd know that 5-star rating with the review ".r@@ " was a toddler who got ahold of his parent's phone and slapped the touch screen a few times to see what noise it would make before hurling it across the room. You didn't earn those 5 stars. Not... Not really. Not yet.

What's missing? Well, there's two parts to good app citizenship. In the previous section we covered listening for Broadcast Intents to make sure we're not being a public nuisance to the "community" (whatever, we're stretching the analogy, just go with it). The second part is giving back. Helping little old apps cross the street. Pitching in. Doing your part. Letting your app be a resource.

Imagine for a moment what it would be like if your media player could respond to voice commands. "Play Dual Core" would kick off some tunes by your favorite Nerdcore artists. "Play something awesome" would pick music that your app had decided was just really great.

But your media player doesn't respond to media related intents, let alone voice commands, so it wallows in it's own siloed feature set. And for a media player, that's just really sad.

Let's fix that.

Add an Intent Filter

An intent filter tells the system what types of intents an app can handle. Intent filters are declared in the app's AndroidManifest.xml file.

Open that file and add an "intent-filter" tag. It should go after the existing intent-filter tag, as a child of the first Activity tag. Here's what it should look like.

<activity
   android:name="com.jarekandshawnmusic.m.MainActivity"
   android:label="@string/title_activity_main" >
...
   <intent-filter>
       <action
           android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
       <category android:name="android.intent.category.DEFAULT" />
       <category android:name="android.intent.category.BROWSABLE" />
   </intent-filter>
...
</activity>

Okay, what did we just do? That intent filter specifies that your application's MainActivity can respond to an intent of the type "android.media.action.MEDIA_PLAY_FROM_SEARCH". Which, according to this list of common Android intents, is an intent to play music based on a search query. Adding an intent to your manifest like that tells the system, "This app is ready and able to respond to this intent". It's a promise that we'll do something useful and relevant to the request. Not just throw up an ad for your completely unrelated cheese shop.

Now that we've promised the Android Framework that we know how to play media from search, we should probably write some code to handle playing media from search.

Respond to the incoming intent

Open up MainActivity.java in Android Studio.

Add code to the onCreate method that

This is how you'd do that.

// Get the intent that launched this Activity.
Intent intent = getIntent();

// Check the intent for an artist name
String artistName = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);

// If the artist isn't null, that means this activity was started with the intention of
// playing music by this specific artist.  Don't wait around for the user, just start
// playing stuff by this artist.
if (artistName != null) {
   MyMusicStore.Artist artist = mMyMusicStore
           .getArtist(artistName);
   if (artist != null) {
       // create another intent to launch ArtistActivity
       Intent artistIntent = new Intent(MainActivity.this,
               ArtistActivity.class);
       // get stored artist URL
       artistIntent.setData(Uri.parse(artist.getUrl()));
       startActivity(artistIntent);
   }
}

Try a Music Action

Ready to ship? Easy there code cowboy! Let's make sure this thing actually works first. Build the app and install it on an Android device. Then in the Google Search bar, type "play dual core". Or tap the microphone and SAY "play dual core", if you want to feel like a mad scientist.

There should be a play icon with a small gray arrow in your search results. That's because there are probably multiple apps on your device that can handle this request. Pick one! No, you know what? Pick yours. If all has gone according to plan, your phone should be playing music, because you told it to. From outside the app.

A high quality Android app doesn't exist in a vacuum. It can respond to the needs of the system it inhabits, whether that means knowing when to get out of the user's way or extending the functionality of other applications. You took a basic application and made it a thoughtful one. By taking this mindset of good citizenship and extending it to your own applications, you'll find that user satisfaction and the longevity of an app install will increase over time.

When you have a spare moment, we'd really appreciate if you could fill out some feedback on your codelab experience. We'll use this information to iterate and improve the codelab over time.

Tell us how we did