Search code examples
androidandroid-intentandroid-broadcastreceiver

Using a service to start an ongoing notification that can take an action


I've created a service so that I can have a persistent notification. I want this notification to have an option that will send an intent to another of my activities.

The option will inherently be stopped every process that the app has spawned. So the notification also needs to close once the action is pressed.

I'm trying to accomplish this using a pending broadcast intent, but for some reason the broadcast receiver isn't catching the intent. I register the receiver dynamically in the service's onCreate() method and unregister it in the service's onDestroy() method.

public class myService extends IntentService {
    private boolean shown = false;
    private ButtonReceiver buttonReceiver;

    //Side note: this is legacy code. Necessary?
    public myService() {
        super("myService");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        IntentFilter filter = new IntentFilter("com.myapp.KILL_NOTIFICATION");
        this.buttonReceiver = new ButtonReceiver();
        this.registerReceiver(this.buttonReceiver, filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(this.buttonReceiver);
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        if(intent.getAction() == "com.myapp.START_SERVICE") {
            //generate a unique id for the notification
            int id = Integer.parseInt(new SimpleDateFormat("ddHHmmss", Locale.US).format(new Date()));

            //create a pending intent to send off when the kill action is pressed
            Intent buttonIntent = new Intent("com.myapp.KILL_NOTIFICATION");
            buttonIntent.putExtra("notificationId", id);
            PendingIntent btPendingIntent = PendingIntent.getBroadcast(this, 0, buttonIntent, 0);

            Notification.Builder builder = new Notification.Builder(getApplicationContext());
            builder.setAutoCancel(false);
            builder.setContentTitle("my app");
            //builder.setContentText("Press this notification to kill.");
            builder.setOngoing(true);
            builder.setSmallIcon(R.drawable.ic_launcher);
            //builder.setContentIntent(pendingIntent);
            builder.addAction(R.drawable.ic_clear_black_24dp, "Kill my app", btPendingIntent);
            builder.setWhen(0);
            builder.setPriority(Notification.PRIORITY_MAX);

            Notification notification = builder.build();
            NotificationManager notificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            //TODO need to find first unused notif id
            notificationManager.notify(id, notification);
        }
    }

    public class ButtonReceiver extends BroadcastReceiver {

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

            int notificationId = intent.getIntExtra("notificationId", 0);

            Intent killIntent = new Intent("com.myapp.KILL_EVERYTHING");
            killIntent.addCategory(Intent.CATEGORY_DEFAULT);
            startActivity(killIntent);

            //cancel notification
            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.cancel(notificationId);
        }
    }
}

Solution

  • An IntentService stops itself after the onHandleIntent() method finishes, so your dynamically registered Receiver is no longer around to get the broadcast when it happens.

    You don't need a Service to maintain an ongoing Notification. In your case, you can simply issue the Notification directly, instead of starting the Service to do it. Then register your Receiver class statically in the manifest, and use an explicit Intent to broadcast to it from the Notification.

    Move ButtonReceiver to it's own class file, and register it in the manifest like so:

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

    You'll need to call startActivity() on the context parameter passed into onReceive(), like you are getSystemService(). You'll also need to add the following flag to the Intent.

    killIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    

    Then just change buttonIntent for the Notification's PendingIntent to:

    Intent buttonIntent = new Intent(this, ButtonReceiver.class);
    

    If that's the only broadcast ButtonReceiver will handle, you won't really need the KILL_NOTIFICATION action anymore.