com.onefishtwo.bbqtimer.Notifier.java Source code

Java tutorial

Introduction

Here is the source code for com.onefishtwo.bbqtimer.Notifier.java

Source

// The MIT License (MIT)
//
// Copyright (c) 2014 Jerry Morrison
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package com.onefishtwo.bbqtimer;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.TaskStackBuilder;

import com.onefishtwo.bbqtimer.notificationCompat.NotificationBuilder;
import com.onefishtwo.bbqtimer.notificationCompat.NotificationBuilderFactory;
import com.onefishtwo.bbqtimer.state.ApplicationState;

/**
 * Manages the app's Android Notifications.
 */
public class Notifier {
    private static final int NOTIFICATION_ID = 7;

    /**
     * On API 21+, allow Paused notifications with Reset/Stop/Start buttons and Running
     * notifications with Pause/Stop buttons, mainly so the user can control the timer from a lock
     * screen notification. Distinguish Stopped from Paused in the UI.
     * <p/>
     * Due to OS bugs in API 17 - 20, changing a notification to Paused would continue showing a
     * running chronometer [despite calling setShowWhen(false) and not calling
     * setUsesChronometer(true)] and sometimes also the notification time of day, both confusing.
     * API < 16 has no action buttons so it has no payoff in Paused notifications.
     */
    static final boolean PAUSEABLE_NOTIFICATIONS = android.os.Build.VERSION.SDK_INT >= 21;

    private static final long[] VIBRATE_PATTERN = { 150, 82, 180, 96 }; // ms off, ms on, ms off, ...
    private static final int[][] ACTION_INDICES = { {}, { 0 }, { 0, 1 }, { 0, 1, 2 } };

    private final Context context;
    private boolean playChime = false;
    private boolean vibrate = false;
    private int numActions; // the number of action buttons added to the notification

    public Notifier(Context context) {
        this.context = context;
    }

    /**
     * Builder-style setter: Whether {@link #openOrCancel} should play a notification chime for a
     * reminder. Default = false.
     */
    public Notifier setPlayChime(boolean playChime) {
        this.playChime = playChime;
        return this;
    }

    /**
     * Builder-style setter: Whether to vibrate and flash the notification light. Default = false.
     */
    public Notifier setVibrate(boolean vibrate) {
        this.vibrate = vibrate;
        return this;
    }

