Jump to Navigation

Me on Twitter

  • RT @rupl: 2 days 15 hours ago
  • Sorry @instapaper I can't wait any longer for you to be back online for Europeans. I'm back to @Pocket 5 weeks 5 days ago
  • Today it's #aiparis. Let's See that... 5 weeks 6 days ago
  • RT @MKBHD: Confirmed: You learn WAY more outside your comfort zone than in it. 7 weeks 46 min ago
  • RT @wselman: I try to not to post too many articles about @firefox but this is the best article I've seen that explains how we are working… 7 weeks 3 days ago
  • RT @MarciRobin: I bought a car today, and the dealership had me check off — with a pen, on paper — that I’m not a robot. https://t.co/x6nJ6… 8 weeks 2 days ago
  • If you want to quickly unregister from #Yahoo #Oath 321 (!) trackers : open browser console (F12) and run `var t=0… https://t.co/qzHuvEXZT0 8 weeks 5 days ago
  • Avis aux connaisseurs ;-) https://t.co/4uEtnV5x2E 8 weeks 5 days ago
  • RT @tewoz: Encore un carnet bien rempli... au suivant !!! #sketchbook #sketchnote #sketch #illustration #happyscribing #scribing #moleskine… 8 weeks 5 days ago
  • Net neutrality. Last chance? https://t.co/nsFcZFQGI7 9 weeks 5 days ago

Tweaking Android Notifications

This is a technical article aimed at Android developers.
This article deals with undocumented features of the Android API and derives from experimental work.

You can browse and download the sample code included in this article at http://bitbucket.org/nicobo/tweaking-android-notifications.

For SwitchDataSwitch, I wanted to provide users with a 1-click solution to enable and disable data traffic (2G/3G/...).
I chose the notification bar since it is a very accessible place, visible almost all the time and that can be expanded without stopping the running activity :

Expanding the notification bar 
The notification bar is usually presented in its reduced form (here the dark bar at the top of the screen with the smallest icons) but can be expanded by sliding it downwards.

Unfortunately, Android's Notification API is really made for instant notifications, not persistent ones, and that implies several inconveniences :

  • when creating a notification, the developer has to put an icon in the reduced notification bar, taking some precious space
  • by default an 'event timestamp' is shown next to the expanded content of the notification, which means nothing for a permanent service
  • there is no programmatic way to know if a notification is currently displayed or not

This article describes a way to create a notification that :

  • doesn't show up in the reduced notification bar
  • doesn't have a timestamp in the expanded notification bar

I haven't found a reusable way to know if a notification is currently displayed...

Creating the Notification

Let's do it step by step, by following the process of notification creation as I understand it.

Declaring the Intent and the Notification

The notification is first declared as usual (you might want to check the online reference doc) :

// The Intent that will be called upon a click on the notification
Intent notificationIntent = new Intent(context, NotifyService.class);
 
// Builds the notification view as it should be displayed in the end
Notification notification = new Notification(R.drawable.icon_notif, "My notification has just been displayed", 0);

Note that the icon R.drawable.icon_notif we provided to the android.app.Notification constructor is really the final one that will be displayed when the user will expand the notification bar : no trick for now.

Initializing the notification view

This is still quite standard code for Android :

// Sets the flags that will determine the behaviour of the notification
// the following tells that there is something currently running
// and places the notification in the 'ongoing' category
notification.flags |= Notification.FLAG_ONGOING_EVENT;
// the following ensures that the notification will not be removed
// if the user chooses to clear notifications
notification.flags |= Notification.FLAG_NO_CLEAR;
 
// Defines the notification's expanded message
CharSequence contentTitle = resources.getString(R.string.app_name);
CharSequence contentText = resources.getString(R.string.notification_text);
 
// Wraps our Intent with a PendingIntent instance
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
 
// Injects all the elements into the notification
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);

On the last line, calling Notification.setLatestEventInfo(...) sets the layout of our Notification.
Now we can start tweaking it !

