Android Oreo Implicit and Explicit Broadcast Receiver

Filed Under: Android

In this tutorial, we’ll discuss the changes in Broadcast Receiver since Android Oreo. We’ll see why the restrictions have been put on Background Operations in Android.

Androd Broadcast Receivers

Broadcast Receivers are like Antennas. Just like Antennas can be tuned to catch certain frequencies, broadcast receivers can be registered to certain intent actions. When that action event triggers, the BroadcastReceiver gets called.

Implicit vs Explicit BroadcastReceivers

Before we differentiate, let’s know the difference between Implicit and Explicit Intents.

Explicit Intents are used to call a particular component that you know of.
Implicit Intents are used when you don’t know the exact component to invoke.

Take the example of capturing a photo from camera or gallery. It’s an implicit intent, since calling the intent shows you a dialog of different applications to choose from. Same goes for email/browser intents (if they show you a dialog chooser).

Implicit Broadcast Receivers aren’t exclusive to your application.
Actions such as ACTION_BOOT_COMPLETED or CONNECTIVITY_CHANGE are categorised in implicit broadcast receivers. This is because when these events happen, all the applications registered with the event will get the information.

Explicit Broadcast Receivers are exclusive to your application. Only your application’s broadcast receiver will get triggered when the custom intent action you define, gets called.

Handling Android Oreo Broadcast Receivers

Now, broadcast receivers run in the background. Hence it drains the battery. Imagine every application has registered android.net.conn.CONNECTIVITY_CHANGE in the receiver which is implicit.

Now, whenever the wifi/mobile internet connection toggles, all the applications, with the receiver would get triggered. Imagine what happens when your wifi router is unstable. It can cause battery issues.

Since Android Oreo, implicit broadcast receivers won’t work when registered in the AndroidManifest.xml

How to handle Implicit Receivers in Android Oreo?

To use Implicit Receivers in your application, you need to define them programmatically in your code, using registerReceiver().

Using registerReceiver() we can programmatically register and unregisterReceiver() during the lifecycle of the activity. This way implicit receivers would only be called when our activity/application is alive and not at other times.

Besides registering receivers programmatically, we can use JobSchedulers too.

Several Implicit Broadcasts are exempted and can be declared in the Manifest:

android implicit broadcast exceptions

Note: Beginning with Android Pie, The NETWORK_STATE_CHANGED_ACTION broadcast doesn’t receive information about the user’s location or personally identifiable data.

In the next section, we’ll be creating an application which implements Broadcast Receivers keeping the Android Oreo limitations.

Project Structure

android oreo broadcast receiver project

Code

The AndroidManifest.xml file looks like the following:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.journaldev.androidoreobroadcastreceiver">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">

        <receiver android:name="com.journaldev.androidoreobroadcastreceiver.MyReceiver"
            android:enabled="true">

            <intent-filter>
                <action android:name="com.journaldev.AN_INTENT" />
            </intent-filter>

        </receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

We’ve defined an intent filter with an action which is explicit.

Implicit ones will be defined in the code only:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnExplicitBroadcast"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Explicit Broadcast" />


</LinearLayout>

The code for the MainActivity.java class is given below:


package com.journaldev.androidoreobroadcastreceiver;

import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    Button btnExplicitBroadcast;

    MyReceiver myReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnExplicitBroadcast = findViewById(R.id.btnExplicitBroadcast);
        btnExplicitBroadcast.setOnClickListener(this);
        myReceiver= new MyReceiver();
    }

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

        IntentFilter filter = new IntentFilter();
        filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");


        registerReceiver(myReceiver, filter);
    }

    public void broadcastIntent() {
        Intent intent = new Intent();
        intent.setAction("com.journaldev.AN_INTENT");
        intent.setComponent(new ComponentName(getPackageName(),"com.journaldev.androidoreobroadcastreceiver.MyReceiver"));
        getApplicationContext().sendBroadcast(intent);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unregisterReceiver(myReceiver);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnExplicitBroadcast:
                broadcastIntent();
                break;
        }
    }
}

On button click sendBroadcast() is called to send the explicit broadcast.

Note: sendStickyBroadcasts() were used to send broadcast intents that stayed around for other receivers to access them at a later point. The method was deprecated in API 21.

The code for the MyReceiver.java BroadcastReceiver class is given below:


package com.journaldev.androidoreobroadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {


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

        String action = intent.getAction();
        if (action.equals("com.journaldev.AN_INTENT")) {
            Toast.makeText(context, "Explicit Broadcast was triggered", Toast.LENGTH_SHORT).show();
        }

        if (("android.net.conn.CONNECTIVITY_CHANGE").equals(action)) {
            Toast.makeText(context, "Implicit Broadcast was triggered using registerReceiver", Toast.LENGTH_SHORT).show();
        }

    }
}

The output of the above application in action is given below:

android oreo broadcast receiver output

LocalBroadcastManager

A LocalBroadcastManager is used to send or receive events locally within the current application only.

LocalBroadcastManager does not listen to system-wide broadcasts and is more secure than BroadcastReceiver.
Plus it has a less overhead hence communication speed is faster.

It can be initialised as:


LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);

localBroadcastManager.sendBroadcast(intent);

LocalBroadcastManager would fail if you make events that are system wide.

We can secure broadcast receivers by setting permissions in them. Only receivers that have requested the permissions in the Manifest can receive the broadcasts.


