Written by Adrian Kremski
Android Developer
Published November 3, 2016

Notifications: From zero to hero Android N* – part 2/3

A notifications guide for all developers who already got the basics. You will learn how to become a pro of the Android notifications. In the 2nd article from the series, we are going to cover Android 5 and 6.

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.

 

Screenshot_20160718-205804

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.
Screenshot_20160718-205857

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

Screenshot_20160720-144918Screenshot_20160720-144914

 

    • Don’t show notifications at all – pretty obvious as you can see on the screen below

Screenshot_20160720-132143

  • Show all notification content – also easy to understand (whole content is available)
    Screenshot_20160720-132101
  • 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);
    

    Screenshot_20160720-132122

2.1 Hide sensitive notification content – visibility options

  • VISIBILITY_PUBLIC – the whole content is available for the user when notification is displayed on lock screen
  • VISIBILITY_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
ezgif.com-resize

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

ezgif.com-resize

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"&gt
        <intent-filter&gt
            <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.

Screen Shot 2016-11-02 at 14.25.30

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

 

Written by Adrian Kremski
Android Developer
Published November 3, 2016