Chapter 4

Broadcast Receivers

In This Chapter

check1 Creating broadcast receivers

check1 Organizing data from broadcast receivers

check1 Restricting a receiver’s access

Chapter 3 of this minibook introduces a broadcast receiver for the purpose of running code at boot time. Here’s a summary of that chapter’s broadcast receiver news:

  • When you send a broadcast, Android fires up all the receivers whose filters satisfy the intent.
  • A broadcast receiver runs long enough to execute the code in the receiver’s onReceive method. A receiver has no onCreate, onDestroy, or onAnythingElse methods — only onReceive. After Android finishes executing the onReceive method’s code, the broadcast receiver becomes dormant, doing nothing until an app sends another matching broadcast.

This chapter describes broadcast receivers in more detail.

Receivers 101

This chapter’s first example contains the world’s simplest broadcast receiver. To be precise, Listing 4-1 contains the receiver class (MyReceiver, which extends BroadcastReceiver), Listing 4-2 contains code to broadcast to the receiver, and Listing 4-3 contains the example’s AndroidManifest.xml file.

Listing 4-1: A Simple Broadcast Receiver

package com.allmycode.rec1;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.util.Log;

 

public class MyReceiver extends BroadcastReceiver {

 

  @Override

  public void onReceive(Context arg0, Intent arg1) {

    Log.i("MyReceiver", "Received a broadcast");

}

}

 

 

A class that extends android.content.BroadcastReceiver must implement the onReceive method. The class in Listing 4-1 says, “When I receive a broadcast, I’ll write an entry in Android’s log file.”

Listing 4-2: A Simple Broadcaster

package com.allmycode.rec1;

 

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

 

public class MyActivity extends Activity {

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

  }

 

  public void onButtonClick(View view) {

    Intent intent = new Intent();

    intent.setAction("com.allmycode.ACTION");

 

    sendBroadcast(intent);

  }

}

 

 

In Listing 4-2, the onButtonClick method sends a broadcast. The method creates an intent and then feeds the intent to the sendBroadcast broadcast method.

Listing 4-3: Declaring a Broadcast Receiver

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android=

    "http://schemas.android.com/apk/res/android"

  package="com.allmycode.rec1">

 

  <application android:icon="@drawable/icon"

               android:label="@string/app_name">

 

    <activity android:name=".MyActivity"

               android:label="@string/app_name">

      <intent-filter>

        <action android:name=

          "android.intent.action.MAIN" />

        <category android:name=

          "android.intent.category.LAUNCHER" />

      </intent-filter>

    </activity>

 

    <receiver android:name=".MyReceiver">

      <intent-filter>

        <action android:name="com.allmycode.ACTION" />

      </intent-filter>

      </receiver>

 

  </application>

</manifest>

 

 

In Listing 4-3, the receiver’s action is "com.allmycode.ACTION". And, sure enough, in Listing 4-2, the broadcast intent’s action is also "com.allmycode.ACTION". With no other constraints in either listing, the broadcast matches the receiver. So, when you run the code in Listings 4-1, 4-2, and 4-3, Android calls the receiver’s onReceive method. The method writes an entry to Android’s log file.

ontheweb You can get the layout file for this example (and for all the book’s examples) at my web site — www.allmycode.com/android.

Creating a receiver on the fly

Of Android’s four components (Activity, Service, BroadcastReceiver, and ContentProvider), the BroadcastReceiver is the only component that doesn’t require its own AndroidManifest.xml element. Instead of creating a <receiver> element the way I do in Listing 4-3, you can register a broadcast receiver on the fly in your code. Listing 4-4 shows you how.

Listing 4-4: Registering a New Broadcast Receiver

public void onButtonClick(View view) {

  IntentFilter filter = new IntentFilter();

  filter.addAction("com.allmycode.ACTION");

  registerReceiver(new MyReceiver(), filter);

 

  Intent intent = new Intent();

  intent.setAction("com.allmycode.ACTION");

  sendBroadcast(intent);

}

 

 

With the bold code in Listing 4-4, you eliminate the need for the <receiver> element in Listing 4-3. So if you’re typing your own code, comment out that <receiver> element.

technicalstuff To comment out a line of XML code, surround the line with <!-- and -->.

Juggling receivers and broadcasts

You can create several instances of a broadcast receiver and send several broadcasts. Listings 4-5 and 4-6 illustrate the situation.

Listing 4-5: Registering Several Receivers

public void onButtonClick(View view) {

  IntentFilter filter = new IntentFilter();

  filter.addAction("com.allmycode.ACTION");

  MyReceiver receiver = new MyReceiver();

 

  registerReceiver(receiver, filter);

  registerReceiver(receiver, filter);

  registerReceiver(new MyReceiver(), filter);

 

  Intent intent = new Intent();

  intent.setAction("com.allmycode.ACTION");

  sendBroadcast(intent);

 

  Log.i("MyActivity",

      "Sent a broadcast; about to send another…");

 

  sendBroadcast(intent);

}

Listing 4-6: Entries in the Log

I/MyActivity? Sent a broadcast; about to send another…

I/MyReceiver? Received a broadcast 1422812727126

I/MyReceiver? Received a broadcast 1422812727127

I/MyReceiver? Received a broadcast 1422812727126

I/MyReceiver? Received a broadcast 1422812727127

 

 

