9781449390501
Android_Broadcast_Receivers.html

Chapter 11. Broadcast Receivers

If you're new to the Android mobile operating system, Learning Android is the perfect way to master the fundamentals. This gentle introduction shows you how to use Android's basic building blocks to develop user interfaces, store data, and more. Buy the print book or ebook.

In this chapter, you will learn what Broadcast receivers are and when to use them. We’ll create couple of different receivers illustrating different usage scenarios. First, you’ll create a broadcast receiver that will start up your update service at boot time, so that users always have their latest friends' timelines the first time they check for them (assuming their preferences are set). Next, you will create a receiver that will update the timeline when it changes while the user is viewing it. This will illustrate the programmatic registration of receivers and get you introduced to broadcasting intents. We’ll implement a receiver that is trigged by changes in network availability. And finally, we’ll learn how to surround our app with some security by defining permissions.

By the end of this chapter, your app has most of the functionality that a user would need. The app can send status updates, get friends' timelines, update itself, and start automatically. It works even when the user is not connected to the network (although of course it cannot send or receive new messages).

About Broadcast Receivers

Broadcast receivers are Android’s implementation of the Publish/Subscribe messaging pattern, more precisely is an Observer pattern. Applications (known as publishers) can generate broadcasts to simply send events not knowing who, if anyone, will get them. Receivers (known as subscribers) that want the information subscribe to specific messages via filters. If the message matches a filter, the subscriber is activated (if it’s not already running) and notified of the message.

As you may recall from Chapter 4, Main Building Blocks, a BroadcastReceiver is a piece of code to which an app subscribes in order to get notified when an action happens. That action is in a form of an intent broadcast. When the right intent is fired, the receiver wakes up and executes. The "wakeup" happens in form of a onReceive() callback method.

BootReceiver

In our Yamba application, the UpdaterService is the one responsible for periodically updating the data from the online service. Currently, the user needs to manually start the service, which she does by first starting the application, then clicking on Start Service menu option.

It would be much cleaner and simpler if somehow UpdaterService was started automatically by the system when the device is powered up. To do this, we create BootReceiver, a broadcast receiver that will get launched by the system when the boot is complete, and in turn will launch our TimelineActivity activity. The following few lines sets up our broadcast receiver.

Example 11.1. BootReceiver.java

package com.marakana.yamba6;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class BootReceiver extends BroadcastReceiver { // 1

  @Override
  public void onReceive(Context context, Intent intent) { // 2
    context.startService(new Intent(context, UpdaterService.class)); // 3
    Log.d("BootReceiver", "onReceived");
  }

}

1

We create BootReceiver by subclassing BroadcastReceiver, the base class for all receivers.

2

The only method that we need to implement is onReceive(). This method gets called when an intent matches this receiver.

3

We launch an intent to start our Updater service. The system passed us a Context object when it invoked our onReceive() method, and we are expected to pass it on to the Updater service. The service doesn’t happen to use the Context object for anything, but we’ll see an important use later for a context.

At this point, we have our boot receiver. But, in order for it to get called - in order for the activity to start at boot - we must register it with the system.

Registering the BootReceiver with the AndroidManifest file

To register BootReceiver, we add it to the manifest file. We also add an intent filter to it. This intent filter specifies which broadcasts trigger the receiver to get activated.

Example 11.2. AndroidManifest.xml: <application> section

...
<receiver android:name=".BootReceiver">
  <intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED" />
  </intent-filter>
</receiver>
...

In order to get notifications for this particular intent filter, we must also specify that we’re using a specific permission required by it, in this case android.permission.RECEIVE_BOOT_COMPLETED.

Example 11.3. AndroidManifest.xml: <manifest> section

...
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...

Note

If we don’t specify the permission we require, we simply won’t get notified when this event occurs, and we won’t have the chance to run our start-up code. We won’t even know we aren’t getting notified, so this is potentially a hard bug to find.

Testing the Boot Receiver

At this point, you can reboot your device. Once it comes back up, your UpdaterService should be up and running. You can verify that either by looking at the LogCat for our output, or by using System Settings and checking that the service is running.

