/**
   * Fix and update all alarm instance when a time change event occurs.
   *
   * @param context application context
   */
  public static void fixAlarmInstances(Context context) {
    // Register all instances after major time changes or when phone restarts
    final ContentResolver contentResolver = context.getContentResolver();
    final Calendar currentTime = getCurrentTime();
    for (AlarmInstance instance : AlarmInstance.getInstances(contentResolver, null)) {
      final Alarm alarm = Alarm.getAlarm(contentResolver, instance.mAlarmId);
      final Calendar priorAlarmTime = alarm.getPreviousAlarmTime(instance.getAlarmTime());
      final Calendar missedTTLTime = instance.getMissedTimeToLive();
      if (currentTime.before(priorAlarmTime) || currentTime.after(missedTTLTime)) {
        final Calendar oldAlarmTime = instance.getAlarmTime();
        final Calendar newAlarmTime = alarm.getNextAlarmTime(currentTime);
        final CharSequence oldTime = DateFormat.format("MM/dd/yyyy hh:mm a", oldAlarmTime);
        final CharSequence newTime = DateFormat.format("MM/dd/yyyy hh:mm a", newAlarmTime);
        LogUtils.i(
            "A time change has caused an existing alarm scheduled to fire at %s to"
                + " be replaced by a new alarm scheduled to fire at %s",
            oldTime, newTime);

        // The time change is so dramatic the AlarmInstance doesn't make any sense;
        // remove it and schedule the new appropriate instance.
        AlarmStateManager.setDismissState(context, instance);
      } else {
        registerInstance(context, instance, false);
      }
    }

    updateNextAlarm(context);
  }
  /**
   * This will set the alarm instance to the FIRED_STATE and update the application notifications
   * and schedule any state changes that need to occur in the future.
   *
   * @param context application context
   * @param instance to set state to
   */
  public static void setFiredState(Context context, AlarmInstance instance) {
    LogUtils.v("Setting fire state to instance " + instance.mId);

    // Update alarm state in db
    ContentResolver contentResolver = context.getContentResolver();
    instance.mAlarmState = AlarmInstance.FIRED_STATE;
    AlarmInstance.updateInstance(contentResolver, instance);

    if (instance.mAlarmId != null) {
      // if the time changed *backward* and pushed an instance from missed back to fired,
      // remove any other scheduled instances that may exist
      AlarmInstance.deleteOtherInstances(contentResolver, instance.mAlarmId, instance.mId);
    }

    // Start the alarm and schedule timeout timer for it
    AlarmService.startAlarm(context, instance);

    Calendar timeout = instance.getTimeout(context);
    if (timeout != null) {
      scheduleInstanceStateChange(context, timeout, instance, AlarmInstance.MISSED_STATE);
    }

    // Instance not valid anymore, so find next alarm that will fire and notify system
    updateNextAlarm(context);
  }
  /**
   * Used by dismissed and missed states, to update parent alarm. This will either disable, delete
   * or reschedule parent alarm.
   *
   * @param context application context
   * @param instance to update parent for
   */
  private static void updateParentAlarm(Context context, AlarmInstance instance) {
    ContentResolver cr = context.getContentResolver();
    Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    if (alarm == null) {
      LogUtils.e("Parent has been deleted with instance: " + instance.toString());
      return;
    }

    if (!alarm.daysOfWeek.isRepeating()) {
      if (alarm.deleteAfterUse) {
        LogUtils.i("Deleting parent alarm: " + alarm.id);
        Alarm.deleteAlarm(cr, alarm.id);
      } else {
        LogUtils.i("Disabling parent alarm: " + alarm.id);
        alarm.enabled = false;
        Alarm.updateAlarm(cr, alarm);
      }
    } else {
      // Schedule the next repeating instance after the current time
      AlarmInstance nextRepeatedInstance = alarm.createInstanceAfter(getCurrentTime());
      LogUtils.i(
          "Creating new instance for repeating alarm "
              + alarm.id
              + " at "
              + AlarmUtils.getFormattedTime(context, nextRepeatedInstance.getAlarmTime()));
      AlarmInstance.addInstance(cr, nextRepeatedInstance);
      registerInstance(context, nextRepeatedInstance, true);
    }
  }
  /** Used in L and later devices where "next alarm" is stored in the Alarm Manager. */
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  private static void updateNextAlarmInAlarmManager(Context context, AlarmInstance nextAlarm) {
    // Sets a surrogate alarm with alarm manager that provides the AlarmClockInfo for the
    // alarm that is going to fire next. The operation is constructed such that it is ignored
    // by AlarmStateManager.

    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    int flags = nextAlarm == null ? PendingIntent.FLAG_NO_CREATE : 0;
    PendingIntent operation =
        PendingIntent.getBroadcast(
            context, 0 /* requestCode */, AlarmStateManager.createIndicatorIntent(context), flags);

    if (nextAlarm != null) {
      long alarmTime = nextAlarm.getAlarmTime().getTimeInMillis();

      // Create an intent that can be used to show or edit details of the next alarm.
      PendingIntent viewIntent =
          PendingIntent.getActivity(
              context,
              nextAlarm.hashCode(),
              AlarmNotifications.createViewAlarmIntent(context, nextAlarm),
              PendingIntent.FLAG_UPDATE_CURRENT);

      AlarmManager.AlarmClockInfo info = new AlarmManager.AlarmClockInfo(alarmTime, viewIntent);
      alarmManager.setAlarmClock(info, operation);
    } else if (operation != null) {
      alarmManager.cancel(operation);
    }
  }
 /**
  * This will delete and unregister all instances associated with alarmId, without affect the alarm
  * itself. This should be used whenever modifying or deleting an alarm.
  *
  * @param context application context
  * @param alarmId to find instances to delete.
  */
 public static void deleteAllInstances(Context context, long alarmId) {
   ContentResolver cr = context.getContentResolver();
   List<AlarmInstance> instances = AlarmInstance.getInstancesByAlarmId(cr, alarmId);
   for (AlarmInstance instance : instances) {
     unregisterInstance(context, instance);
     AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
   }
   updateNextAlarm(context);
 }
  /**
   * This will set the alarm instance to the HIGH_NOTIFICATION_STATE and update the application
   * notifications and schedule any state changes that need to occur in the future.
   *
   * @param context application context
   * @param instance to set state to
   */
  public static void setHighNotificationState(Context context, AlarmInstance instance) {
    LogUtils.v("Setting high notification state to instance " + instance.mId);

    // Update alarm state in db
    ContentResolver contentResolver = context.getContentResolver();
    instance.mAlarmState = AlarmInstance.HIGH_NOTIFICATION_STATE;
    AlarmInstance.updateInstance(contentResolver, instance);

    // Setup instance notification and scheduling timers
    AlarmNotifications.showHighPriorityNotification(context, instance);
    scheduleInstanceStateChange(
        context, instance.getAlarmTime(), instance, AlarmInstance.FIRED_STATE);
  }
  /**
   * This will set the alarm instance to the SILENT_STATE and update the application notifications
   * and schedule any state changes that need to occur in the future.
   *
   * @param context application context
   * @param instance to set state to
   */
  public static void setSilentState(Context context, AlarmInstance instance) {
    LogUtils.v("Setting silent state to instance " + instance.mId);

    // Update alarm in db
    ContentResolver contentResolver = context.getContentResolver();
    instance.mAlarmState = AlarmInstance.SILENT_STATE;
    AlarmInstance.updateInstance(contentResolver, instance);

    // Setup instance notification and scheduling timers
    AlarmNotifications.clearNotification(context, instance);
    scheduleInstanceStateChange(
        context, instance.getLowNotificationTime(), instance, AlarmInstance.LOW_NOTIFICATION_STATE);
  }
  /**
   * Returns an alarm instance of an alarm that's going to fire next.
   *
   * @param context application context
   * @return an alarm instance that will fire earliest relative to current time.
   */
  public static AlarmInstance getNextFiringAlarm(Context context) {
    final ContentResolver cr = context.getContentResolver();
    final String activeAlarmQuery = AlarmInstance.ALARM_STATE + "<" + AlarmInstance.FIRED_STATE;
    final List<AlarmInstance> alarmInstances = AlarmInstance.getInstances(cr, activeAlarmQuery);

    AlarmInstance nextAlarm = null;
    for (AlarmInstance instance : alarmInstances) {
      if (nextAlarm == null || instance.getAlarmTime().before(nextAlarm.getAlarmTime())) {
        nextAlarm = instance;
      }
    }
    return nextAlarm;
  }
  /**
   * This will set the alarm instance to the SNOOZE_STATE and update the application notifications
   * and schedule any state changes that need to occur in the future.
   *
   * @param context application context
   * @param instance to set state to
   */
  public static void setSnoozeState(
      final Context context, AlarmInstance instance, boolean showToast) {
    // Stop alarm if this instance is firing it
    AlarmService.stopAlarm(context, instance);

    // Calculate the new snooze alarm time
    String snoozeMinutesStr =
        PreferenceManager.getDefaultSharedPreferences(context)
            .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE_MINUTES);
    final int snoozeMinutes = Integer.parseInt(snoozeMinutesStr);
    Calendar newAlarmTime = Calendar.getInstance();
    newAlarmTime.add(Calendar.MINUTE, snoozeMinutes);

    // Update alarm state and new alarm time in db.
    LogUtils.v(
        "Setting snoozed state to instance "
            + instance.mId
            + " for "
            + AlarmUtils.getFormattedTime(context, newAlarmTime));
    instance.setAlarmTime(newAlarmTime);
    instance.mAlarmState = AlarmInstance.SNOOZE_STATE;
    AlarmInstance.updateInstance(context.getContentResolver(), instance);

    // Setup instance notification and scheduling timers
    AlarmNotifications.showSnoozeNotification(context, instance);
    scheduleInstanceStateChange(
        context, instance.getAlarmTime(), instance, AlarmInstance.FIRED_STATE);

    // Display the snooze minutes in a toast.
    if (showToast) {
      final Handler mainHandler = new Handler(context.getMainLooper());
      final Runnable myRunnable =
          new Runnable() {
            @Override
            public void run() {
              String displayTime =
                  String.format(
                      context
                          .getResources()
                          .getQuantityText(R.plurals.alarm_alert_snooze_set, snoozeMinutes)
                          .toString(),
                      snoozeMinutes);
              Toast.makeText(context, displayTime, Toast.LENGTH_LONG).show();
            }
          };
      mainHandler.post(myRunnable);
    }

    // Instance time changed, so find next alarm that will fire and notify system
    updateNextAlarm(context);
  }