    private Uri getSoundUri(int soundId) {
        return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundId);
    }

    /** Constructs a PendingIntent to use as a Notification Action. */
    private PendingIntent makeActionIntent(String action) {
        return TimerAppWidgetProvider.makeActionIntent(context, action);
    }

    /**
     * Adds an action button to the given NotificationBuilder and to the
     * {@link #setMediaStyleActionsInCompactView} list.
     */
    private void addAction(NotificationBuilder builder, int iconId, int titleId, PendingIntent intent) {
        builder.addAction(iconId, context.getString(titleId), intent);
        ++numActions;
    }

    /**
     * Makes the first 3 added {@link #addAction} actions appear in MediaStyle's compact
     * notification view (which is the view that appears in lock screen notifications in API V21
     * Lollipop). No-op before API 21.
     */
    private void setMediaStyleActionsInCompactView(NotificationBuilder builder) {
        int num = Math.min(numActions, ACTION_INDICES.length - 1);

        if (num < 1) {
            return;
        }
        builder.setMediaStyleActionsInCompactView(ACTION_INDICES[num]);
    }

    /** Returns a localized description of the timer's run state, e.g. "Paused 00:12.3". */
    String timerRunState(TimeCounter timer) {
        if (timer.isRunning()) {
            return context.getString(R.string.timer_running);
        } else if (timer.isPaused()) {
            return context.getString(R.string.timer_paused, timer.formatHhMmSsFraction());
        } else {
            return context.getString(R.string.timer_stopped);
        }
    }

    /**
     * <em>Opens</em> this app's notification with visible, audible, and/or tactile content
     * depending on {@code state}, {@link #setPlayChime(boolean)}, and {@link #setVibrate(boolean)},
     * <em>or cancels</em> the app's notification if there's nothing to show or sound.
     *
     * @param state -- the ApplicationState state to display.
     */
    public void openOrCancel(ApplicationState state) {
        boolean isMainActivityVisible = state.isMainActivityVisible();
        TimeCounter timer = state.getTimeCounter();
        boolean showable = PAUSEABLE_NOTIFICATIONS ? !timer.isStopped() : timer.isRunning();
        boolean show = showable && !isMainActivityVisible;

        if (!(show || playChime || vibrate)) {
            cancelAll();
            return;
        }

        Notification notification = buildNotification(state, show);
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
        notificationManager.notify(NOTIFICATION_ID, notification);
    }

    /** Builds a notification. */
    protected Notification buildNotification(ApplicationState state, boolean show) {
        NotificationBuilder builder = NotificationBuilderFactory.builder(context)
                .setPriority(NotificationCompat.PRIORITY_HIGH).setCategory(NotificationCompat.CATEGORY_ALARM)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);

        if (show) {
            TimeCounter timer = state.getTimeCounter();
            boolean isRunning = timer.isRunning();
            Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(),
                    R.drawable.ic_large_notification);

            builder.setSmallIcon(R.drawable.notification_icon).setLargeIcon(largeIcon)
                    .setContentTitle(context.getString(R.string.app_name));

            if (isRunning) {
                builder.setWhen(System.currentTimeMillis() - timer.getElapsedTime()).setUsesChronometer(true); // added in API 17
            } else {
                // Hide the "when" field, which isn't useful while Paused, so it doesn't take space
                // in the compact view along with 3 action buttons (Reset, Start, Stop).
                // This doesn't actually work in API 17-18, so in API 18- it'd show the time of day
                // when the notification is built, but it's even more broken after changing the
                // "When" info in an open notification.
                builder.setShowWhen(false);
            }

            // Set the notification body text to explain the run state.
            if (isRunning && state.isEnableReminders()) {
                int reminderSecs = state.getSecondsPerReminder();
                int minutes = reminderSecs / 60;
                String contentText = context.getResources().getQuantityString(R.plurals.notification_body, minutes,
                        minutes);
                int numReminders = AlarmReceiver.numRemindersSoFar(state);

                builder.setContentText(contentText);

                if (numReminders > 0) {
                    builder.setNumber(numReminders);
                }
            } else {
                builder.setContentText(timerRunState(timer));
                if (timer.isPaused()) {
                    builder.setSubText(context.getString(R.string.dismiss_tip));
                } else if (isRunning) {
                    builder.setSubText(context.getString(R.string.no_reminders_tip));
                }
            }

            // Make an Intent to launch the Activity from the notification.
            Intent activityIntent = new Intent(context, MainActivity.class);

            // So navigating back from the Activity goes from the app to the Home screen.
            TaskStackBuilder stackBuilder = TaskStackBuilder.create(context).addParentStack(MainActivity.class)
                    .addNextIntent(activityIntent);
            PendingIntent activityPendingIntent = stackBuilder.getPendingIntent(0,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            builder.setContentIntent(activityPendingIntent);

            numActions = 0;

            // Action button to reset the timer.
            PendingIntent resetIntent = makeActionIntent(TimerAppWidgetProvider.ACTION_RESET);
            if (timer.isPaused() && !timer.isReset()) {
                addAction(builder, R.drawable.ic_action_replay, R.string.reset, resetIntent);
            }

            // Action button to run (start) the timer.
            PendingIntent runIntent = makeActionIntent(TimerAppWidgetProvider.ACTION_RUN);
            if (!isRunning) {
                addAction(builder, R.drawable.ic_action_play, R.string.start, runIntent);
            }

            // Action button to pause the timer.
            PendingIntent pauseIntent = makeActionIntent(TimerAppWidgetProvider.ACTION_PAUSE);
            if (!timer.isPaused()) {
                addAction(builder, R.drawable.ic_action_pause, R.string.pause, pauseIntent);
            }

            // Action button to stop the timer.
            PendingIntent stopIntent = makeActionIntent(TimerAppWidgetProvider.ACTION_STOP);
            if (PAUSEABLE_NOTIFICATIONS && !timer.isStopped()) {
                addAction(builder, R.drawable.ic_action_stop, R.string.stop, stopIntent);
            }

            // Allow stopping via dismissing the notification (unless it's "ongoing").
            builder.setDeleteIntent(stopIntent);

            setMediaStyleActionsInCompactView(builder);

            if (isRunning) {
                builder.setOngoing(true);
            }
        }

        if (playChime || vibrate) {
            int defaults = Notification.DEFAULT_LIGHTS;

            if (playChime) {
                builder.setSound(getSoundUri(R.raw.cowbell4));
            }

            if (vibrate) {
                builder.setVibrate(VIBRATE_PATTERN);
            }
            builder.setDefaults(defaults);
        }

        return builder.build();
    }

    /** Cancels all of this app's notifications. */
    public void cancelAll() {
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);

        notificationManager.cancelAll();
    }
}