cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.receiver.NotificationHelper.java Source code

Java tutorial

Introduction

Here is the source code for cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.receiver.NotificationHelper.java

Source

/*
 * Copyright (c) 2015 Jonas Kalderstam.
 *
 * 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
 * (at your option) 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 cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.receiver;

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.content.SharedPreferences;
import android.database.ContentObserver;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.R;
import cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Task;
import cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.util.Log;

public class NotificationHelper extends BroadcastReceiver {

    public static final int NOTIFICATION_ID = 4364;
    public static final String NOTIFICATION_CANCEL_ARG = "notification_cancel_arg";
    public static final String NOTIFICATION_DELETE_ARG = "notification_delete_arg";
    // static final String ARG_MAX_TIME = "maxtime";
    // static final String ARG_LISTID = "listid";
    static final String ARG_TASKID = "taskid";
    private static final String ACTION_COMPLETE = "com.nononsenseapps.notepad.ACTION.COMPLETE";
    private static final String ACTION_SNOOZE = "com.nononsenseapps.notepad.ACTION.SNOOZE";
    private static final String ACTION_RESCHEDULE = "cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.ACTION.RESCHEDULE";
    private static final String TAG = "nononsenseapps.NotificationHelper";

    private static ContextObserver observer = null;

    private static ContextObserver getObserver(final Context context) {
        if (observer == null) {
            observer = new ContextObserver(context, null);
        }
        return observer;
    }

    private static void monitorUri(final Context context) {
        context.getContentResolver().unregisterContentObserver(getObserver(context));
        context.getContentResolver().registerContentObserver(
                cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification.URI,
                true, getObserver(context));
    }

    public static void clearNotification(@NonNull final Context context, @NonNull final Intent intent) {
        if (intent.getLongExtra(NOTIFICATION_DELETE_ARG, -1) > 0) {
            cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                    .deleteOrReschedule(context,
                            cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                                    .getUri(intent.getLongExtra(NOTIFICATION_DELETE_ARG, -1)));
        }
        if (intent.getLongExtra(NOTIFICATION_CANCEL_ARG, -1) > 0) {
            NotificationHelper.cancelNotification(context, (int) intent.getLongExtra(NOTIFICATION_CANCEL_ARG, -1));
        }
    }

    /**
     * Displays notifications that have a time occurring in the past (and no
     * location). If no notifications like that exist, will make sure to cancel
     * any notifications showing.
     */
    private static void notifyPast(Context context, boolean alertOnce) {
        // Get list of past notifications
        final Calendar now = Calendar.getInstance();

        final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications = cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                .getNotificationsWithTime(context, now.getTimeInMillis(), true);

        // Remove duplicates
        makeUnique(context, notifications);

        final NotificationManager notificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);

        Log.d(TAG, "Number of notifications: " + notifications.size());

        // If empty, cancel
        if (notifications.isEmpty()) {
            // cancelAll permanent notifications here if/when that is
            // implemented. Don't touch others.
            // Dont do this, it clears location
            // notificationManager.cancelAll();
        } else {
            // else, notify
            // Fetch sound and vibrate settings
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

            // Always use default lights
            int lightAndVibrate = Notification.DEFAULT_LIGHTS;
            // If vibrate on, use default vibration pattern also
            if (prefs.getBoolean(context.getString(R.string.const_preference_vibrate_key), false))
                lightAndVibrate |= Notification.DEFAULT_VIBRATE;

            // Need to get a new one because the action buttons will duplicate
            // otherwise
            NotificationCompat.Builder builder;

            // if (false)
            // //
            // prefs.getBoolean(context.getString(R.string.key_pref_group_on_lists),
            // // false))
            // {
            // // Group together notes contained in the same list.
            // // Always use listid
            // for (long listId : getRelatedLists(notifications)) {
            // builder = getNotificationBuilder(context,
            // Integer.parseInt(prefs.getString(
            // context.getString(R.string.key_pref_prio),
            // "0")), lightAndVibrate,
            // Uri.parse(prefs.getString(context
            // .getString(R.string.key_pref_ringtone),
            // "DEFAULT_NOTIFICATION_URI")), alertOnce);
            //
            // List<com.nononsenseapps.notepad.data.model.sql.Notification> subList =
            // getSubList(
            // listId, notifications);
            // if (subList.size() == 1) {
            // // Notify as single
            // notifyBigText(context, notificationManager, builder,
            // listId, subList.get(0));
            // }
            // else {
            // notifyInboxStyle(context, notificationManager, builder,
            // listId, subList);
            // }
            // }
            // }
            // else {
            // Notify for each individually
            for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification note : notifications) {
                builder = getNotificationBuilder(context, lightAndVibrate,
                        Uri.parse(prefs.getString(context.getString(R.string.const_preference_ringtone_key),
                                "DEFAULT_NOTIFICATION_URI")),
                        alertOnce);

                notifyBigText(context, notificationManager, builder, note);
            }
            // }
        }
    }

    /**
     * Returns a notification builder set with non-item specific properties.
     */
    private static NotificationCompat.Builder getNotificationBuilder(final Context context,
            final int lightAndVibrate, final Uri ringtone, final boolean alertOnce) {
        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setWhen(0)
                .setSmallIcon(R.drawable.ic_stat_notification_edit)
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.app_icon))
                .setPriority(NotificationCompat.PRIORITY_DEFAULT).setDefaults(lightAndVibrate).setAutoCancel(true)
                .setOnlyAlertOnce(alertOnce).setSound(ringtone);
        return builder;
    }

    /**
     * Remove from the database, and the specified list, duplicate
     * notifications. The result is that each note is only associated with ONE
     * EXPIRED notification.
     *
     * @param context
     * @param notifications
     */
    private static void makeUnique(final Context context,
            final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications) {
        // get duplicates and iterate over them
        for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification noti : getLatestOccurence(
                notifications)) {
            // remove all but the first one from database, and big list
            for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification dupNoti : getDuplicates(
                    noti, notifications)) {
                notifications.remove(dupNoti);
                cancelNotification(context, dupNoti);
                // Cancelled called in delete
                dupNoti.deleteOrReschedule(context);
            }
        }

    }

    /**
     * Returns the first occurrence of each note's notification. Effectively the
     * returned list has unique elements with regard to the note id.
     *
     * @param notifications
     * @return
     */
    private static List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> getLatestOccurence(
            final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications) {
        final ArrayList<Long> seenIds = new ArrayList<Long>();
        final ArrayList<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> firsts = new ArrayList<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification>();

        cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification noti;
        for (int i = notifications.size() - 1; i >= 0; i--) {
            noti = notifications.get(i);
            if (!seenIds.contains(noti.taskID)) {
                seenIds.add(noti.taskID);
                firsts.add(noti);
            }
        }
        return firsts;
    }

    private static List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> getDuplicates(
            final cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification first,
            final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications) {
        final ArrayList<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> dups = new ArrayList<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification>();

        for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification noti : notifications) {
            if (noti.taskID == first.taskID && noti._id != first._id) {
                dups.add(noti);
            }
        }
        return dups;
    }

    /**
     * Needs the builder that contains non-note specific values.
     *
     */
    private static void notifyBigText(final Context context, final NotificationManager notificationManager,
            final NotificationCompat.Builder builder,
            final cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification note) {
        final Intent delIntent = new Intent(Intent.ACTION_DELETE, note.getUri());
        if (note.repeats != 0) {
            delIntent.setAction(ACTION_RESCHEDULE);
        }
        // Add extra so we don't delete all
        // if (note.time != null) {
        // delIntent.putExtra(ARG_MAX_TIME, note.time);
        // }
        delIntent.putExtra(ARG_TASKID, note.taskID);
        // Delete it on clear
        PendingIntent deleteIntent = PendingIntent.getBroadcast(context, 0, delIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Open intent
        final Intent openIntent = new Intent(Intent.ACTION_VIEW, Task.getUri(note.taskID));
        // Should create a new instance to avoid fragment problems
        openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);

        // Delete intent on non-location repeats
        // Opening the note should delete/reschedule the notification
        openIntent.putExtra(NOTIFICATION_DELETE_ARG, note._id);

        // Opening always cancels the notification though
        openIntent.putExtra(NOTIFICATION_CANCEL_ARG, note._id);

        // Open note on click
        PendingIntent clickIntent = PendingIntent.getActivity(context, 0, openIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Action to complete
        PendingIntent completeIntent = PendingIntent.getBroadcast(context, 0,
                new Intent(ACTION_COMPLETE, note.getUri()).putExtra(ARG_TASKID, note.taskID),
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Action to snooze
        PendingIntent snoozeIntent = PendingIntent.getBroadcast(context, 0,
                new Intent(ACTION_SNOOZE, note.getUri()).putExtra(ARG_TASKID, note.taskID),
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Build notification
        builder.setContentTitle(note.taskTitle).setContentText(note.taskNote).setContentIntent(clickIntent)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(note.taskNote));

        // Delete intent on non-location repeats
        builder.setDeleteIntent(deleteIntent);

        // Snooze button only on time non-repeating
        if (note.time != null && note.repeats == 0) {
            builder.addAction(R.drawable.ic_alarm_24dp_white, context.getText(R.string.snooze), snoozeIntent);
        }
        // Complete button only on non-repeating, both time and location
        if (note.repeats == 0) {
            builder.addAction(R.drawable.ic_check_24dp_white, context.getText(R.string.completed), completeIntent);
        }

        final Notification noti = builder.build();

        notificationManager.notify((int) note._id, noti);
    }

    private static long getLatestTime(
            final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications) {
        long latest = 0;
        for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification noti : notifications) {
            if (noti.time > latest)
                latest = noti.time;
        }
        return latest;
    }

    // private static void notifyInboxStyle(
    // final Context context,
    // final NotificationManager notificationManager,
    // final NotificationCompat.Builder builder,
    // final Long idToUse,
    // final List<com.nononsenseapps.notepad.data.model.sql.Notification>
    // notifications) {
    // // Delete intent must delete all notifications
    // Intent delint = new Intent(Intent.ACTION_DELETE,
    // com.nononsenseapps.notepad.data.model.sql.Notification.URI);
    // // Add extra so we don't delete all
    // final long maxTime = getLatestTime(notifications);
    // delint.putExtra(ARG_MAX_TIME, maxTime);
    // delint.putExtra(ARG_LISTID, idToUse);
    // PendingIntent deleteIntent = PendingIntent.getBroadcast(context, 0,
    // delint, PendingIntent.FLAG_UPDATE_CURRENT);
    //
    // // Open intent should open the list
    // PendingIntent clickIntent = PendingIntent.getActivity(context, 0,
    // new Intent(Intent.ACTION_VIEW, TaskList.getUri(idToUse),
    // context, ActivityMain_.class),
    // PendingIntent.FLAG_UPDATE_CURRENT);
    //
    // final String title = notifications.get(0).listTitle + " ("
    // + notifications.size() + ")";
    // // Build notification
    // builder.setContentTitle(title).setNumber(notifications.size())
    // .setContentText(notifications.get(0).taskTitle)
    // .setContentIntent(clickIntent).setDeleteIntent(deleteIntent);
    //
    // // Action to snooze
    // PendingIntent snoozeIntent = PendingIntent.getBroadcast(
    // context,
    // 0,
    // new Intent(Intent.ACTION_EDIT, TaskList.getUri(idToUse))
    // .putExtra(ARG_SNOOZE, true)
    // .putExtra(ARG_LISTID, idToUse)
    // .setClass(context, NotificationHelper.class),
    // PendingIntent.FLAG_UPDATE_CURRENT);
    // builder.addAction(R.drawable.ic_stat_snooze,
    // context.getText(R.string.snooze), snoozeIntent);
    //
    // // Action to complete
    // PendingIntent completeIntent = PendingIntent.getBroadcast(
    // context,
    // 0,
    // new Intent(Intent.ACTION_DELETE, TaskList.getUri(idToUse))
    // .putExtra(ARG_COMPLETE, true)
    // .putExtra(ARG_LISTID, idToUse)
    // .putExtra(ARG_MAX_TIME, maxTime)
    // .setClass(context, NotificationHelper.class),
    // PendingIntent.FLAG_UPDATE_CURRENT);
    // builder.addAction(R.drawable.navigation_accept_dark,
    // context.getText(R.string.completed), completeIntent);
    //
    // NotificationCompat.InboxStyle ib = new NotificationCompat.InboxStyle()
    // .setBigContentTitle(title);
    // if (notifications.size() > 6)
    // ib.setSummaryText("+" + (notifications.size() - 6) + " "
    // + context.getString(R.string.more));
    //
    // for (com.nononsenseapps.notepad.data.model.sql.Notification e : notifications)
    // {
    // ib.addLine(e.taskTitle);
    // }
    //
    // final Notification noti = builder.setStyle(ib).build();
    // notificationManager.notify(idToUse.intValue(), noti);
    // }

    /**
     * Schedules to be woken up at the next notification time.
     */
    private static void scheduleNext(Context context) {
        // Get first future notification
        final Calendar now = Calendar.getInstance();
        final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications = cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                .getNotificationsWithTime(context, now.getTimeInMillis(), false);

        // if not empty, schedule alarm wake up
        if (!notifications.isEmpty()) {
            // at first's time
            // Create a new PendingIntent and add it to the AlarmManager
            Intent intent = new Intent(Intent.ACTION_RUN);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent,
                    PendingIntent.FLAG_CANCEL_CURRENT);
            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            am.cancel(pendingIntent);
            am.set(AlarmManager.RTC_WAKEUP, notifications.get(0).time, pendingIntent);
        }

        monitorUri(context);
    }

    /**
     * Schedules coming notifications, and displays expired ones. Only
     * notififies once for existing notifications.
     */
    public static void schedule(final Context context) {
        notifyPast(context, true);
        scheduleNext(context);
    }

    /**
     * Updates/Inserts notifications in the database. Immediately notifies and
     * schedules next wake up on finish.
     *
     * @param context
     * @param notification
     */
    public static void updateNotification(final Context context,
            final cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification notification) {
        /*
         * Only don't insert if update is success This way the editor can update
         * a deleted notification and still have it persisted in the database
         */
        boolean shouldInsert = true;
        // If id is -1, then this should be inserted
        if (notification._id > 0) {
            // Else it should be updated
            int result = notification.save(context);
            if (result > 0)
                shouldInsert = false;
        }

        if (shouldInsert) {
            notification._id = -1;
            notification.save(context);
        }

        notifyPast(context, true);
        scheduleNext(context);
    }

    /**
     * Deletes the indicated notification from the notification tray (does not
     * touch database)
     *
     * Called by notification.delete()
     *
     * @param context
     * @param not
     */
    public static void cancelNotification(final Context context,
            final cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification not) {
        if (not != null) {
            cancelNotification(context, not.getUri());
        }
    }

    /**
     * Does not touch db
     */
    public static void cancelNotification(final Context context, final Uri uri) {
        if (uri != null)
            cancelNotification(context, Integer.parseInt(uri.getLastPathSegment()));
    }

    /**
     * Does not touch db
     */
    public static void cancelNotification(final Context context, final int notId) {
        final NotificationManager notificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(notId);
    }

    /**
     * Given a list of notifications, returns a list of the lists the notes
     * belong to.
     */
    private static Collection<Long> getRelatedLists(
            final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications) {
        final HashSet<Long> lists = new HashSet<Long>();
        for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification not : notifications) {
            lists.add(not.listID);
        }

        return lists;
    }

    /**
      * Modifies DB
      */
    // public static void deleteNotification(final Context context, long listId,
    // long maxTime) {
    // com.nononsenseapps.notepad.data.model.sql.Notification.removeWithListId(
    // context, listId, maxTime);
    //
    // final NotificationManager notificationManager = (NotificationManager)
    // context
    // .getSystemService(Context.NOTIFICATION_SERVICE);
    // notificationManager.cancel((int) listId);
    // }

    /**
     * Returns a list of those notifications that are associated to notes in the
    * specified list.
    */
    private static List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> getSubList(
            final long listId,
            final List<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> notifications) {
        final ArrayList<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification> subList = new ArrayList<cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification>();
        for (cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification not : notifications) {
            if (not.listID == listId) {
                subList.add(not);
            }
        }

        return subList;
    }

    /**
     * Fires notifications that have elapsed and sets an alarm to be woken at
     * the next notification.
     * <p/>
     * If the intent action is ACTION_DELETE, will delete the notification with
     * the indicated ID, and cancel it from any active notifications.
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
                || Intent.ACTION_RUN.equals(intent.getAction())) {
            // Can't cancel anything. Just schedule and notify at end
        } else {
            // Always cancel
            cancelNotification(context, intent.getData());

            if (Intent.ACTION_DELETE.equals(intent.getAction()) || ACTION_RESCHEDULE.equals(intent.getAction())) {
                // Just a notification
                cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                        .deleteOrReschedule(context, intent.getData());
            } else if (ACTION_SNOOZE.equals(intent.getAction())) {
                // msec/sec * sec/min * 30
                long delay30min = 1000 * 60 * 30;
                final Calendar now = Calendar.getInstance();

                cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                        .setTime(context, intent.getData(), delay30min + now.getTimeInMillis());
            } else if (ACTION_COMPLETE.equals(intent.getAction())) {
                // Complete note
                Task.setCompletedSynced(context, true, intent.getLongExtra(ARG_TASKID, -1));
                // Delete notifications with the same task id
                cn.studyjams.s2.sj0132.bowenyan.mygirlfriend.nononsenseapps.notepad.data.model.sql.Notification
                        .removeWithTaskIdsSynced(context, intent.getLongExtra(ARG_TASKID, -1));
            }
        }

        notifyPast(context, true);
        scheduleNext(context);
    }

    private static class ContextObserver extends ContentObserver {
        private final Context context;

        public ContextObserver(final Context context, Handler h) {
            super(h);
            this.context = context.getApplicationContext();
        }

        // Implement the onChange(boolean) method to delegate the
        // change notification to the onChange(boolean, Uri) method
        // to ensure correct operation on older versions
        // of the framework that did not have the onChange(boolean,
        // Uri) method.
        @Override
        public void onChange(boolean selfChange) {
            onChange(selfChange, null);
        }

        // Implement the onChange(boolean, Uri) method to take
        // advantage of the new Uri argument.
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            // Handle change but don't spam
            notifyPast(context, true);
        }
    }
}