示例#10
0
 /**
  * Utility method to create a proper change state intent.
  *
  * @param context application context
  * @param tag used to make intent differ from other state change intents.
  * @param instance to change state to
  * @param state to change to.
  * @return intent that can be used to change an alarm instance state
  */
 public static Intent createStateChangeIntent(
     Context context, String tag, AlarmInstance instance, Integer state) {
   Intent intent = AlarmInstance.createIntent(context, AlarmStateManager.class, instance.mId);
   intent.setAction(CHANGE_STATE_ACTION);
   intent.addCategory(tag);
   intent.putExtra(ALARM_GLOBAL_ID_EXTRA, getGlobalIntentId(context));
   if (state != null) {
     intent.putExtra(ALARM_STATE_EXTRA, state.intValue());
   }
   return intent;
 }
示例#11
0
  /**
   * This will set the alarm instance to the PREDISMISSED_STATE and schedule an instance state
   * change to DISMISSED_STATE at the regularly scheduled firing time.
   *
   * @param context
   * @param instance
   */
  public static void setPreDismissState(Context context, AlarmInstance instance) {
    LogUtils.v("Setting predismissed state to instance " + instance.mId);

    // Update alarm in db
    final ContentResolver contentResolver = context.getContentResolver();
    instance.mAlarmState = AlarmInstance.PREDISMISSED_STATE;
    AlarmInstance.updateInstance(contentResolver, instance);

    // Setup instance notification and scheduling timers
    AlarmNotifications.clearNotification(context, instance);
    scheduleInstanceStateChange(
        context, instance.getAlarmTime(), instance, AlarmInstance.DISMISSED_STATE);

    final Alarm alarm = Alarm.getAlarm(contentResolver, instance.mAlarmId);
    // if it's a one time alarm set the toggle to off
    if (alarm != null && !alarm.daysOfWeek.isRepeating()) {
      // Check parent if it needs to reschedule, disable or delete itself
      if (instance.mAlarmId != null) {
        updateParentAlarm(context, instance);
      }
    }
  }
