You’re almost there — sign up to start building in Notion today.
Sign up or login
Broadcast Receivers

Broadcast Receivers

Think of a broadcast receiver like a doorbell. When someone rings the doorbell, you get a notification and can do something about it, like answering the door. Similarly, a broadcast receiver listens for certain events to happen on your Android phone, like getting a text message or losing network connectivity. When the event happens, the broadcast receiver gets a notification and can perform an action, like displaying a notification or starting a specific task.
There are two types of broadcast receivers: static and dynamic. Static receivers are declared in the app’s manifest file and can work even if the app is closed. Dynamic receivers are registered in the app’s code and only work if the app is active or minimized.
Static Receiver
<!-- AndroidManifest.xml --> <manifest> <application> <receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED" /> </intent-filter> </receiver> </application> </manifest>
App manifest.
ALT
Java code:
// MyBroadcastReceiver.java public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { // Perform action when device boots up } else if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) { // Perform action when device is plugged in } } }
In this example, the MyBroadcastReceiver class is registered statically in the app's manifest file to listen for the BOOT_COMPLETED and ACTION_POWER_CONNECTED events.
ALT
Dynamic Receiver
// MainActivity.java public class MainActivity extends AppCompatActivity { private BroadcastReceiver myBroadcastReceiver; private IntentFilter intentFilter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Perform action when event occurs } }; intentFilter = new IntentFilter(); intentFilter.addAction("com.myapp.EVENT"); registerReceiver(myBroadcastReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(myBroadcastReceiver); } }

How To Broadcast an Event

// MyService.java public class MyService extends Service { @Override public void onCreate() { super.onCreate(); Intent intent = new Intent("com.myapp.EVENT"); sendBroadcast(intent); } }
In this example, the MyService class sends a broadcast intent with the custom "com.myapp.EVENT" action to trigger the event and notify any registered broadcast receivers.
ALT

System-Level Events

System-level events are events that are triggered by the Android operating system and are not specific to any particular app. These events are typically related to the overall functioning of the device, such as changes in network connectivity, battery level, or device orientation.
Examples of system-level events in Android include:
ACTION_BOOT_COMPLETED
: Triggered when the device finishes booting up.
ACTION_BATTERY_LOW
: Triggered when the battery level drops below a certain threshold.
ACTION_SCREEN_ON
 and 
ACTION_SCREEN_OFF
: Triggered when the device screen is turned on or off.
ACTION_AIRPLANE_MODE_CHANGED
: Triggered when the device's airplane mode is turned on or off.
ACTION_TIMEZONE_CHANGED
: Triggered when the device's timezone is changed.
These events are broadcast by the system and can be received by any app that has registered a broadcast receiver for the corresponding event. Apps can use these events to perform actions or update their user interface based on the current state of the device.

Can we as normal developer send system-level events?

No, your application cannot broadcast the system-level events on its own. These events are triggered by the system privilege.

Priority of Receivers

In the AndroidManifest.xml file, you can set the priority of a broadcast receiver to determine the order in which multiple broadcast receivers should receive a particular broadcast event. The priority is an integer value that can range from -1000 to 1000, where a higher value indicates a higher priority. The default priority value for a broadcast receiver is 0.
Here is an example of how to set the priority of a broadcast receiver in the manifest file:
<receiver android:name=".MyBroadcastReceiver" android:priority="100"> <intent-filter> <action android:name="android.intent.action.AIRPLANE_MODE"/> </intent-filter> </receiver>
💡 Callout icon
It is important to keep in mind that when multiple receivers are registered for an event like SMS_RECEIVE in Android, the system will send the SMS message to the receiver with the highest priority. From there, the app can decide whether to forward the event to lower priority receivers or not.

A Story of Vulnerability