To verify via System Settings, at the Home screen, click on th Menu button and choose Settings→Applications→Running Services. You should see UpdaterService listed there. At this point, you know the BootReceiver did indeed get the broadcast and has started the UpdaterService.

The TimelineReceiver

Currently, if you view your Timeline activity while a new status update comes in, you wouldn’t know about it. That’s because the UpdaterService doesn’t have a way to notify TimelineActivity to refresh itself.

To address this, we create another broadcast receiver, this time as an inner class of TimelineActivity.

Example 11.4. TimelineActivity.java with TimelineReceiver inner class

...
class TimelineReceiver extends BroadcastReceiver { // 1
  @Override
  public void onReceive(Context context, Intent intent) { // 2
    cursor.requery(); // 3
    adapter.notifyDataSetChanged(); // 4
    Log.d("TimelineReceiver", "onReceived");
  }
}
...

1

As before, to create a broadcast receiver, we subclass BroadcastReceiver class.

2

The only method we need to override is onReceive(). This is where we put the work we want done when this receiver is triggered.

3

The work we want done is simply to tell the cursor object to refresh itself. We do this by invoking requery(), which executes the same query that was executed initially to obtain this cursor object.

4

Notifies the adapter that underlying data has changed.

At this point, our receiver is ready but not registered. Unlike BootReceiver, where we registered our receiver with the system statically, via the manifest file, we’ll register TimelineReceiver programmatically. This is because TimelineReceiver makes sense only within TimelineActivity as it’s purpose is to refreshing the list when the user is looking at Timeline Activity.

Example 11.5. TimelineActivity.java with TimelineReceiver

...
@Override
protected void onResume() {
  super.onResume();

  // Get the data from the database
  cursor = db.query(DbHelper.TABLE, null, null, null, null, null,
      DbHelper.C_CREATED_AT + " DESC");
  startManagingCursor(cursor);

  // Create the adapter
  adapter = new TimelineAdapter(this, cursor);
  listTimeline.setAdapter(adapter);

  // Register the receiver
  registerReceiver(receiver, filter);   // 1
}

@Override
protected void onPause() {
  super.onPause();

  // UNregister the receiver
  unregisterReceiver(receiver);  // 2
}
...

1

We register the receiver in onResume() so that it’s registered whenever the TimelineActivity is running. Recall that all paths to the Running state go through the onResume() method, as described in the section called “Running State”.

2

Similarly, we unregister the receiver on the way to the Stopped state (recall the section called “Stopped State”). onPause() is a good place to do that.

What’s missing now is the explanation of what filter is. To specify what triggers the receiver, we need an instance of IntentFilter, which simply indicates which intent actions we want to be notified about. In this case, we make up an action string through which we filter intents.

Example 11.6. TimelineActivity.java with update onCreate()

...
filter = new IntentFilter("com.marakana.yamba.NEW_STATUS"); // 1
...

1

Create a new instance of IntentFilter to filter for the com.marakana.yamba.NEW_STATUS intent action. Since this is a text constant, we’ll define it as such and refer to it as a constant later on. A good place to define it is the UpdaterService, because that’s the code that generates the events we’re waiting for.

Broadcasting Intents

Finally, to trigger the filter, we need to broadcast an intent that matches the action that the intent filter is listening for. In case of BootReceiver, earlier, we didn’t have to do this because the system was already broadcasting the appropriate intent. However, for TimelineReceiver, the broadcast is ours to do because the intent is specific to our application.

If you recall from Chapter 8, Services, our UpdaterService had an inner class called Updater. This was the separate thread that connected to the online service and pulled down the data. Becausee this is where we know whether or not there are any new statuses, this is a good place to send notifications from as well.

Example 11.7. UpdaterService.java with Updater inner class

...
private class Updater extends Thread {
  Intent intent;

  public Updater() {
    super("UpdaterService-Updater");
  }