示例#12
0
  /**
   * This will set the alarm instance to the MISSED_STATE and update the application notifications
   * and schedule any state changes that need to occur in the future.
   *
   * @param context application context
   * @param instance to set state to
   */
  public static void setMissedState(Context context, AlarmInstance instance) {
    LogUtils.v("Setting missed state to instance " + instance.mId);
    // Stop alarm if this instance is firing it
    AlarmService.stopAlarm(context, instance);

    // Check parent if it needs to reschedule, disable or delete itself
    if (instance.mAlarmId != null) {
      updateParentAlarm(context, instance);
    }

    // Update alarm state
    ContentResolver contentResolver = context.getContentResolver();
    instance.mAlarmState = AlarmInstance.MISSED_STATE;
    AlarmInstance.updateInstance(contentResolver, instance);

    // Setup instance notification and scheduling timers
    AlarmNotifications.showMissedNotification(context, instance);
    scheduleInstanceStateChange(
        context, instance.getMissedTimeToLive(), instance, AlarmInstance.DISMISSED_STATE);

    // Instance is not valid anymore, so find next alarm that will fire and notify system
    updateNextAlarm(context);
  }
示例#13
0
  /**
   * This will set the alarm instance to the SILENT_STATE and update the application notifications
   * and schedule any state changes that need to occur in the future.
   *
   * @param context application context
   * @param instance to set state to
   */
  public static void setDismissState(Context context, AlarmInstance instance) {
    LogUtils.v("Setting dismissed state to instance " + instance.mId);

    // Remove all other timers and notifications associated to it
    unregisterInstance(context, instance);

    // Check parent if it needs to reschedule, disable or delete itself
    if (instance.mAlarmId != null) {
      updateParentAlarm(context, instance);
    }

    // Delete instance as it is not needed anymore
    AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);

    // Instance is not valid anymore, so find next alarm that will fire and notify system
    updateNextAlarm(context);
  }
示例#14
0
    @Override
    public void cancelScheduledInstanceStateChange(Context context, AlarmInstance instance) {
      LogUtils.v("Canceling instance " + instance.mId + " timers");

      // Create a PendingIntent that will match any one set for this instance
      PendingIntent pendingIntent =
          PendingIntent.getBroadcast(
              context,
              instance.hashCode(),
              createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, null),
              PendingIntent.FLAG_NO_CREATE);

      if (pendingIntent != null) {
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        am.cancel(pendingIntent);
        pendingIntent.cancel();
      }
    }
示例#15
0
  /** Used in pre-L devices, where "next alarm" is stored in system settings. */
  private static void updateNextAlarmInSystemSettings(Context context, AlarmInstance nextAlarm) {
    // Send broadcast message so pre-L AppWidgets will recognize an update
    String timeString = "";
    boolean showStatusIcon = false;
    if (nextAlarm != null) {
      timeString = AlarmUtils.getFormattedTime(context, nextAlarm.getAlarmTime());
      showStatusIcon = true;
    }

    // Set and notify next alarm text to system
    LogUtils.i("Displaying next alarm time: \'" + timeString + '\'');
    // Write directly to NEXT_ALARM_FORMATTED in all pre-L versions
    Settings.System.putString(
        context.getContentResolver(), Settings.System.NEXT_ALARM_FORMATTED, timeString);
    Intent alarmChanged = new Intent(SYSTEM_ALARM_CHANGE_ACTION);
    alarmChanged.putExtra("alarmSet", showStatusIcon);
    context.sendBroadcast(alarmChanged);
  }