Note that if we were to build a more standard notification but still with no timestamp displayed next to it, we could set notification.when=0 at this point to disable the display of the timestamp.

Tweaking the notification

EDIT : Since Android Jelly Bean (4.1) you don't need the trick explained in this chapter anymore : you can hide icons from the notification bar simply by setting a priority field to PRIORITY_MIN.

This is the interesting part !

/**
 * The time of the notification rules if it is aligned to the right or left
 * of the notification bar, and therefore we use it to place the empty icon
 * where it will not be noticed by the user.
 */
private static final long TIME_HIDDEN = SDK_VERSION >= 9 ? -Long.MAX_VALUE : Long.MAX_VALUE;
 
// ...
 
// 1. After the notification's layout has been filled, we reset the time that will be used
// to place it on the edge of the notification bar in order to hide its icon
notification.when = TIME_HIDDEN;
 
// 2. The following line only changes the icon displayed in the notification bar when reduced.
// The icon displayed in the expanded view is still the one that was initially set.
// Together with a placement to the right (or left depending on the SDK version), it hides the icon.
notification.icon = R.drawable.icon_hidden;
 
// 3. Triggers the notification.
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(MYNOTIFS_1, notification);
// After that, we cannot tell about the state of the notification...

It is very important to place this code after the call to Notification.setLatestEventInfo(...), to make sure that the notification's view has already been built.

The magic happens in two steps :

  1. we move the icon to the edge of the bar so that it doesn't take some place between 2 other icons
  2. we replace the original icon with a transparent 1x1px icon (R.drawable.icon_hidden)

The first step simply implies defining a fake timestamp that will be the greatest (Long.MAX_VALUE) or the smallest possible one (-Long.MAX_VALUE) so that the icon will be placed before of after any other icon (that has a reasonable timestamp). To know which value to use, we must test the SDK version.

The other step is to replace the icon with a transparent one, making it invisible to the user. We simply used a transparent PNG image.
The best part is that the icon we used to build the Notification is still there when the notification bar is expanded, it is not replaced with the transparent one... 

The last lines are standard Android code that trigger the notification.

Et voilà !

Some more details

Note : each of the following cases is included with the sample app attached to this article.

Playing with the timestamp

If you are curious, you might want to see what happens if you set Notification.when = TIME_HIDDEN before calling Notification.setLatestEventInfo(...) :

The notification with a long date next to it

This date is very long and has no meaning for us...

 

If you don't change Notification.when at all, the icon is placed depending on its creation time (notifications are sorted on their timestamp) :

A space is visible between 2 icons of the notification bar

There is an empty place between two icons of the notification bar : this is our transparent icon.

 

In general, if you want to have more control on the date displayed next to the notification text, simply set the when field right before calling setLatestEventInfo(...).

An alternative

Finally, instead of building a default view for the notification and then tweaking it, we could build a custom notification view.

// The following code makes uses a custom layout for the notification.
RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.notification);
contentView.setImageViewResource(R.id.image, R.drawable.icon);
contentView.setTextViewText(R.id.title, contentTitle);
contentView.setTextViewText(R.id.text, contentText);
notification.contentView = contentView;
notification.contentIntent = contentIntent;

There is some documentation about this cool option, but be aware that this could easily lead to build views that are not consistent with the OS' default style...

Even though it allows us to hide the timestamp in the expanded view, it doesn't help hiding the icon in the reduced notification bar, so in our case this option is not quite helpful...
Also, I could not make it work on Android 1.5 (I neither tried nor investigated further).

End note

Don't forget this is experimental work, and the method described in this article may not be available on all devices, and could be deprecated as well in future releases of Android.
However, the app I've created based on this article has already been installed on several kind of devices without issue so you may be confident for now... I'll update this article whenever I have feedback about it.

Happy notifying !

References

AttachmentSize
tweaking-android-notifications.zip10.23 KB

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • E-Mail addresses are hidden with reCAPTCHA Mailhide.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>. The supported tag styles are: <foo>, [foo].
  • Twitter-style #hashtags are linked to search.twitter.com.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.