Listing 4-5 contains an alternative to the onButtonClick method in Listing 4-2, and Listing 4-6 shows the output (using the MyReceiver class from Listing 4-1). Here’s how it all works:

  • Listing 4-5 registers two instances of MyReceiver.

    Sure, the code in Listing 4-5 calls registerReceiver three times. But the second call is redundant because it contains the same MyReceiver instance as the first registerReceiver call.

  • Listing 4-5 sends two broadcasts.

    No argument about that.

  • After sending the first of the two broadcasts, the activity logs the words “Sent a broadcast; about to send another.

    But in Listing 4-6, you see log entries in a different order. In Listing 4-6, you see the activity bragging about having sent one broadcast. Then you see two broadcasts landing on two receivers (for a total of four log entries).

    Remember that a broadcast isn’t a method call. Sending a broadcast means flinging a message to the Android operating system. The system then calls onReceive methods in its own good time. So calls to onReceive (and their corresponding log entries) arrive out of sync with the sender’s code. That asynchronous effect happens even if the sender and receiver classes belong to the same app.

How to unregister a receiver

You can unregister, reregister, and re-unregister broadcast receivers. You can even un-re-un-re-unregister broadcast receivers. Listings 4-7 and 4-8 illustrate all this with some code.

Listing 4-7: Registering and Unregistering

package com.allmycode.rec1;

 

import android.app.Activity;

import android.content.Intent;

import android.content.IntentFilter;

import android.net.Uri;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

 

public class MyActivity extends Activity {

  MyReceiver receiver1 = new MyReceiver(1);

  MyReceiver receiver2 = new MyReceiver(2);

  Intent intent = new Intent();

 

  IntentFilter filter = new IntentFilter();

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

  }

 

  public void onButtonClick(View view) {

    Log.i("MyActivity",

          "The user clicked the first button.");

 

    filter.addAction("com.allmycode.ACTION");

    filter.addDataScheme("letter");

 

    registerReceiver(receiver1, filter);

    registerReceiver(receiver2, filter);

 

    intent.setAction("com.allmycode.ACTION");

    intent.setData(Uri.parse("letter:A"));

    sendBroadcast(intent);

  }

 

  public void onClickOfSecondButton(View view) {

    Log.i("MyActivity", "---------------");

    Log.i("MyActivity",

          "The user clicked the second button.");

 

    unregisterReceiver(receiver1);

 

 

    sendBroadcast(intent);

 

    intent.setData(Uri.parse("letter:B"));

    sendBroadcast(intent);

 

    registerReceiver(receiver1, filter);

  }

 

}

 

 

In Listing 4-7, I break the activity’s behavior into two clicks (of two different buttons). I do this to make sure that the unregistering of receiver1 doesn’t happen too quickly. (You can read more about this strategy later in this section.)

Also in Listing 4-7, I create two receivers and, in each receiver’s constructor call, I give the receiver its own int value. This helps the receiver identify itself in a log entry.

I also add identifying letters to the code’s intents. I could paste these letters as extras in the intents. But instead, I create my own URI scheme (the Letter scheme) and send an opaque URI along with each intent.

crossreference An opaque URI is a URI that has no particular structure to the right of the first colon. I describe opaque URIs in Chapter 2 of this minibook.

Listing 4-8 contains the receiver’s code. The receiver writes its number and the broadcast’s letter to each log entry.

Listing 4-8: A More Verbose Broadcast Receiver

package com.allmycode.rec1;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.util.Log;

 

public class MyReceiver extends BroadcastReceiver {

  private int number;

 

  public MyReceiver(int number) {

    this.number = number;

  }

 

  @Override

  public void onReceive(Context context, Intent intent) {

    String letter =

        intent.getData().getSchemeSpecificPart();

    Log.i("MyReceiver", number + " Received a broadcast "

        + letter);

  }

}

 

 

What’s the log output of the code in Listings 4-7 and 4-8? Listing 4-9 has the answer.

Listing 4-9: Entries in the Log

I/MyActivity? The user clicked the first button.

I/MyReceiver? 1 Received a broadcast A

I/MyReceiver? 2 Received a broadcast A

I/MyActivity? ---------------

I/MyActivity? The user clicked the second button.

I/MyReceiver? 2 Received a broadcast A

I/MyReceiver? 2 Received a broadcast B

 

 

Here’s what happens when you run the code in Listing 4-7:

  • The user clicks the first button, so the code registers two instances of MyReceiver, numbered 1 and 2.
  • The code sends a broadcast with letter A.

    Both receivers get the broadcast. (See the first two lines in Listing 4-9.)

  • The user clicks the second button, so the code unregisters receiver1.

    At this point, only receiver2 is registered.

  • The code sends another broadcast with letter A.

    Only receiver2 gets the broadcast. (See the third line in Listing 4-9.)

  • The code sends another broadcast with letter B.

    Again, receiver2 gets the broadcast. (See the last line in Listing 4-9.)

  • The code reregisters receiver1.

    Too late. All the broadcasts have propagated through the system, and each broadcast has died its own quiet death. So receiver1 doesn’t get a broadcast, and nothing new appears in Listing 4-9.

A receiver can continue to receive until you unregister the receiver. (Notice how receiver2 gets all three broadcasts in this section’s example.)

In contrast, Android wipes away a broadcast after the broadcast reaches all currently registered receivers. (At the end of this section’s example, reregistering receiver1 has no visible effect because all the code’s broadcasts have run their course.)

crossreference The preceding paragraph says, “Android wipes away a broadcast after the broadcast reaches all currently registered receivers.” That’s a half-truth. You can send a broadcast that sticks around on the system long after the broadcast has finished reaching all currently registered receivers. In other words, you can create a sticky broadcast. To find out more about it, skip ahead to the “How to be a stickler” section.