示例#16
0
    @Override
    public void scheduleInstanceStateChange(
        Context context, Calendar time, AlarmInstance instance, int newState) {
      final long timeInMillis = time.getTimeInMillis();
      LogUtils.v(
          "Scheduling state change %d to instance %d at %s (%d)",
          newState, instance.mId, AlarmUtils.getFormattedTime(context, time), timeInMillis);
      final Intent stateChangeIntent =
          createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, newState);
      // Treat alarm state change as high priority, use foreground broadcasts
      stateChangeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
      PendingIntent pendingIntent =
          PendingIntent.getBroadcast(
              context, instance.hashCode(), stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT);

      final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
      if (Utils.isKitKatOrLater()) {
        am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
      } else {
        am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
      }
    }
  @Override
  public void run() {
    // only allow on background thread
    if (Looper.myLooper() == Looper.getMainLooper()) {
      throw new IllegalStateException("Must be called on a background thread");
    }

    final String searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE);
    // if search mode isn't specified show all alarms in the UI picker
    if (searchMode == null) {
      mMatchingAlarms.addAll(mAlarms);
      return;
    }

    final ContentResolver cr = mContext.getContentResolver();
    switch (searchMode) {
      case AlarmClock.ALARM_SEARCH_MODE_TIME:
        // at least one of these has to be specified in this search mode.
        final int hour = mIntent.getIntExtra(AlarmClock.EXTRA_HOUR, -1);
        // if minutes weren't specified default to 0
        final int minutes = mIntent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0);
        final Boolean isPm = (Boolean) mIntent.getExtras().get(AlarmClock.EXTRA_IS_PM);
        boolean badInput = isPm != null && hour > 12 && isPm;
        badInput |= hour < 0 || hour > 23;
        badInput |= minutes < 0 || minutes > 59;

        if (badInput) {
          final String[] ampm = new DateFormatSymbols().getAmPmStrings();
          final String amPm = isPm == null ? "" : (isPm ? ampm[1] : ampm[0]);
          final String reason = mContext.getString(R.string.invalid_time, hour, minutes, amPm);
          notifyFailureAndLog(reason, mActivity);
          return;
        }

        final int hour24 = Boolean.TRUE.equals(isPm) && hour < 12 ? (hour + 12) : hour;

        // there might me multiple alarms at the same time
        for (Alarm alarm : mAlarms) {
          if (alarm.hour == hour24 && alarm.minutes == minutes) {
            mMatchingAlarms.add(alarm);
          }
        }
        if (mMatchingAlarms.isEmpty()) {
          final String reason = mContext.getString(R.string.no_alarm_at, hour24, minutes);
          notifyFailureAndLog(reason, mActivity);
          return;
        }
        break;
      case AlarmClock.ALARM_SEARCH_MODE_NEXT:
        final AlarmInstance nextAlarm = AlarmStateManager.getNextFiringAlarm(mContext);
        if (nextAlarm == null) {
          final String reason = mContext.getString(R.string.no_scheduled_alarms);
          notifyFailureAndLog(reason, mActivity);
          return;
        }

        // get time from nextAlarm and see if there are any other alarms matching this time
        final Calendar nextTime = nextAlarm.getAlarmTime();
        final List<Alarm> alarmsFiringAtSameTime =
            getAlarmsByHourMinutes(
                nextTime.get(Calendar.HOUR_OF_DAY), nextTime.get(Calendar.MINUTE), cr);
        // there might me multiple alarms firing next
        mMatchingAlarms.addAll(alarmsFiringAtSameTime);
        break;
      case AlarmClock.ALARM_SEARCH_MODE_ALL:
        mMatchingAlarms.addAll(mAlarms);
        break;
      case AlarmClock.ALARM_SEARCH_MODE_LABEL:
        // EXTRA_MESSAGE has to be set in this mode
        final String label = mIntent.getStringExtra(AlarmClock.EXTRA_MESSAGE);
        if (label == null) {
          final String reason = mContext.getString(R.string.no_label_specified);
          notifyFailureAndLog(reason, mActivity);
          return;
        }

        // there might me multiple alarms with this label
        for (Alarm alarm : mAlarms) {
          if (alarm.label.contains(label)) {
            mMatchingAlarms.add(alarm);
          }
        }

        if (mMatchingAlarms.isEmpty()) {
          final String reason = mContext.getString(R.string.no_alarms_with_label);
          notifyFailureAndLog(reason, mActivity);
          return;
        }
        break;
    }
  }
