/** * 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); }
private List<Alarm> getAlarmsByHourMinutes(int hour24, int minutes, ContentResolver cr) { // if we want to dismiss we should only add enabled alarms final String selection = String.format("%s=? AND %s=? AND %s=?", Alarm.HOUR, Alarm.MINUTES, Alarm.ENABLED); final String[] args = {String.valueOf(hour24), String.valueOf(minutes), "1"}; return Alarm.getAlarms(cr, selection, args); }
/** * 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); } }
/** * 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); } } }
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); } }
/** * 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); } }