AlarmManager / Android Wear / Notification / Notification.Builder / Notification.WearableExtender

Match Timer – Part 4

Previously in this series on developing Match Timer (for timing football matches on Android Wear) we’ve looked at the timing engine and the BroadcastReceiver which is used to register alarms and handle events both from those alarms and from user actions. In this article we’ll turn our attention to notifications which are the primary interface from the handlers in the BroadcastReceiver to the user.

matchtimerOn Wear Notifications are the primary mechanism for updating the user and providing easy access to that information, and that is very true of how Match Timer is implemented. During normal operation we will display a Notification which will show the number of minutes elapsed, but when the user swipes in to the notification, we want to display detailed time information with per-second accuracy, and all of this can be achieved via the Notification framework.

Notifications on Wear are based on standard Android Notifications, but there are some extensions for Wear which we can make really good use of. Forst let’s have a look at the NotificationBuilder code:

public class NotificationBuilder {
    private static final Intent START_INTENT = new Intent(MatchTimerReceiver.ACTION_START);
    private static final Intent STOP_INTENT = new Intent(MatchTimerReceiver.ACTION_STOP);
    private static final Intent PAUSE_INTENT = new Intent(MatchTimerReceiver.ACTION_PAUSE);
    private static final Intent RESUME_INTENT = new Intent(MatchTimerReceiver.ACTION_RESUME);
    private static final Intent RESET_INTENT = new Intent(MatchTimerReceiver.ACTION_RESET);

    private static final int ID_ACTIVITY = 1;
    private static final int ID_START = 2;
    private static final int ID_STOP = 3;
    private static final int ID_PAUSE = 4;
    private static final int ID_RESUME = 5;
    private static final int ID_RESET = 6;

    private final Context context;
    private final MatchTimer matchTimer;

    public NotificationBuilder(Context context, MatchTimer matchTimer) {
        this.context = context;
        this.matchTimer = matchTimer;
    }

    public Notification buildNotification() {
        Intent activityIntent = new Intent(context, MatchTimerNotificationActivity.class);
        PendingIntent activityPendingIntent = PendingIntent.getActivity(context, ID_ACTIVITY, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        Notification.Builder builder = new Notification.Builder(context);
        Notification.WearableExtender extender = new Notification.WearableExtender();
        extender.setBackground(BitmapFactory.decodeResource(context.getResources(), R.drawable.bkg_football));
        extender.setDisplayIntent(activityPendingIntent);
        boolean ongoing = true;
        if (matchTimer.isPaused()) {
            buildPausedActions(extender);
        } else if (matchTimer.isRunning()) {
            buildRunningActions(extender);
        } else {
            buildStoppedActions(extender);
            ongoing = false;
        }
        builder.setContentTitle(buildNotificationTitle(matchTimer))
                .setSmallIcon(R.drawable.ic_football)
                .setStyle(new Notification.BigTextStyle())
                .setOngoing(ongoing);
        builder.setPriority(Notification.PRIORITY_MAX);
        builder.extend(extender);

        return builder.build();
    }

    private void buildStoppedActions(Notification.WearableExtender extender) {
        PendingIntent startPendingIntent = PendingIntent.getBroadcast(context, ID_START, START_INTENT, PendingIntent.FLAG_UPDATE_CURRENT);
        PendingIntent resetPendingIntent = PendingIntent.getBroadcast(context, ID_RESET, RESET_INTENT, PendingIntent.FLAG_UPDATE_CURRENT);
        extender.addAction(new Notification.Action.Builder(R.drawable.ic_play, "Start", startPendingIntent).build());
        extender.addAction(new Notification.Action.Builder(R.drawable.ic_reset, "Reset", resetPendingIntent).build());
    }

    private void buildRunningActions(Notification.WearableExtender extender) {
        PendingIntent pausePendingIntent = PendingIntent.getBroadcast(context, ID_PAUSE, PAUSE_INTENT, PendingIntent.FLAG_UPDATE_CURRENT);
        PendingIntent stopPendingIntent = PendingIntent.getBroadcast(context, ID_STOP, STOP_INTENT, PendingIntent.FLAG_UPDATE_CURRENT);
        extender.addAction(new Notification.Action.Builder(R.drawable.ic_pause, "Pause", pausePendingIntent).build());
        extender.addAction(new Notification.Action.Builder(R.drawable.ic_stop, "Stop", stopPendingIntent).build());
    }

    private void buildPausedActions(Notification.WearableExtender extender) {
        PendingIntent stopPendingIntent = PendingIntent.getBroadcast(context, ID_STOP, STOP_INTENT, PendingIntent.FLAG_UPDATE_CURRENT);
        PendingIntent resumePendingIntent = PendingIntent.getBroadcast(context, ID_RESUME, RESUME_INTENT, PendingIntent.FLAG_UPDATE_CURRENT);
        extender.addAction(new Notification.Action.Builder(R.drawable.ic_play, "Resume", resumePendingIntent).build());
        extender.addAction(new Notification.Action.Builder(R.drawable.ic_stop, "Stop", stopPendingIntent).build());
    }

    private String buildNotificationTitle(MatchTimer timer) {
        if (timer.isPaused()) {
            return context.getString(R.string.title_paused, timer.getElapsedMinutes());
        } else if (timer.isRunning()) {
            return context.getString(R.string.title_running, timer.getElapsedMinutes());
        }
        return context.getString(R.string.title_stopped);
    }

}

The first thing that we do is create a PendingIntent for our MatchTimerNotificationActivity which will contain the detailed, per-second accuracy Views. We then create a standard Notification.Builder and a Notification.WearableExtender (which is provided by the Wearable support library).

One thing that WearableExtender permits us to do is provide a custom background, so we add a graphic of a football field. We also set a Display Intent which is a really powerful feature of WearableExtender which allows us to display an Activity as part of our notification. When the device is asleep, of the watch-face Activity is active, then just a simple Notification title will be shown, but when the user swipes in to the notification, they will be shown our custom Activity sitting on top of the background graphic, with the Notification Icon showing:

matchtimer

Next we add some custom actions depending on the timer state. If it is paused we want to include Resume & Stop actions; if it is running we want to include Pause & Stop actions; and if it is stopped we want to include Start and Reset actions. These actions are added to WearableExtender and will each fire a broadcast which will be handled by MatchTimerReceiver that we looked at in the previous article in this series.

Next we set some standard Notification options for the Notification title (which will be our minute-accurate information), the icon, style, and whether the Notification is ongoing. I have chosen to make it an ongoing notification when the timer is running or paused to reduce the chance of the user accidentally dismissing it – it requires the timer to be stopped before the notification can be dismissed. That said, the timer is pretty robust because of its state being stored to SharedPreferences, and it even survives the device being restarted.

I have also choses to give the Notification MAX priority to try and ensure that it stays near the top of the Notification list to ensure that the user can get to it quickly while the timer is active.

In the next article we’ll take a look at MatchTimerNotificationActivity which displays our details timing information.

Match Timer is available on Google Play.


Get it on Google Play

© 2014, Mark Allison. All rights reserved.

Copyright © 2014 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.