remember When I write “registered receivers,” I include any receivers declared in the AndroidManifest.xml file. You don’t call registerReceiver to start these manifest file broadcast receivers. Android registers an instance of each manifest file receiver when you install the file’s app. If you happen to call registerReceiver for a receiver that you’ve declared in the AndroidManifest.xml file, Android responds by registering an additional instance of your broadcast receiver class.

In this example, the timing of things can make a big difference. Imagine merging the code in Listing 4-7 so that it’s all performed in one button click.

registerReceiver(receiver1, filter);

registerReceiver(receiver2, filter);

sendBroadcast(intent);

unregisterReceiver(receiver1);

// … Etc.

 

 

The code flings a broadcast into the air, and two receivers (receiver1 and receiver2) are prepared to get the broadcast. But these receivers might not get the broadcast immediately. While the broadcast percolates through the system, the code marches on relentlessly and unregisters receiver1. So receiver1 might be unregistered before it ever receives the broadcast. Things don’t always happen this way. (Sometimes, receiver1 might get the broadcast and respond before being unregistered.) But you have no way of knowing which things will happen in which order. That’s why, in Listing 4-7, I create two separate buttons.

Beyond the Fundamentals

The earlier section of this chapter deals with some minimalist, no-nonsense broadcast receiver examples. This section covers some additional broadcast receiver features.

Managing receivers

The previous section’s code is nice and simple. At least I think it’s nice because I’m a teacher, both by profession and in spirit. I like the little examples, even if they’re not sturdy enough to survive real-world use.

But some hard-core developers don’t agree with me. They’d call Listings 4-4, 4-5, and 4-7 “bad and simple” because (and I’m being painfully honest) the code in these listings can’t take a beating in the real world. In fact, the code in these listings probably wouldn’t survive gentle petting. To find out why, read on.

What’s the problem?

Try the following experiment:

  1. Create a brand-new Android project.
  2. In the AndroidManifest.xml file, declare a receiver (refer to Listing 4-3).
  3. In the main activity’s layout file, add a button.
  4. In the main activity’s Java code, add a method to handle the button click.

    For the method’s body, use the body of the onButtonClick method in Listing 4-4.

  5. Restart your emulator.

    If you’ve been testing this chapter’s examples on a real Android device, restart the device. Restarting things ensures that you don’t have any receivers lurking in the background as you begin this section’s experiment.

  6. Run the app.
  7. Click the button on the app’s screen, and look at the resulting entries in Android Studio’s Logcat panel.

    You see two MyReceiver Received a broadcast entries because Android is running two MyReceiver instances. One instance comes from the <receiver> element in the AndroidManifest.xml document. The other receiver comes from the registerReceiver method call in your app’s main activity.

    tip You might not see both Received a broadcast entries right away. If not, be patient.

    Having two MyReceiver instances isn’t bad. But in most cases, it’s probably not what you want. Observing these two instances is a side benefit that comes from performing this experiment.

  8. While the app is still running, press the emulator’s Back button, and look again at the Logcat panel.

    In the Logcat panel, you see a big, ugly activity has leaked an IntentReceiver error message. The message tells you that Android has destroyed your activity. In doing so, Android noticed that you didn’t unregister the receiver that you registered in Listing 4-4, so Android unregisters this receiver for you. (In essence, Android behaves like your mother when you don’t clean your room. Android says, “If you refuse to clean up after yourself, I’ll clean up for you. I’ll terminate your broadcast receiver. And just like your mother, I’ll show my disapproval by writing an entry to the log file.” If only Mom had been so even-tempered!)

    The problem is, you’re getting rid of your activity and leaving your registered receiver in limbo. If other developers do the same thing, the user’s Android device can have all kinds of broadcast receivers floating around after their parent activities have been destroyed. The device experiences its own Night of the Living Broadcast Receivers.

    technicalstuff Android doesn’t like the MyReceiver instance that I register and don’t unregister in Listing 4-4. But Android isn’t upset about the MyReceiver instance from the AndroidManifest.xml file (refer to Listing 4-3). Android expects receivers declared this way to have a life of their own, surviving past the lifetime of any activities in the application.

    technicalstuff The error message activity has leaked IntentReceiver hints that Android’s SDK has an IntentReceiver class. But that’s misleading. The name IntentReceiver is an artifact from Android’s early history. What used to be called an IntentReceiver is now a BroadcastReceiver.

    You might not be impressed by Step 9's activity has leaked IntentReceiver message. After all, Android doesn’t alert the user, so your app doesn’t look bad. And when the user clicks the Back button, you probably don’t mind that Android terminates your broadcast receiver. So what’s the big deal? Well, try the next few steps …

  9. In Android Studio’s main menu, choose Run ⇒ Run ‘app' to restart this section’s app.
  10. Again, press the button on the activity’s screen to invoke the code in Listing 4-4.
  11. While the app is still running, turn the emulator sideways by pressing Ctrl+F11.

    Of course, if you’re testing on a real device, simply turn the device sideways.

    In Android Studio’s Logcat panel, look again for the insulting activity has leaked IntentReceiver error message. Unless you override the default behavior, Android destroys and re-creates your activity when the device’s orientation changes. As far as the user is concerned, the activity is still alive and well. But unbeknownst to the user, Android killed the broadcast receiver and hasn’t revived it. You’ve lost a broadcast receiver just by tilting the device. It’s difficult to imagine a scenario in which you want that to happen.

What’s the solution?

In most of this chapter’s simple examples, I register and unregister receivers in one or more onButtonClick methods. I do it to illustrate a step-by-step progression of registrations, broadcasts, receipts of broadcasts, and so on. It’s okay if I include logic to deal with the nasty things that happen in the previous section’s example.

But the logic to avoid the previous section’s pitfalls can become complicated, and it’s easy to make mistakes. That’s why it’s best to do receiver registering and unregistering in an activity’s lifecycle methods. Listing 4-10 shows you what to do.

Listing 4-10: Dealing with the Component Lifecycle

package com.allmycode.rec1;

 

import android.app.Activity;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.Bundle;

import android.view.View;

 

public class MyActivity extends Activity {

  MyReceiver receiver = new MyReceiver();

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

  }

 

  @Override

  public void onResume() {

    super.onResume();

    IntentFilter filter = new IntentFilter();

    filter.addAction("com.allmycode.ACTION");

 

    registerReceiver(receiver, filter);

  }

 

  @Override

  public void onPause() {

    super.onPause();

    unregisterReceiver(receiver);

  }

 

  public void onButtonClick(View view) {

    Intent intent = new Intent();

    intent.setAction("com.allmycode.ACTION");

    sendBroadcast(intent);

  }

}

 

 

I can state the big message in Listing 4-10 very simply: Make things in an activity’s onResume method and then get rid of these things in the activity’s onPause method. If you want things to live longer, make things in the activity’s onCreate method and get rid of these things in the activity’s onDestroy method. That’s it. (And yes, I’m aware that I wrote the whole “Managing receivers” section to pontificate about something that I can summarize at the end in only two sentences. Thanks for noticing!)

How to be a stickler

An ordinary broadcast disintegrates after it’s sent to all the matching, currently running receivers. But another kind of broadcast — a sticky broadcast — hangs on until someone or something explicitly removes the broadcast. To remove a sticky broadcast, you can call removeStickyBroadcast. Alternatively, you can turn off your device, hit your device with a hammer, or do other unpleasant things. Listing 4-11 contains some informative code.

Listing 4-11: Sending a Sticky Broadcast

package com.allmycode.rec1;

 

import android.app.Activity;

import android.content.Intent;

import android.content.IntentFilter;

import android.net.Uri;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

 

public class MyActivity extends Activity {

  MyReceiver receiver1 = new MyReceiver(1);

  MyReceiver receiver2 = new MyReceiver(2);

  MyReceiver receiver3 = new MyReceiver(3);

  Intent intent = new Intent();

  IntentFilter filter = new IntentFilter();

  Button button, button2, button3, button4, button5;

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

 

    button = (Button) findViewById(R.id.button);

    button2 = (Button) findViewById(R.id.button2);

    button3 = (Button) findViewById(R.id.button3);

    button4 = (Button) findViewById(R.id.button4);

    button5 = (Button) findViewById(R.id.button5);

 

  }

 

  public void onButtonClick(View view) {

    if (view == button) {

 

    filter.addAction("com.allmycode.ACTION");

    filter.addDataScheme("letter");

    registerReceiver(receiver1, filter);

 

    } else if (view == button2) {

 

      intent.setAction("com.allmycode.ACTION");

      intent.setData(Uri.parse("letter:A"));

      sendStickyBroadcast(intent);

 

      } else if (view == button3) {

 

        registerReceiver(receiver2, filter);

 

      } else if (view == button4) {

 

        removeStickyBroadcast(intent);

 

      } else if (view == button5) {

 

        registerReceiver(receiver3, filter);

 

      }

    }

 

}

 

 

This section’s experiment involves so many buttons that I decided to put all button-clicking code into one big method. In the activity’s layout file, I set each button’s onClick attribute to onButtonClick. Then in the Java code (Listing 4-11), the onButtonClick method tests to find out which of the five buttons experienced the click event.

Imagine that the user clicks button, button2, button3, and so on, in order one after another. With the receiver that I set up back in Listing 4-8, Android logs the entries shown in Listing 4-12.

Listing 4-12: Log File Entries

I/MyReceiver? 1 Received a broadcast A

I/MyReceiver? 2 Received a broadcast A

 

 

By clicking the first and second buttons, I register receiver1 and then send the broadcast. So receiver1 receives the broadcast, and you get the first line in Listing 4-12. No big deal here.

At this point in the run of Listing 4-11, receiver1 is the only currently registered receiver, and receiver1 has received the broadcast. But the broadcast is sticky, so the broadcast lives on. When I click the third button, I register receiver2, and at that point, receiver2 receives the broadcast. That’s what stickiness does.

By pressing the last two buttons, I remove the sticky broadcast (with a method call, not with turpentine), and I register receiver3. Because I’ve removed the only matching broadcast, receiver3 receives nothing.

remember A component that calls sendStickyBroadcast (or calls the closely related sendStickyOrderedBroadcast method) must have the <uses-permission android:name="android.permission.BROADCAST_STICKY" /> element in its app’s AndroidManifest.xml document. A component that calls sendBroadcast (or its friend, the sendOrderedBroadcast method) doesn’t need permission to do so.

crossreference For a bare-bones <uses-permission> example (one that doesn’t involve broadcast receivers), see Book I, Chapter 6.

Using receiver intents

At some point, you might have several receivers and several sticky broadcasts vying for attention in a multiprocess, nondeterministic fashion. Sounds like fun, doesn’t it? You may also be dealing with broadcasts from other apps and from the system itself. To help you keep track of the comings and goings, the registerReceiver method returns an intent. This intent comes from one of the (possibly many) broadcasts that the newly registered receiver catches.

