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.LogUtils;
     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     // Intent action for an AlarmManager alarm serving only to set the next alarm indicators
    106     private static final String INDICATOR_ACTION = "indicator";
    107 
    108     // Extra key to set the desired state change.
    109     public static final String ALARM_STATE_EXTRA = "intent.extra.alarm.state";
    110 
    111     // Extra key to set the global broadcast id.
    112     private static final String ALARM_GLOBAL_ID_EXTRA = "intent.extra.alarm.global.id";
    113 
    114     // Intent category tags used to dismiss, snooze or delete an alarm
    115     public static final String ALARM_DISMISS_TAG = "DISMISS_TAG";
    116     public static final String ALARM_SNOOZE_TAG = "SNOOZE_TAG";
    117     public static final String ALARM_DELETE_TAG = "DELETE_TAG";
    118 
    119     // Intent category tag used when schedule state change intents in alarm manager.
    120     private static final String ALARM_MANAGER_TAG = "ALARM_MANAGER";
    121 
    122     // Buffer time in seconds to fire alarm instead of marking it missed.
    123     public static final int ALARM_FIRE_BUFFER = 15;
    124 
    125     public static int getGlobalIntentId(Context context) {
    126         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    127         return prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1);
    128     }
    129 
    130     public static void updateGlobalIntentId(Context context) {
    131         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    132         int globalId = prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1) + 1;
    133         prefs.edit().putInt(ALARM_GLOBAL_ID_EXTRA, globalId).commit();
    134     }
    135 
    136     /**
    137      * Find and notify system what the next alarm that will fire. This is used
    138      * to update text in the system and widgets.
    139      *
    140      * @param context application context
    141      */
    142     public static void updateNextAlarm(Context context) {
    143         AlarmInstance nextAlarm = null;
    144         ContentResolver cr = context.getContentResolver();
    145         String activeAlarmQuery = AlarmInstance.ALARM_STATE + "<" + AlarmInstance.FIRED_STATE;
    146         for (AlarmInstance instance : AlarmInstance.getInstances(cr, activeAlarmQuery)) {
    147             if (nextAlarm == null || instance.getAlarmTime().before(nextAlarm.getAlarmTime())) {
    148                 nextAlarm = instance;
    149             }
    150         }
    151         AlarmNotifications.registerNextAlarmWithAlarmManager(context, nextAlarm);
    152     }
    153 
    154     /**
    155      * Used by dismissed and missed states, to update parent alarm. This will either
    156      * disable, delete or reschedule parent alarm.
    157      *
    158      * @param context application context
    159      * @param instance to update parent for
    160      */
    161     private static void updateParentAlarm(Context context, AlarmInstance instance) {
    162         ContentResolver cr = context.getContentResolver();
    163         Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    164         if (alarm == null) {
    165             LogUtils.e("Parent has been deleted with instance: " + instance.toString());
    166             return;
    167         }
    168 
    169         if (!alarm.daysOfWeek.isRepeating()) {
    170             if (alarm.deleteAfterUse) {
    171                 LogUtils.i("Deleting parent alarm: " + alarm.id);
    172                 Alarm.deleteAlarm(cr, alarm.id);
    173             } else {
    174                 LogUtils.i("Disabling parent alarm: " + alarm.id);
    175                 alarm.enabled = false;
    176                 Alarm.updateAlarm(cr, alarm);
    177             }
    178         } else {
    179             // This is a optimization for really old alarm instances. This prevent us
    180             // from scheduling and dismissing alarms up to current time.
    181             Calendar currentTime = Calendar.getInstance();
    182             Calendar alarmTime = instance.getAlarmTime();
    183             if (currentTime.after(alarmTime)) {
    184                 alarmTime = currentTime;
    185             }
    186             AlarmInstance nextRepeatedInstance = alarm.createInstanceAfter(alarmTime);
    187             LogUtils.i("Creating new instance for repeating alarm " + alarm.id + " at " +
    188                     AlarmUtils.getFormattedTime(context, nextRepeatedInstance.getAlarmTime()));
    189             AlarmInstance.addInstance(cr, nextRepeatedInstance);
    190             registerInstance(context, nextRepeatedInstance, true);
    191         }
    192     }
    193 
    194     /**
    195      * Utility method to create a proper change state intent.
    196      *
    197      * @param context application context
    198      * @param tag used to make intent differ from other state change intents.
    199      * @param instance to change state to
    200      * @param state to change to.
    201      * @return intent that can be used to change an alarm instance state
    202      */
    203     public static Intent createStateChangeIntent(Context context, String tag,
    204             AlarmInstance instance, Integer state) {
    205         Intent intent = AlarmInstance.createIntent(context, AlarmStateManager.class, instance.mId);
    206         intent.setAction(CHANGE_STATE_ACTION);
    207         intent.addCategory(tag);
    208         intent.putExtra(ALARM_GLOBAL_ID_EXTRA, getGlobalIntentId(context));
    209         if (state != null) {
    210             intent.putExtra(ALARM_STATE_EXTRA, state.intValue());
    211         }
    212         return intent;
    213     }
    214 
    215     /**
    216      * Schedule alarm instance state changes with {@link AlarmManager}.
    217      *
    218      * @param context application context
    219      * @param time to trigger state change
    220      * @param instance to change state to
    221      * @param newState to change to
    222      */
    223     private static void scheduleInstanceStateChange(Context context, Calendar time,
    224             AlarmInstance instance, int newState) {
    225         long timeInMillis = time.getTimeInMillis();
    226         LogUtils.v("Scheduling state change " + newState + " to instance " + instance.mId +
    227                 " at " + AlarmUtils.getFormattedTime(context, time) + " (" + timeInMillis + ")");
    228         Intent stateChangeIntent = createStateChangeIntent(context, ALARM_MANAGER_TAG, instance,
    229                 newState);
    230         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(),
    231                 stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    232 
    233         AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    234         if (Utils.isKitKatOrLater()) {
    235             am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
    236         } else {
    237             am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
    238         }
    239     }
    240 
    241     /**
    242      * Cancel all {@link AlarmManager} timers for instance.
    243      *
    244      * @param context application context
    245      * @param instance to disable all {@link AlarmManager} timers
    246      */
    247     private static void cancelScheduledInstance(Context context, AlarmInstance instance) {
    248         LogUtils.v("Canceling instance " + instance.mId + " timers");
    249 
    250         // Create a PendingIntent that will match any one set for this instance
    251         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(),
    252                 createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, null),
    253                 PendingIntent.FLAG_UPDATE_CURRENT);
    254 
    255         AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    256         am.cancel(pendingIntent);
    257     }
    258 
    259 
    260     /**
    261      * This will set the alarm instance to the SILENT_STATE and update
    262      * the application notifications and schedule any state changes that need
    263      * to occur in the future.
    264      *
    265      * @param context application context
    266      * @param instance to set state to
    267      */
    268     public static void setSilentState(Context context, AlarmInstance instance) {
    269         LogUtils.v("Setting silent state to instance " + instance.mId);
    270 
    271         // Update alarm in db
    272         ContentResolver contentResolver = context.getContentResolver();
    273         instance.mAlarmState = AlarmInstance.SILENT_STATE;
    274         AlarmInstance.updateInstance(contentResolver, instance);
    275 
    276         // Setup instance notification and scheduling timers
    277         AlarmNotifications.clearNotification(context, instance);
    278         scheduleInstanceStateChange(context, instance.getLowNotificationTime(),
    279                 instance, AlarmInstance.LOW_NOTIFICATION_STATE);
    280     }
    281 
    282     /**
    283      * This will set the alarm instance to the LOW_NOTIFICATION_STATE and update
    284      * the application notifications and schedule any state changes that need
    285      * to occur in the future.
    286      *
    287      * @param context application context
    288      * @param instance to set state to
    289      */
    290     public static void setLowNotificationState(Context context, AlarmInstance instance) {
    291         LogUtils.v("Setting low notification state to instance " + instance.mId);
    292 
    293         // Update alarm state in db
    294         ContentResolver contentResolver = context.getContentResolver();
    295         instance.mAlarmState = AlarmInstance.LOW_NOTIFICATION_STATE;
    296         AlarmInstance.updateInstance(contentResolver, instance);
    297 
    298         // Setup instance notification and scheduling timers
    299         AlarmNotifications.showLowPriorityNotification(context, instance);
    300         scheduleInstanceStateChange(context, instance.getHighNotificationTime(),
    301                 instance, AlarmInstance.HIGH_NOTIFICATION_STATE);
    302     }
    303 
    304     /**
    305      * This will set the alarm instance to the HIDE_NOTIFICATION_STATE and update
    306      * the application notifications and schedule any state changes that need
    307      * to occur in the future.
    308      *
    309      * @param context application context
    310      * @param instance to set state to
    311      */
    312     public static void setHideNotificationState(Context context, AlarmInstance instance) {
    313         LogUtils.v("Setting hide notification state to instance " + instance.mId);
    314 
    315         // Update alarm state in db
    316         ContentResolver contentResolver = context.getContentResolver();
    317         instance.mAlarmState = AlarmInstance.HIDE_NOTIFICATION_STATE;
    318         AlarmInstance.updateInstance(contentResolver, instance);
    319 
    320         // Setup instance notification and scheduling timers
    321         AlarmNotifications.clearNotification(context, instance);
    322         scheduleInstanceStateChange(context, instance.getHighNotificationTime(),
    323                 instance, AlarmInstance.HIGH_NOTIFICATION_STATE);
    324     }
    325 
    326     /**
    327      * This will set the alarm instance to the HIGH_NOTIFICATION_STATE and update
    328      * the application notifications and schedule any state changes that need
    329      * to occur in the future.
    330      *
    331      * @param context application context
    332      * @param instance to set state to
    333      */
    334     public static void setHighNotificationState(Context context, AlarmInstance instance) {
    335         LogUtils.v("Setting high notification state to instance " + instance.mId);
    336 
    337         // Update alarm state in db
    338         ContentResolver contentResolver = context.getContentResolver();
    339         instance.mAlarmState = AlarmInstance.HIGH_NOTIFICATION_STATE;
    340         AlarmInstance.updateInstance(contentResolver, instance);
    341 
    342         // Setup instance notification and scheduling timers
    343         AlarmNotifications.showHighPriorityNotification(context, instance);
    344         scheduleInstanceStateChange(context, instance.getAlarmTime(),
    345                 instance, AlarmInstance.FIRED_STATE);
    346     }
    347 
    348     /**
    349      * This will set the alarm instance to the FIRED_STATE and update
    350      * the application notifications and schedule any state changes that need
    351      * to occur in the future.
    352      *
    353      * @param context application context
    354      * @param instance to set state to
    355      */
    356     public static void setFiredState(Context context, AlarmInstance instance) {
    357         LogUtils.v("Setting fire state to instance " + instance.mId);
    358 
    359         // Update alarm state in db
    360         ContentResolver contentResolver = context.getContentResolver();
    361         instance.mAlarmState = AlarmInstance.FIRED_STATE;
    362         AlarmInstance.updateInstance(contentResolver, instance);
    363 
    364         // Start the alarm and schedule timeout timer for it
    365         AlarmService.startAlarm(context, instance);
    366 
    367         Calendar timeout = instance.getTimeout(context);
    368         if (timeout != null) {
    369             scheduleInstanceStateChange(context, timeout, instance, AlarmInstance.MISSED_STATE);
    370         }
    371 
    372         // Instance not valid anymore, so find next alarm that will fire and notify system
    373         updateNextAlarm(context);
    374     }
    375 
    376     /**
    377      * This will set the alarm instance to the SNOOZE_STATE and update
    378      * the application notifications and schedule any state changes that need
    379      * to occur in the future.
    380      *
    381      * @param context application context
    382      * @param instance to set state to
    383      */
    384     public static void setSnoozeState(Context context, AlarmInstance instance, boolean showToast) {
    385         // Stop alarm if this instance is firing it
    386         AlarmService.stopAlarm(context, instance);
    387 
    388         // Calculate the new snooze alarm time
    389         String snoozeMinutesStr = PreferenceManager.getDefaultSharedPreferences(context)
    390                 .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE_MINUTES);
    391         int snoozeMinutes = Integer.parseInt(snoozeMinutesStr);
    392         Calendar newAlarmTime = Calendar.getInstance();
    393         newAlarmTime.add(Calendar.MINUTE, snoozeMinutes);
    394 
    395         // Update alarm state and new alarm time in db.
    396         LogUtils.v("Setting snoozed state to instance " + instance.mId + " for "
    397                 + AlarmUtils.getFormattedTime(context, newAlarmTime));
    398         instance.setAlarmTime(newAlarmTime);
    399         instance.mAlarmState = AlarmInstance.SNOOZE_STATE;
    400         AlarmInstance.updateInstance(context.getContentResolver(), instance);
    401 
    402         // Setup instance notification and scheduling timers
    403         AlarmNotifications.showSnoozeNotification(context, instance);
    404         scheduleInstanceStateChange(context, instance.getAlarmTime(),
    405                 instance, AlarmInstance.FIRED_STATE);
    406 
    407         // Display the snooze minutes in a toast.
    408         if (showToast) {
    409             String displayTime = String.format(context.getResources().getQuantityText
    410                     (R.plurals.alarm_alert_snooze_set, snoozeMinutes).toString(), snoozeMinutes);
    411             Toast.makeText(context, displayTime, Toast.LENGTH_LONG).show();
    412         }
    413 
    414         // Instance time changed, so find next alarm that will fire and notify system
    415         updateNextAlarm(context);
    416 
    417     }
    418 
    419     public static int getSnoozedMinutes(Context context) {
    420         final String snoozeMinutesStr = PreferenceManager.getDefaultSharedPreferences(context)
    421                 .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE_MINUTES);
    422         return Integer.parseInt(snoozeMinutesStr);
    423     }
    424 
    425     /**
    426      * This will set the alarm instance to the MISSED_STATE and update
    427      * the application notifications and schedule any state changes that need
    428      * to occur in the future.
    429      *
    430      * @param context application context
    431      * @param instance to set state to
    432      */
    433     public static void setMissedState(Context context, AlarmInstance instance) {
    434         LogUtils.v("Setting missed state to instance " + instance.mId);
    435         // Stop alarm if this instance is firing it
    436         AlarmService.stopAlarm(context, instance);
    437 
    438         // Check parent if it needs to reschedule, disable or delete itself
    439         if (instance.mAlarmId != null) {
    440             updateParentAlarm(context, instance);
    441         }
    442 
    443         // Update alarm state
    444         ContentResolver contentResolver = context.getContentResolver();
    445         instance.mAlarmState = AlarmInstance.MISSED_STATE;
    446         AlarmInstance.updateInstance(contentResolver, instance);
    447 
    448         // Setup instance notification and scheduling timers
    449         AlarmNotifications.showMissedNotification(context, instance);
    450         scheduleInstanceStateChange(context, instance.getMissedTimeToLive(),
    451                 instance, AlarmInstance.DISMISSED_STATE);
    452 
    453         // Instance is not valid anymore, so find next alarm that will fire and notify system
    454         updateNextAlarm(context);
    455 
    456     }
    457 
    458     /**
    459      * This will set the alarm instance to the SILENT_STATE and update
    460      * the application notifications and schedule any state changes that need
    461      * to occur in the future.
    462      *
    463      * @param context application context
    464      * @param instance to set state to
    465      */
    466     public static void setDismissState(Context context, AlarmInstance instance) {
    467         LogUtils.v("Setting dismissed state to instance " + instance.mId);
    468 
    469         // Remove all other timers and notifications associated to it
    470         unregisterInstance(context, instance);
    471 
    472         // Check parent if it needs to reschedule, disable or delete itself
    473         if (instance.mAlarmId != null) {
    474             updateParentAlarm(context, instance);
    475         }
    476 
    477         // Delete instance as it is not needed anymore
    478         AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    479 
    480         // Instance is not valid anymore, so find next alarm that will fire and notify system
    481         updateNextAlarm(context);
    482     }
    483 
    484     /**
    485      * This will not change the state of instance, but remove it's notifications and
    486      * alarm timers.
    487      *
    488      * @param context application context
    489      * @param instance to unregister
    490      */
    491     public static void unregisterInstance(Context context, AlarmInstance instance) {
    492         // Stop alarm if this instance is firing it
    493         AlarmService.stopAlarm(context, instance);
    494         AlarmNotifications.clearNotification(context, instance);
    495         cancelScheduledInstance(context, instance);
    496     }
    497 
    498     /**
    499      * This registers the AlarmInstance to the state manager. This will look at the instance
    500      * and choose the most appropriate state to put it in. This is primarily used by new
    501      * alarms, but it can also be called when the system time changes.
    502      *
    503      * Most state changes are handled by the states themselves, but during major time changes we
    504      * have to correct the alarm instance state. This means we have to handle special cases as
    505      * describe below:
    506      *
    507      * <ul>
    508      *     <li>Make sure all dismissed alarms are never re-activated</li>
    509      *     <li>Make sure firing alarms stayed fired unless they should be auto-silenced</li>
    510      *     <li>Missed instance that have parents should be re-enabled if we went back in time</li>
    511      *     <li>If alarm was SNOOZED, then show the notification but don't update time</li>
    512      *     <li>If low priority notification was hidden, then make sure it stays hidden</li>
    513      * </ul>
    514      *
    515      * If none of these special case are found, then we just check the time and see what is the
    516      * proper state for the instance.
    517      *
    518      * @param context application context
    519      * @param instance to register
    520      */
    521     public static void registerInstance(Context context, AlarmInstance instance,
    522             boolean updateNextAlarm) {
    523         Calendar currentTime = Calendar.getInstance();
    524         Calendar alarmTime = instance.getAlarmTime();
    525         Calendar timeoutTime = instance.getTimeout(context);
    526         Calendar lowNotificationTime = instance.getLowNotificationTime();
    527         Calendar highNotificationTime = instance.getHighNotificationTime();
    528         Calendar missedTTL = instance.getMissedTimeToLive();
    529 
    530         // Handle special use cases here
    531         if (instance.mAlarmState == AlarmInstance.DISMISSED_STATE) {
    532             // This should never happen, but add a quick check here
    533             LogUtils.e("Alarm Instance is dismissed, but never deleted");
    534             setDismissState(context, instance);
    535             return;
    536         } else if (instance.mAlarmState == AlarmInstance.FIRED_STATE) {
    537             // Keep alarm firing, unless it should be timed out
    538             boolean hasTimeout = timeoutTime != null && currentTime.after(timeoutTime);
    539             if (!hasTimeout) {
    540                 setFiredState(context, instance);
    541                 return;
    542             }
    543         } else if (instance.mAlarmState == AlarmInstance.MISSED_STATE) {
    544             if (currentTime.before(alarmTime)) {
    545                 if (instance.mAlarmId == null) {
    546                     // This instance parent got deleted (ie. deleteAfterUse), so
    547                     // we should not re-activate it.-
    548                     setDismissState(context, instance);
    549                     return;
    550                 }
    551 
    552                 // TODO: This will re-activate missed snoozed alarms, but will
    553                 // use our normal notifications. This is not ideal, but very rare use-case.
    554                 // We should look into fixing this in the future.
    555 
    556                 // Make sure we re-enable the parent alarm of the instance
    557                 // because it will get activated by by the below code
    558                 ContentResolver cr = context.getContentResolver();
    559                 Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    560                 alarm.enabled = true;
    561                 Alarm.updateAlarm(cr, alarm);
    562             }
    563         }
    564 
    565         // Fix states that are time sensitive
    566         if (currentTime.after(missedTTL)) {
    567             // Alarm is so old, just dismiss it
    568             setDismissState(context, instance);
    569         } else if (currentTime.after(alarmTime)) {
    570             // There is a chance that the TIME_SET occurred right when the alarm should go off, so
    571             // we need to add a check to see if we should fire the alarm instead of marking it
    572             // missed.
    573             Calendar alarmBuffer = Calendar.getInstance();
    574             alarmBuffer.setTime(alarmTime.getTime());
    575             alarmBuffer.add(Calendar.SECOND, ALARM_FIRE_BUFFER);
    576             if (currentTime.before(alarmBuffer)) {
    577                 setFiredState(context, instance);
    578             } else {
    579                 setMissedState(context, instance);
    580             }
    581         } else if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
    582             // We only want to display snooze notification and not update the time,
    583             // so handle showing the notification directly
    584             AlarmNotifications.showSnoozeNotification(context, instance);
    585             scheduleInstanceStateChange(context, instance.getAlarmTime(),
    586                     instance, AlarmInstance.FIRED_STATE);
    587         } else if (currentTime.after(highNotificationTime)) {
    588             setHighNotificationState(context, instance);
    589         } else if (currentTime.after(lowNotificationTime)) {
    590             // Only show low notification if it wasn't hidden in the past
    591             if (instance.mAlarmState == AlarmInstance.HIDE_NOTIFICATION_STATE) {
    592                 setHideNotificationState(context, instance);
    593             } else {
    594                 setLowNotificationState(context, instance);
    595             }
    596         } else {
    597           // Alarm is still active, so initialize as a silent alarm
    598           setSilentState(context, instance);
    599         }
    600 
    601         // The caller prefers to handle updateNextAlarm for optimization
    602         if (updateNextAlarm) {
    603             updateNextAlarm(context);
    604         }
    605     }
    606 
    607     /**
    608      * This will delete and unregister all instances associated with alarmId, without affect
    609      * the alarm itself. This should be used whenever modifying or deleting an alarm.
    610      *
    611      * @param context application context
    612      * @param alarmId to find instances to delete.
    613      */
    614     public static void deleteAllInstances(Context context, long alarmId) {
    615         ContentResolver cr = context.getContentResolver();
    616         List<AlarmInstance> instances = AlarmInstance.getInstancesByAlarmId(cr, alarmId);
    617         for (AlarmInstance instance : instances) {
    618             unregisterInstance(context, instance);
    619             AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    620         }
    621         updateNextAlarm(context);
    622     }
    623 
    624     /**
    625      * Fix and update all alarm instance when a time change event occurs.
    626      *
    627      * @param context application context
    628      */
    629     public static void fixAlarmInstances(Context context) {
    630         // Register all instances after major time changes or when phone restarts
    631         // TODO: Refactor this code to not use the overloaded registerInstance method.
    632         ContentResolver contentResolver = context.getContentResolver();
    633         for (AlarmInstance instance : AlarmInstance.getInstances(contentResolver, null)) {
    634             AlarmStateManager.registerInstance(context, instance, false);
    635         }
    636         AlarmStateManager.updateNextAlarm(context);
    637     }
    638 
    639     /**
    640      * Utility method to set alarm instance state via constants.
    641      *
    642      * @param context application context
    643      * @param instance to change state on
    644      * @param state to change to
    645      */
    646     public void setAlarmState(Context context, AlarmInstance instance, int state) {
    647         switch(state) {
    648             case AlarmInstance.SILENT_STATE:
    649                 setSilentState(context, instance);
    650                 break;
    651             case AlarmInstance.LOW_NOTIFICATION_STATE:
    652                 setLowNotificationState(context, instance);
    653                 break;
    654             case AlarmInstance.HIDE_NOTIFICATION_STATE:
    655                 setHideNotificationState(context, instance);
    656                 break;
    657             case AlarmInstance.HIGH_NOTIFICATION_STATE:
    658                 setHighNotificationState(context, instance);
    659                 break;
    660             case AlarmInstance.FIRED_STATE:
    661                 setFiredState(context, instance);
    662                 break;
    663             case AlarmInstance.SNOOZE_STATE:
    664                 setSnoozeState(context, instance, true /* showToast */);
    665                 break;
    666             case AlarmInstance.MISSED_STATE:
    667                 setMissedState(context, instance);
    668                 break;
    669             case AlarmInstance.DISMISSED_STATE:
    670                 setDismissState(context, instance);
    671                 break;
    672             default:
    673                 LogUtils.e("Trying to change to unknown alarm state: " + state);
    674         }
    675     }
    676 
    677     @Override
    678     public void onReceive(final Context context, final Intent intent) {
    679         if (INDICATOR_ACTION.equals(intent.getAction())) {
    680             return;
    681         }
    682 
    683         final PendingResult result = goAsync();
    684         final PowerManager.WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context);
    685         wl.acquire();
    686         AsyncHandler.post(new Runnable() {
    687             @Override
    688             public void run() {
    689                 handleIntent(context, intent);
    690                 result.finish();
    691                 wl.release();
    692             }
    693         });
    694     }
    695 
    696     private void handleIntent(Context context, Intent intent) {
    697         final String action = intent.getAction();
    698         LogUtils.v("AlarmStateManager received intent " + intent);
    699         if (CHANGE_STATE_ACTION.equals(action)) {
    700             Uri uri = intent.getData();
    701             AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
    702                     AlarmInstance.getId(uri));
    703             if (instance == null) {
    704                 // Not a big deal, but it shouldn't happen
    705                 LogUtils.e("Can not change state for unknown instance: " + uri);
    706                 return;
    707             }
    708 
    709             int globalId = getGlobalIntentId(context);
    710             int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1);
    711             int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1);
    712             if (intentId != globalId) {
    713                 LogUtils.i("IntentId: " + intentId + " GlobalId: " + globalId + " AlarmState: " +
    714                         alarmState);
    715                 // Allows dismiss/snooze requests to go through
    716                 if (!intent.hasCategory(ALARM_DISMISS_TAG) &&
    717                         !intent.hasCategory(ALARM_SNOOZE_TAG)) {
    718                     LogUtils.i("Ignoring old Intent");
    719                     return;
    720                 }
    721             }
    722 
    723             if (alarmState >= 0) {
    724                 setAlarmState(context, instance, alarmState);
    725             } else {
    726                 registerInstance(context, instance, true);
    727             }
    728         } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) {
    729             Uri uri = intent.getData();
    730             AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
    731                     AlarmInstance.getId(uri));
    732 
    733             long alarmId = instance.mAlarmId == null ? Alarm.INVALID_ID : instance.mAlarmId;
    734             Intent viewAlarmIntent = Alarm.createIntent(context, DeskClock.class, alarmId);
    735             viewAlarmIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
    736             viewAlarmIntent.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId);
    737             viewAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    738             context.startActivity(viewAlarmIntent);
    739             setDismissState(context, instance);
    740         }
    741     }
    742 
    743     /**
    744      * Creates an intent that can be used to set an AlarmManager alarm to set the next alarm
    745      * indicators.
    746      */
    747     public static Intent createIndicatorIntent(Context context) {
    748         return new Intent(context, AlarmStateManager.class).setAction(INDICATOR_ACTION);
    749     }
    750 }
    751