1. Heads-up notifications
Starting from Android 5.0, developers are able to use a new type of notifications called “heads-up notifications”. However, this type of notification may be very irritating for users (it’s shown as a small dialog window over the main screen), so use it with caution.
The implementation is really straightforward. All you need to do is set the priority of notification to NotificationCompat.PRIORITY_HIGH
and make sure that your notification uses ringtones or vibrations (most of the time NotificationCompat.DEFAULT_ALL
flag gives us this kind of signals).
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this); notificationBuilder.setContentTitle("Alert!") .setContentText("This is heads-up notification") .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_HIGH);
But if you are like me and want to disable this feature, then open Settings app, Sound & notification, App notifications, choose your app and switch off “Allow peeking” option.
2. Lock Screen Notifications
Lock Screen Notifications are another nice thing introduced by Lollipop. To use this feature, user needs to have an appropriate option (Show all notification content/Hide sensitive notification content) turned on in Settings App -> Sound & notification -> When device is locked section
- Don’t show notifications at all – pretty obvious as you can see on the screen below
- Show all notification content – also easy to understand (whole content is available)
- Hide sensitive notification content – if this one is enabled, a developer can choose one of the three possible visibility options for the notifications (`VISIBILITY_PUBLIC`
,
`VISIBILITY_SECRET`,
`VISIBILITY_PRIVATE`)NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this); notificationBuilder.setVisibility(visibility);
2.1 Hide sensitive notification content – visibility options
VISIBILITY_PUBLIC
– the whole content is available for the user when notification is displayed on lock screenVISIBILITY_SECRET
– the notification won’t be presented on lock screen (user can access it only when the screen is unlocked)VISIBILITY_PRIVATE
– the default option for the notification – it was presented on the last screenshot above (showing the icon and the name of the app). By using this option, the developer can also specify a public version of notification which will be available on the locked screen.
Example how to specify public and private version of notification
NotificationCompat.Builder publicNotificationBuilder = new NotificationCompat.Builder(this); publicNotificationBuilder.setContentTitle("Alert!") .setContentText("This is public part of notification") .setSmallIcon(android.R.drawable.ic_dialog_email) .setAutoCancel(true); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this); notificationBuilder.setContentTitle("Alert!") .setContentText("This is private part of notification") .setSmallIcon(android.R.drawable.ic_dialog_email) .setAutoCancel(true) .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) .setDefaults(NotificationCompat.DEFAULT_ALL) .setPublicVersion(publicNotificationBuilder.build()); showNotification(notificationBuilder);
Result
3. Custom views
Instead of using the default notification layout, we are able to create our custom one by using `RemoteViews` object (it’s similar to creating app widgets).
To demonstrate this technique, we will start with defining our XML layout for the notification.
custom_notification.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ProgressBar android:id="@+id/progress" style="@android:style/Widget.Material.ProgressBar.Large" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:indeterminate="true" /> <TextView style="@android:style/TextAppearance.Material.Notification.Title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_margin="4dp" android:layout_toRightOf="@id/progress" android:textSize="20sp" android:singleLine="true" android:text="This is some custom view" /> </RelativeLayout>
Now we will send the notification the same way as before but with one exception. We are now specifying the layout to use in the notification by passing RemoteView object to setContent method.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this); RemoteViews content = new RemoteViews(getPackageName(), R.layout.custom_notification); builder .setSmallIcon(android.R.drawable.ic_dialog_email) .setContent(content) .setPriority(NotificationCompat.PRIORITY_HIGH) .setDefaults(NotificationCompat.DEFAULT_ALL); Intent intentWithDefinedAction = new Intent(); builder.setContentIntent(PendingIntent.getActivity(this, 0, intentWithDefinedAction, PendingIntent.FLAG_UPDATE_CURRENT)); notificationManager.notify(notificationId, builder.build());
Now that our notification is visible, the question is how can we update it? It’s not possible by only altering RemoteView; the proper way is to resend the new notification with changed RemoteView object.
In my GitHub projectGitHub project (it contains all examples from this post), this “update” process is presented by using an EditText which changes RemoteView’s TextView containing notification title.
textInput.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { sendNotification(s.toString()); } }); private void sendNotification(String title) { NotificationCompat.Builder builder = new NotificationCompat.Builder(this); RemoteViews content = new RemoteViews(getPackageName(), R.layout.custom_notification); if (title != null) { content.setTextViewText(R.id.title, title); } builder .setSmallIcon(android.R.drawable.ic_dialog_email) .setContent(content) .setPriority(NotificationCompat.PRIORITY_HIGH) .setDefaults(NotificationCompat.DEFAULT_ALL); Intent intentWithDefinedAction = new Intent(); builder.setContentIntent(PendingIntent.getActivity(this, 0, intentWithDefinedAction, PendingIntent.FLAG_UPDATE_CURRENT)); notificationManager.notify(notificationId, builder.build()); }
Result
4. Active notifications
Sometimes we want to iterate over the visible notifications in our code.
One way to achieve this would be to use the `notificationManager.getActiveNotifications()` method
StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications(); String notifications = ""; for (StatusBarNotification notification : activeNotifications) { notifications += String.valueOf(notification.getId()) + ". " + notification.getNotification().extras.getString(Notification.EXTRA_TEXT) + "\n"; Log.i("Active notifications", notifications); } Toast.makeText(this, notifications, Toast.LENGTH_LONG).show();
However, this method is only available in Android M and higher versions.
Another option would be to use NotificationListenerService available from Android 4.3 as shown below.
First, we need to declare our NotificationService in AndroidManifest.xml
<service android:name=".part2.NotificationService" android:label="@string/service_name" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> <intent-filter> <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service>
And then implement it
public class NotificationService extends NotificationListenerService { @Override public void onNotificationPosted(StatusBarNotification notification) { String notificationDescription = String.valueOf(notification.getId()) + ". " + notification.getNotification().extras.getString(Notification.EXTRA_TEXT); Log.i(getClass().toString(), notificationDescription + " onNotificationPosted"); } @Override public void onNotificationRemoved(StatusBarNotification notification) { String notificationDescription = String.valueOf(notification.getId()) + ". " + notification.getNotification().extras.getString(Notification.EXTRA_TEXT); Log.i(getClass().toString(), notificationDescription + " onNotificationRemoved"); } }
Looks easy? Yes, but hold your horses.
There is an issue regarding NotificationListenerService since 2013: “NotificationListenerService not posting notifications on android 4.4 after an app is updated”
Long story short, the NotificationListenerService may not always work ;). Welcome to the Android world!
The last possibility that I am aware of, and which can help you keep the list of your active notifications, is to use AccessibilityService
Accessibility services are a feature of the Android framework designed to provide alternative navigation feedback to the user on behalf of applications installed on Android devices
AndroidManifest.xml
<service android:name=".part2.NotificationAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> </service>
Implementation
public class NotificationAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { Log.i(getClass().toString(), "onAccessibilityEvent"); if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) { Parcelable data = event.getParcelableData(); if (data instanceof Notification) { Log.i(getClass().toString(), "receivedNotification"); Notification notification = (Notification) data; String notificationDescription = notification.getGroup() + " " + notification.getGroup(); Log.i(getClass().toString(), notificationDescription + " onNotificationPosted"); } } } @Override public void onInterrupt() { } @Override public void onServiceConnected() { // Set the type of events that this service wants to listen to. Others // won't be passed to this service. AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.eventTypes = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED; info.feedbackType = AccessibilityServiceInfo.DEFAULT; this.setServiceInfo(info); } }
However there is a downside of this approach. User needs to explicitly enable your Service in Settings > System > Accessibility screen.
You can open this screen from within your app with the intent shown below.
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivity(intent);
Thanks for reading and stay tuned for part 3 of this series!
All examples from this article are available on Github page NotificationsDemo