Home | History | Annotate | Download | only in alarms
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.deskclock.alarms;
     17 
     18 import android.app.AlarmManager;
     19 import android.app.PendingIntent;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.SharedPreferences;
     25 import android.net.Uri;
     26 import android.os.PowerManager;
     27 import android.preference.PreferenceManager;
     28 import android.widget.Toast;
     29 
     30 import com.android.deskclock.AlarmAlertWakeLock;
     31 import com.android.deskclock.AlarmClockFragment;
     32 import com.android.deskclock.AlarmUtils;
     33 import com.android.deskclock.AsyncHandler;
     34 import com.android.deskclock.DeskClock;
     35 import com.android.deskclock.Log;
     36 import com.android.deskclock.R;
     37 import com.android.deskclock.SettingsActivity;
     38 import com.android.deskclock.Utils;
     39 import com.android.deskclock.provider.Alarm;
     40 import com.android.deskclock.provider.AlarmInstance;
     41 
     42 import java.util.Calendar;
     43 import java.util.List;
     44 
     45 /**
     46  * This class handles all the state changes for alarm instances. You need to
     47  * register all alarm instances with the state manager if you want them to
     48  * be activated. If a major time change has occurred (ie. TIMEZONE_CHANGE, TIMESET_CHANGE),
     49  * then you must also re-register instances to fix their states.
     50  *
     51  * Please see {@link #registerInstance) for special transitions when major time changes
     52  * occur.
     53  *
     54  * Following states:
     55  *
     56  * SILENT_STATE:
     57  * This state is used when the alarm is activated, but doesn't need to display anything. It
     58  * is in charge of changing the alarm instance state to a LOW_NOTIFICATION_STATE.
     59  *
     60  * LOW_NOTIFICATION_STATE:
     61  * This state is used to notify the user that the alarm will go off
     62  * {@link AlarmInstance.LOW_NOTIFICATION_HOUR_OFFSET}. This
     63  * state handles the state changes to HIGH_NOTIFICATION_STATE, HIDE_NOTIFICATION_STATE and
     64  * DISMISS_STATE.
     65  *
     66  * HIDE_NOTIFICATION_STATE:
     67  * This is a transient state of the LOW_NOTIFICATION_STATE, where the user wants to hide the
     68  * notification. This will sit and wait until the HIGH_PRIORITY_NOTIFICATION should go off.
     69  *
     70  * HIGH_NOTIFICATION_STATE:
     71  * This state behaves like the LOW_NOTIFICATION_STATE, but doesn't allow the user to hide it.
     72  * This state is in charge of triggering a FIRED_STATE or DISMISS_STATE.
     73  *
     74  * SNOOZED_STATE:
     75  * The SNOOZED_STATE behaves like a HIGH_NOTIFICATION_STATE, but with a different message. It
     76  * also increments the alarm time in the instance to reflect the new snooze time.
     77  *
     78  * FIRED_STATE:
     79  * The FIRED_STATE is used when the alarm is firing. It will start the AlarmService, and wait
     80  * until the user interacts with the alarm via SNOOZED_STATE or DISMISS_STATE change. If the user
     81  * doesn't then it might be change to MISSED_STATE if auto-silenced was enabled.
     82  *
     83  * MISSED_STATE:
     84  * The MISSED_STATE is used when the alarm already fired, but the user could not interact with
     85  * it. At this point the alarm instance is dead and we check the parent alarm to see if we need
     86  * to disable or schedule a new alarm_instance. There is also a notification shown to the user
     87  * that he/she missed the alarm and that stays for
     88  * {@link AlarmInstance.MISSED_TIME_TO_LIVE_HOUR_OFFSET} or until the user acknownledges it.
     89  *
     90  * DISMISS_STATE:
     91  * This is really a transient state that will properly delete the alarm instance. Use this state,
     92  * whenever you want to get rid of the alarm instance. This state will also check the alarm
     93  * parent to see if it should disable or schedule a new alarm instance.
     94  */
     95 public final class AlarmStateManager extends BroadcastReceiver {
     96     // These defaults must match the values in res/xml/settings.xml
     97     private static final String DEFAULT_SNOOZE_MINUTES = "10";
     98 
     99     // Intent action to trigger an instance state change.
    100     public static final String CHANGE_STATE_ACTION = "change_state";
    101 
    102     // Intent action to show the alarm and dismiss the instance
    103     public static final String SHOW_AND_DISMISS_ALARM_ACTION = "show_and_dismiss_alarm";
    104 
    105     // Extra key to set the desired state change.
    106     public static final String ALARM_STATE_EXTRA = "intent.extra.alarm.state";
    107 
    108     // Extra key to set the global broadcast id.
    109     private static final String ALARM_GLOBAL_ID_EXTRA = "intent.extra.alarm.global.id";
    110 
    111     // Intent category tag used when schedule state change intents in alarm manager.
    112     public static final String ALARM_MANAGER_TAG = "ALARM_MANAGER";
    113 
    114     // Buffer time in seconds to fire alarm instead of marking it missed.
    115     public static final int ALARM_FIRE_BUFFER = 15;
    116 
    117     public static int getGlobalIntentId(Context context) {
    118         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    119         return prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1);
    120     }
    121 
    122     public static void updateGloablIntentId(Context context) {
    123         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    124         int globalId = prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1) + 1;
    125         prefs.edit().putInt(ALARM_GLOBAL_ID_EXTRA, globalId).commit();
    126     }
    127 
    128     /**
    129      * Find and notify system what the next alarm that will fire. This is used
    130      * to update text in the system and widgets.
    131      *
    132      * @param context application context
    133      */
    134     public static void updateNextAlarm(Context context) {
    135         AlarmInstance nextAlarm = null;
    136         ContentResolver cr = context.getContentResolver();
    137         String activeAlarmQuery = AlarmInstance.ALARM_STATE + "<" + AlarmInstance.FIRED_STATE;
    138         for (AlarmInstance instance : AlarmInstance.getInstances(cr, activeAlarmQuery)) {
    139             if (nextAlarm == null || instance.getAlarmTime().before(nextAlarm.getAlarmTime())) {
    140                 nextAlarm = instance;
    141             }
    142         }
    143         AlarmNotifications.broadcastNextAlarm(context, nextAlarm);
    144     }
    145 
    146     /**
    147      * Used by dismissed and missed states, to update parent alarm. This will either
    148      * disable, delete or reschedule parent alarm.
    149      *
    150      * @param context application context
    151      * @param instance to update parent for
    152      */
    153     private static void updateParentAlarm(Context context, AlarmInstance instance) {
    154         ContentResolver cr = context.getContentResolver();
    155         Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    156         if (alarm == null) {
    157             Log.e("Parent has been deleted with instance: " + instance.toString());
    158             return;
    159         }
    160 
    161         if (!alarm.daysOfWeek.isRepeating()) {
    162             if (alarm.deleteAfterUse) {
    163                 Log.i("Deleting parent alarm: " + alarm.id);
    164                 Alarm.deleteAlarm(cr, alarm.id);
    165             } else {
    166                 Log.i("Disabling parent alarm: " + alarm.id);
    167                 alarm.enabled = false;
    168                 Alarm.updateAlarm(cr, alarm);
    169             }
    170         } else {
    171             // This is a optimization for really old alarm instances. This prevent us
    172             // from scheduling and dismissing alarms up to current time.
    173             Calendar currentTime = Calendar.getInstance();
    174             Calendar alarmTime = instance.getAlarmTime();
    175             if (currentTime.after(alarmTime)) {
    176                 alarmTime = currentTime;
    177             }
    178             AlarmInstance nextRepeatedInstance = alarm.createInstanceAfter(alarmTime);
    179             Log.i("Creating new instance for repeating alarm " + alarm.id + " at "  +
    180                     AlarmUtils.getFormattedTime(context, nextRepeatedInstance.getAlarmTime()));
    181             AlarmInstance.addInstance(cr, nextRepeatedInstance);
    182             registerInstance(context, nextRepeatedInstance, true);
    183         }
    184     }
    185 
    186     /**
    187      * Utility method to create a proper change state intent.
    188      *
    189      * @param context application context
    190      * @param tag used to make intent differ from other state change intents.
    191      * @param instance to change state to
    192      * @param state to change to.
    193      * @return intent that can be used to change an alarm instance state
    194      */
    195     public static Intent createStateChangeIntent(Context context, String tag,
    196             AlarmInstance instance, Integer state) {
    197         Intent intent = AlarmInstance.createIntent(context, AlarmStateManager.class, instance.mId);
    198         intent.setAction(CHANGE_STATE_ACTION);
    199         intent.addCategory(tag);
    200         intent.putExtra(ALARM_GLOBAL_ID_EXTRA, getGlobalIntentId(context));
    201         if (state != null) {
    202             intent.putExtra(ALARM_STATE_EXTRA, state.intValue());
    203         }
    204         return intent;
    205     }
    206 
    207     /**
    208      * Schedule alarm instance state changes with {@link AlarmManager}.
    209      *
    210      * @param context application context
    211      * @param time to trigger state change
    212      * @param instance to change state to
    213      * @param newState to change to
    214      */
    215     private static void scheduleInstanceStateChange(Context context, Calendar time,
    216             AlarmInstance instance, int newState) {
    217         long timeInMillis = time.getTimeInMillis();
    218         Log.v("Scheduling state change " + newState + " to instance " + instance.mId +
    219                 " at " + AlarmUtils.getFormattedTime(context, time) + " (" + timeInMillis + ")");
    220         Intent stateChangeIntent = createStateChangeIntent(context, ALARM_MANAGER_TAG, instance,
    221                 newState);
    222         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(),
    223                 stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    224 
    225         AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    226         if (Utils.isKitKatOrLater()) {
    227             am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
    228         } else {
    229             am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
    230         }
    231     }
    232 
    233     /**
    234      * Cancel all {@link AlarmManager} timers for instance.
    235      *
    236      * @param context application context
    237      * @param instance to disable all {@link AlarmManager} timers
    238      */
    239     private static void cancelScheduledInstance(Context context, AlarmInstance instance) {
    240         Log.v("Canceling instance " + instance.mId + " timers");
    241 
    242         // Create a PendingIntent that will match any one set for this instance
    243         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(),
    244                 createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, null),
    245                 PendingIntent.FLAG_UPDATE_CURRENT);
    246 
    247         AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    248         am.cancel(pendingIntent);
    249     }
    250 
    251 
    252     /**
    253      * This will set the alarm instance to the SILENT_STATE and update
    254      * the application notifications and schedule any state changes that need
    255      * to occur in the future.
    256      *
    257      * @param context application context
    258      * @param instance to set state to
    259      */
    260     public static void setSilentState(Context context, AlarmInstance instance) {
    261         Log.v("Setting silent state to instance " + instance.mId);
    262 
    263         // Update alarm in db
    264         ContentResolver contentResolver = context.getContentResolver();
    265         instance.mAlarmState = AlarmInstance.SILENT_STATE;
    266         AlarmInstance.updateInstance(contentResolver, instance);
    267 
    268         // Setup instance notification and scheduling timers
    269         AlarmNotifications.clearNotification(context, instance);
    270         scheduleInstanceStateChange(context, instance.getLowNotificationTime(),
    271                 instance, AlarmInstance.LOW_NOTIFICATION_STATE);
    272     }
    273 
    274     /**
    275      * This will set the alarm instance to the LOW_NOTIFICATION_STATE and update
    276      * the application notifications and schedule any state changes that need
    277      * to occur in the future.
    278      *
    279      * @param context application context
    280      * @param instance to set state to
    281      */
    282     public static void setLowNotificationState(Context context, AlarmInstance instance) {
    283         Log.v("Setting low notification state to instance " + instance.mId);
    284 
    285         // Update alarm state in db
    286         ContentResolver contentResolver = context.getContentResolver();
    287         instance.mAlarmState = AlarmInstance.LOW_NOTIFICATION_STATE;
    288         AlarmInstance.updateInstance(contentResolver, instance);
    289 
    290         // Setup instance notification and scheduling timers
    291         AlarmNotifications.showLowPriorityNotification(context, instance);
    292         scheduleInstanceStateChange(context, instance.getHighNotificationTime(),
    293                 instance, AlarmInstance.HIGH_NOTIFICATION_STATE);
    294     }
    295 
    296     /**
    297      * This will set the alarm instance to the HIDE_NOTIFICATION_STATE and update
    298      * the application notifications and schedule any state changes that need
    299      * to occur in the future.
    300      *
    301      * @param context application context
    302      * @param instance to set state to
    303      */
    304     public static void setHideNotificationState(Context context, AlarmInstance instance) {
    305         Log.v("Setting hide notification state to instance " + instance.mId);
    306 
    307         // Update alarm state in db
    308         ContentResolver contentResolver = context.getContentResolver();
    309         instance.mAlarmState = AlarmInstance.HIDE_NOTIFICATION_STATE;
    310         AlarmInstance.updateInstance(contentResolver, instance);
    311 
    312         // Setup instance notification and scheduling timers
    313         AlarmNotifications.clearNotification(context, instance);
    314         scheduleInstanceStateChange(context, instance.getHighNotificationTime(),
    315                 instance, AlarmInstance.HIGH_NOTIFICATION_STATE);
    316     }
    317 
    318     /**
    319      * This will set the alarm instance to the HIGH_NOTIFICATION_STATE and update
    320      * the application notifications and schedule any state changes that need
    321      * to occur in the future.
    322      *
    323      * @param context application context
    324      * @param instance to set state to
    325      */
    326     public static void setHighNotificationState(Context context, AlarmInstance instance) {
    327         Log.v("Setting high notification state to instance " + instance.mId);
    328 
    329         // Update alarm state in db
    330         ContentResolver contentResolver = context.getContentResolver();
    331         instance.mAlarmState = AlarmInstance.HIGH_NOTIFICATION_STATE;
    332         AlarmInstance.updateInstance(contentResolver, instance);
    333 
    334         // Setup instance notification and scheduling timers
    335         AlarmNotifications.showHighPriorityNotification(context, instance);
    336         scheduleInstanceStateChange(context, instance.getAlarmTime(),
    337                 instance, AlarmInstance.FIRED_STATE);
    338     }
    339 
    340     /**
    341      * This will set the alarm instance to the FIRED_STATE and update
    342      * the application notifications and schedule any state changes that need
    343      * to occur in the future.
    344      *
    345      * @param context application context
    346      * @param instance to set state to
    347      */
    348     public static void setFiredState(Context context, AlarmInstance instance) {
    349         Log.v("Setting fire state to instance " + instance.mId);
    350 
    351         // Update alarm state in db
    352         ContentResolver contentResolver = context.getContentResolver();
    353         instance.mAlarmState = AlarmInstance.FIRED_STATE;
    354         AlarmInstance.updateInstance(contentResolver, instance);
    355 
    356         // Start the alarm and schedule timeout timer for it
    357         AlarmService.startAlarm(context, instance);
    358 
    359         Calendar timeout = instance.getTimeout(context);
    360         if (timeout != null) {
    361             scheduleInstanceStateChange(context, timeout, instance, AlarmInstance.MISSED_STATE);
    362         }
    363 
    364         // Instance not valid anymore, so find next alarm that will fire and notify system
    365         updateNextAlarm(context);
    366     }
    367 
    368     /**
    369      * This will set the alarm instance to the SNOOZE_STATE and update
    370      * the application notifications and schedule any state changes that need
    371      * to occur in the future.
    372      *
    373      * @param context application context
    374      * @param instance to set state to
    375      */
    376     public static void setSnoozeState(Context context, AlarmInstance instance) {
    377         // Stop alarm if this instance is firing it
    378         AlarmService.stopAlarm(context, instance);
    379 
    380         // Calculate the new snooze alarm time
    381         String snoozeMinutesStr = PreferenceManager.getDefaultSharedPreferences(context)
    382                 .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE_MINUTES);
    383         int snoozeMinutes = Integer.parseInt(snoozeMinutesStr);
    384         Calendar newAlarmTime = Calendar.getInstance();
    385         newAlarmTime.add(Calendar.MINUTE, snoozeMinutes);
    386 
    387         // Update alarm state and new alarm time in db.
    388         Log.v("Setting snoozed state to instance " + instance.mId + " for "
    389                 + AlarmUtils.getFormattedTime(context, newAlarmTime));
    390         instance.setAlarmTime(newAlarmTime);
    391         instance.mAlarmState = AlarmInstance.SNOOZE_STATE;
    392         AlarmInstance.updateInstance(context.getContentResolver(), instance);
    393 
    394         // Setup instance notification and scheduling timers
    395         AlarmNotifications.showSnoozeNotification(context, instance);
    396         scheduleInstanceStateChange(context, instance.getAlarmTime(),
    397                 instance, AlarmInstance.FIRED_STATE);
    398 
    399         // Display the snooze minutes in a toast.
    400         String displayTime = String.format(context.getResources().getQuantityText
    401                 (R.plurals.alarm_alert_snooze_set, snoozeMinutes).toString(), snoozeMinutes);
    402         Toast.makeText(context, displayTime, Toast.LENGTH_LONG).show();
    403 
    404         // Instance time changed, so find next alarm that will fire and notify system
    405         updateNextAlarm(context);
    406 
    407     }
    408 
    409     /**
    410      * This will set the alarm instance to the MISSED_STATE and update
    411      * the application notifications and schedule any state changes that need
    412      * to occur in the future.
    413      *
    414      * @param context application context
    415      * @param instance to set state to
    416      */
    417     public static void setMissedState(Context context, AlarmInstance instance) {
    418         Log.v("Setting missed state to instance " + instance.mId);
    419         // Stop alarm if this instance is firing it
    420         AlarmService.stopAlarm(context, instance);
    421 
    422         // Check parent if it needs to reschedule, disable or delete itself
    423         if (instance.mAlarmId != null) {
    424             updateParentAlarm(context, instance);
    425         }
    426 
    427         // Update alarm state
    428         ContentResolver contentResolver = context.getContentResolver();
    429         instance.mAlarmState = AlarmInstance.MISSED_STATE;
    430         AlarmInstance.updateInstance(contentResolver, instance);
    431 
    432         // Setup instance notification and scheduling timers
    433         AlarmNotifications.showMissedNotification(context, instance);
    434         scheduleInstanceStateChange(context, instance.getMissedTimeToLive(),
    435                 instance, AlarmInstance.DISMISSED_STATE);
    436 
    437         // Instance is not valid anymore, so find next alarm that will fire and notify system
    438         updateNextAlarm(context);
    439 
    440     }
    441 
    442     /**
    443      * This will set the alarm instance to the SILENT_STATE and update
    444      * the application notifications and schedule any state changes that need
    445      * to occur in the future.
    446      *
    447      * @param context application context
    448      * @param instance to set state to
    449      */
    450     public static void setDismissState(Context context, AlarmInstance instance) {
    451         Log.v("Setting dismissed state to instance " + instance.mId);
    452 
    453         // Remove all other timers and notifications associated to it
    454         unregisterInstance(context, instance);
    455 
    456         // Check parent if it needs to reschedule, disable or delete itself
    457         if (instance.mAlarmId != null) {
    458             updateParentAlarm(context, instance);
    459         }
    460 
    461         // Delete instance as it is not needed anymore
    462         AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    463 
    464         // Instance is not valid anymore, so find next alarm that will fire and notify system
    465         updateNextAlarm(context);
    466     }
    467 
    468     /**
    469      * This will not change the state of instance, but remove it's notifications and
    470      * alarm timers.
    471      *
    472      * @param context application context
    473      * @param instance to unregister
    474      */
    475     public static void unregisterInstance(Context context, AlarmInstance instance) {
    476         // Stop alarm if this instance is firing it
    477         AlarmService.stopAlarm(context, instance);
    478         AlarmNotifications.clearNotification(context, instance);
    479         cancelScheduledInstance(context, instance);
    480     }
    481 
    482     /**
    483      * This registers the AlarmInstance to the state manager. This will look at the instance
    484      * and choose the most appropriate state to put it in. This is primarily used by new
    485      * alarms, but it can also be called when the system time changes.
    486      *
    487      * Most state changes are handled by the states themselves, but during major time changes we
    488      * have to correct the alarm instance state. This means we have to handle special cases as
    489      * describe below:
    490      *
    491      * <ul>
    492      *     <li>Make sure all dismissed alarms are never re-activated</li>
    493      *     <li>Make sure firing alarms stayed fired unless they should be auto-silenced</li>
    494      *     <li>Missed instance that have parents should be re-enabled if we went back in time</li>
    495      *     <li>If alarm was SNOOZED, then show the notification but don't update time</li>
    496      *     <li>If low priority notification was hidden, then make sure it stays hidden</li>
    497      * </ul>
    498      *
    499      * If none of these special case are found, then we just check the time and see what is the
    500      * proper state for the instance.
    501      *
    502      * @param context application context
    503      * @param instance to register
    504      */
    505     public static void registerInstance(Context context, AlarmInstance instance,
    506             boolean updateNextAlarm) {
    507         Calendar currentTime = Calendar.getInstance();
    508         Calendar alarmTime = instance.getAlarmTime();
    509         Calendar timeoutTime = instance.getTimeout(context);
    510         Calendar lowNotificationTime = instance.getLowNotificationTime();
    511         Calendar highNotificationTime = instance.getHighNotificationTime();
    512         Calendar missedTTL = instance.getMissedTimeToLive();
    513 
    514         // Handle special use cases here
    515         if (instance.mAlarmState == AlarmInstance.DISMISSED_STATE) {
    516             // This should never happen, but add a quick check here
    517             Log.e("Alarm Instance is dismissed, but never deleted");
    518             setDismissState(context, instance);
    519             return;
    520         } else if (instance.mAlarmState == AlarmInstance.FIRED_STATE) {
    521             // Keep alarm firing, unless it should be timed out
    522             boolean hasTimeout = timeoutTime != null && currentTime.after(timeoutTime);
    523             if (!hasTimeout) {
    524                 setFiredState(context, instance);
    525                 return;
    526             }
    527         } else if (instance.mAlarmState == AlarmInstance.MISSED_STATE) {
    528             if (currentTime.before(alarmTime)) {
    529                 if (instance.mAlarmId == null) {
    530                     // This instance parent got deleted (ie. deleteAfterUse), so
    531                     // we should not re-activate it.-
    532                     setDismissState(context, instance);
    533                     return;
    534                 }
    535 
    536                 // TODO: This will re-activate missed snoozed alarms, but will
    537                 // use our normal notifications. This is not ideal, but very rare use-case.
    538                 // We should look into fixing this in the future.
    539 
    540                 // Make sure we re-enable the parent alarm of the instance
    541                 // because it will get activated by by the below code
    542                 ContentResolver cr = context.getContentResolver();
    543                 Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    544                 alarm.enabled = true;
    545                 Alarm.updateAlarm(cr, alarm);
    546             }
    547         }
    548 
    549         // Fix states that are time sensitive
    550         if (currentTime.after(missedTTL)) {
    551             // Alarm is so old, just dismiss it
    552             setDismissState(context, instance);
    553         } else if (currentTime.after(alarmTime)) {
    554             // There is a chance that the TIME_SET occurred right when the alarm should go off, so
    555             // we need to add a check to see if we should fire the alarm instead of marking it
    556             // missed.
    557             Calendar alarmBuffer = Calendar.getInstance();
    558             alarmBuffer.setTime(alarmTime.getTime());
    559             alarmBuffer.add(Calendar.SECOND, ALARM_FIRE_BUFFER);
    560             if (currentTime.before(alarmBuffer)) {
    561                 setFiredState(context, instance);
    562             } else {
    563                 setMissedState(context, instance);
    564             }
    565         } else if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
    566             // We only want to display snooze notification and not update the time,
    567             // so handle showing the notification directly
    568             AlarmNotifications.showSnoozeNotification(context, instance);
    569             scheduleInstanceStateChange(context, instance.getAlarmTime(),
    570                     instance, AlarmInstance.FIRED_STATE);
    571         } else if (currentTime.after(highNotificationTime)) {
    572             setHighNotificationState(context, instance);
    573         } else if (currentTime.after(lowNotificationTime)) {
    574             // Only show low notification if it wasn't hidden in the past
    575             if (instance.mAlarmState == AlarmInstance.HIDE_NOTIFICATION_STATE) {
    576                 setHideNotificationState(context, instance);
    577             } else {
    578                 setLowNotificationState(context, instance);
    579             }
    580         } else {
    581           // Alarm is still active, so initialize as a silent alarm
    582           setSilentState(context, instance);
    583         }
    584 
    585         // The caller prefers to handle updateNextAlarm for optimization
    586         if (updateNextAlarm) {
    587             updateNextAlarm(context);
    588         }
    589     }
    590 
    591     /**
    592      * This will delete and unregister all instances associated with alarmId, without affect
    593      * the alarm itself. This should be used whenever modifying or deleting an alarm.
    594      *
    595      * @param context application context
    596      * @param alarmId to find instances to delete.
    597      */
    598     public static void deleteAllInstances(Context context, long alarmId) {
    599         ContentResolver cr = context.getContentResolver();
    600         List<AlarmInstance> instances = AlarmInstance.getInstancesByAlarmId(cr, alarmId);
    601         for (AlarmInstance instance : instances) {
    602             unregisterInstance(context, instance);
    603             AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    604         }
    605         updateNextAlarm(context);
    606     }
    607 
    608     /**
    609      * Fix and update all alarm instance when a time change event occurs.
    610      *
    611      * @param context application context
    612      */
    613     public static void fixAlarmInstances(Context context) {
    614         // Register all instances after major time changes or when phone restarts
    615         // TODO: Refactor this code to not use the overloaded registerInstance method.
    616         ContentResolver contentResolver = context.getContentResolver();
    617         for (AlarmInstance instance : AlarmInstance.getInstances(contentResolver, null)) {
    618             AlarmStateManager.registerInstance(context, instance, false);
    619         }
    620         AlarmStateManager.updateNextAlarm(context);
    621     }
    622 
    623     /**
    624      * Utility method to set alarm instance state via constants.
    625      *
    626      * @param context application context
    627      * @param instance to change state on
    628      * @param state to change to
    629      */
    630     public void setAlarmState(Context context, AlarmInstance instance, int state) {
    631         switch(state) {
    632             case AlarmInstance.SILENT_STATE:
    633                 setSilentState(context, instance);
    634                 break;
    635             case AlarmInstance.LOW_NOTIFICATION_STATE:
    636                 setLowNotificationState(context, instance);
    637                 break;
    638             case AlarmInstance.HIDE_NOTIFICATION_STATE:
    639                 setHideNotificationState(context, instance);
    640                 break;
    641             case AlarmInstance.HIGH_NOTIFICATION_STATE:
    642                 setHighNotificationState(context, instance);
    643                 break;
    644             case AlarmInstance.FIRED_STATE:
    645                 setFiredState(context, instance);
    646                 break;
    647             case AlarmInstance.SNOOZE_STATE:
    648                 setSnoozeState(context, instance);
    649                 break;
    650             case AlarmInstance.MISSED_STATE:
    651                 setMissedState(context, instance);
    652                 break;
    653             case AlarmInstance.DISMISSED_STATE:
    654                 setDismissState(context, instance);
    655                 break;
    656             default:
    657                 Log.e("Trying to change to unknown alarm state: " + state);
    658         }
    659     }
    660 
    661     @Override
    662     public void onReceive(final Context context, final Intent intent) {
    663         final PendingResult result = goAsync();
    664         final PowerManager.WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context);
    665         wl.acquire();
    666         AsyncHandler.post(new Runnable() {
    667             @Override
    668             public void run() {
    669                 handleIntent(context, intent);
    670                 result.finish();
    671                 wl.release();
    672             }
    673         });
    674     }
    675 
    676     private void handleIntent(Context context, Intent intent) {
    677         final String action = intent.getAction();
    678         Log.v("AlarmStateManager received intent " + intent);
    679         if (CHANGE_STATE_ACTION.equals(action)) {
    680             Uri uri = intent.getData();
    681             AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
    682                     AlarmInstance.getId(uri));
    683             if (instance == null) {
    684                 // Not a big deal, but it shouldn't happen
    685                 Log.e("Can not change state for unknown instance: " + uri);
    686                 return;
    687             }
    688 
    689             int globalId = getGlobalIntentId(context);
    690             int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1);
    691             int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1);
    692             if (intentId != globalId) {
    693                 Log.i("Ignoring old Intent. IntentId: " + intentId + " GlobalId: " + globalId +
    694                         " AlarmState: " + alarmState);
    695                 return;
    696             }
    697 
    698             if (alarmState >= 0) {
    699                 setAlarmState(context, instance, alarmState);
    700             } else {
    701                 registerInstance(context, instance, true);
    702             }
    703         } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) {
    704             Uri uri = intent.getData();
    705             AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
    706                     AlarmInstance.getId(uri));
    707 
    708             long alarmId = instance.mAlarmId == null ? Alarm.INVALID_ID : instance.mAlarmId;
    709             Intent viewAlarmIntent = Alarm.createIntent(context, DeskClock.class, alarmId);
    710             viewAlarmIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
    711             viewAlarmIntent.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId);
    712             viewAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    713             context.startActivity(viewAlarmIntent);
    714             setDismissState(context, instance);
    715         }
    716     }
    717 }
    718