gt:         if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) {
context: /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.providers.calendar;
  
  import com.android.providers.calendar.CalendarDatabaseHelper.Tables;
  import com.android.providers.calendar.CalendarDatabaseHelper.Views;
  import com.google.common.annotations.VisibleForTesting;
  
  import android.app.AlarmManager;
  import android.app.PendingIntent;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
  import android.net.Uri;
  import android.os.PowerManager;
  import android.os.PowerManager.WakeLock;
  import android.os.SystemClock;
  import android.provider.CalendarContract;
  import android.provider.CalendarContract.CalendarAlerts;
  import android.provider.CalendarContract.Calendars;
  import android.provider.CalendarContract.Events;
  import android.provider.CalendarContract.Instances;
  import android.provider.CalendarContract.Reminders;
  import android.text.format.DateUtils;
  import android.text.format.Time;
  import android.util.Log;
  
  import java.util.concurrent.atomic.AtomicBoolean;
  
  /**
   * We are using the CalendarAlertManager to be able to mock the AlarmManager as the AlarmManager
   * cannot be extended.
   *
   * CalendarAlertManager is delegating its calls to the real AlarmService.
   */
  public class CalendarAlarmManager {
      protected static final String TAG = "CalendarAlarmManager";
  
      // SCHEDULE_ALARM_URI runs scheduleNextAlarm(false)
      // SCHEDULE_ALARM_REMOVE_URI runs scheduleNextAlarm(true)
      // TODO: use a service to schedule alarms rather than private URI
      /* package */static final String SCHEDULE_ALARM_PATH = "schedule_alarms";
      /* package */static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
      private static final String REMOVE_ALARM_VALUE = "removeAlarms";
      /* package */static final Uri SCHEDULE_ALARM_REMOVE_URI = Uri.withAppendedPath(
              CalendarContract.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
      /* package */static final Uri SCHEDULE_ALARM_URI = Uri.withAppendedPath(
              CalendarContract.CONTENT_URI, SCHEDULE_ALARM_PATH);
  
      static final String INVALID_CALENDARALERTS_SELECTOR =
      "_id IN (SELECT ca." + CalendarAlerts._ID + " FROM "
              + Tables.CALENDAR_ALERTS + " AS ca"
              + " LEFT OUTER JOIN " + Tables.INSTANCES
              + " USING (" + Instances.EVENT_ID + ","
              + Instances.BEGIN + "," + Instances.END + ")"
              + " LEFT OUTER JOIN " + Tables.REMINDERS + " AS r ON"
              + " (ca." + CalendarAlerts.EVENT_ID + "=r." + Reminders.EVENT_ID
              + " AND ca." + CalendarAlerts.MINUTES + "=r." + Reminders.MINUTES + ")"
              + " LEFT OUTER JOIN " + Views.EVENTS + " AS e ON"
              + " (ca." + CalendarAlerts.EVENT_ID + "=e." + Events._ID + ")"
              + " WHERE " + Tables.INSTANCES + "." + Instances.BEGIN + " ISNULL"
              + "   OR ca." + CalendarAlerts.ALARM_TIME + "<?"
              + "   OR (r." + Reminders.MINUTES + " ISNULL"
              + "       AND ca." + CalendarAlerts.MINUTES + "<>0)"
              + "   OR e." + Calendars.VISIBLE + "=0)";
  
      /**
       * We search backward in time for event reminders that we may have missed
       * and schedule them if the event has not yet expired. The amount in the
       * past to search backwards is controlled by this constant. It should be at
       * least a few minutes to allow for an event that was recently created on
       * the web to make its way to the phone. Two hours might seem like overkill,
       * but it is useful in the case where the user just crossed into a new
       * timezone and might have just missed an alarm.
       */
      private static final long SCHEDULE_ALARM_SLACK = 2 * DateUtils.HOUR_IN_MILLIS;
      /**
       * Alarms older than this threshold will be deleted from the CalendarAlerts
       * table. This should be at least a day because if the timezone is wrong and
       * the user corrects it we might delete good alarms that appear to be old
       * because the device time was incorrectly in the future. This threshold
       * must also be larger than SCHEDULE_ALARM_SLACK. We add the
       * SCHEDULE_ALARM_SLACK to ensure this. To make it easier to find and debug
       * problems with missed reminders, set this to something greater than a day.
       */
      private static final long CLEAR_OLD_ALARM_THRESHOLD = 7 * DateUtils.DAY_IN_MILLIS
              + SCHEDULE_ALARM_SLACK;
      private static final String SCHEDULE_NEXT_ALARM_WAKE_LOCK = "ScheduleNextAlarmWakeLock";
      protected static final String ACTION_CHECK_NEXT_ALARM =
              "com.android.providers.calendar.intent.CalendarProvider2";
      static final int ALARM_CHECK_DELAY_MILLIS = 5000;
  
      /**
       * Used for tracking if the next alarm is already scheduled
       */
      @VisibleForTesting
      protected AtomicBoolean mNextAlarmCheckScheduled;
      /**
       * Used for synchronization
       */
      @VisibleForTesting
      protected Object mAlarmLock;
      /**
       * Used to keep the process from getting killed while scheduling alarms
       */
      private WakeLock mScheduleNextAlarmWakeLock;
  
      @VisibleForTesting
      protected Context mContext;
      private AlarmManager mAlarmManager;
  
      public CalendarAlarmManager(Context context) {
          initializeWithContext(context);
      }
  
      protected void initializeWithContext(Context context) {
          mContext = context;
          mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
          mNextAlarmCheckScheduled = new AtomicBoolean(false);
          mAlarmLock = new Object();
      }
  
      void scheduleNextAlarm(boolean removeAlarms) {
          // We must always run the following when 'removeAlarms' is true.  Previously it
          // was possible to have a race condition on startup between TIME_CHANGED and
          // BOOT_COMPLETED broadcast actions.  This resulted in alarms being
          // missed (Bug 7221716) when the TIME_CHANGED broadcast ('removeAlarms' = false)
          // happened right before the BOOT_COMPLETED ('removeAlarms' = true), and the
          // BOOT_COMPLETED action was skipped since there was concurrent scheduling in progress.
          if (!mNextAlarmCheckScheduled.getAndSet(true) || removeAlarms) {
              if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
                  Log.d(CalendarProvider2.TAG, "Scheduling check of next Alarm");
              }
              Intent intent = new Intent(ACTION_CHECK_NEXT_ALARM);
              intent.putExtra(REMOVE_ALARM_VALUE, removeAlarms);
              PendingIntent pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent,
                      PendingIntent.FLAG_NO_CREATE);
              if (pending != null) {
                  // Cancel any previous Alarm check requests
                  cancel(pending);
              }
              pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent,
                      PendingIntent.FLAG_CANCEL_CURRENT);
  
              // Trigger the check in 5s from now
              long triggerAtTime = SystemClock.elapsedRealtime() + ALARM_CHECK_DELAY_MILLIS;
              set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pending);
          }
      }
  
      PowerManager.WakeLock getScheduleNextAlarmWakeLock() {
          if (mScheduleNextAlarmWakeLock == null) {
              PowerManager powerManager = (PowerManager) mContext.getSystemService(
                      Context.POWER_SERVICE);
              // Create a wake lock that will be used when we are actually
              // scheduling the next alarm
              mScheduleNextAlarmWakeLock = powerManager.newWakeLock(
                      PowerManager.PARTIAL_WAKE_LOCK, SCHEDULE_NEXT_ALARM_WAKE_LOCK);
              // We want the Wake Lock to be reference counted (so that we dont
              // need to take care
              // about its reference counting)
              mScheduleNextAlarmWakeLock.setReferenceCounted(true);
          }
          return mScheduleNextAlarmWakeLock;
      }
  
      void acquireScheduleNextAlarmWakeLock() {
          getScheduleNextAlarmWakeLock().acquire();
      }
  
      void releaseScheduleNextAlarmWakeLock() {
          getScheduleNextAlarmWakeLock().release();
      }
  
      void rescheduleMissedAlarms() {
          rescheduleMissedAlarms(mContext.getContentResolver());
      }
  
      /**
       * This method runs in a background thread and schedules an alarm for the
       * next calendar event, if necessary.
       *
       * @param db TODO
       */
      void runScheduleNextAlarm(boolean removeAlarms, CalendarProvider2 cp2) {
          SQLiteDatabase db = cp2.mDb;
          if (db == null) {
              return;
          }
  
          // Reset so that we can accept other schedules of next alarm
          mNextAlarmCheckScheduled.set(false);
          db.beginTransaction();
          try {
              if (removeAlarms) {
                  removeScheduledAlarmsLocked(db);
              }
              scheduleNextAlarmLocked(db, cp2);
              db.setTransactionSuccessful();
          } finally {
              db.endTransaction();
          }
      }
  
      void scheduleNextAlarmCheck(long triggerTime) {
          Intent intent = new Intent(CalendarReceiver.SCHEDULE);
          intent.setClass(mContext, CalendarReceiver.class);
          PendingIntent pending = PendingIntent.getBroadcast(
                  mContext, 0, intent, PendingIntent.FLAG_NO_CREATE);
          if (pending != null) {
              // Cancel any previous alarms that do the same thing.
              cancel(pending);
          }
          pending = PendingIntent.getBroadcast(
                  mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
  
          if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
              Time time = new Time();
              time.set(triggerTime);
              String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
              Log.d(CalendarProvider2.TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr);
          }
  
          set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
      }
  
      /**
       * This method looks at the 24-hour window from now for any events that it
       * needs to schedule. This method runs within a database transaction. It
       * also runs in a background thread. The CalendarProvider2 keeps track of
       * which alarms it has already scheduled to avoid scheduling them more than
       * once and for debugging problems with alarms. It stores this knowledge in
       * a database table called CalendarAlerts which persists across reboots. But
       * the actual alarm list is in memory and disappears if the phone loses
       * power. To avoid missing an alarm, we clear the entries in the
       * CalendarAlerts table when we start up the CalendarProvider2. Scheduling
       * an alarm multiple times is not tragic -- we filter out the extra ones
       * when we receive them. But we still need to keep track of the scheduled
       * alarms. The main reason is that we need to prevent multiple notifications
       * for the same alarm (on the receive side) in case we accidentally schedule
       * the same alarm multiple times. We don't have visibility into the system's
       * alarm list so we can never know for sure if we have already scheduled an
       * alarm and it's better to err on scheduling an alarm twice rather than
       * missing an alarm. Another reason we keep track of scheduled alarms in a
       * database table is that it makes it easy to run an SQL query to find the
       * next reminder that we haven't scheduled.
       *
       * @param db the database
       * @param cp2 TODO
       */
      private void scheduleNextAlarmLocked(SQLiteDatabase db, CalendarProvider2 cp2) {
          Time time = new Time();
  
          final long currentMillis = System.currentTimeMillis();
          final long start = currentMillis - SCHEDULE_ALARM_SLACK;
          final long end = start + (24 * 60 * 60 * 1000);
          if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
              time.set(start);
              String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
              Log.d(CalendarProvider2.TAG, "runScheduleNextAlarm() start search: " + startTimeStr);
          }
  
          // Delete rows in CalendarAlert where the corresponding Instance or
          // Reminder no longer exist.
          // Also clear old alarms but keep alarms around for a while to prevent
          // multiple alerts for the same reminder. The "clearUpToTime'
          // should be further in the past than the point in time where
          // we start searching for events (the "start" variable defined above).
          String selectArg[] = new String[] { Long.toString(
                  currentMillis - CLEAR_OLD_ALARM_THRESHOLD) };
  
          int rowsDeleted = db.delete(
                  CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg);
  
          long nextAlarmTime = end;
          final ContentResolver resolver = mContext.getContentResolver();
          final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(resolver, currentMillis);
  