se.erichansander.retrotimer.RetroTimer.java Source code

Java tutorial

Introduction

Here is the source code for se.erichansander.retrotimer.RetroTimer.java

Source

/*
 * Copyright (C) 2010-2014  Eric Hansander
 *
 *  This file is part of Retro Timer.
 *
 *  Retro Timer is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Retro Timer is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Retro Timer.  If not, see <http://www.gnu.org/licenses/>.
 */

package se.erichansander.retrotimer;

import android.app.AlarmManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.text.format.DateFormat;

/**
 * Main application class. Handles state shared between activities, and holds
 * shared constants etc.
 */
public class RetroTimer extends Application {
    /**
     * When broadcasted, will cause the alarm to sound/vibrate and issue a
     * notification, that will dismiss the alarm when clicked.
     */
    public static final String ALARM_TRIGGER_ACTION = "se.erichansander.retrotimer.ALARM_TRIGGER";
    /**
     * When sent to the TimerKlaxon service, will sound alarm and/or vibrate
     * device
     */
    public static final String ALARM_PLAY_ACTION = "se.erichansander.retrotimer.ALARM_PLAY";
    /**
     * When broadcasted, will silence alarm and put up a notification stating
     * that the timer has been silenced.
     */
    public static final String ALARM_SILENCE_ACTION = "se.erichansander.retrotimer.ALARM_SILENCE";
    /**
     * When broadcasted, will silence alarm and remove any notifications.
     */
    public static final String ALARM_DISMISS_ACTION = "se.erichansander.retrotimer.ALARM_DISMISS";

    /** For passing the alarm time through an intent */
    public static final String ALARM_TIME_EXTRA = "intent.extra.alarmtime";

    /** Is true when an alarm is set */
    public static final String PREF_ALARM_SET = "prefs.alarm_set";
    /** Max num of millis to play alarm before silencing it automatically */
    public static final String PREF_ALARM_TIMEOUT_MILLIS = "prefs.alarm_timeout_millis";
    /** Absolute time when alarm should go off, in millis since epoch */
    public static final String PREF_ALARM_TIME = "prefs.alarm_time";
    /** Is true if alert should play audio */
    public static final String PREF_RING_ON_ALARM = "prefs.ring_on_alarm";
    /** Is true if alert should vibrate device */
    public static final String PREF_VIBRATE_ON_ALARM = "prefs.vibrate_on_alarm";
    /**
     * Is true if the licensing dialog has been shown (when the app was started
     * for the first time).
     */
    public static final String PREF_HAVE_SHOWN_LICENSE = "prefs.have_shown_license";

    /** Notification ID for messages about pending alarms */
    public static final int NOTIF_SET_ID = 1;
    /**
     * Notification ID for messages about triggering or triggered alarms
     */
    public static final int NOTIF_TRIGGERED_ID = 2;
    /**
     * Notification ID for messages about automatically silenced alarms
     */
    public static final int NOTIF_SILENCED_ID = 3;

    /**
     * Initializes the app state, and initializes the AlarmManager if any alarms
     * are pending from before the device was rebooted.
     * 
     * Should be called at device boot and at application start (in case the app
     * has been killed).
     */
    public static void initAlarm(Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

        if (prefs.getBoolean(PREF_ALARM_SET, false) || prefs.getLong(PREF_ALARM_TIME, 0) > 0) {
            if (getMillisLeftToAlarm(context) > 1000) {
                /*
                 * If there is time left until the alarm should trigger, we
                 * register it again with the AlarmManager
                 */
                setAlarmAt(context, prefs.getLong(PREF_ALARM_TIME, 0));
            } else {
                /* Otherwise, we do some clean-up */
                clearAlarm(context);
            }
        }
    }

    /**
     * Convenience method for setting an alarm to trigger in millisLeft millis.
     */
    public static void setAlarmDelayed(Context context, long millisLeft) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor ed = prefs.edit();
        /*
         * Set the alarm timeout to 5 + (mins to alarm)/2 seconds, i.e. timeout
         * will be in the range 5..34.5 seconds if the time max countdown is 59
         * minutes
         */
        ed.putLong(RetroTimer.PREF_ALARM_TIMEOUT_MILLIS, 15000 + millisLeft / 60);
        ed.commit();

