Java tutorial
/******************************************************************************* * Mirakel is an Android App for managing your ToDo-Lists * * Copyright (c) 2013-2014 Anatolij Zelenin, Georg Semmler. * * This program 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 * any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package de.azapps.mirakel.reminders; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import android.util.Pair; import com.google.common.base.Optional; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import de.azapps.mirakel.DefinitionsHelper; import de.azapps.mirakel.DefinitionsHelper.NoSuchTaskException; import de.azapps.mirakel.helper.DateTimeHelper; import de.azapps.mirakel.helper.Helpers; import de.azapps.mirakel.helper.MirakelCommonPreferences; import de.azapps.mirakel.helper.error.ErrorReporter; import de.azapps.mirakel.helper.error.ErrorType; import de.azapps.mirakel.model.MirakelContentObserver; import de.azapps.mirakel.model.R; import de.azapps.mirakel.model.recurring.Recurring; import de.azapps.mirakel.model.task.Task; import de.azapps.mirakel.services.NotificationService; import de.azapps.mirakel.services.TaskService; import de.azapps.tools.Log; import de.azapps.tools.OptionalUtils; import static com.google.common.base.Optional.absent; import static com.google.common.base.Optional.of; public class ReminderAlarm extends BroadcastReceiver { private static final String TAG = "ReminderAlarm"; public static final String UPDATE_NOTIFICATION = "de.azapps.mirakel.reminders.ReminderAlarm.UPDATE_NOTIFICATION"; public static final String SHOW_TASK = "de.azapps.mirakel.reminders.ReminderAlarm.SHOW_TASK"; public static final String EXTRA_ID = "de.azapps.mirakel.reminders.ReminderAlarm.EXTRA_ID"; @NonNull private static final Set<Long> allReminders = new HashSet<>(); @NonNull private static Optional<MirakelContentObserver> observer = absent(); @Override public void onReceive(final Context context, final Intent intent) { if (UPDATE_NOTIFICATION.equals(intent.getAction())) { NotificationService.updateServices(context); } if (!SHOW_TASK.equals(intent.getAction())) { return; } final long taskId = intent.getLongExtra(EXTRA_ID, 0); if (taskId == 0) { return; } final Optional<Task> task = Task.get(taskId); if (!task.isPresent()) { final PendingIntent pd = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); alarmManager.cancel(pd); ErrorReporter.report(ErrorType.TASK_VANISHED); } else { createNotification(context, task.get()); } } private static void createNotification(final Context context, final Task task) { Log.w(TAG, task.getName()); final NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); final Optional<Class<?>> main = Helpers.getMainActivity(); if (!main.isPresent()) { return; } final Intent openIntent = new Intent(context, main.get()); final Bundle withTask = new Bundle(); withTask.putParcelable(DefinitionsHelper.EXTRA_TASK, task); openIntent.setAction(DefinitionsHelper.SHOW_TASK_REMINDER); openIntent.putExtra(DefinitionsHelper.EXTRA_TASK_REMINDER, task); openIntent.putExtra(String.valueOf(task.getId()), task.getId()); openIntent.setData(Uri.parse(openIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent pOpenIntent = PendingIntent.getActivity(context, 0, openIntent, 0); final Intent doneIntent = new Intent(context, TaskService.class); doneIntent.setAction(TaskService.TASK_DONE); doneIntent.putExtra(DefinitionsHelper.BUNDLE_WRAPPER, withTask); doneIntent.putExtra(String.valueOf(task.getId()), task.getId()); doneIntent.setData(Uri.parse(doneIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent pDoneIntent = PendingIntent.getService(context, 0, doneIntent, 0); final Intent laterIntent = new Intent(context, TaskService.class); laterIntent.setAction(TaskService.TASK_LATER); laterIntent.putExtra(DefinitionsHelper.BUNDLE_WRAPPER, withTask); laterIntent.putExtra(String.valueOf(task.getId()), task.getId()); laterIntent.setData(Uri.parse(laterIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent pLaterIntent = PendingIntent.getService(context, 0, laterIntent, 0); final boolean persistent = MirakelCommonPreferences.usePersistentReminders(); // Build Notification final NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder.setContentTitle(context.getString(R.string.reminder_notification_title, task.getName())) .setContentText(task.getContent()).setSmallIcon(R.drawable.ic_mirakel) .setLargeIcon(Helpers.getBitmap(R.drawable.mirakel, context)).setContentIntent(pOpenIntent) .setPriority(NotificationCompat.PRIORITY_HIGH).setLights(Color.BLUE, 1500, 300) .setOngoing(persistent).setDefaults(Notification.DEFAULT_VIBRATE) .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .addAction(R.drawable.ic_checkmark_holo_light, context.getString(R.string.reminder_notification_done), pDoneIntent) .addAction(android.R.drawable.ic_menu_close_clear_cancel, context.getString(R.string.reminder_notification_later), pLaterIntent); final NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); final String priority = ((task.getPriority() > 0) ? ("+" + task.getPriority()) : String.valueOf(task.getPriority())); final CharSequence due; if (!task.getDue().isPresent()) { due = context.getString(R.string.no_date); } else { due = DateTimeHelper.formatDate(context, task.getDue()); } inboxStyle.addLine(context.getString(R.string.reminder_notification_list, task.getList().getName())); inboxStyle.addLine(context.getString(R.string.reminder_notification_priority, priority)); inboxStyle.addLine(context.getString(R.string.reminder_notification_due, due)); builder.setStyle(inboxStyle); // Build notification if (!allReminders.contains(task.getId())) { allReminders.add(task.getId()); nm.notify(DefinitionsHelper.NOTIF_REMINDER + (int) task.getId(), builder.build()); } } private static AlarmManager alarmManager; private static List<Pair<Task, PendingIntent>> activeAlarms = new CopyOnWriteArrayList<>(); public static void init(final Context ctx) { observer = of(new MirakelContentObserver(new Handler(ctx.getMainLooper()), ctx, Task.URI, new MirakelContentObserver.ObserverCallBack() { @Override public void handleChange() { updateAlarms(ctx); } @Override public void handleChange(final long id) { final Optional<Task> t = Task.get(id); final Calendar c = new GregorianCalendar(); if (t.isPresent()) { final Task task = t.get(); if (task.getReminder().isPresent() && c.after(task.getReminder().get())) { updateAlarm(ctx, t.get()); } else { cancelAlarm(ctx, t.get()); } } } })); updateAlarms(ctx); } public static void destroy(final Context ctx) { if (observer.isPresent()) { observer.get().unregister(ctx); observer = absent(); } } private static void updateAlarms(final Context ctx) { new Thread(new Runnable() { @Override public void run() { Log.e(TAG, "update"); alarmManager = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); // Update the Notifications at midnight final Intent intent = new Intent(ctx, ReminderAlarm.class); intent.setAction(UPDATE_NOTIFICATION); final PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); final Calendar triggerCal = new GregorianCalendar(); triggerCal.set(Calendar.HOUR_OF_DAY, 0); triggerCal.set(Calendar.MINUTE, 0); triggerCal.add(Calendar.DAY_OF_MONTH, 1); alarmManager.setRepeating(AlarmManager.RTC, triggerCal.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); // Alarms final List<Task> tasks = Task.getTasksWithReminders(); final Calendar now = new GregorianCalendar(); for (final Pair<Task, PendingIntent> p : activeAlarms) { final Task t = p.first; final Task newTask = Task.get(t.getId()).orNull(); if ((newTask == null) || !newTask.getReminder().isPresent() || newTask.isDone() || newTask.getReminder().get().after(new GregorianCalendar())) { cancelAlarm(ctx, t, newTask, p, p.second); } else if (newTask.getReminder().isPresent()) { if (newTask.getReminder().get().after(now) && !newTask.getRecurringReminder().isPresent()) { closeNotificationFor(ctx, t.getId()); updateAlarm(ctx, newTask); } else if (newTask.getReminder().get().after(now) && newTask.getRecurringReminder().isPresent() && (newTask.getReminder().get() .compareTo(newTask.getRecurringReminder().get() .addRecurring(newTask.getReminder()).orNull()) > 0) && !now.after(newTask.getReminder())) { updateAlarm(ctx, newTask); } else if ((t.getRecurringReminderId() != newTask.getRecurringReminderId()) || t.getRecurringReminder().isPresent()) { if (t.getRecurringReminder().isPresent() && newTask.getRecurringReminder().isPresent()) { if (!t.getRecurringReminder().get().equals(newTask.getRecurringReminder().get())) { updateAlarm(ctx, newTask); cancelAlarm(ctx, t, newTask, p, p.second); } } else if (t.getRecurringReminder().isPresent() != newTask.getRecurringReminder() .isPresent()) { updateAlarm(ctx, newTask); cancelAlarm(ctx, t, newTask, p, p.second); } } else { updateAlarm(ctx, newTask); } } } for (final Task t : tasks) { try { if (!isAlarm(t)) { Log.d(TAG, "add: " + t.getName()); Log.i(TAG, "id " + t.getId()); final PendingIntent p = updateAlarm(ctx, t); activeAlarms.add(new Pair<>(t, p)); } } catch (final NoSuchTaskException e) { Log.wtf(TAG, "Task not found", e); } } } }).start(); } private static boolean isAlarm(final Task t2) throws NoSuchTaskException { for (final Pair<Task, PendingIntent> pair : activeAlarms) { final Task t = pair.first; if ((t == null) || (t2 == null)) { throw new NoSuchTaskException(); } if (t.getId() == t2.getId()) { return true; } } return false; } private static void cancelAlarm(final Context ctx, final Task t, final Task newTask, final Pair<Task, PendingIntent> p, final PendingIntent pendingIntent) { activeAlarms.remove(p); closeNotificationFor(ctx, t.getId()); if (newTask == null) { return; } alarmManager.cancel(pendingIntent); } public static void cancelAlarm(final Context ctx, final Task task) { try { final Pair<Task, PendingIntent> p = findTask(task.getId()); final Optional<Task> taskOptional = Task.get(task.getId()); OptionalUtils.withOptional(taskOptional, new OptionalUtils.Procedure<Task>() { @Override public void apply(Task input) { cancelAlarm(ctx, task, input, p, p.second); } }); } catch (final IndexOutOfBoundsException e) { Log.d(TAG, "task not found", e); } } private static Pair<Task, PendingIntent> findTask(final long id) { for (final Pair<Task, PendingIntent> p : activeAlarms) { if (id == p.first.getId()) { return p; } } throw new IndexOutOfBoundsException(); } private static PendingIntent updateAlarm(final Context ctx, final Task task) { final Intent intent = new Intent(ctx, ReminderAlarm.class); intent.setAction(SHOW_TASK); intent.putExtra(EXTRA_ID, task.getId()); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); if ((pendingIntent == null) || !task.getReminder().isPresent()) { return null; } Log.v(TAG, "Set alarm for " + task.getName() + " on " + task.getReminder().get().getTimeInMillis()); final Optional<Recurring> recurringReminder = task.getRecurringReminder(); if (!recurringReminder.isPresent()) { alarmManager.set(AlarmManager.RTC_WAKEUP, task.getReminder().get().getTimeInMillis(), pendingIntent); } else { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, task.getReminder().get().getTimeInMillis(), recurringReminder.get().getInterval(), pendingIntent); } return pendingIntent; } public static void closeNotificationFor(final Context context, final Long taskId) { final NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); nm.cancel(DefinitionsHelper.NOTIF_REMINDER + taskId.intValue()); allReminders.remove(taskId); } public static void restart(final Context context) { final NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); // This hack is a must because otherwise we get a // concurrentModificationException final Long[] reminders = allReminders.toArray(new Long[allReminders.size()]); for (final Long id : reminders) { nm.cancel(DefinitionsHelper.NOTIF_REMINDER + id.intValue()); cancelAlarm(context, Task.get(id).orNull()); } updateAlarms(context); } }