Saif Thaha
Saif Thaha

Reputation: 51

How to implement Broadcast Receiver on Device Boot?

I've have a simple app to schedule a notification 5 minutes after a button is pressed. It works fine for me. But if I restart the phone within that 5 minutes I don't get the notification. I have done a research on Alarm Manager and Scheduling Notifications on device reboots. I have 5 classes in my App.

They are:

I have 2 classes that extends BroadCast Receivers. They are:

From my research and other stack overflow questions I learned that in order to receive notifications after the device is rebooted I should create a class that extends BroadCast Receiver. It will run whatever code is in the onReceive() method whenever the phone is booted up.

This is my MainActivity.class

public class MainActivity extends AppCompatActivity {
    private Button button;
    private Context context;

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

        button = findViewById(R.id.notification);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                NotificationUtil.createNotification(MainActivity.this,NotificationView.class,"Notification","You have a new Task");
            }
        });
    }
}

This is my NotificationUtil.class. This is the class that handles all the Notifications and this is the class I call on Button Click in the MainActivity.

public class NotificationUtil
{
    public static void createNotification(Context context,Class<?> cls, String title, String content)
    {
        Intent intent = new Intent(context,cls);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

        sendNotification(getNotification(pendingIntent,context,title,content),context);
    }

    private static void sendNotification(Notification notification,Context context)
    {
        Intent notificationIntent = new Intent(context, NotificationPublisher.class);
        Intent deviceBootIntent = new Intent(context,DeviceBootReceiver.class);

        notificationIntent.putExtra(NotificationPublisher.NOTIFICATION_ID,1);
        notificationIntent.putExtra(NotificationPublisher.NOTIFICATION,notification);

        deviceBootIntent.putExtra(DeviceBootReceiver.NOTIFICATION_ID, 1);
        deviceBootIntent.putExtra(DeviceBootReceiver.NOTIFICATION, notification);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60 * 1000,pendingIntent);
    }

    private static Notification getNotification(PendingIntent pendingIntent, Context context, String title, String content)
    {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context,"ChannelID");
        builder.setSmallIcon(R.drawable.notification_bell);
        builder.setContentTitle(title);
        builder.setContentText("You have a Notification");
        builder.setSubText("Tap To View");
        builder.setStyle(new NotificationCompat.BigTextStyle().bigText(content));
        builder.setContentIntent(pendingIntent);
        return builder.build();
    }
}

This is my NotificationPublisher.class. This is the class responsible for sending the Notification.

public class NotificationPublisher extends BroadcastReceiver {

    public static String NOTIFICATION_ID = "notification_id";
    public static String NOTIFICATION = "notification";

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