        long now = System.currentTimeMillis();
        setAlarmAt(context, now + millisLeft);

        TinyTracelog.init(context);
        TinyTracelog.clear();
        TinyTracelog.trace("1 " + now + "," + (millisLeft / 60000));
    }

    /**
     * Sets an alarm at absolute time alarmTime (in millis from epoch)
     */
    public static void setAlarmAt(Context context, long alarmTime) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor ed = prefs.edit();
        ed.putLong(RetroTimer.PREF_ALARM_TIME, alarmTime);
        ed.putBoolean(RetroTimer.PREF_ALARM_SET, true);
        ed.commit();

        Intent intent = new Intent(RetroTimer.ALARM_TRIGGER_ACTION);
        intent.putExtra(RetroTimer.ALARM_TIME_EXTRA, alarmTime);
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

        /*
         * Activate alarm in AlarmManager. Since the API for activating the
         * alarm changed in KitKat, we need to treat the two versions
         * differently, and since "Dalvik in Android 1.x was very conservative
         * and would crash if you try to load a class that contains a reference
         * that it cannot resolve" we have to put it in a separate class...
         * 
         * See
         * http://stackoverflow.com/questions/13444255/could-not-find-method-
         * from-the-newer-api-with-using-targetapi-annotation
         */
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            am.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
        } else {
            AlarmManagerKitKat.set(am, alarmTime, sender);
        }

        // Trigger a notification that, when clicked, will open TimerSet
        Intent viewAlarm = new Intent(context, TimerSet.class);
        viewAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pendingNotify = PendingIntent.getActivity(context, 0, viewAlarm, 0);

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
                .setContentIntent(pendingNotify).setDefaults(Notification.DEFAULT_LIGHTS).setOngoing(true)
                .setSmallIcon(R.drawable.ic_stat_alarm_set)
                .setContentTitle(context.getString(R.string.notify_set_label)).setContentText(context
                        .getString(R.string.notify_set_text, DateFormat.getTimeFormat(context).format(alarmTime)));

        /*
         * Send the notification using the alarm id to easily identify the
         * correct notification.
         */
        NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        nm.notify(RetroTimer.NOTIF_SET_ID, mBuilder.build());
    }

    /** Cancels the alarm in the AlarmManager and updates app state */
    public static void cancelAlarm(Context context) {
        // Cancel the alarm in AlarmManager
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, new Intent(RetroTimer.ALARM_TRIGGER_ACTION),
                PendingIntent.FLAG_CANCEL_CURRENT);
        am.cancel(sender);

        // Cancel the notification
        NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        nm.cancel(RetroTimer.NOTIF_SET_ID);

        clearAlarm(context);
    }

    /**
     * Clears the shared information about the alarm.
     * 
     * Can be called either after the alarm has been cancelled, or after it has
     * triggered.
     */
    public static void clearAlarm(Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor ed = prefs.edit();

        // Update the shared state
        ed.putLong(RetroTimer.PREF_ALARM_TIME, 0);
        ed.putBoolean(RetroTimer.PREF_ALARM_SET, false);
        ed.commit();
    }

    /** Returns millis left to alarm, or zero if no alarm is set */
    public static long getMillisLeftToAlarm(Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        if (prefs.getBoolean(RetroTimer.PREF_ALARM_SET, false)) {
            return prefs.getLong(RetroTimer.PREF_ALARM_TIME, 0) - System.currentTimeMillis();
        } else {
            return 0;
        }
    }

    /**
     * Returns the absolute time when alarm will trigger, in millis since epoch
     */
    public static long getAlarmTime(Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        if (prefs.getBoolean(RetroTimer.PREF_ALARM_SET, false)) {
            return prefs.getLong(RetroTimer.PREF_ALARM_TIME, 0);
        } else {
            return 0;
        }
    }

    /**
     * Try to return the app to its initial state
     */
    public static void handleFatalError(Context context) {
        Intent intent = new Intent(RetroTimer.ALARM_DISMISS_ACTION);
        context.sendBroadcast(intent);

        cancelAlarm(context);
    }
}