In Listing 4-13, I register two receivers and fling two sticky broadcasts ("letter:A" and "letter:O") into the air. For each receiver registration, Listing 4-13 logs an intent caught by the receiver.

Listing 4-13: Getting an Intent from a Receiver’s Registration

package com.allmycode.rec1;

 

import android.app.Activity;

import android.content.Intent;

import android.content.IntentFilter;

import android.net.Uri;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

 

public class MyActivity extends Activity {

  Button button, button2, button3, button4, button5;

  IntentFilter filter = new IntentFilter();

  MyReceiver receiver1 = new MyReceiver(1);

  MyReceiver receiver2 = new MyReceiver(2);

  Intent returnedIntent, intentAct, intentOth;

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    button = (Button) findViewById(R.id.button);

    button2 = (Button) findViewById(R.id.button2);

    button3 = (Button) findViewById(R.id.button3);

    button4 = (Button) findViewById(R.id.button4);

    button5 = (Button) findViewById(R.id.button5);

  }

 

  public void onButtonClick(View view) {

    if (view == button) {

 

      filter.addAction("com.allmycode.ACTION");

 

      filter.addAction("com.allmycode.OTHER_ACTION");

      filter.addDataScheme("letter");

      returnedIntent = registerReceiver(receiver1, filter);

      Log.i("MyActivity", getStatus(returnedIntent));

 

    } else if (view == button2) {

 

      intentAct = new Intent();

      intentAct.setAction("com.allmycode.ACTION");

      intentAct.setData(Uri.parse("letter:A"));

      sendStickyBroadcast(intentAct);

 

    } else if (view == button3) {

 

      intentOth = new Intent();

      intentOth.setAction("com.allmycode.OTHER_ACTION");

      intentOth.setData(Uri.parse("letter:O"));

      sendStickyBroadcast(intentOth);

 

    } else if (view == button4) {

 

      returnedIntent = registerReceiver(receiver2, filter);

      Log.i("MyActivity", getStatus(returnedIntent));

 

    } else if (view == button5) {

 

      removeStickyBroadcast(intentAct);

      removeStickyBroadcast(intentOth);

      unregisterReceiver(receiver1);

      unregisterReceiver(receiver2);

 

    }

  }

 

  private String getStatus(Intent returnedIntent) {

    if (returnedIntent == null) {

      return "null";

    } else {

      return returnedIntent.toString();

    }

  }

}

 

 

Listing 4-14 shows the results of a run of Listing 4-13's code (using the receiver in Listing 4-8, and pressing the buttons in order during the run). The first registration returns null rather than an actual intent. This happens because no broadcast is alive when the code executes this first registration.

Listing 4-14: Log This!

I/MyActivity? null

I/MyReceiver? 1 Received a broadcast A 1422834917781

I/MyReceiver? 1 Received a broadcast O 1422834917781

I/MyActivity?

  Intent { act=com.allmycode.ACTION dat=letter:A flg=0x10 }

I/MyReceiver? 2 Received a broadcast A 1422835011653

I/MyReceiver? 2 Received a broadcast O 1422835011653

 

 

The second receiver registration returns the "com.allmycode.ACTION" intent. The receiver’s filter has both "com.allmycode.ACTION" and "com.allmycode.OTHER_ACTION", and both of these actions belong to active sticky broadcasts. But the call to registerReceiver returns only one of the broadcasts' intents. One way or another, two receivers catch two broadcasts.

Ordered broadcasts

Android takes a regular broadcast and throws it into the air. Then the receivers with matching filters jump like basketball players, catching the broadcast in no particular order. This “no particular order” behavior can be nice because it frees up the system to make the most of any available processing time.

But occasionally you want a predictable sequence of onReceive calls. To achieve such behavior, you assign priorities to the receivers' intent filters and then send an ordered broadcast.

technicalstuff In this chapter’s log listings, receivers seem to form a first-come/first-served waiting line to catch broadcasts. That’s fine. But in general, Android makes no promises about this polite behavior. In fact, Android might run two receivers at once. You never know.

Listing 4-15 prioritizes receivers and sends an ordered broadcast.

Listing 4-15: Set Your Priorities

public void onButtonClick(View view) {

  IntentFilter filter = new IntentFilter();

  filter.addAction("com.allmycode.ACTION");

  filter.addDataScheme("letter");

 

  IntentFilter filter1 = new IntentFilter(filter);

  IntentFilter filter2 = new IntentFilter(filter);

  IntentFilter filter3 = new IntentFilter(filter);

 

  filter1.setPriority(17);

  filter2

      .setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1);

  filter3.setPriority(-853);

 

  MyReceiver receiver1 = new MyReceiver(1);

  MyReceiver receiver2 = new MyReceiver(2);

  MyReceiver receiver3 = new MyReceiver(3);

 

  registerReceiver(receiver1, filter1);

  registerReceiver(receiver2, filter2);

  registerReceiver(receiver3, filter3);

 

  Intent intent = new Intent();

  intent.setAction("com.allmycode.ACTION");

  intent.setData(Uri.parse("letter:A"));

 

  sendOrderedBroadcast(intent, null);

  Log.i("MyActivity",

      "Now watch the log entries pour in…");

}

 

 

From a single intent filter, Listing 4-15 stamps out three copies. Then the code assigns a priority to each copy. Priorities are int values, ranging from @nd999 to 999. Android reserves the values –1000 (IntentFilter.SYSTEM_LOW_PRIORITY) and 1000 (IntentFilter.SYSTEM_HIGH_PRIORITY) for its own private use.