示例#18
0
  private void handleIntent(Context context, Intent intent) {
    final String action = intent.getAction();
    LogUtils.v("AlarmStateManager received intent " + intent);
    if (CHANGE_STATE_ACTION.equals(action)) {
      Uri uri = intent.getData();
      AlarmInstance instance =
          AlarmInstance.getInstance(context.getContentResolver(), AlarmInstance.getId(uri));
      if (instance == null) {
        // Not a big deal, but it shouldn't happen
        LogUtils.e("Can not change state for unknown instance: " + uri);
        return;
      }

      int globalId = getGlobalIntentId(context);
      int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1);
      int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1);
      if (intentId != globalId) {
        LogUtils.i(
            "IntentId: " + intentId + " GlobalId: " + globalId + " AlarmState: " + alarmState);
        // Allows dismiss/snooze requests to go through
        if (!intent.hasCategory(ALARM_DISMISS_TAG) && !intent.hasCategory(ALARM_SNOOZE_TAG)) {
          LogUtils.i("Ignoring old Intent");
          return;
        }
      }

      if (intent.getBooleanExtra(FROM_NOTIFICATION_EXTRA, false)) {
        if (intent.hasCategory(ALARM_DISMISS_TAG)) {
          Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_notification);
        } else if (intent.hasCategory(ALARM_SNOOZE_TAG)) {
          Events.sendAlarmEvent(R.string.action_snooze, R.string.label_notification);
        }
      }

      if (alarmState >= 0) {
        setAlarmState(context, instance, alarmState);
      } else {
        registerInstance(context, instance, true);
      }
    } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) {
      Uri uri = intent.getData();
      AlarmInstance instance =
          AlarmInstance.getInstance(context.getContentResolver(), AlarmInstance.getId(uri));

      if (instance == null) {
        LogUtils.e("Null alarminstance for SHOW_AND_DISMISS");
        // dismiss the notification
        final int id = intent.getIntExtra(AlarmNotifications.EXTRA_NOTIFICATION_ID, -1);
        if (id != -1) {
          NotificationManagerCompat.from(context).cancel(id);
        }
        return;
      }

      long alarmId = instance.mAlarmId == null ? Alarm.INVALID_ID : instance.mAlarmId;
      Intent viewAlarmIntent = Alarm.createIntent(context, DeskClock.class, alarmId);
      viewAlarmIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
      viewAlarmIntent.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId);
      viewAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(viewAlarmIntent);
      setDismissState(context, instance);
    }
  }