  @Override
  public void run() {
    UpdaterService updaterService = UpdaterService.this;
    while (updaterService.runFlag) {
      Log.d(TAG, "Running background thread");
      try {
        YambaApplication yamba =
            (YambaApplication) updaterService.getApplication(); // 1
        int newUpdates = yamba.fetchStatusUpdates();  // 2
        if (newUpdates > 0) { // 3
          Log.d(TAG, "We have a new status");
          intent = new Intent(NEW_STATUS_INTENT); // 4
          intent.putExtra(NEW_STATUS_EXTRA_COUNT, newUpdates); // 5
          updaterService.sendBroadcast(intent); // 6
        }
        Thread.sleep(60000); // 7
      } catch (InterruptedException e) {
        updaterService.runFlag = false; // 8
      }
    }
  }
}
...

1

We get the application object to access our common application methods.

2

If you recall, our application provides fetchStatusUpdates() to get all the latest status updates and populate the database. This method returns the number of new statuses.

3

We check whether there are any new statutes at all.

4

This is the intent we are about to broadcast. NEW_STATUS_INTENT is a constant that represents an arbitrary action. In our case, we define it as com.marakana.yamba.NEW_STATUS, but it could be any string without spaces. However, using something that resembles your package name is a good practice.

5

There’s a way to add data to an Intent. In our case, it would be useful to communicate to others as part of this broadcast how many new statuses there arre. In this line, we use Intent’s putExtra() method to add the number of new statuses under a key NEW_STATUS_EXTRA_COUNT, which is just our arbitrary constant.

6

At this point, we know there’s at least one new status. sendBroadcast() is part of Context, which is a superclass of Service and therefore also a superclass of our UpdaterService. Since we’re inside the Updater inner class, we have to refer to the parent’s updaterService instance in order to call sendBroadcast(). This method simply takes the intent we just created.

7

We tell this thread to sleep for a minute, so that it doesn’t overload the device’s CPU while checking regularly for updates.

8

In case this thread is interrupted for whatever reason, we update this service’s runFlag so we know it’s not running any more.

Note