tip You can set an intent filter’s priority in an app’s AndroidManifest.xml document. Do so with an attribute, such as android:priority="17".

After registering three receivers (one for each of the three filters), Listing 4-15 sends an ordered broadcast and lets the chips fall where they may. The chips fall in Listing 4-16.

Listing 4-16: Yet Another Log

I/MyActivity? Now watch the log entries pour in…

I/MyReceiver? 2 Received a broadcast A 1422835783590

I/MyReceiver? 1 Received a broadcast A 1422835783613

I/MyReceiver? 3 Received a broadcast A 1422835783618

 

 

Listing 4-16 confirms that receiver2 — the receiver with the highest priority — receives the broadcast first. Poor receiver3 — the receiver with the lowest priority — receives the broadcast last.

Stopping a broadcast in its tracks

In the preceding section, an ordered broadcast travels from one receiver to another. The sequence of receivers depends on their relative priorities.

In this section, you play a nasty trick on all but one of the receiver instances. To do so, change the MyReceiver class’s code, as in Listing 4-17.

Listing 4-17: Aborting a Broadcast

package com.allmycode.rec1;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.util.Log;

 

public class MyReceiver extends BroadcastReceiver {

  private int number;

 

  public MyReceiver(int number) {

    this.number = number;

  }

 

  @Override

  public void onReceive(Context context, Intent intent) {

    String letter =

        intent.getData().getSchemeSpecificPart();

    Log.i("MyReceiver", number + " Received a broadcast "

        + letter);

    abortBroadcast();

  }

}

 

 

With the call to abortBroadcast in Listing 4-17, a run of the code in Listing 4-15 creates only two log entries:

I/MyActivity? Now watch the log entries pour in…

I/MyReceiver? 2 Received a broadcast A

 

 

The second log entry comes from an instance of the receiver in Listing 4-17. The listing’s call to abortBroadcast stops the ordered broadcast in its tracks. Other instances of MyReceiver never see the broadcast.

The abortBroadcast method works only with ordered broadcasts. Normally, you have a MyReceiver instance abort a broadcast so that some other receiver (maybe a YourReceiver instance) doesn’t get the broadcast. But in this chapter’s examples, I keep things simple by creating only one MyReceiver class and several instances of the class.

Getting results from receivers

What will they think of next? You have sticky broadcasts and ordered broadcasts. Why not have a broadcast that’s both sticky and ordered? Developers typically use sticky, ordered broadcasts to collect results from several broadcast receivers.

Listing 4-18 contains a receiver on steroids.

Listing 4-18: A Receiver Manages Data

package com.allmycode.rec1;

 

import java.util.ArrayList;

 

import android.app.Activity;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.util.Log;

 

public class MyReceiver extends BroadcastReceiver {

  private int number;

  private boolean INTENTIONALLY_FAIL = false;

 

  public MyReceiver(int number) {

    this.number = number;

  }

 

  @Override

  public void onReceive(Context context, Intent intent) {

    String letter =

        intent.getData().getSchemeSpecificPart();

    Log.i("MyReceiver", number + " Received a broadcast "

        + letter);

 

        if (INTENTIONALLY_FAIL) {

          setResultCode(Activity.RESULT_CANCELED);

          return;

        }

 

        if (getResultCode() == Activity.RESULT_OK) {

          Bundle bundle = getResultExtras(true);

          ArrayList<Integer> receiverNums =

              bundle.getIntegerArrayList("receiverNums");

          if (receiverNums != null) {

              receiverNums.add(new Integer(number));

 

          }

          setResultExtras(bundle);

        }

    }

}

 

 

An ordered broadcast goes to an ordered chain of receiver instances. Along with the broadcast, each receiver instance gets result extras from the previous receiver in the chain. These result extras take the form of a bundle.

crossreference For the lowdown on bundles, see Chapter 3 of this minibook.

An instance of the receiver in Listing 4-18 gets a bundle containing an ArrayList of integers. This ArrayList happens to contain the numbers of all the previous receiver instances in the ordered broadcast’s chain. The instance in Listing 4-18 adds its own number to the ArrayList and then sets its own result to be the newly enhanced ArrayList. The next receiver instance in the chain gets this newly enhanced ArrayList.

Notice the getResultCode and setResultCode method calls in Listing 4-18. For an ordered broadcast, each receiver gets a result code from the previous receiver in the chain, and sets a result code for the next receiver in the chain. In Listing 4-18, the call to getResultCode checks for the android.app.Activity.RESULT_OK code. (The value of android.app.Activity.RESULT_OK happens to be –1, but you never test if (getResultCode() == -1). You always test if (getResultCode() == Activity.RESULT_OK).) Any receiver instance in the chain can mess up the works with a result code that’s not OK.

In Listing 4-18, I add an extra INTENTIONALLY_FAIL constant to test undesirable situations. Changing the constant’s value to true forces Listing 4-18 to set the result code to android.app.Activity.RESULT_CANCELED. After that, any result from the ordered broadcast can’t be trusted.

tip Always remove testing and debugging code (such as the INTENTIONALLY_FAIL code in Listing 4-18) before you publish your app.

Listing 4-19 puts the receiver in Listing 4-18 through its paces.

Listing 4-19: Dealing with the Result from a Chain of Receivers

package com.allmycode.rec1;

 

import java.util.ArrayList;

 

import android.app.Activity;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.content.IntentFilter;

import android.net.Uri;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

 

