se.toxbee.sleepfighter.service.AlarmPlannerService.java Source code

Java tutorial

Introduction

Here is the source code for se.toxbee.sleepfighter.service.AlarmPlannerService.java

Source

/*******************************************************************************
 * Copyright (c) 2013 See AUTHORS file.
 * 
 * This file is part of SleepFighter.
 * 
 * SleepFighter 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.
 * 
 * SleepFighter 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 SleepFighter. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package se.toxbee.sleepfighter.service;

import net.engio.mbassy.listener.Handler;

import org.joda.time.DateTime;
import org.joda.time.MutableDateTime;

import se.toxbee.sleepfighter.R;
import se.toxbee.sleepfighter.SFApplication;
import se.toxbee.sleepfighter.activity.AlarmActivity;
import se.toxbee.sleepfighter.activity.MainActivity;
import se.toxbee.sleepfighter.helper.AlarmIntentHelper;
import se.toxbee.sleepfighter.helper.NotificationHelper;
import se.toxbee.sleepfighter.model.Alarm;
import se.toxbee.sleepfighter.model.Alarm.ScheduleChangeEvent;
import se.toxbee.sleepfighter.model.AlarmList;
import se.toxbee.sleepfighter.model.AlarmTimestamp;
import se.toxbee.sleepfighter.receiver.AlarmReceiver;
import se.toxbee.sleepfighter.receiver.GPSFilterRefreshReceiver;
import se.toxbee.sleepfighter.receiver.LocationReceiver;
import se.toxbee.sleepfighter.text.MetaTextUtils;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

/**
 * <p>AlarmPlannerService is responsible for registering and canceling alarms to Android OS.<br/>
 * It also handles drawer notifications.</p>
 *
 * Inspired by: http://www.appsrox.com/android/tutorials/remindme/2/#7
 *
 * @author Centril<twingoow@gmail.com> / Mazdak Farrokhzad.
 * @version 1.0
 * @since Sep 25, 2013
 */
public class AlarmPlannerService extends IntentService {
    private static final String TAG = AlarmPlannerService.class.getSimpleName();

    /**
     * Handles changes in alarms and alarm list and regarding and plans.
     *
     * @author Centril<twingoow@gmail.com> / Mazdak Farrokhzad.
     * @version 1.0
     * @since Sep 26, 2013
     */
    public static final class ChangeHandler {
        private Context context;
        private AlarmList list;

        /**
         * Constructs the ChangeHandler.
         *
         * @param context android context.
         * @param list the AlarmList object.
         */
        public ChangeHandler(Context context, AlarmList list) {
            this.context = context;
            this.list = list;
        }

        private void handleChange() {
            AlarmTimestamp at = this.list.getEarliestAlarm(new DateTime().getMillis());
            if (at == AlarmTimestamp.INVALID) {
                call(this.context, Command.CANCEL, Alarm.NOT_COMMITTED_ID);
            } else {
                call(this.context, Command.CREATE, at.getAlarm().getId());
            }
        }

        /**
         * Handles a change in time related data in any alarm.
         *
         * @param evt the event.
         */
        @Handler
        public void handleListChange(AlarmList.Event evt) {
            this.handleChange();
        }

        /**
         * Handles a change in time related data in any alarm.
         *
         * @param evt the event.
         */
        @Handler
        public void handleDateChange(ScheduleChangeEvent evt) {
            this.handleChange();
        }
    }

    /**
     * Registered commands for AlarmPlannerService
     *
     * @author Centril<twingoow@gmail.com> / Mazdak Farrokhzad.
     * @version 1.0
     * @since Sep 26, 2013
     */
    public enum Command {
        /**
         * Used with an {@link Alarm} ID to schedule the alarm at its next
         * occurrence. This will override any previous scheduled Alarm.<br/>
         * 
         * At the alarm's time, {@code onRecieve} in {@link AlarmReceiver} will
         * be called.
         */
        CREATE,
        /**
         * Used to cancel the scheduled {@link Alarm}.
         */
        CANCEL,
        /**
         * Used with an {@link Alarm} ID to tell the service to reschedule an alarm that
         * has gone of, in the amount of minutes from now specified in {@link Alarm}.
         */
        SNOOZE
    }

    /**
     * Makes a call to the service for given context, command and alarm id.
     *
     * @param context android context.
     * @param command the command to handle.
     * @param alarmId the alarm id, not used if command == Command.CANCEL.
     */
    public static void call(Context context, Command command, int alarmId) {
        Intent service = new Intent(context, AlarmPlannerService.class);
        service.setAction(command.name());
        new AlarmIntentHelper(service).setAlarmId(alarmId);
        context.startService(service);
    }

    private IntentFilter matcher;

    public AlarmPlannerService() {
        super(TAG);

        this.matcher = new IntentFilter();
        for (Command command : Command.values()) {
            this.matcher.addAction(command.name());
        }
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getAction();

        if (!matcher.matchAction(action)) {
            return;
        }

        // Handle commands.
        switch (Command.valueOf(action)) {
        case CREATE:
            this.create(new AlarmIntentHelper(intent).getAlarmId());
            break;
        case SNOOZE:
            this.snooze(new AlarmIntentHelper(intent).getAlarmId());
            break;
        case CANCEL:
            this.cancel();
            break;
        }
    }