UpdaterService may be sending broadcasts even when the TimelineReceiver` is not registered. That is perfectly fine. Those broadcasts will simply be ignored.

At this point, a new status received by UpdaterService causes an intent to be broadcast over to the TimelineActivity, where the message gets received by the TimelineReceiver, which in turn refreshes the ListView of statuses.

The Network Receiver

With the current design, our service will start automatically at boot time and will attempt to connect to the cloud and retrieve latest updates approximately every minute. One of the problems with the current design is that the service will try to do so even when there’s no Internet connection available. This adds unnecessary attempts to wake up the radio and connect to the server, all of which is taxing the battery. Imagine how many wasteful attempts would be made while your phone is in Flight Mode on a cross-country flight. This highlights some of the inherit constraints for programming for mobile devices - we’re limited by the battery life and network connectivity.

A better approach would be to listen to network availability broadcasts and use that information to intelligently turn the service off when the Internet is unavailable and turn it back on when data connection comes back up. The system does send an intent whenever connection availability changes. Another system service allows us to find out what changed and act accordingly.

In this case, we’re creating another receiver, NetworkReceiver. Just as before, we need to create a Java class that subclasses BroadcastReceiver, and register it via the Android manifest file.

Example 11.8. NetworkReceiver.java

package com.marakana.yamba6;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.util.Log;

public class NetworkReceiver extends BroadcastReceiver { // 1
  public static final String TAG = "NetworkReceiver";

  @Override
  public void onReceive(Context context, Intent intent) {

    boolean isNetworkDown = intent.getBooleanExtra(
        ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);  // 2

    if (isNetworkDown) {
      Log.d(TAG, "onReceive: NOT connected, stopping UpdaterService");
      context.stopService(new Intent(context, UpdaterService.class)); // 3
    } else {
      Log.d(TAG, "onReceive: connected, starting UpdaterService");
      context.startService(new Intent(context, UpdaterService.class)); // 4
    }
  }

}

1

As we said before, when you create a new broadcast receiver, you typically start by subclassing Android’s own BroadcastReceiver class.

2

When the system broadcasts the particular intent action that we are subscribed to get in this receiver, the intent will have an extra piece of information indicating if the network is up or down. In this case, the variable is a Boolean value keyed to the ConnectivityManager.EXTRA_NO_CONNECTIVITY constant. In the previous section we associated a value to a string of our own invention; here we’re on the other end of the message, extracting a value from a Boolean. A value of true indicates that the network is down.

3

If the network is down, we simply send an intent to our UpdaterService. We now have a use for the Context object that the system passed to this method. We call its stopService() method, passing the Intent.

4

If the flag was false, we know that the network has changed and is now available. So we start our UpdaterService, the inverse of our previous stop action.

Note

Inside an activity or a service, we simply used methods startActivity(), startService(), stopService() and so on. This is because activities and services are subclasses of Context and thus they inherited these methods. So, there’s an is-a relationship between them and Context. Broadcast Receivers on the other hand have Context object passed into it, thus having a has-a relationship with it.

Now that we have created this new receiver, we need to register it with the manifest file:

Example 11.9. AndroidManifest.xml: <application> section

...
<receiver android:name=".NetworkReceiver">
  <intent-filter>
    <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
  </intent-filter>
</receiver>
...

We also need to update the permissions that our application uses, because that particular action filter for a network change is protected and requires us to ask the user to grant us this particular permission.

Example 11.10. AndroidManifest.xml: <manifest> section

...
<uses-permission android:name="android.permission.INTERNET" /> <!-- 1 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 2 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 3 -->
...

1

Used by our Twitter object to connect to the Internet to get and post status updates. We saw this permission already in Chapter 6, Android User Interface. Not having this permission will cause our app to crash when it attempts to access the network (unless we catch and handle that network exception).

2

Required in order to receive broadcasts that the system has booted. As mentioned earlier, if we don’t have this permission, we will silently be ignored at boot time and our boot code won’t run.

3

Needed in order to receive network state updates. Just as with the boot receiver, if we don’t have this permission, we will be silently passed by when the network state changes.

Adding Custom Permissions to Send and Receive Broadcasts

As discussed in the section called “Updating Manifest File for Internet Permission”, an application must be granted permissions to access certain restricted features of the system, such as connecting to Internet, sending SMS messages, making phone calls, reading the user’s contacts, taking photos, and so on. It is user who has to grant all or none of the permissions to the application at installation time, and it is the job of the application developer to list all the permissions the app needs to successfully run by adding the <uses-permission> element to the manifest file. So far, we’ve added permissions to Yamba in order to access the Internet, kick off our boot-time service, and learn about network changes.

But now that we have our Updater service sending a broadcast action to our Timeline receiver, we may want to restrict permission to send and receive that broadcast to our own app. Otherwise, it would be possible for another app, knowing what our action looks like, to send it and cause actions in our application that we didn’t intent.

To fill up this security hole, we define our own permission and ask the user to grant it to the Yamba application. Next, we’ll enforce both sending and receiving of the permissions.

Declaring Permissions in the Manifest File

The first step is to declare our permissions, explaining what they are, how they are to be used, and what protection level they are at.

Example 11.11. Adding Permissions to Manifest File

<manifest>

  ...

  <permission android:name="com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS" <!-- 1 -->
    android:label="@string/send_timeline_notifications_permission_label" <!-- 2 -->
    android:description="@string/send_timeline_notifications_permission_description" <!-- 3 -->
    android:permissionGroup="android.permission-group.PERSONAL_INFO" <!-- 4 -->
    android:protectionLevel="normal" /> <!-- 5 -->

  <!-- 6 -->
  <permission android:name="com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS"
    android:label="@string/receive_timeline_notifications_permission_label"
    android:description="@string/receive_timeline_notifications_permission_description"
    android:permissionGroup="android.permission-group.PERSONAL_INFO"
    android:protectionLevel="normal" />

  <!-- 7 -->
  <uses-permission android:name="com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS" />
  <uses-permission android:name="com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS" />

</manifest>

1

This is the name of our permission, the handle we are going to use later to refer to it both when we request it and when we enforce it. In our app, we’ll be using the permission to securely send timeline notifications.

2

Label that will be displayed to the user when she is prompted to grant this permission to our app at the installation time. It should be relatively short. Note that we have defined this label in our strings.xml resource file.

3

A description should be provided to offer information about why this permission is needed and how it’s going to be used.

4

The permission group is optional, but helps the system to group your permission with other common permissions in one of the system-defined permission groups. You could also define your own group, but that is rarely done.

5

The permission level is a required value specifying the severity or risk posed by granting the permission. A level of ‘normal’ is the lowest and most basic of the four standard permission levels.

6

We do the same to define the other permission, which allows us to receive the timeline notifications we are generating.

7

Once we have our permissions defined, we need to ask the user to grant them to the application. We do that via the <uses-permission> element, just as we did it for the other system permissions we specified earlier.

At this point, we have defined our two custom permissions and have requested them for our application. Next, we need to make sure the sender and receiver both play by the rules.

Updating the Services to Enforce Permissions

It is our Updater service that broadcasts the intent to the rest of the system once there’s a new status update. Now, since we do not want everyone to receive this intent, we want to ensure that the receiver won’t be allowed to receive it unless the receiver defines the right permission.

Example 11.12. Updater in UpdaterService

  ...
  private class Updater extends Thread {
    static final String RECEIVE_TIMELINE_NOTIFICATIONS =
        "com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS"; // 1
    Intent intent;

    public Updater() {
      super("UpdaterService-Updater");
    }

    @Override
    public void run() {
      UpdaterService updaterService = UpdaterService.this;
      while (updaterService.runFlag) {
        Log.d(TAG, "Running background thread");
        try {
          YambaApplication yamba = (YambaApplication) updaterService
              .getApplication();
          int newUpdates = yamba.fetchStatusUpdates();
          if (newUpdates > 0) {
            Log.d(TAG, "We have a new status");
            intent = new Intent(NEW_STATUS_INTENT);
            intent.putExtra(NEW_STATUS_EXTRA_COUNT, newUpdates);
            updaterService.sendBroadcast(intent, RECEIVE_TIMELINE_NOTIFICATIONS); // 2
          }
          Thread.sleep(DELAY);
        } catch (InterruptedException e) {
          updaterService.runFlag = false;
        }
      }
    }
  } // Updater
  ...

1

This is the name of the permission that we are requiring the receiver to have. It needs to be the same as the permission name in the manifest file that we specified previously.

2

To enforce the permission on the receiver, we simply add it to the sendBroadcast() call as the optional second parameter. If the receiver doesn’t have this particular permission granted to it by the user, the receiver won’t be notified and will never know that our message just got dropped.

To complete the security in the sending direction, we don’t have to do anything to TimelineReceiver. It will be able to receive the permission because the user granted it. But there is a corresponding responsibility on the TimelineReceiver side. It should check that the sender had permission to send the message it is receiving.

Update Timeline Receiver to Enforce Permissions

Now we will check on the receiver side that the broadcaster is allowed to talk to us. To do this, we add the broadcast permission that the sender should have to our receiver when we register it.

Example 11.13. TimelineReceiver in TimelineActivity.java

...
public class TimelineActivity extends BaseActivity {
  static final String SEND_TIMELINE_NOTIFICATIONS =
      "com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS"; // 1
  ...
  @Override
  protected void onResume() {
    super.onResume();
    ...
    // Register the receiver
    super.registerReceiver(receiver, filter,
        SEND_TIMELINE_NOTIFICATIONS, null); // 2
  }
  ...
}

1

We define the permission name as a constant. This needs to be the same name as we declared for this permission in the manifest file.

2

In the onResume() method where we register our TimelineReceiver, we now add a parameter specifying this permission as a requirement placed on anyone who wants to send us this type of broadcast.

We now have a pair of custom permissions and we are enforcing them in both the sender and the receiver of the broadcast. This illustrates some of the capabilities of Android to fine-tune the permission system.

Summary

Yamba is now complete and ready for prime time. Our application can now send status updates to our online service, get the latest statuses from our friends, get started automatically on boot time, and be refreshed live when a new status is received.

Figure 11.1, “Yamba Completion” illustrates what we have done so far as part of the design outlined in Figure 5.4, “Yamba Design Diagram”.

Figure 11.1. Yamba Completion

Yamba Completion

Site last updated on: April 8, 2011 at 12:51:47 PM PDT
Cover for Learning Android

View 1 comment

  1. Marilyn Escue – Posted Dec. 4, 2010

    It "has" most of "the" functionality that a "user" would need.

Add a comment

View 1 comment

  1. Frank Maker – Posted Jan. 16, 2011

    Publish/Subscribe, also more generally known as Observer pattern.

Add a comment

View 1 comment

  1. Frank Maker – Posted Jan. 16, 2011

    filters which trigger out of all the triggers begin broadcast on the system.

Add a comment

View 1 comment

  1. Frank Maker – Posted Jan. 16, 2011

    Could be a good place to give a link to the list of Android generated system broadcasts.

Add a comment

View 1 comment

  1. Kimberley Coburn – Posted July 29, 2011

    I got an error on cursor.requery() that resulted in the list view not being refreshed (instead, it was just cleared). I replaced it with:

            setUpTimelineList();
    

    (which has all our code for initializing the cursor and adapter) and that worked. While doing this, I saw that cursor.requery() is deprecated.

Add a comment

View 3 comments

  1. Alex Me – Posted March 7, 2011

    receiver is not instantiated

  2. ProudGeekDad – Posted Oct. 22, 2011

    "Receiver cannot be resolved to a variable" fix...

    Declare the following class variables:

    TimelineReceiver receiver;
    IntentFilter filter;
    

    In onCreate(), add the following code:

    // Create the receiver
    receiver = new TimelineReceiver();
    

    Edited on October 22, 2011, 6:58 p.m. PDT

  3. Zachary Rowitsch – Posted Jan. 12, 2012

    The code from the final form of TimelineActivity does not match this snippet at all. Also - startManagingCursor(Cursor c) is deprecated...

Add a comment

View 1 comment

  1. Stuart Jackson – Posted Nov. 29, 2010

    I don't have an inner class called updater. I have an UpdaterRunnable that implements Runnable?

Add a comment

View 1 comment

  1. Zachary Rowitsch – Posted Jan. 12, 2012

    Here we use the constant version of the Strings instead of the bare String. This is probably a good idea, but is inconsistent with the creation of the intent just a few lines above. Is there a best practice for where these should be stored? Seems like something you'd want to expose to the outside world (outside the project)?

    Edited on January 12, 2012, 9:55 p.m. PST

Add a comment

View 1 comment

  1. Frank Maker – Posted Jan. 16, 2011

    I'd mention here that this highlight two important differences on the mobile platform: battery life and network connectivity.

Add a comment

View 2 comments

  1. Kimberley Coburn – Posted July 29, 2011

    boolean isNetworkDown = intent.getBooleanExtra( ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); //

    didn't work for me. Instead, I used:

        NetworkInfo ni = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
        boolean isDisconnecting = ni.getState().equals(NetworkInfo.State.DISCONNECTING);
        boolean isDisconnected = ni.getState().equals(NetworkInfo.State.DISCONNECTED);
    
  2. Zachary Rowitsch – Posted Jan. 12, 2012

    This comment makes me think - what other intents are available? I'll go find it on the internet, but it might be a good idea just to mention where or in which docs this info would be found.

Add a comment

View 1 comment

  1. Leo Liang – Posted Dec. 22, 2010

    Below section: "android.permission.RECEIVE_BOOT_COMPLETED" should be "android.permission.ACCESS_NETWORK_STATE"?

Add a comment

View 1 comment

  1. Nigel Gilbert – Posted Feb. 23, 2011

    s/intent/intend/

Add a comment

View 1 comment

  1. Zachary Rowitsch – Posted Jan. 12, 2012

    Is there really no way to not have to use the same string defined in multiple places? ie in the manifest as well as here? Just seems like poor design, typos are going to be super fun to debug.

Add a comment