        try
        {
            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

            Notification notification = intent.getParcelableExtra(NOTIFICATION);
            int notificationId = intent.getIntExtra(NOTIFICATION_ID, 1);
            notificationManager.notify(notificationId, notification);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

This is my DeviceBootReceiver.class. I'm trying to implement my code here to receive Notification after the device is rebooted.

public class DeviceBootReceiver extends BroadcastReceiver {

    public static String NOTIFICATION_ID = "notification_id";
    public static String NOTIFICATION = "notification";

    public void onReceive(Context context, Intent intent)
    {
        if(Objects.requireNonNull(intent.getAction()).equalsIgnoreCase("android.intent.action.BOOT_COMPLETED"))
        {
            Notification notification = intent.getParcelableExtra(NOTIFICATION);
            int notificationID = intent.getIntExtra(NOTIFICATION_ID,1);

            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.notify(notificationID, notification);
        }
    }
}

After restarting my phone I get the message "Application has Stopped Working". I'm not sure where I'm going wrong.

This is my manifest file:

<?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.example.notificationtest">

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

    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <activity android:name=".NotificationView"
            android:parentActivityName=".MainActivity"/>

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


        <receiver android:name=".DeviceBootReceiver"
            android:enabled="true"
            android:exported="true"
            android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.ACTION_BOOT_COMPLETED" />
                <action android:name="android.intent.action.REBOOT" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.ACTION_SHUTDOWN" />
            </intent-filter>
        </receiver>


    </application>

</manifest>

From what I understood the class that extends BroadCast Receivers gets called on Device Reboot. So I tried passing my notification and notificationID from my NotificationUtil.class to my DeviceBootReceiver.class like this using Intent.putExtra

Intent deviceBootIntent = new Intent(context,DeviceBootReceiver.class);
deviceBootIntent.putExtra(DeviceBootReceiver.NOTIFICATION_ID, 1);
deviceBootIntent.putExtra(DeviceBootReceiver.NOTIFICATION, notification);

Then in my DeviceBootReceiver.class under the onReceive method I get the values using getExtra. I further pass the 2 values I got to my NotificationPublisher.class.

This "WAS" my earlier implementation of my DeviceBootReceiver.class. Since this didn't work I tried a new implementation as given above.

public class DeviceBootReceiver extends BroadcastReceiver {

    public static String NOTIFICATION_ID = "notification_id";
    public static String NOTIFICATION = "notification";

    public void onReceive(Context context, Intent intent)
    {
        if(Objects.requireNonNull(intent.getAction()).equals("android.intent.action.BOOT_COMPLETED"))
        {
            Intent pushIntent = new Intent(context, NotificationPublisher.class);

            Notification notification = intent.getParcelableExtra(NOTIFICATION);
            int notificationID = intent.getIntExtra(NOTIFICATION_ID,1);

            pushIntent.putExtra(NotificationPublisher.NOTIFICATION_ID,notificationID);
            pushIntent.putExtra(NotificationPublisher.NOTIFICATION,notification);

            PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,pushIntent,PendingIntent.FLAG_UPDATE_CURRENT);

            AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
            alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60 * 1000,pendingIntent);
        }

    }
}

What I expected was, since the DeviceBootReceiver.class is called on startup the 2 values will be sent to my NotificationPublisher.class and I'll get my Notification on startup. Instead the Application crashes with my Old and New Implementation in the onReceivemethod.

Can someone who has experience with receiving Notification on Device reboot help me with this? Please go through my code and try to implement it in your project to understand what I have done. I have uploaded all the codes here in this question.

These are my crash logs:

10-14 17:26:03.088 5438-5438/? I/SELinux: Function: selinux_android_load_priority [0], There is no sepolicy file.

10-14 17:26:03.088 5438-5438/? I/SELinux: Function: selinux_android_load_priority [1], There is no sepolicy version file.

10-14 17:26:03.088 5438-5438/? I/SELinux: Function: selinux_android_load_priority , priority is 3. priority version is VE=SEPF_SM-G357FZ_4.4.4_A042