    /**
     * Schedules an alarm at the next time it should go off.
     * 
     * @param alarmId
     *            the alarm's ID
     */
    private void create(int alarmId) {
        SFApplication app = SFApplication.get();

        // Fetch alarm.
        Alarm alarm = app.getPersister().fetchAlarmById(alarmId);
        if (alarm == null) {
            throw new IllegalArgumentException("No alarm was found with given id");
        }

        // Get alarm RTC time, could be null cause of threading, so check!
        Long scheduleTime = alarm.getNextMillis(new DateTime().getMillis());
        if (scheduleTime == null) {
            return;
        }

        GPSFilterRefreshReceiver.scheduleFix(app, scheduleTime);

        if (alarm.isSpeech()) {
            LocationReceiver.scheduleFix(app, scheduleTime);
        }
        schedule(scheduleTime, alarm);

        showPendingNotification(alarm);
    }

    /**
     * Schedule an alarm to go off at a certain time.
     * 
     * @param scheduleTime
     *            the time, in milliseconds (UTC), when the Alarm should go off
     * @param alarm
     *            the alarm
     */
    private void schedule(long scheduleTime, Alarm alarm) {
        PendingIntent pi = this.makePendingIntent(alarm.getId());

        this.getAlarmManager().set(AlarmManager.RTC_WAKEUP, scheduleTime, pi);

        Log.d(getClass().getSimpleName(), "Scheduled alarm [" + alarm.toString() + "] at " + scheduleTime);
    }

    /**
     * Shows a notification for a pending alarm.
     * 
     * The user can click on it to get to MainActivity, where it can be turned
     * off easily.
     * 
     * @param alarm
     *            the alarm
     */
    private void showPendingNotification(Alarm alarm) {
        Intent mainActIntent = new Intent(getApplicationContext(), MainActivity.class);
        PendingIntent mainActPI = PendingIntent.getActivity(this, 0, mainActIntent, 0);

        String name = MetaTextUtils.printAlarmName(this, alarm);
        String time = alarm.getTimeString();

        // Localized strings which we inserts current time and name into
        String titleFormat = getString(R.string.notification_pending_title);
        String messageFormat = getString(R.string.notification_pending_message);

        String title = String.format(titleFormat, name);
        String message = String.format(messageFormat, time);

        NotificationHelper.getInstance().showNotification(this, title, message, mainActPI);
    }

    /**
     * Shows a notification for an alarm when snoozing has been done.
     * 
     * The user can click on it to get to {@link AlarmActivity}, where it can be
     * disabled.
     * 
     * @param alarm
     *            the alarm
     * @param time
     *            the new time the alarm will go off, as a string
     */
    private void showSnoozingNotification(Alarm alarm, String time) {
        Intent alarmIntent = new Intent(getApplicationContext(), AlarmActivity.class);
        new AlarmIntentHelper(alarmIntent).setAlarmId(alarm);
        PendingIntent alarmPI = PendingIntent.getActivity(this, 0, alarmIntent, 0);

        String name = MetaTextUtils.printAlarmName(this, alarm);

        // Localized strings which we inserts current time and name into
        String titleFormat = getString(R.string.notification_snooze_title);
        String messageFormat = getString(R.string.notification_snooze_message);

        String title = String.format(titleFormat, name);
        String message = String.format(messageFormat, time);

        NotificationHelper.getInstance().showNotification(getApplicationContext(), title, message, alarmPI);
    }

    /**
     * Cancels any scheduled alarm.
     */
    private void cancel() {
        this.getAlarmManager().cancel(this.makePendingIntent(Alarm.NOT_COMMITTED_ID));
        Log.d("AlarmPlannerService", "Cancelling!");

        // Remove app's sticky notification
        NotificationHelper.getInstance().removeNotification(getApplicationContext());

        // Unschedule any location fixes.
        GPSFilterRefreshReceiver.unscheduleFix(SFApplication.get());
        LocationReceiver.unscheduleFix(SFApplication.get());
    }

    /**
     * Reschedule an {@link Alarm}, that has gone of, to some minutes from now,
     * defined by the alarm itself.
     * 
     * @param alarmId
     *            the alarm's id
     */
    private void snooze(int alarmId) {
        Alarm alarm = SFApplication.get().getPersister().fetchAlarmById(alarmId);
        if (alarm == null) {
            throw new IllegalArgumentException("No alarm was found with given id");
        }

        // Determine which time to schedule at by adding offset minutes from alarm to current time
        MutableDateTime dateTime = MutableDateTime.now();

        int mins = alarm.getSnoozeConfig().getSnoozeTime();

        dateTime.addMinutes(mins);

        long scheduleTime = dateTime.getMillis();
        schedule(scheduleTime, alarm);
        showSnoozingNotification(alarm, dateTime.toString("HH:mm"));
    }

    private AlarmManager getAlarmManager() {
        return (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
    }

    private PendingIntent makePendingIntent(int alarmId) {
        Intent intent = new Intent(this, AlarmReceiver.class);
        new AlarmIntentHelper(intent).setAlarmId(alarmId);
        return PendingIntent.getBroadcast(this, -1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }
}