Before Android 4.4, it was possible to define a receive priority higher than system apps for SMS broadcast receivers. However, this created a security vulnerability that attackers could exploit to capture all incoming SMS messages. By defining a high-priority SMS receiver, an attacker could intercept SMS messages, including sensitive information such as banking transaction details, before they reached the intended recipient. The attacker could then choose to forward the SMS messages to the system SMS manager or not, depending on their objectives.
With the release of Android 4.4, the platform introduced a new SMS API that restricted access to SMS messages, making it impossible for third-party apps to define a higher receive priority than system apps.
Code Example:
<receiver android:name=".MySmsReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="999"> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>
Also attacker needs SMS permission:
<uses-permission android:name="android.permission.RECEIVE_SMS" />
And the Java code:
public class MySmsReceiver extends BroadcastReceiver { private static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED"; @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(SMS_RECEIVED_ACTION)) { Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); if (pdus != null) { for (Object pdu : pdus) { SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu); String sender = message.getOriginatingAddress(); String body = message.getMessageBody(); // Handle the SMS message } } } } } }

A Favorite of Malware Receiver

When an app registers a 
BroadcastReceiver
 that listens for the 
ACTION_BOOT_COMPLETED
 event in its AndroidManifest.xml file, the app will receive this event when the device finishes booting up. The app can then perform certain actions or start services as needed, such as initializing background tasks or starting a service that runs in the background and performs certain tasks.
It's important to note that the 
ACTION_BOOT_COMPLETED
 event is a privileged broadcast that requires the 
android.permission.RECEIVE_BOOT_COMPLETED
 permission to receive. This permission is classified as a "Normal" permission .
Starting in Android 11, apps that target API level 30 or higher cannot receive the 
ACTION_BOOT_COMPLETED
 event unless they are a device policy controller app, which is a special type of app that is typically used by IT administrators to manage devices in an enterprise setting. This change was made to improve the security and privacy of Android devices.( Note that it’s different in various manufactures)
Code Example:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <receiver android:name=".BootReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/> <action android:name="android.intent.action.REBOOT" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver>
Android Manifest.
ALT
💡 Callout icon
Intent 
android.intent.action.BOOT_COMPLETED
 is received after a "cold" boot.
Intent 
android.intent.action.QUICKBOOT_POWERON
 is received after a "restart" or a "reboot".
💡 Callout icon
Direcbootaware
is an attribute that you can set in your Android manifest file to indicate whether your app components are encryption aware, that is, whether they can run before the user unlocks the device. This is useful for apps that need to access device encrypted storage or perform tasks during Direct Boot mode.
To mark your component as encryption aware, set the
android:directBootAware
attribute to
true
in your manifest. For example:
<service android:name=".MyService" android:directBootAware="true" />
Encryption aware components can register to receive an
ACTION_LOCKED_BOOT_COMPLETED
broadcast message from the system when the device has been restarted. This allows them to perform initialization tasks or respond to user actions before unlocking the device.
Since Android 8.0 (Oreo), you also need to register the 
BroadcastReceiver
 dynamically at runtime, as follows:
public class MainActivity extends AppCompatActivity { private BootCompleteReceiver mReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mReceiver = new BootCompleteReceiver(); IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); filter.addAction(Intent.ACTION_REBOOT); filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); registerReceiver(mReceiver, filter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mReceiver); } }

How to Test?

Method 1: Simply restart your device or emulator. (Keep in mind to choose cold boot in emulator)
Method 2: Use Activity Manager (am)
am broadcast --user all -a android.intent.action.BOOT_COMPLETED # Note: You need root acceess to send this event. # To send custom event with data: am broadcast --user all -a lab.seczone64.event -e "key" "value"
You could also select specific user. How to find user ID? Easy:
pm list packages -3 -U # For showing 3third party pacakges or pm list packages -s -U # For showing System pacakges then ls -lihsa /data/data | grep -i lab.seczone64 # Need root access
💡 Callout icon
In some devices, the user ID like this: u0_a124. The “a” mean starting user ID. Usually this number is 10,000. So in this example the real user id is: 10124

Local Broadcast Manager

LocalBroadcastManager
 is a class in Android that allows you to send and receive broadcasts within your own app. It provides a mechanism for sending broadcast messages that are only visible to components within your app, rather than being broadcast to all apps on the device.
LocalBroadcastManager
 is part of the Android Support Library, which means that it is compatible with Android versions as far back as Android 2.3 (Gingerbread).
Here are some key features of 
LocalBroadcastManager
:
Broadcasting: You can use 
LocalBroadcastManager
 to send broadcast messages within your own app. When you send a broadcast message, only components within your app that have registered to receive that particular broadcast will receive it.
Security: Because broadcasts are only visible within your app, 
LocalBroadcastManager
 can help improve the security of your app. This is because other apps on the device cannot intercept or modify the broadcasts that are sent within your app.
Performance: Because 
LocalBroadcastManager
 only broadcasts messages within your app, it is generally faster and more efficient than sending broadcasts that are visible to all apps on the device.
Simplicity: 
LocalBroadcastManager
 is easy to use and requires minimal setup. You can use it to send and receive broadcast messages within your app with just a few lines of code.

How to create a Local Broadcast Receiver

Create a new
BroadcastReceiver
class:
class MyReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { // Handle the broadcast message here } }
Register the
MyReceiver
class to receive broadcast messages in your activity or fragment:
class MyActivity : AppCompatActivity() { private val myReceiver = MyReceiver() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val filter = IntentFilter() filter.addAction("com.example.myapp.MY_ACTION") LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver, filter) } override fun onDestroy() { super.onDestroy() LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver) } }
In this example, the
MyActivity
class registers the
MyReceiver
class to receive broadcast messages with the action
"com.example.myapp.MY_ACTION"
. The
LocalBroadcastManager.getInstance()
method is used to obtain an instance of the
LocalBroadcastManager
class.
Send a broadcast message from another component within your app:
val intent = Intent("com.example.myapp.MY_ACTION") LocalBroadcastManager.getInstance(context).sendBroadcast(intent)

Tasks

Develop app which send broadcast receiver and hack the app