10-14 17:26:03.088 5438-5438/? E/SELinux: [DEBUG] get_category: variable seinfocat: default sensitivity: NULL, cateogry: NULL
10-14 17:26:03.088 5438-5438/? E/SELinux: seapp_context_lookup: str_security_ctx is null
10-14 17:26:03.088 5438-5438/? E/dalvikvm: >>>>> Normal User
10-14 17:26:03.088 5438-5438/? E/dalvikvm: >>>>> com.example.notificationtest [ userId:0 | appId:10181 ]
10-14 17:26:03.088 5438-5438/? E/SELinux: [DEBUG] get_category: variable seinfocat: default sensitivity: NULL, cateogry: NULL
10-14 17:26:03.088 5438-5438/? E/SELinux: seapp_context_lookup: str_security_ctx is null
10-14 17:26:03.088 5438-5438/? D/dalvikvm: Late-enabling CheckJNI
10-14 17:26:03.088 5438-5438/? I/libpersona: KNOX_SDCARD checking this for 10181
10-14 17:26:03.088 5438-5438/? I/libpersona: KNOX_SDCARD not a persona
10-14 17:26:03.138 5438-5438/? D/TimaKeyStoreProvider: in addTimaSignatureService
10-14 17:26:03.148 5438-5438/? D/TimaKeyStoreProvider: Cannot add TimaSignature Service, License check Failed
10-14 17:26:03.148 5438-5438/? D/ActivityThread: Added TimaKesytore provider
10-14 17:26:03.168 5438-5438/com.example.notificationtest D/DisplayManagerGlobal: getDisplayInfo: displayId=0, info=DisplayInfo{"Built-in Screen", app 480 x 800, real 480 x 800, largest app 800 x 762, smallest app 480 x 442, 60.0 fps, rotation0, density 240 (217.714 x 216.17) dpi, layerStack 0, type BUILT_IN, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
10-14 17:26:03.168 5438-5438/com.example.notificationtest D/DisplayManagerGlobal: getDisplayInfo: displayId=0, info=DisplayInfo{"Built-in Screen", app 480 x 800, real 480 x 800, largest app 800 x 762, smallest app 480 x 442, 60.0 fps, rotation0, density 240 (217.714 x 216.17) dpi, layerStack 0, type BUILT_IN, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
10-14 17:26:03.168 5438-5438/com.example.notificationtest D/ActivityThread: handleBindApplication:com.example.notificationtest
10-14 17:26:03.168 5438-5438/com.example.notificationtest D/DisplayManagerGlobal: getDisplayInfo: displayId=0, info=DisplayInfo{"Built-in Screen", app 480 x 800, real 480 x 800, largest app 800 x 762, smallest app 480 x 442, 60.0 fps, rotation0, density 240 (217.714 x 216.17) dpi, layerStack 0, type BUILT_IN, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
10-14 17:26:03.168 5438-5438/com.example.notificationtest D/DisplayManagerGlobal: getDisplayInfo: displayId=0, info=DisplayInfo{"Built-in Screen", app 480 x 800, real 480 x 800, largest app 800 x 762, smallest app 480 x 442, 60.0 fps, rotation0, density 240 (217.714 x 216.17) dpi, layerStack 0, type BUILT_IN, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
10-14 17:26:03.238 5438-5438/com.example.notificationtest D/AndroidRuntime: Shutting down VM
10-14 17:26:03.238 5438-5438/com.example.notificationtest W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x416a7d58)
10-14 17:26:03.238 5438-5438/com.example.notificationtest E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.notificationtest, PID: 5438
    java.lang.RuntimeException: Unable to start receiver com.example.notificationtest.NotificationPublisher: java.lang.NullPointerException
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:2551)
        at android.app.ActivityThread.access$1700(ActivityThread.java:155)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1319)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5426)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
        at dalvik.system.NativeStart.main(Native Method)
     Caused by: java.lang.NullPointerException
        at android.app.NotificationManager.notify(NotificationManager.java:150)
        at android.app.NotificationManager.notify(NotificationManager.java:120)
        at com.example.notificationtest.NotificationPublisher.onReceive(NotificationPublisher.java:23)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:2544)
        at android.app.ActivityThread.access$1700(ActivityThread.java:155) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1319) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:136) 
        at android.app.ActivityThread.main(ActivityThread.java:5426) 
        at java.lang.reflect.Method.invokeNative(Native Method) 
        at java.lang.reflect.Method.invoke(Method.java:515) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084) 
        at dalvik.system.NativeStart.main(Native Method) 
10-14 17:26:07.708 5438-5438/com.example.notificationtest I/Process: Sending signal. PID: 5438 SIG: 9

Upvotes: 3

Views: 2783

Answers (2)

iCantC
iCantC

Reputation: 3180

ACTION_BOOT_COMPLETED is on the implicit broadcast whitelist, and so you can register for it in the manifest even at and after Android 8.0.

This answer was originally answered by @CommonsWare at this thread

All in all, don't forget to test it once implemented.

Upvotes: 1

IntelliJ Amiya
IntelliJ Amiya

Reputation: 75788

Implicit Broadcast Exceptions

As part of the Android 8.0 (API level 26) Background Execution Limits, apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest. However, several broadcasts are currently exempted from these limitations. Apps can continue to register listeners for the following broadcasts, no matter what API level the apps target.

Even though these implicit broadcasts still work in the background, you should avoid registering listeners for them.

You can use scheduled jobs instead.

FYI

   java.lang.RuntimeException: Unable to start receiver com.example.notificationtest.NotificationPublisher: java.lang.NullPointerException
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:2551)
        at android.app.ActivityThread.access$1700(ActivityThread.java:155)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1319)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:136)

NullPointerException is thrown when an application attempts to use an object reference that has the null value.

Upvotes: 1

Related Questions