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.annotation.TargetApi;
     19 import android.app.AlarmManager;
     20 import android.app.AlarmManager.AlarmClockInfo;
     21 import android.app.PendingIntent;
     22 import android.content.BroadcastReceiver;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.net.Uri;
     27 import android.os.Build;
     28 import android.os.Handler;
     29 import android.os.PowerManager;
     30 import android.provider.Settings;
     31 import android.support.v4.app.NotificationManagerCompat;
     32 import android.text.format.DateFormat;
     33 import android.widget.Toast;
     34 
     35 import com.android.deskclock.AlarmAlertWakeLock;
     36 import com.android.deskclock.AlarmClockFragment;
     37 import com.android.deskclock.AlarmUtils;
     38 import com.android.deskclock.AsyncHandler;
     39 import com.android.deskclock.DeskClock;
     40 import com.android.deskclock.LogUtils;
     41 import com.android.deskclock.R;
     42 import com.android.deskclock.Utils;
     43 import com.android.deskclock.data.DataModel;
     44 import com.android.deskclock.events.Events;
     45 import com.android.deskclock.provider.Alarm;
     46 import com.android.deskclock.provider.AlarmInstance;
     47 
     48 import java.util.Calendar;
     49 import java.util.Collections;
     50 import java.util.Comparator;
     51 import java.util.List;
     52 
     53 import static android.content.Context.ALARM_SERVICE;
     54 import static android.provider.Settings.System.NEXT_ALARM_FORMATTED;
     55 
     56 /**
     57  * This class handles all the state changes for alarm instances. You need to
     58  * register all alarm instances with the state manager if you want them to
     59  * be activated. If a major time change has occurred (ie. TIMEZONE_CHANGE, TIMESET_CHANGE),
     60  * then you must also re-register instances to fix their states.
     61  *
     62  * Please see {@link #registerInstance) for special transitions when major time changes
     63  * occur.
     64  *
     65  * Following states:
     66  *
     67  * SILENT_STATE:
     68  * This state is used when the alarm is activated, but doesn't need to display anything. It
     69  * is in charge of changing the alarm instance state to a LOW_NOTIFICATION_STATE.
     70  *
     71  * LOW_NOTIFICATION_STATE:
     72  * This state is used to notify the user that the alarm will go off
     73  * {@link AlarmInstance#LOW_NOTIFICATION_HOUR_OFFSET}. This
     74  * state handles the state changes to HIGH_NOTIFICATION_STATE, HIDE_NOTIFICATION_STATE and
     75  * DISMISS_STATE.
     76  *
     77  * HIDE_NOTIFICATION_STATE:
     78  * This is a transient state of the LOW_NOTIFICATION_STATE, where the user wants to hide the
     79  * notification. This will sit and wait until the HIGH_PRIORITY_NOTIFICATION should go off.
     80  *
     81  * HIGH_NOTIFICATION_STATE:
     82  * This state behaves like the LOW_NOTIFICATION_STATE, but doesn't allow the user to hide it.
     83  * This state is in charge of triggering a FIRED_STATE or DISMISS_STATE.
     84  *
     85  * SNOOZED_STATE:
     86  * The SNOOZED_STATE behaves like a HIGH_NOTIFICATION_STATE, but with a different message. It
     87  * also increments the alarm time in the instance to reflect the new snooze time.
     88  *
     89  * FIRED_STATE:
     90  * The FIRED_STATE is used when the alarm is firing. It will start the AlarmService, and wait
     91  * until the user interacts with the alarm via SNOOZED_STATE or DISMISS_STATE change. If the user
     92  * doesn't then it might be change to MISSED_STATE if auto-silenced was enabled.
     93  *
     94  * MISSED_STATE:
     95  * The MISSED_STATE is used when the alarm already fired, but the user could not interact with
     96  * it. At this point the alarm instance is dead and we check the parent alarm to see if we need
     97  * to disable or schedule a new alarm_instance. There is also a notification shown to the user
     98  * that he/she missed the alarm and that stays for
     99  * {@link AlarmInstance#MISSED_TIME_TO_LIVE_HOUR_OFFSET} or until the user acknownledges it.
    100  *
    101  * DISMISS_STATE:
    102  * This is really a transient state that will properly delete the alarm instance. Use this state,
    103  * whenever you want to get rid of the alarm instance. This state will also check the alarm
    104  * parent to see if it should disable or schedule a new alarm instance.
    105  */
    106 public final class AlarmStateManager extends BroadcastReceiver {
    107     // Intent action to trigger an instance state change.
    108     public static final String CHANGE_STATE_ACTION = "change_state";
    109 
    110     // Intent action to show the alarm and dismiss the instance
    111     public static final String SHOW_AND_DISMISS_ALARM_ACTION = "show_and_dismiss_alarm";
    112 
    113     // Intent action for an AlarmManager alarm serving only to set the next alarm indicators
    114     private static final String INDICATOR_ACTION = "indicator";
    115 
    116     // System intent action to notify AppWidget that we changed the alarm text.
    117     public static final String ACTION_ALARM_CHANGED = "com.android.deskclock.ALARM_CHANGED";
    118 
    119     // Extra key to set the desired state change.
    120     public static final String ALARM_STATE_EXTRA = "intent.extra.alarm.state";
    121 
    122     // Extra key to indicate the state change was launched from a notification.
    123     public static final String FROM_NOTIFICATION_EXTRA = "intent.extra.from.notification";
    124 
    125     // Extra key to set the global broadcast id.
    126     private static final String ALARM_GLOBAL_ID_EXTRA = "intent.extra.alarm.global.id";
    127 
    128     // Intent category tags used to dismiss, snooze or delete an alarm
    129     public static final String ALARM_DISMISS_TAG = "DISMISS_TAG";
    130     public static final String ALARM_SNOOZE_TAG = "SNOOZE_TAG";
    131     public static final String ALARM_DELETE_TAG = "DELETE_TAG";
    132 
    133     // Intent category tag used when schedule state change intents in alarm manager.
    134     private static final String ALARM_MANAGER_TAG = "ALARM_MANAGER";
    135 
    136     // Buffer time in seconds to fire alarm instead of marking it missed.
    137     public static final int ALARM_FIRE_BUFFER = 15;
    138 
    139     // A factory for the current time; can be mocked for testing purposes.
    140     private static CurrentTimeFactory sCurrentTimeFactory;
    141 
    142     // Schedules alarm state transitions; can be mocked for testing purposes.
    143     private static StateChangeScheduler sStateChangeScheduler =
    144             new AlarmManagerStateChangeScheduler();
    145 
    146     private static Calendar getCurrentTime() {
    147         return sCurrentTimeFactory == null
    148                 ? DataModel.getDataModel().getCalendar()
    149                 : sCurrentTimeFactory.getCurrentTime();
    150     }
    151 
    152     static void setCurrentTimeFactory(CurrentTimeFactory currentTimeFactory) {
    153         sCurrentTimeFactory = currentTimeFactory;
    154     }
    155 
    156     static void setStateChangeScheduler(StateChangeScheduler stateChangeScheduler) {
    157         if (stateChangeScheduler == null) {
    158             stateChangeScheduler = new AlarmManagerStateChangeScheduler();
    159         }
    160         sStateChangeScheduler = stateChangeScheduler;
    161     }
    162 
    163     /**
    164      * Update the next alarm stored in framework. This value is also displayed in digital widgets
    165      * and the clock tab in this app.
    166      */
    167     private static void updateNextAlarm(Context context) {
    168         final AlarmInstance nextAlarm = getNextFiringAlarm(context);
    169 
    170         if (Utils.isPreL()) {
    171             updateNextAlarmInSystemSettings(context, nextAlarm);
    172         } else {
    173             updateNextAlarmInAlarmManager(context, nextAlarm);
    174         }
    175     }
    176 
    177     /**
    178      * Returns an alarm instance of an alarm that's going to fire next.
    179      *
    180      * @param context application context
    181      * @return an alarm instance that will fire earliest relative to current time.
    182      */
    183     public static AlarmInstance getNextFiringAlarm(Context context) {
    184         final ContentResolver cr = context.getContentResolver();
    185         final String activeAlarmQuery = AlarmInstance.ALARM_STATE + "<" + AlarmInstance.FIRED_STATE;
    186         final List<AlarmInstance> alarmInstances = AlarmInstance.getInstances(cr, activeAlarmQuery);
    187 
    188         AlarmInstance nextAlarm = null;
    189         for (AlarmInstance instance : alarmInstances) {
    190             if (nextAlarm == null || instance.getAlarmTime().before(nextAlarm.getAlarmTime())) {
    191                 nextAlarm = instance;
    192             }
    193         }
    194         return nextAlarm;
    195     }
    196 
    197     /**
    198      * Used in pre-L devices, where "next alarm" is stored in system settings.
    199      */
    200     @SuppressWarnings("deprecation")
    201     @TargetApi(Build.VERSION_CODES.KITKAT)
    202     private static void updateNextAlarmInSystemSettings(Context context, AlarmInstance nextAlarm) {
    203         // Format the next alarm time if an alarm is scheduled.
    204         String time = "";
    205         if (nextAlarm != null) {
    206             time = AlarmUtils.getFormattedTime(context, nextAlarm.getAlarmTime());
    207         }
    208 
    209         try {
    210             // Write directly to NEXT_ALARM_FORMATTED in all pre-L versions
    211             Settings.System.putString(context.getContentResolver(), NEXT_ALARM_FORMATTED, time);
    212 
    213             LogUtils.i("Updated next alarm time to: \'" + time + '\'');
    214 
    215             // Send broadcast message so pre-L AppWidgets will recognize an update.
    216             context.sendBroadcast(new Intent(ACTION_ALARM_CHANGED));
    217         } catch (SecurityException se) {
    218             // The user has most likely revoked WRITE_SETTINGS.
    219             LogUtils.e("Unable to update next alarm to: \'" + time + '\'', se);
    220         }
    221     }
    222 
    223     /**
    224      * Used in L and later devices where "next alarm" is stored in the Alarm Manager.
    225      */
    226     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    227     private static void updateNextAlarmInAlarmManager(Context context, AlarmInstance nextAlarm) {
    228         // Sets a surrogate alarm with alarm manager that provides the AlarmClockInfo for the
    229         // alarm that is going to fire next. The operation is constructed such that it is ignored
    230         // by AlarmStateManager.
    231 
    232         final AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
    233 
    234         final int flags = nextAlarm == null ? PendingIntent.FLAG_NO_CREATE : 0;
    235         final PendingIntent operation = PendingIntent.getBroadcast(context, 0 /* requestCode */,
    236                 AlarmStateManager.createIndicatorIntent(context), flags);
    237 
    238         if (nextAlarm != null) {
    239             LogUtils.i("Setting upcoming AlarmClockInfo for alarm: " + nextAlarm.mId);
    240             long alarmTime = nextAlarm.getAlarmTime().getTimeInMillis();
    241 
    242             // Create an intent that can be used to show or edit details of the next alarm.
    243             PendingIntent viewIntent = PendingIntent.getActivity(context, nextAlarm.hashCode(),
    244                     AlarmNotifications.createViewAlarmIntent(context, nextAlarm),
    245                     PendingIntent.FLAG_UPDATE_CURRENT);
    246 
    247             final AlarmClockInfo info = new AlarmClockInfo(alarmTime, viewIntent);
    248             Utils.updateNextAlarm(alarmManager, info, operation);
    249         } else if (operation != null) {
    250             LogUtils.i("Canceling upcoming AlarmClockInfo");
    251             alarmManager.cancel(operation);
    252         }
    253     }
    254 
    255     /**
    256      * Used by dismissed and missed states, to update parent alarm. This will either
    257      * disable, delete or reschedule parent alarm.
    258      *
    259      * @param context  application context
    260      * @param instance to update parent for
    261      */
    262     private static void updateParentAlarm(Context context, AlarmInstance instance) {
    263         ContentResolver cr = context.getContentResolver();
    264         Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    265         if (alarm == null) {
    266             LogUtils.e("Parent has been deleted with instance: " + instance.toString());
    267             return;
    268         }
    269 
    270         if (!alarm.daysOfWeek.isRepeating()) {
    271             if (alarm.deleteAfterUse) {
    272                 LogUtils.i("Deleting parent alarm: " + alarm.id);
    273                 Alarm.deleteAlarm(cr, alarm.id);
    274             } else {
    275                 LogUtils.i("Disabling parent alarm: " + alarm.id);
    276                 alarm.enabled = false;
    277                 Alarm.updateAlarm(cr, alarm);
    278             }
    279         } else {
    280             // Schedule the next repeating instance which may be before the current instance if a
    281             // time jump has occurred. Otherwise, if the current instance is the next instance
    282             // and has already been fired, schedule the subsequent instance.
    283             AlarmInstance nextRepeatedInstance = alarm.createInstanceAfter(getCurrentTime());
    284             if (instance.mAlarmState > AlarmInstance.FIRED_STATE
    285                     && nextRepeatedInstance.getAlarmTime().equals(instance.getAlarmTime())) {
    286                 nextRepeatedInstance = alarm.createInstanceAfter(instance.getAlarmTime());
    287             }
    288 
    289             LogUtils.i("Creating new instance for repeating alarm " + alarm.id + " at " +
    290                     AlarmUtils.getFormattedTime(context, nextRepeatedInstance.getAlarmTime()));
    291             AlarmInstance.addInstance(cr, nextRepeatedInstance);
    292             registerInstance(context, nextRepeatedInstance, true);
    293         }
    294     }
    295 
    296     /**
    297      * Utility method to create a proper change state intent.
    298      *
    299      * @param context  application context
    300      * @param tag      used to make intent differ from other state change intents.
    301      * @param instance to change state to
    302      * @param state    to change to.
    303      * @return intent that can be used to change an alarm instance state
    304      */
    305     public static Intent createStateChangeIntent(Context context, String tag,
    306             AlarmInstance instance, Integer state) {
    307         // This intent is directed to AlarmService, though the actual handling of it occurs here
    308         // in AlarmStateManager. The reason is that evidence exists showing the jump between the
    309         // broadcast receiver (AlarmStateManager) and service (AlarmService) can be thwarted by the
    310         // Out Of Memory killer. If clock is killed during that jump, firing an alarm can fail to
    311         // occur. To be safer, the call begins in AlarmService, which has the power to display the
    312         // firing alarm if needed, so no jump is needed.
    313         Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId);
    314         intent.setAction(CHANGE_STATE_ACTION);
    315         intent.addCategory(tag);
    316         intent.putExtra(ALARM_GLOBAL_ID_EXTRA, DataModel.getDataModel().getGlobalIntentId());
    317         if (state != null) {
    318             intent.putExtra(ALARM_STATE_EXTRA, state.intValue());
    319         }
    320         return intent;
    321     }
    322 
    323     /**
    324      * Schedule alarm instance state changes with {@link AlarmManager}.
    325      *
    326      * @param ctx      application context
    327      * @param time     to trigger state change
    328      * @param instance to change state to
    329      * @param newState to change to
    330      */
    331     private static void scheduleInstanceStateChange(Context ctx, Calendar time,
    332             AlarmInstance instance, int newState) {
    333         sStateChangeScheduler.scheduleInstanceStateChange(ctx, time, instance, newState);
    334     }
    335 
    336     /**
    337      * Cancel all {@link AlarmManager} timers for instance.
    338      *
    339      * @param ctx      application context
    340      * @param instance to disable all {@link AlarmManager} timers
    341      */
    342     private static void cancelScheduledInstanceStateChange(Context ctx, AlarmInstance instance) {
    343         sStateChangeScheduler.cancelScheduledInstanceStateChange(ctx, instance);
    344     }
    345 
    346 
    347     /**
    348      * This will set the alarm instance to the SILENT_STATE and update
    349      * the application notifications and schedule any state changes that need
    350      * to occur in the future.
    351      *
    352      * @param context  application context
    353      * @param instance to set state to
    354      */
    355     public static void setSilentState(Context context, AlarmInstance instance) {
    356         LogUtils.i("Setting silent state to instance " + instance.mId);
    357 
    358         // Update alarm in db
    359         ContentResolver contentResolver = context.getContentResolver();
    360         instance.mAlarmState = AlarmInstance.SILENT_STATE;
    361         AlarmInstance.updateInstance(contentResolver, instance);
    362 
    363         // Setup instance notification and scheduling timers
    364         AlarmNotifications.clearNotification(context, instance);
    365         scheduleInstanceStateChange(context, instance.getLowNotificationTime(),
    366                 instance, AlarmInstance.LOW_NOTIFICATION_STATE);
    367     }
    368 
    369     /**
    370      * This will set the alarm instance to the LOW_NOTIFICATION_STATE and update
    371      * the application notifications and schedule any state changes that need
    372      * to occur in the future.
    373      *
    374      * @param context  application context
    375      * @param instance to set state to
    376      */
    377     public static void setLowNotificationState(Context context, AlarmInstance instance) {
    378         LogUtils.i("Setting low notification state to instance " + instance.mId);
    379 
    380         // Update alarm state in db
    381         ContentResolver contentResolver = context.getContentResolver();
    382         instance.mAlarmState = AlarmInstance.LOW_NOTIFICATION_STATE;
    383         AlarmInstance.updateInstance(contentResolver, instance);
    384 
    385         // Setup instance notification and scheduling timers
    386         AlarmNotifications.showLowPriorityNotification(context, instance);
    387         scheduleInstanceStateChange(context, instance.getHighNotificationTime(),
    388                 instance, AlarmInstance.HIGH_NOTIFICATION_STATE);
    389     }
    390 
    391     /**
    392      * This will set the alarm instance to the HIDE_NOTIFICATION_STATE and update
    393      * the application notifications and schedule any state changes that need
    394      * to occur in the future.
    395      *
    396      * @param context  application context
    397      * @param instance to set state to
    398      */
    399     public static void setHideNotificationState(Context context, AlarmInstance instance) {
    400         LogUtils.i("Setting hide notification state to instance " + instance.mId);
    401 
    402         // Update alarm state in db
    403         ContentResolver contentResolver = context.getContentResolver();
    404         instance.mAlarmState = AlarmInstance.HIDE_NOTIFICATION_STATE;
    405         AlarmInstance.updateInstance(contentResolver, instance);
    406 
    407         // Setup instance notification and scheduling timers
    408         AlarmNotifications.clearNotification(context, instance);
    409         scheduleInstanceStateChange(context, instance.getHighNotificationTime(),
    410                 instance, AlarmInstance.HIGH_NOTIFICATION_STATE);
    411     }
    412 
    413     /**
    414      * This will set the alarm instance to the HIGH_NOTIFICATION_STATE and update
    415      * the application notifications and schedule any state changes that need
    416      * to occur in the future.
    417      *
    418      * @param context  application context
    419      * @param instance to set state to
    420      */
    421     public static void setHighNotificationState(Context context, AlarmInstance instance) {
    422         LogUtils.i("Setting high notification state to instance " + instance.mId);
    423 
    424         // Update alarm state in db
    425         ContentResolver contentResolver = context.getContentResolver();
    426         instance.mAlarmState = AlarmInstance.HIGH_NOTIFICATION_STATE;
    427         AlarmInstance.updateInstance(contentResolver, instance);
    428 
    429         // Setup instance notification and scheduling timers
    430         AlarmNotifications.showHighPriorityNotification(context, instance);
    431         scheduleInstanceStateChange(context, instance.getAlarmTime(),
    432                 instance, AlarmInstance.FIRED_STATE);
    433     }
    434 
    435     /**
    436      * This will set the alarm instance to the FIRED_STATE and update
    437      * the application notifications and schedule any state changes that need
    438      * to occur in the future.
    439      *
    440      * @param context  application context
    441      * @param instance to set state to
    442      */
    443     public static void setFiredState(Context context, AlarmInstance instance) {
    444         LogUtils.i("Setting fire state to instance " + instance.mId);
    445 
    446         // Update alarm state in db
    447         ContentResolver contentResolver = context.getContentResolver();
    448         instance.mAlarmState = AlarmInstance.FIRED_STATE;
    449         AlarmInstance.updateInstance(contentResolver, instance);
    450 
    451         if (instance.mAlarmId != null) {
    452             // if the time changed *backward* and pushed an instance from missed back to fired,
    453             // remove any other scheduled instances that may exist
    454             AlarmInstance.deleteOtherInstances(context, contentResolver, instance.mAlarmId,
    455                     instance.mId);
    456         }
    457 
    458         Events.sendAlarmEvent(R.string.action_fire, 0);
    459 
    460         Calendar timeout = instance.getTimeout();
    461         if (timeout != null) {
    462             scheduleInstanceStateChange(context, timeout, instance, AlarmInstance.MISSED_STATE);
    463         }
    464 
    465         // Instance not valid anymore, so find next alarm that will fire and notify system
    466         updateNextAlarm(context);
    467     }
    468 
    469     /**
    470      * This will set the alarm instance to the SNOOZE_STATE and update
    471      * the application notifications and schedule any state changes that need
    472      * to occur in the future.
    473      *
    474      * @param context  application context
    475      * @param instance to set state to
    476      */
    477     public static void setSnoozeState(final Context context, AlarmInstance instance,
    478             boolean showToast) {
    479         // Stop alarm if this instance is firing it
    480         AlarmService.stopAlarm(context, instance);
    481 
    482         // Calculate the new snooze alarm time
    483         final int snoozeMinutes = DataModel.getDataModel().getSnoozeLength();
    484         Calendar newAlarmTime = Calendar.getInstance();
    485         newAlarmTime.add(Calendar.MINUTE, snoozeMinutes);
    486 
    487         // Update alarm state and new alarm time in db.
    488         LogUtils.i("Setting snoozed state to instance " + instance.mId + " for "
    489                 + AlarmUtils.getFormattedTime(context, newAlarmTime));
    490         instance.setAlarmTime(newAlarmTime);
    491         instance.mAlarmState = AlarmInstance.SNOOZE_STATE;
    492         AlarmInstance.updateInstance(context.getContentResolver(), instance);
    493 
    494         // Setup instance notification and scheduling timers
    495         AlarmNotifications.showSnoozeNotification(context, instance);
    496         scheduleInstanceStateChange(context, instance.getAlarmTime(),
    497                 instance, AlarmInstance.FIRED_STATE);
    498 
    499         // Display the snooze minutes in a toast.
    500         if (showToast) {
    501             final Handler mainHandler = new Handler(context.getMainLooper());
    502             final Runnable myRunnable = new Runnable() {
    503                 @Override
    504                 public void run() {
    505                     String displayTime = String.format(context.getResources().getQuantityText
    506                             (R.plurals.alarm_alert_snooze_set, snoozeMinutes).toString(),
    507                             snoozeMinutes);
    508                     Toast.makeText(context, displayTime, Toast.LENGTH_LONG).show();
    509                 }
    510             };
    511             mainHandler.post(myRunnable);
    512         }
    513 
    514         // Instance time changed, so find next alarm that will fire and notify system
    515         updateNextAlarm(context);
    516     }
    517 
    518     /**
    519      * This will set the alarm instance to the MISSED_STATE and update
    520      * the application notifications and schedule any state changes that need
    521      * to occur in the future.
    522      *
    523      * @param context  application context
    524      * @param instance to set state to
    525      */
    526     public static void setMissedState(Context context, AlarmInstance instance) {
    527         LogUtils.i("Setting missed state to instance " + instance.mId);
    528         // Stop alarm if this instance is firing it
    529         AlarmService.stopAlarm(context, instance);
    530 
    531         // Check parent if it needs to reschedule, disable or delete itself
    532         if (instance.mAlarmId != null) {
    533             updateParentAlarm(context, instance);
    534         }
    535 
    536         // Update alarm state
    537         ContentResolver contentResolver = context.getContentResolver();
    538         instance.mAlarmState = AlarmInstance.MISSED_STATE;
    539         AlarmInstance.updateInstance(contentResolver, instance);
    540 
    541         // Setup instance notification and scheduling timers
    542         AlarmNotifications.showMissedNotification(context, instance);
    543         scheduleInstanceStateChange(context, instance.getMissedTimeToLive(),
    544                 instance, AlarmInstance.DISMISSED_STATE);
    545 
    546         // Instance is not valid anymore, so find next alarm that will fire and notify system
    547         updateNextAlarm(context);
    548     }
    549 
    550     /**
    551      * This will set the alarm instance to the PREDISMISSED_STATE and schedule an instance state
    552      * change to DISMISSED_STATE at the regularly scheduled firing time.
    553      *
    554      * @param context  application context
    555      * @param instance to set state to
    556      */
    557     public static void setPreDismissState(Context context, AlarmInstance instance) {
    558         LogUtils.i("Setting predismissed state to instance " + instance.mId);
    559 
    560         // Update alarm in db
    561         final ContentResolver contentResolver = context.getContentResolver();
    562         instance.mAlarmState = AlarmInstance.PREDISMISSED_STATE;
    563         AlarmInstance.updateInstance(contentResolver, instance);
    564 
    565         // Setup instance notification and scheduling timers
    566         AlarmNotifications.clearNotification(context, instance);
    567         scheduleInstanceStateChange(context, instance.getAlarmTime(), instance,
    568                 AlarmInstance.DISMISSED_STATE);
    569 
    570         // Check parent if it needs to reschedule, disable or delete itself
    571         if (instance.mAlarmId != null) {
    572             updateParentAlarm(context, instance);
    573         }
    574 
    575         updateNextAlarm(context);
    576     }
    577 
    578     /**
    579      * This just sets the alarm instance to DISMISSED_STATE.
    580      */
    581     public static void setDismissState(Context context, AlarmInstance instance) {
    582         LogUtils.i("Setting dismissed state to instance " + instance.mId);
    583         instance.mAlarmState = AlarmInstance.DISMISSED_STATE;
    584         final ContentResolver contentResolver = context.getContentResolver();
    585         AlarmInstance.updateInstance(contentResolver, instance);
    586     }
    587 
    588     /**
    589      * This will delete the alarm instance, update the application notifications, and schedule
    590      * any state changes that need to occur in the future.
    591      *
    592      * @param context  application context
    593      * @param instance to set state to
    594      */
    595     public static void deleteInstanceAndUpdateParent(Context context, AlarmInstance instance) {
    596         LogUtils.i("Deleting instance " + instance.mId + " and updating parent alarm.");
    597 
    598         // Remove all other timers and notifications associated to it
    599         unregisterInstance(context, instance);
    600 
    601         // Check parent if it needs to reschedule, disable or delete itself
    602         if (instance.mAlarmId != null) {
    603             updateParentAlarm(context, instance);
    604         }
    605 
    606         // Delete instance as it is not needed anymore
    607         AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    608 
    609         // Instance is not valid anymore, so find next alarm that will fire and notify system
    610         updateNextAlarm(context);
    611     }
    612 
    613     /**
    614      * This will set the instance state to DISMISSED_STATE and remove its notifications and
    615      * alarm timers.
    616      *
    617      * @param context  application context
    618      * @param instance to unregister
    619      */
    620     public static void unregisterInstance(Context context, AlarmInstance instance) {
    621         LogUtils.i("Unregistering instance " + instance.mId);
    622         // Stop alarm if this instance is firing it
    623         AlarmService.stopAlarm(context, instance);
    624         AlarmNotifications.clearNotification(context, instance);
    625         cancelScheduledInstanceStateChange(context, instance);
    626         setDismissState(context, instance);
    627     }
    628 
    629     /**
    630      * This registers the AlarmInstance to the state manager. This will look at the instance
    631      * and choose the most appropriate state to put it in. This is primarily used by new
    632      * alarms, but it can also be called when the system time changes.
    633      *
    634      * Most state changes are handled by the states themselves, but during major time changes we
    635      * have to correct the alarm instance state. This means we have to handle special cases as
    636      * describe below:
    637      *
    638      * <ul>
    639      *     <li>Make sure all dismissed alarms are never re-activated</li>
    640      *     <li>Make sure pre-dismissed alarms stay predismissed</li>
    641      *     <li>Make sure firing alarms stayed fired unless they should be auto-silenced</li>
    642      *     <li>Missed instance that have parents should be re-enabled if we went back in time</li>
    643      *     <li>If alarm was SNOOZED, then show the notification but don't update time</li>
    644      *     <li>If low priority notification was hidden, then make sure it stays hidden</li>
    645      * </ul>
    646      *
    647      * If none of these special case are found, then we just check the time and see what is the
    648      * proper state for the instance.
    649      *
    650      * @param context  application context
    651      * @param instance to register
    652      */
    653     public static void registerInstance(Context context, AlarmInstance instance,
    654             boolean updateNextAlarm) {
    655         LogUtils.i("Registering instance: " + instance.mId);
    656         final ContentResolver cr = context.getContentResolver();
    657         final Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    658         final Calendar currentTime = getCurrentTime();
    659         final Calendar alarmTime = instance.getAlarmTime();
    660         final Calendar timeoutTime = instance.getTimeout();
    661         final Calendar lowNotificationTime = instance.getLowNotificationTime();
    662         final Calendar highNotificationTime = instance.getHighNotificationTime();
    663         final Calendar missedTTL = instance.getMissedTimeToLive();
    664 
    665         // Handle special use cases here
    666         if (instance.mAlarmState == AlarmInstance.DISMISSED_STATE) {
    667             // This should never happen, but add a quick check here
    668             LogUtils.e("Alarm Instance is dismissed, but never deleted");
    669             deleteInstanceAndUpdateParent(context, instance);
    670             return;
    671         } else if (instance.mAlarmState == AlarmInstance.FIRED_STATE) {
    672             // Keep alarm firing, unless it should be timed out
    673             boolean hasTimeout = timeoutTime != null && currentTime.after(timeoutTime);
    674             if (!hasTimeout) {
    675                 setFiredState(context, instance);
    676                 return;
    677             }
    678         } else if (instance.mAlarmState == AlarmInstance.MISSED_STATE) {
    679             if (currentTime.before(alarmTime)) {
    680                 if (instance.mAlarmId == null) {
    681                     LogUtils.i("Cannot restore missed instance for one-time alarm");
    682                     // This instance parent got deleted (ie. deleteAfterUse), so
    683                     // we should not re-activate it.-
    684                     deleteInstanceAndUpdateParent(context, instance);
    685                     return;
    686                 }
    687 
    688                 // TODO: This will re-activate missed snoozed alarms, but will
    689                 // use our normal notifications. This is not ideal, but very rare use-case.
    690                 // We should look into fixing this in the future.
    691 
    692                 // Make sure we re-enable the parent alarm of the instance
    693                 // because it will get activated by by the below code
    694                 alarm.enabled = true;
    695                 Alarm.updateAlarm(cr, alarm);
    696             }
    697         } else if (instance.mAlarmState == AlarmInstance.PREDISMISSED_STATE) {
    698             if (currentTime.before(alarmTime)) {
    699                 setPreDismissState(context, instance);
    700             } else {
    701                 deleteInstanceAndUpdateParent(context, instance);
    702             }
    703             return;
    704         }
    705 
    706         // Fix states that are time sensitive
    707         if (currentTime.after(missedTTL)) {
    708             // Alarm is so old, just dismiss it
    709             deleteInstanceAndUpdateParent(context, instance);
    710         } else if (currentTime.after(alarmTime)) {
    711             // There is a chance that the TIME_SET occurred right when the alarm should go off, so
    712             // we need to add a check to see if we should fire the alarm instead of marking it
    713             // missed.
    714             Calendar alarmBuffer = Calendar.getInstance();
    715             alarmBuffer.setTime(alarmTime.getTime());
    716             alarmBuffer.add(Calendar.SECOND, ALARM_FIRE_BUFFER);
    717             if (currentTime.before(alarmBuffer)) {
    718                 setFiredState(context, instance);
    719             } else {
    720                 setMissedState(context, instance);
    721             }
    722         } else if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
    723             // We only want to display snooze notification and not update the time,
    724             // so handle showing the notification directly
    725             AlarmNotifications.showSnoozeNotification(context, instance);
    726             scheduleInstanceStateChange(context, instance.getAlarmTime(),
    727                     instance, AlarmInstance.FIRED_STATE);
    728         } else if (currentTime.after(highNotificationTime)) {
    729             setHighNotificationState(context, instance);
    730         } else if (currentTime.after(lowNotificationTime)) {
    731             // Only show low notification if it wasn't hidden in the past
    732             if (instance.mAlarmState == AlarmInstance.HIDE_NOTIFICATION_STATE) {
    733                 setHideNotificationState(context, instance);
    734             } else {
    735                 setLowNotificationState(context, instance);
    736             }
    737         } else {
    738             // Alarm is still active, so initialize as a silent alarm
    739             setSilentState(context, instance);
    740         }
    741 
    742         // The caller prefers to handle updateNextAlarm for optimization
    743         if (updateNextAlarm) {
    744             updateNextAlarm(context);
    745         }
    746     }
    747 
    748     /**
    749      * This will delete and unregister all instances associated with alarmId, without affect
    750      * the alarm itself. This should be used whenever modifying or deleting an alarm.
    751      *
    752      * @param context application context
    753      * @param alarmId to find instances to delete.
    754      */
    755     public static void deleteAllInstances(Context context, long alarmId) {
    756         LogUtils.i("Deleting all instances of alarm: " + alarmId);
    757         ContentResolver cr = context.getContentResolver();
    758         List<AlarmInstance> instances = AlarmInstance.getInstancesByAlarmId(cr, alarmId);
    759         for (AlarmInstance instance : instances) {
    760             unregisterInstance(context, instance);
    761             AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    762         }
    763         updateNextAlarm(context);
    764     }
    765 
    766     /**
    767      * Delete and unregister all instances unless they are snoozed. This is used whenever an alarm
    768      * is modified superficially (label, vibrate, or ringtone change).
    769      */
    770     public static void deleteNonSnoozeInstances(Context context, long alarmId) {
    771         LogUtils.i("Deleting all non-snooze instances of alarm: " + alarmId);
    772         ContentResolver cr = context.getContentResolver();
    773         List<AlarmInstance> instances = AlarmInstance.getInstancesByAlarmId(cr, alarmId);
    774         for (AlarmInstance instance : instances) {
    775             if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
    776                 continue;
    777             }
    778             unregisterInstance(context, instance);
    779             AlarmInstance.deleteInstance(context.getContentResolver(), instance.mId);
    780         }
    781         updateNextAlarm(context);
    782     }
    783 
    784     /**
    785      * Fix and update all alarm instance when a time change event occurs.
    786      *
    787      * @param context application context
    788      */
    789     public static void fixAlarmInstances(Context context) {
    790         LogUtils.i("Fixing alarm instances");
    791         // Register all instances after major time changes or when phone restarts
    792         final ContentResolver contentResolver = context.getContentResolver();
    793         final Calendar currentTime = getCurrentTime();
    794 
    795         // Sort the instances in reverse chronological order so that later instances are fixed or
    796         // deleted before re-scheduling prior instances (which may re-create or update the later
    797         // instances).
    798         final List<AlarmInstance> instances = AlarmInstance.getInstances(
    799                 contentResolver, null /* selection */);
    800         Collections.sort(instances, new Comparator<AlarmInstance>() {
    801             @Override
    802             public int compare(AlarmInstance lhs, AlarmInstance rhs) {
    803                 return rhs.getAlarmTime().compareTo(lhs.getAlarmTime());
    804             }
    805         });
    806 
    807         for (AlarmInstance instance : instances) {
    808             final Alarm alarm = Alarm.getAlarm(contentResolver, instance.mAlarmId);
    809             if (alarm == null) {
    810                 unregisterInstance(context, instance);
    811                 AlarmInstance.deleteInstance(contentResolver, instance.mId);
    812                 LogUtils.e("Found instance without matching alarm; deleting instance %s", instance);
    813                 continue;
    814             }
    815             final Calendar priorAlarmTime = alarm.getPreviousAlarmTime(instance.getAlarmTime());
    816             final Calendar missedTTLTime = instance.getMissedTimeToLive();
    817             if (currentTime.before(priorAlarmTime) || currentTime.after(missedTTLTime)) {
    818                 final Calendar oldAlarmTime = instance.getAlarmTime();
    819                 final Calendar newAlarmTime = alarm.getNextAlarmTime(currentTime);
    820                 final CharSequence oldTime = DateFormat.format("MM/dd/yyyy hh:mm a", oldAlarmTime);
    821                 final CharSequence newTime = DateFormat.format("MM/dd/yyyy hh:mm a", newAlarmTime);
    822                 LogUtils.i("A time change has caused an existing alarm scheduled to fire at %s to" +
    823                         " be replaced by a new alarm scheduled to fire at %s", oldTime, newTime);
    824 
    825                 // The time change is so dramatic the AlarmInstance doesn't make any sense;
    826                 // remove it and schedule the new appropriate instance.
    827                 AlarmStateManager.deleteInstanceAndUpdateParent(context, instance);
    828             } else {
    829                 registerInstance(context, instance, false /* updateNextAlarm */);
    830             }
    831         }
    832 
    833         updateNextAlarm(context);
    834     }
    835 
    836     /**
    837      * Utility method to set alarm instance state via constants.
    838      *
    839      * @param context  application context
    840      * @param instance to change state on
    841      * @param state    to change to
    842      */
    843     private static void setAlarmState(Context context, AlarmInstance instance, int state) {
    844         if (instance == null) {
    845             LogUtils.e("Null alarm instance while setting state to %d", state);
    846             return;
    847         }
    848         switch (state) {
    849             case AlarmInstance.SILENT_STATE:
    850                 setSilentState(context, instance);
    851                 break;
    852             case AlarmInstance.LOW_NOTIFICATION_STATE:
    853                 setLowNotificationState(context, instance);
    854                 break;
    855             case AlarmInstance.HIDE_NOTIFICATION_STATE:
    856                 setHideNotificationState(context, instance);
    857                 break;
    858             case AlarmInstance.HIGH_NOTIFICATION_STATE:
    859                 setHighNotificationState(context, instance);
    860                 break;
    861             case AlarmInstance.FIRED_STATE:
    862                 setFiredState(context, instance);
    863                 break;
    864             case AlarmInstance.SNOOZE_STATE:
    865                 setSnoozeState(context, instance, true /* showToast */);
    866                 break;
    867             case AlarmInstance.MISSED_STATE:
    868                 setMissedState(context, instance);
    869                 break;
    870             case AlarmInstance.PREDISMISSED_STATE:
    871                 setPreDismissState(context, instance);
    872                 break;
    873             case AlarmInstance.DISMISSED_STATE:
    874                 deleteInstanceAndUpdateParent(context, instance);
    875                 break;
    876             default:
    877                 LogUtils.e("Trying to change to unknown alarm state: " + state);
    878         }
    879     }
    880 
    881     @Override
    882     public void onReceive(final Context context, final Intent intent) {
    883         if (INDICATOR_ACTION.equals(intent.getAction())) {
    884             return;
    885         }
    886 
    887         final PendingResult result = goAsync();
    888         final PowerManager.WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context);
    889         wl.acquire();
    890         AsyncHandler.post(new Runnable() {
    891             @Override
    892             public void run() {
    893                 handleIntent(context, intent);
    894                 result.finish();
    895                 wl.release();
    896             }
    897         });
    898     }
    899 
    900     public static void handleIntent(Context context, Intent intent) {
    901         final String action = intent.getAction();
    902         LogUtils.v("AlarmStateManager received intent " + intent);
    903         if (CHANGE_STATE_ACTION.equals(action)) {
    904             Uri uri = intent.getData();
    905             AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
    906                     AlarmInstance.getId(uri));
    907             if (instance == null) {
    908                 LogUtils.e("Can not change state for unknown instance: " + uri);
    909                 return;
    910             }
    911 
    912             int globalId = DataModel.getDataModel().getGlobalIntentId();
    913             int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1);
    914             int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1);
    915             if (intentId != globalId) {
    916                 LogUtils.i("IntentId: " + intentId + " GlobalId: " + globalId + " AlarmState: " +
    917                         alarmState);
    918                 // Allows dismiss/snooze requests to go through
    919                 if (!intent.hasCategory(ALARM_DISMISS_TAG) &&
    920                         !intent.hasCategory(ALARM_SNOOZE_TAG)) {
    921                     LogUtils.i("Ignoring old Intent");
    922                     return;
    923                 }
    924             }
    925 
    926             if (intent.getBooleanExtra(FROM_NOTIFICATION_EXTRA, false)) {
    927                 if (intent.hasCategory(ALARM_DISMISS_TAG)) {
    928                     Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_notification);
    929                 } else if (intent.hasCategory(ALARM_SNOOZE_TAG)) {
    930                     Events.sendAlarmEvent(R.string.action_snooze, R.string.label_notification);
    931                 }
    932             }
    933 
    934             if (alarmState >= 0) {
    935                 setAlarmState(context, instance, alarmState);
    936             } else {
    937                 registerInstance(context, instance, true);
    938             }
    939         } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) {
    940             Uri uri = intent.getData();
    941             AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
    942                     AlarmInstance.getId(uri));
    943 
    944             if (instance == null) {
    945                 LogUtils.e("Null alarminstance for SHOW_AND_DISMISS");
    946                 // dismiss the notification
    947                 final int id = intent.getIntExtra(AlarmNotifications.EXTRA_NOTIFICATION_ID, -1);
    948                 if (id != -1) {
    949                     NotificationManagerCompat.from(context).cancel(id);
    950                 }
    951                 return;
    952             }
    953 
    954             long alarmId = instance.mAlarmId == null ? Alarm.INVALID_ID : instance.mAlarmId;
    955             final Intent viewAlarmIntent = Alarm.createIntent(context, DeskClock.class, alarmId)
    956                     .putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId)
    957                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    958 
    959             // Open DeskClock which is now positioned on the alarms tab.
    960             context.startActivity(viewAlarmIntent);
    961 
    962             deleteInstanceAndUpdateParent(context, instance);
    963         }
    964     }
    965 
    966     /**
    967      * Creates an intent that can be used to set an AlarmManager alarm to set the next alarm
    968      * indicators.
    969      */
    970     public static Intent createIndicatorIntent(Context context) {
    971         return new Intent(context, AlarmStateManager.class).setAction(INDICATOR_ACTION);
    972     }
    973 
    974     /**
    975      * Abstract away how the current time is computed. If no implementation of this interface is
    976      * given the default is to return {@link Calendar#getInstance()}. Otherwise, the factory
    977      * instance is consulted for the current time.
    978      */
    979     interface CurrentTimeFactory {
    980         Calendar getCurrentTime();
    981     }
    982 
    983     /**
    984      * Abstracts away how state changes are scheduled. The {@link AlarmManagerStateChangeScheduler}
    985      * implementation schedules callbacks within the system AlarmManager. Alternate
    986      * implementations, such as test case mocks can subvert this behavior.
    987      */
    988     interface StateChangeScheduler {
    989         void scheduleInstanceStateChange(Context context, Calendar time,
    990                 AlarmInstance instance, int newState);
    991 
    992         void cancelScheduledInstanceStateChange(Context context, AlarmInstance instance);
    993     }
    994 
    995     /**
    996      * Schedules state change callbacks within the AlarmManager.
    997      */
    998     private static class AlarmManagerStateChangeScheduler implements StateChangeScheduler {
    999         @Override
   1000         public void scheduleInstanceStateChange(Context context, Calendar time,
   1001                 AlarmInstance instance, int newState) {
   1002             final long timeInMillis = time.getTimeInMillis();
   1003             LogUtils.i("Scheduling state change %d to instance %d at %s (%d)", newState,
   1004                     instance.mId, AlarmUtils.getFormattedTime(context, time), timeInMillis);
   1005             final Intent stateChangeIntent =
   1006                     createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, newState);
   1007             // Treat alarm state change as high priority, use foreground broadcasts
   1008             stateChangeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
   1009             PendingIntent pendingIntent = PendingIntent.getService(context, instance.hashCode(),
   1010                     stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
   1011 
   1012             final AlarmManager am = (AlarmManager) context.getSystemService(ALARM_SERVICE);
   1013             if (Utils.isMOrLater()) {
   1014                 // Ensure the alarm fires even if the device is dozing.
   1015                 am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
   1016             } else {
   1017                 am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
   1018             }
   1019         }
   1020 
   1021         @Override
   1022         public void cancelScheduledInstanceStateChange(Context context, AlarmInstance instance) {
   1023             LogUtils.v("Canceling instance " + instance.mId + " timers");
   1024 
   1025             // Create a PendingIntent that will match any one set for this instance
   1026             PendingIntent pendingIntent = PendingIntent.getService(context, instance.hashCode(),
   1027                     createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, null),
   1028                     PendingIntent.FLAG_NO_CREATE);
   1029 
   1030             if (pendingIntent != null) {
   1031                 AlarmManager am = (AlarmManager) context.getSystemService(ALARM_SERVICE);
   1032                 am.cancel(pendingIntent);
   1033                 pendingIntent.cancel();
   1034             }
   1035         }
   1036     }
   1037 }
   1038