示例#19
0
  /**
   * This registers the AlarmInstance to the state manager. This will look at the instance and
   * choose the most appropriate state to put it in. This is primarily used by new alarms, but it
   * can also be called when the system time changes.
   *
   * <p>Most state changes are handled by the states themselves, but during major time changes we
   * have to correct the alarm instance state. This means we have to handle special cases as
   * describe below:
   *
   * <ul>
   *   <li>Make sure all dismissed alarms are never re-activated
   *   <li>Make sure pre-dismissed alarms stay predismissed
   *   <li>Make sure firing alarms stayed fired unless they should be auto-silenced
   *   <li>Missed instance that have parents should be re-enabled if we went back in time
   *   <li>If alarm was SNOOZED, then show the notification but don't update time
   *   <li>If low priority notification was hidden, then make sure it stays hidden
   * </ul>
   *
   * If none of these special case are found, then we just check the time and see what is the proper
   * state for the instance.
   *
   * @param context application context
   * @param instance to register
   */
  public static void registerInstance(
      Context context, AlarmInstance instance, boolean updateNextAlarm) {
    final ContentResolver cr = context.getContentResolver();
    final Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    final Calendar currentTime = getCurrentTime();
    final Calendar alarmTime = instance.getAlarmTime();
    final Calendar timeoutTime = instance.getTimeout(context);
    final Calendar lowNotificationTime = instance.getLowNotificationTime();
    final Calendar highNotificationTime = instance.getHighNotificationTime();
    final Calendar missedTTL = instance.getMissedTimeToLive();

    // Handle special use cases here
    if (instance.mAlarmState == AlarmInstance.DISMISSED_STATE) {
      // This should never happen, but add a quick check here
      LogUtils.e("Alarm Instance is dismissed, but never deleted");
      setDismissState(context, instance);
      return;
    } else if (instance.mAlarmState == AlarmInstance.FIRED_STATE) {
      // Keep alarm firing, unless it should be timed out
      boolean hasTimeout = timeoutTime != null && currentTime.after(timeoutTime);
      if (!hasTimeout) {
        setFiredState(context, instance);
        return;
      }
    } else if (instance.mAlarmState == AlarmInstance.MISSED_STATE) {
      if (currentTime.before(alarmTime)) {
        if (instance.mAlarmId == null) {
          // This instance parent got deleted (ie. deleteAfterUse), so
          // we should not re-activate it.-
          setDismissState(context, instance);
          return;
        }

        // TODO: This will re-activate missed snoozed alarms, but will
        // use our normal notifications. This is not ideal, but very rare use-case.
        // We should look into fixing this in the future.

        // Make sure we re-enable the parent alarm of the instance
        // because it will get activated by by the below code
        alarm.enabled = true;
        Alarm.updateAlarm(cr, alarm);
      }
    } else if (instance.mAlarmState == AlarmInstance.PREDISMISSED_STATE) {
      if (currentTime.before(alarmTime)) {
        setPreDismissState(context, instance);
      } else {
        setDismissState(context, instance);
      }
      return;
    }

    // Fix states that are time sensitive
    if (currentTime.after(missedTTL)) {
      // Alarm is so old, just dismiss it
      setDismissState(context, instance);
    } else if (currentTime.after(alarmTime)) {
      // There is a chance that the TIME_SET occurred right when the alarm should go off, so
      // we need to add a check to see if we should fire the alarm instead of marking it
      // missed.
      Calendar alarmBuffer = Calendar.getInstance();
      alarmBuffer.setTime(alarmTime.getTime());
      alarmBuffer.add(Calendar.SECOND, ALARM_FIRE_BUFFER);
      if (currentTime.before(alarmBuffer)) {
        setFiredState(context, instance);
      } else {
        setMissedState(context, instance);
      }
    } else if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
      // We only want to display snooze notification and not update the time,
      // so handle showing the notification directly
      AlarmNotifications.showSnoozeNotification(context, instance);
      scheduleInstanceStateChange(
          context, instance.getAlarmTime(), instance, AlarmInstance.FIRED_STATE);
    } else if (currentTime.after(highNotificationTime)) {
      setHighNotificationState(context, instance);
    } else if (currentTime.after(lowNotificationTime)) {
      // Only show low notification if it wasn't hidden in the past
      if (instance.mAlarmState == AlarmInstance.HIDE_NOTIFICATION_STATE) {
        setHideNotificationState(context, instance);
      } else {
        setLowNotificationState(context, instance);
      }
    } else {
      // Alarm is still active, so initialize as a silent alarm
      setSilentState(context, instance);
    }

    // The caller prefers to handle updateNextAlarm for optimization
    if (updateNextAlarm) {
      updateNextAlarm(context);
    }
  }