public class MyActivity extends Activity {

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

  }

 

  public void onButtonClick(View view) {

    IntentFilter filter = new IntentFilter();

    filter.addAction("com.allmycode.ACTION");

    filter.addDataScheme("letter");

 

    IntentFilter filter1 = new IntentFilter(filter);

    IntentFilter filter2 = new IntentFilter(filter);

    IntentFilter filter3 = new IntentFilter(filter);

 

    MyReceiver receiver1 = new MyReceiver(1);

    MyReceiver receiver2 = new MyReceiver(2);

    MyReceiver receiver3 = new MyReceiver(3);

 

    registerReceiver(receiver1, filter1);

    registerReceiver(receiver2, filter2);

    registerReceiver(receiver3, filter3);

 

    Intent intent = new Intent();

    intent.setAction("com.allmycode.ACTION");

    intent.setData(Uri.parse("letter:A"));

 

    MyEndResultReceiver resultReceiver =

        new MyEndResultReceiver();

    ArrayList<Integer> receiverNums =

        new ArrayList<Integer>();

    Bundle bundle = new Bundle();

    bundle.putIntegerArrayList("receiverNums",

        receiverNums);

 

    sendStickyOrderedBroadcast(intent, resultReceiver,

        null, Activity.RESULT_OK, null, bundle);

  }

}

 

class MyEndResultReceiver extends BroadcastReceiver {

 

  final static String CLASSNAME = "MyEndResultReceiver";

 

  @Override

  public void onReceive(Context context, Intent intent) {

    if (getResultCode() == Activity.RESULT_OK) {

      Bundle bundle = getResultExtras(true);

      ArrayList<Integer> receiverNums =

        bundle.getIntegerArrayList("receiverNums");

      Log.i(CLASSNAME, receiverNums.toString());

    } else {

      Log.i(

          CLASSNAME,

          "Result code: "

 

    }

  }

}

 

 

In Listing 4-19, the call to sendStickyOrderedBroadcast takes a boatload of parameters. The official signature of method sendStickyOrderedBroadcast is as follows:

public void

sendStickyOrderedBroadcast(Intent intent,

                           BroadcastReceiver resultReceiver,

                           Handler scheduler,

                           int initialCode,

                           String initialData,

                           Bundle initialExtras)

  • The intent parameter plays the same role as any other broadcast’s intent.

    The intent presents a list of criteria to test against receivers' filters.

  • The resultReceiver is the last instance in the ordered broadcast’s calling chain.

    By specifying the result receiver, you know where to look for the accumulated results.

  • The scheduler (if it’s not null) handles messages coming from the resultReceiver.
  • The initialCode is the starting value for the sequence of result codes passed from one receiver to the next.

    In most apps, the initialCode's value is Activity.RESULT_OK. You give the initialCode a different value only when you’re making up your own custom result code values. When you do such a thing, you program your app to respond sensibly to each of the made-up values.

  • The initialData (if it’s not null) is a starting value for a string that’s passed from receiver to receiver in the chain.

    An ordered broadcast carries a bundle (the result extras) and a code (an int value, such as Activity.RESULT_OK). In addition, an order broadcast carries result data — a String value that can be examined and modified by each receiver instance in the chain.

  • The initialExtras is a starting value for the broadcast’s bundle of extra stuff.

    In Listing 4-19, the initialExtras bundle is an empty ArrayList. Each receiver instance that gets the broadcast adds its number to this ArrayList.

Listing 4-20 shows the output of the code in Listings 4-18 and 4-19.

Listing 4-20: More Log Entries

I/MyReceiver? 1 Received a broadcast A

I/MyReceiver? 2 Received a broadcast A

I/MyReceiver? 3 Received a broadcast A

I/MyEndResultReceiver? [1, 2, 3]

 

 

The broadcast ends its run at an instance of MyEndResultReceiver — the instance named last in the chain by the sendStickyOrderedBroadcast call in Listing 4-19. When this last receiver does it stuff, the receiver logs [1, 2, 3] — the accumulated ArrayList of receiver numbers.

Using permissions and other tricks

To send a broadcast, you toss an intent into the ether. A broadcast receiver gets the intent if the receiver’s filter matches the intent. And that’s the whole story. Or is it?

When you send a broadcast, you can also specify a permission. Permissions come from those <uses-permission> elements that you put in your AndroidManifest.xml document (after first forgetting to do it and getting an error message).

In Listing 4-21, the sendBroadcast call’s second parameter is a permission.

Listing 4-21: Requiring a Permission

public void onButtonClick(View view) {

  Intent intent = new Intent();

  intent.setAction("THIS_ACTION");

 

  sendBroadcast(intent,

      android.Manifest.permission.INTERNET);

}

 

 

The receiver declared in Listing 4-22 catches the broadcast in Listing 4-21.

Listing 4-22: Declaring That an App Has a Permission

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android=

    "http://schemas.android.com/apk/res/android"

          package="com.allmycode.receiver2"

          android:versionCode="1"

          android:versionName="1.0">

 

  <uses-sdk android:minSdkVersion="8" />

 

  <uses-permission

    android:name="android.permission.INTERNET" />

 

  <application android:icon="@drawable/icon"

               android:label="@string/app_name">

 

    <receiver android:name=

      "com.allmycode.receiver2.MyReceiverWithPermission">

 

      <intent-filter>

        <action android:name="THIS_ACTION" />

      </intent-filter>

    </receiver>

 