<receiver android:name=".MyReciever"
          android:permission="android.permission.SEND_SMS">
    <intent-filter>
    <action android:name="MY_ACTION"/>
    </intent-filter>
</receiver>

sendBroadcast(new Intent("MY_ACTION"), Manifest.permission.SEND_SMS);

Set the android:exported attribute to “false” in the receiver tag in the manifest restricts the application from receiving broadcasts from other applications.

This brings an end to this tutorial. You can download the project from the below link:

Comments

  1. Daniel Achamisinya says:

    I have been trying to do a simple SMS app to will toast a message when it is received. can you help?

    Here is my receiver class

    package com.example.smsappteks;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import android.telephony.SmsMessage;
    import android.widget.Toast;
    public class SmsBroadcastReceiver extends BroadcastReceiver {
        private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(SMS_RECEIVED)) {
                Bundle bundle = intent.getExtras();
                if (bundle != null) {
                    // get sms objects
                    Object[] pdus = (Object[]) bundle.get("pdus");
                    if (pdus.length == 0) {
                        return;
                    }
                    SmsMessage[] messages = new SmsMessage[pdus.length];
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < pdus.length; i++) {
                        messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                        sb.append(messages[i].getMessageBody());
                    }
                    String sender = messages[0].getOriginatingAddress();
                    String message = sb.toString();
                    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
                    
                }
            }
        }
    }
    
    //The message does get toast
    
    
    //Below is the main activity
    
    package com.example.smsappteks;
    import android.Manifest;
    import android.content.ContentResolver;
    import android.content.IntentFilter;
    import android.content.pm.PackageManager;
    import android.database.Cursor;
    import android.net.Uri;
    import android.support.annotation.NonNull;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.content.ContextCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.telephony.SmsManager;
    import android.util.Log;
    import android.view.View;
    import android.widget.ArrayAdapter;
    import android.widget.EditText;
    import android.widget.ListView;
    import android.widget.Toast;
    import java.util.ArrayList;
    
    public class MainActivity extends AppCompatActivity {
        final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
    SmsBroadcastReceiver br =new SmsBroadcastReceiver();
        ArrayList smsMessagesList = new ArrayList();
        ListView messages;
        ArrayAdapter arrayAdapter;
        EditText input;
        SmsManager smsManager = SmsManager.getDefault();
        private static MainActivity inst;
    
        private static final int READ_SMS_PERMISSIONS_REQUEST = 1;
    
        private int MY_PERMISSIONS_REQUEST_SMS_RECEIVE = 10;
    
        public static MainActivity instance() {
            return inst;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            messages = (ListView) findViewById(R.id.messages);
            input = (EditText) findViewById(R.id.input);
            arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, smsMessagesList);
            messages.setAdapter(arrayAdapter);
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
                    != PackageManager.PERMISSION_GRANTED) {
                getPermissionToReadSMS();
    
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.RECEIVE_SMS},
                        MY_PERMISSIONS_REQUEST_SMS_RECEIVE);
    
    
            } else {
                refreshSmsInbox();
            }
    
    
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            IntentFilter filter = new IntentFilter(SMS_RECEIVED);
            registerReceiver(br, filter);
            inst = this;
        }
    
        @Override
        protected void onStop() {
            super.onStop();
          unregisterReceiver(br);
        }
    
    
        public void updateInbox(final String smsMessage) {
            arrayAdapter.insert(smsMessage, 0);
            arrayAdapter.notifyDataSetChanged();
        }
    
        public void onSendClick(View view) {
    
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS)
                    != PackageManager.PERMISSION_GRANTED) {
                getPermissionToReadSMS();
            } else {
                smsManager.sendTextMessage("07701056337", null, input.getText().toString(), null, null);
                Toast.makeText(this, "Message sent!", Toast.LENGTH_SHORT).show();
            }
        }
    
        public void getPermissionToReadSMS() {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
                    != PackageManager.PERMISSION_GRANTED) {
                if (shouldShowRequestPermissionRationale(
                        Manifest.permission.READ_SMS)) {
                    Toast.makeText(this, "Please allow permission!", Toast.LENGTH_SHORT).show();
                }
                requestPermissions(new String[]{Manifest.permission.READ_SMS},
                        READ_SMS_PERMISSIONS_REQUEST);
            }
        }
    
    
        @Override
    
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode == MY_PERMISSIONS_REQUEST_SMS_RECEIVE) {
                // YES!!
                Log.i("TAG", "MY_PERMISSIONS_REQUEST_SMS_RECEIVE --> YES");
            }
        }
        public void refreshSmsInbox() {
            ContentResolver contentResolver = getContentResolver();
            Cursor smsInboxCursor = contentResolver.query(Uri.parse("content://sms/inbox"), null, null, null, null);
            int indexBody = smsInboxCursor.getColumnIndex("body");
            int indexAddress = smsInboxCursor.getColumnIndex("address");
            if (indexBody < 0 || !smsInboxCursor.moveToFirst()) return;
            arrayAdapter.clear();
            do {
                String str = "SMS From: " + smsInboxCursor.getString(indexAddress) +
                        "\n" + smsInboxCursor.getString(indexBody) + "\n";
                arrayAdapter.add(str);
            } while (smsInboxCursor.moveToNext());
    
        }
    
    
    
    }
    

    Please help me to make the app toast messages when they arrived
    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages