Search This Blog

Saturday, January 16, 2010

Android Braodcast Receivers

As we all know There are four types of components which can be used in Android, a good android developer should be Familiar with these components and the idea behind them in order to design and implement efficient applications.
I think it is safe to say that the most significant thing about android is the sharing philosophy on which it is based and understanding this concept could help us to have a more solid view on when and how to use each one of these component types.
Android applications can share their stuff and interact with each other and that is the main idea behind Content Providers,Services and Broadcast Receivers in Android: Sharing Data, Services and Events.
In this article I am gonna talk about Broadcast Receivers in Android and I will try to cover almost everything that we might need to know about them in different situations. Broadcast Receiver is actually a mechanism to send and receive events so that all interested applications can be informed when something happens. there are heaps of System events which get broadcast by Android OS such as SMS related events, Connectivity related events, Camera related events and many more.
We are able to broadcast our application specific events as well, so for example if we have a RSS news reader application and we want to do something whenever a new item is available, it would be a good idea to use Broadcast Receiver method, not only because it will separate your event handling code but most importantly because it will enable other applications to register and receive a notification whenever that event takes place.
The first thing we will be talking about is how to implement a Receiver and register it so that you can receive events that you want to receive. Implementing a Receiver is pretty simple and straight forward, all you need to do is to extend Broadcast Receiver class and override its onReceive() method. once you implement your Receiver you need to register it and determine what kind of events you are interested in to be sent to you.
For example if you want to get notified whenever there is an incoming call or SMS you will need to add something like this to you manifest file :



<receiver android:name="amir.android.icebreaking.CallAndSMSListener">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>


and here is CallAndSMSListener class source code :



public class CallAndSMSListener extends BroadcastReceiver {

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

String action = intent.getAction();
if(action.equalsIgnoreCase("android.intent.action.PHONE_STATE")){
if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
TelephonyManager.EXTRA_STATE_RINGING)) {

doSomething(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
}
}
else {

Bundle bundle = intent.getExtras();
Object[] pdus = (Object[]) bundle.get("pdus");
SmsMessage message = SmsMessage.createFromPdu((byte[])pdus[0]);
if(!message.isEmail())
doSomething(message.getOriginatingAddress());

}

}

private void doSomething(String number){
Log.d("<<<>>>",number);
}

}


In this example I have registered my Receiver in Manifest file, but we can also register a Receiver dynamically in our code like this :



this.reciever = new ConnectivityListener();
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(this.reciever, filter);


in this case after calling registerReceiver our ConnectivityListener will be notified when Wi-Fi state or Connectivity state of mobile phone change. Just remember to unregister all Receivers that you have registered in your code using unregisterReceiver() method.
ConnectivityListener class looks like this :



public class ConnectivityListener extends BroadcastReceiver {

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

String action = intent.getAction();

if(action.equalsIgnoreCase(WifiManager.NETWORK_STATE_CHANGED_ACTION)){

int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
//Do Something.....
}
else {

NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
//Do Something.....

}


}

}


So far we have seen how to register a Receiver both statically and dynamically,but it is just half the story; as i said we can not only receive events but can also broadcast our events either locally for just a specific Receiver or globally for all other applications.
In my application I have something like this :



Intent intent = new Intent(this,MyReceiver.class);
intent.putExtra("Message", "Something has Changed!!");

this.sendBroadcast(intent);


As you can see i specified the class type for the Intent, so when i broadcast the Intent, only MyReceiver will be notified. if we wanted to globally broadcast it we should set an Action string for our Intent so that all receivers which have indicated that action in their intent filter would be notified.
this is what you need to put in your manifest file for this example:



<receiver android:name=".MyReceiver"/>



and MyReceiver class:



public class MyReceiver extends BroadcastReceiver {

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

String msg = intent.getStringExtra("Message");
Vibrator vib = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);

Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
vib.vibrate(2000);

}

}


Sometimes you dont want to broadcast an event directly from your activity but for whatever reason you want another application do that on your behalf, in these sort of cases you should use PendingIntent class instead of Intent. AlarmManager class is a good example to illustrate this kind of scenarios, AlarmManager class allows you to set a time to alarm system goes off, you can also specify a PendingIntent object to be broadcast when alarm goes off.
I have used this piece of code in my Activity onStop() method :



Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.HOUR, 4);

Intent intent = new Intent(this, Receiver.class);
intent.setAction("Start");
PendingIntent sender = PendingIntent.getBroadcast(this,
0, intent, 0);

AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);


It means that alarm goes off 4 hours after each time we close the application and when it happens it also broadcast the specified intent it has been provided earlier.
and here is the content of Reciever's onRecieve() method :



Intent startIntent = new Intent(context, MainActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

context.startActivity(startIntent);


so simple, it just starts our activity again. 4 hours after that we close the application the alarm goes off and the application comes up again.

when we send an ordinary broadcast all Registered Receivers will get notified but the order in which they are notified is undefined, in other words you wouldn't know which Receiver is called first and which one last. another thing to remember is you are not allowed to stop the propagation of an event in a Receiver. but what if you really need to be able to stop the propagation of an event in an Receiver so that other receivers dont get it or what if you need to set an order according to which Receivers should be called, thankfully it's not a big deal, all we would need to do is to call sendOrderedBroadcast() method instead of sendBroadcast() to solve the problem.
For example I have used this code in my application :



Intent intent = new Intent("com.test.ACTION");
intent.putExtra("Random", (int)(Math.random()*10));

this.sendOrderedBroadcast(intent,
null,
new LastReceiver(),
null,
Activity.RESULT_OK,
null,
null);


I also have two receivers in my manifest file :



<receiver android:name="amir.android.icebreaking.FirstReceiver">
<intent-filter android:priority="100">
<action android:name="com.test.ACTION" />
</intent-filter>
</receiver>

<receiver android:name="amir.android.icebreaking.SecondReceiver">
<intent-filter android:priority="50">
<action android:name="com.test.ACTION" />
</intent-filter>
</receiver>


When we call OrderedBroadcast() method we have two registered Receiver which need to be called, but as you can see we have used android:priority attribute for them, it means that Receiver with higher value will be called first, in this example FirstReciever is notified first and then SecondReciever get notified if FirstReciever have not aborted the intent.
LastReciever is always gonna get notified regardless of what has happened in other Receivers.
and here is these three classes source codes :



public class FirstReceiver extends BroadcastReceiver {

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

int value = intent.getIntExtra("Random", -1);

Bundle bundle = getResultExtras(true);
bundle.putInt("FirstReciever", value*13);
setResultExtras(bundle);

if(value < 0 || value%2 != 0)
abortBroadcast();

}

}




public class SecondReceiver extends BroadcastReceiver {

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

int value = intent.getIntExtra("Random", -1);
if(value > 0){
Bundle bundle = getResultExtras(true);
bundle.putInt("SecondReciever", value*17);
setResultExtras(bundle);
}


}

}





public class LastReceiver extends BroadcastReceiver {

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

Bundle bundle = getResultExtras(false);
StringBuilder builder = new StringBuilder();
builder.append("---Last Reciever---\n");
builder.append("<<>>"+intent.getIntExtra("Random", -1)+"\n");
builder.append("<<>>"+bundle.getInt("FirstReciever")+"\n");
builder.append("<<>>"+bundle.getInt("SecondReciever")+"\n");

Toast.makeText(context, builder.toString(), Toast.LENGTH_LONG).show();

}

}



Do not forget to add these Permission if you want to test these examples:



<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />

10 comments:

Unknown said...

Good info Amir! Quick question, do you know if you have your broadcast receivers setup in your manifest do they get registered at the time the software is downloaded and "installed" by Android prior to actually running the application the first time, or are they registered after the user actually runs the application for the first time? Thanks!

Amir said...

Actually I'm not sure about it, but I think the easiest way to find out would be to write a simple application with a Receiver, say a SMS-Receiver or Call-Receiver and then just test it see if it gets notified before running the application for the first time or not....

Roverguy said...

Hi Amir,

Good info, however, I have a query regarding the Alarm notifications. Yo've mentioned that we can use the Alarm Manager class to set off alarms and then handle them in the receivers accordingly. However, if my application has no requirement of the alarm features at all, but still might want to handle any alarm notification that hasn't been set programmatically but by the user itself. In this scenario, how can I specify the intent receiver and the intent action to handle for any Alarm notification?

Thanks,
Asheesh

Chrisdadswell said...

Fantastic article, just what I have been looking for. Explained very concisely.

sneha said...

onReceive method I want the ID or number of particular device which receive my message.. so how can I get particular device number?? pleas help me
registerReceiver(new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
}
}

Tofeeq said...

Thank you Friend you are doing a good deed..Thank you again

Anonymous said...

With regards to Sly's question as to when the static broadcast receivers are installed, it looks like it will depend on the API level of the device. On my N1 they are registered during the installation. On my Xoom I actually need to run the app before they get registered. (My test was with BOOT_COMPLETE). This is a bit annoying :(

AW said...

It does depend on API level. In Android API < 3.1 broadcast receivers are registered on application install. Starting from Android 3.1 installed packages (applications) are put in "stopped" state and are activated (and broadcast receivers registered) after the very first launch. Read more at http://developer.android.com/sdk/android-3.1.html#launchcontrols

Anonymous said...

thanks amir

Anonymous said...

May I simply just say what a comfort to find a person that really understands what they're talking about online.
You definitely know how to bring a problem to light and
make it important. More and more people ought to look
at this and understand this side of your story. I was surprised you aren't more popular since you surely have the gift.