  </application>

</manifest>

 

 

Another receiver that’s in an app whose manifest doesn’t have the <uses-permission> element can’t receive the broadcast from Listing 4-21.

remember Android’s built-in android.Manifest.permission.INTERNET constant (used in Listing 4-21) has String value "android.permission.INTERNET". At the risk of being gauche, you can use the quoted string "android.permission.INTERNET" in the Java code of Listing 4-21. But you can’t use the android.Manifest.permission.INTERNET constant in Listing 4-13 or in any other AndroidManifest.xml document.

technicalstuff Android has all kinds of mechanisms for shielding components from other components. For example, you can add an attribute to the <receiver> start tag in Listing 4-22:

<receiver android:name=

  "com.allmycode.receiver2.MyReceiverWithPermission"

  android:exported="false">

 

 

If you do, no component outside the receiver’s app can send a broadcast to this receiver.

Standard Broadcasts

Chapter 2 of this minibook contains a list of some standard actions for starting activities. Android’s SDK also contains standard actions for sending broadcasts. Table 4-1 has a list of some actions that your app can broadcast.

Table 4-1 Some Standard Broadcast Actions

String Value

Constant Name

A Broadcast Receiver with This Action in One of Its Filters Can …

"android.intent.action.CAMERA_BUTTON"

Intent.ACTION_CAMERA_BUTTON

Respond to the user pressing the camera button.

"android.intent.action.HEADSET_PLUG"

Intent.ACTION_HEADSET_PLUG

Respond to a wired headset being plugged or unplugged.

"android.intent.action.DATE_CHANGED"

Intent.ACTION_DATE_CHANGED

Respond to the system’s date being changed.

"android.intent.action.TIME_SET"

Intent.ACTION_TIME_CHANGED

Respond to the system’s time being changed.

"android.intent.action.INPUT_METHOD_CHANGED"

Intent.ACTION_INPUT_METHOD_CHANGED

Respond to a change of the device’s input method.

"android.intent.action.MEDIA_MOUNTED"

Intent.ACTION_MEDIA_MOUNTED

Respond to the insertion of an SD card (or some other media).

"android.intent.action.MEDIA_EJECT"

Intent.ACTION_MEDIA_EJECT

Respond to a request to allow media to be removed.

"android.intent.action.MEDIA_REMOVED"

Intent.ACTION_MEDIA_REMOVED

Respond to the proper removal of media.

"android.intent.action.MEDIA_BAD_REMOVAL"

Intent.ACTION_MEDIA_BAD_REMOVAL

Respond to the improper removal of an SD card (or some other media).

The actions in Table 4-1 are both libre and gratis. Or, to paraphrase Richard Stallman, the actions are free as in “free speech” and free as in “free beer.”* Whatever metaphor you prefer, you can broadcast or receive intents with the actions in Table 4-1.

The actions in Table 4-2 resemble beer more than they resemble speech. In your app’s code, a broadcast receiver’s filter can include these actions. But your app can’t broadcast an intent having any of these actions. Only the operating system can broadcast intents that include the actions in Table 4-2.

Table 4-2 Some Standard System Broadcast Actions

String Value

Constant Name

A Broadcast Receiver with This Action in One of Its Filters Can …

"android.intent.action.SCREEN_OFF"

Intent.ACTION_SCREEN_OFF

Respond to the device going to sleep (whether or not the screen turns off).

"android.intent.action.SCREEN_ON"

Intent.ACTION_SCREEN_ON

Respond to the device waking up (whether or not screen turns on).

"android.intent.action.DREAMING_STARTED"

intent.ACTION_DREAMING_STARTED

Respond when the device starts dreaming. (Yes, this is for real!)

"android.intent.action.BOOT_COMPLETED"

Intent.ACTION_BOOT_COMPLETED

Respond to the end of system startup.

"android.intent.action.BATTERY_LOW"

Intent.ACTION_BATTERY_LOW

Respond to a low-battery condition.

"android.intent.action.BATTERY_OKAY"

Intent.ACTION_BATTERY_OKAY

Respond to the battery’s condition no longer being low.

"android.intent.action.DEVICE_STORAGE_LOW"

Intent.DEVICE_STORAGE_LOW

Respond to memory becoming low.

"android.intent.action.DEVICE_STORAGE_OK"

Intent.DEVICE_STORAGE_OK

Respond to memory no longer being low.

"android.intent.action.ACTION_POWER_CONNECTED"

Intent.ACTION_POWER_CONNECTED

Respond to a power cord being connected.

"android.intent.action.ACTION_POWER_DISCONNECTED"

Intent.ACTION_POWER_DISCONNECTED

Respond to a power cord being removed.

"android.intent.action.LOCALE_CHANGED"

Intent.LOCALE_CHANGED

Respond to a change in locale (from the United States to the United Kingdom, for example).

"android.intent.action.TIMEZONE_CHANGED"

Intent.ACTION_TIMEZONE_CHANGED

Respond to the system’s time zone being changed.

"android.intent.action.TIME_TICK"

Intent.ACTION_TIME_TICK

Respond when the system broadcasts this TIME_TICK action once each minute.

"android.intent.action.NEW_OUTGOING_CALL"

Intent.NEW_OUTGOING_CALL

Respond to the start of an outgoing call.

tip As an Android developer, you can test the effect of broadcasting any of the actions in Table 4-2. To do so, you become superuser on the Android shell and issue an am command. (In Linux, a superuser has administrative privileges. A consumer becomes superuser when he or she roots a device.) For an example of the use of the am command, see Chapter 3 of this minibook.

crossreference For a complete list of Android’s standard actions, visit http://developer.android.com/reference/android/content/Intent.html.

Notes

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.102.160