5

For some Android applications, I would like to integrate the following feature: The user can define a time when he wants to be reminded of something. When the time has come then, the application should create a notification in the notification bar even when the user doesn't use the app at this moment.

For this purpose, the classes AlarmManager, NotificationManager und Notification.Builder are the ones to look at, right?

So how do I create a timed notification in advance? My code (so far) is this:

Add this under to the AndroidManifest to register the broadcast receiver:

<receiver android:name="AlarmNotificationReceiver"></receiver>

Create a new class file which handles the alarm that it receives:

public class AlarmNotificationReceiver extends BroadcastReceiver {

    public void onReceive(Context context, Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras != null) {
            String additionalData = extras.getString("displayText");
            // show the notification now
            NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            Notification mNotification = new Notification(R.drawable.ic_launcher, context.getString(R.string.app_name), System.currentTimeMillis());
            PendingIntent pi = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); // open MainActivity if the user selects this notification
            mNotification.setLatestEventInfo(context, context.getString(R.string.app_name), additionalData, pi);
            mNotification.flags |= Notification.FLAG_AUTO_CANCEL | Notification.DEFAULT_SOUND;
            mNotificationManager.notify(1, mNotification);
        }
    }

}

Use this code (for example in MainActivity) to set the alarm to 3 seconds from now:

    Intent i = new Intent(this, AlarmNotificationReceiver.class);
    i.putExtra("displayText", "sample text");
    PendingIntent pi = PendingIntent.getBroadcast(this.getApplicationContext(), 234324246, i, 0);
    AlarmManager mAlarm = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    mAlarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+3*1000, pi);

What do I need to change to make this work? Thank you!

The two problems are:

  1. The notification's text does not change when I change it in code. It only changes when I change the requestCode in PendingIntent.getBroadcast(...). What is this request code all about? Can it be a random value or 0?
  2. After rebooting my phone, the "planned" notification, or the alarm, is gone. But now I've seen that this is normal behaviour, right? How can I circumvent this?
caw
  • 30,999
  • 61
  • 181
  • 291

2 Answers2

4

Not sure about part 1, but for part 2 the general approach is to intercept the BOOT_COMPLETED intent and use that to re-register all alarms. This does unfortunately mean that for each alarm you have registered with the alarm manager you have to store it in your app's db as well.

So, you'll need a broadcast receiver to intercept the BOOT_COMPLETED intent:

public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // get your stored alarms from your database
        // reregister them with the alarm manager
    }
}

To get the BOOT_COMPLETED intent, you must put the following permission in your manifest:

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

And the BootReceiver also needs to be registered in your manifest with the following intent filter:

    <receiver android:enabled="true" android:name=".receiver.BootReceiver"
    android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
        <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>

It's important to note that if your app is installed to the sdcard, it can never receive the BOOT_COMPLETED intent. Also, it's worth noting that this implementation is a bit naive in that it executes code immediately on booting which can slow the user's phone down at startup. So, I recommend delaying your execution for a few minutes after intercepting the boot intent.

danh32
  • 6,234
  • 2
  • 38
  • 39
  • Thank you! There is really no other way to do this? This sounds a bit (too) complex. You need an additional permission and perform those register actions. Well, isn't the number of alarms per application limited, anyway? I think every application is only allowed to register one alarm and after that the previous ones will be overwritten. Or is this the rule for notifications? – caw Jan 09 '12 at 16:00
  • Unfortunately there is no other way to do this. I'm not sure on the limitations of notifications, but I know for sure that you can register multiple alarms without issue. – danh32 Jan 09 '12 at 16:23
  • So I have to do what you described here, okay :) Do I need to store the IDs of these alarms? Or can I just create new alarms without storing them? – caw Jan 09 '12 at 17:10
  • What I mean is: In my application, I don't want to store the alarm object. I think I could maybe just create new alarms as I did it before the reboot (querying some data and creating an alarm). – caw Jan 09 '12 at 17:11
  • Yeah, there's no need to explicitly store the alarm object. If you can infer what alarms you need from your already existing data, that would be fine :) – danh32 Jan 09 '12 at 17:14
  • Thank you! A few questions are left ... ;) Can I set the name to "BootReceiver" instead of ".receiver.BootReceiver"? Is the attribute "android:enabled" necessary? And why do I need a "category" in the intent filter? Thanks in advance! – caw Jan 09 '12 at 23:01
  • 1
    No problem. I set the name to .receiver.BootReceiver because that's the package I put my BootReceiver class in. You should have no problem setting it to BootReceiver if you put this class in your base package. You don't have to include the "android:enabled" attribute, as it is set to true by default. – danh32 Jan 10 '12 at 14:22
  • 1
    And finally, per this page: http://developer.android.com/guide/topics/intents/intents-filters.html "...activities that are willing to receive implicit intents must include "android.intent.category.DEFAULT" in their intent filters." I'm not entirely sure that this applies for system launched intents to BroadcastReceivers, but I've always included it. Feel free to test it without :) – danh32 Jan 10 '12 at 14:22
1

I personally would do it without a Broadcast Receiver. I'd get the AlarmManager to fire the intent to start a seperate Activity, rather than receiver. Then this new Activity could make the notification for you. I'm not sure if this is a better way, but it seems less complicated to me.

Edit: A Service would probably be better still than an Activity

In your MainActivity:

Intent i = new Intent(getBaseContext(), NotificationService.class);
PendingIntent pi = PendingIntent.getService(getBaseContext(), 0, i, 0);
AlarmManager mAlarm = (AlarmManager) Context.getSystemService(Context.ALARM_SERVICE);
mAlarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+10*60*1000, pi);

Your Service:

public class NotificationService extends Service {

public int onStartCommand(Intent intent, int flags, int startId) {   

//Create the notification here

return START_NOT_STICKY;
}

Your Manifest:

<service android:name="com.android.yourpath.NotificationService"></service>
Matt Harris
  • 3,496
  • 5
  • 32
  • 43
  • Thank you! Can you explain why using a Service is better than using a Broadcast? I found the example with broadcasts on several pages covering alarms. – caw Jan 07 '12 at 18:29
  • It's personal preference really. To me a Service seems a simpler way because you just are setting the Alarm to run the Service at a certain time, rather than setting the Alarm to run the Broadcast, and then receiving the Broadcast to run the action. I think you would need to write a custom intent to make the Broadcast method work. – Matt Harris Jan 08 '12 at 12:27
  • Ah, ok. But the documentation says a service is for long-running background tasks. So I thought I should rather use a broadcast. And in this question here (http://stackoverflow.com/questions/1026973/android-whats-the-difference-between-the-various-methods-to-get-a-context), they say one should not use getBaseContext() ;) – caw Jan 08 '12 at 23:29