/** 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 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); }
/** * 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); }
/** * 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); } } }
/** * 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); }
/** * 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); } }
/** * This will not change the state of instance, but remove it's notifications and alarm timers. * * @param context application context * @param instance to unregister */ public static void unregisterInstance(Context context, AlarmInstance instance) { // Stop alarm if this instance is firing it AlarmService.stopAlarm(context, instance); AlarmNotifications.clearNotification(context, instance); cancelScheduledInstanceStateChange(context, instance); }