• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getGlobalIntentId(Context context)125     public static int getGlobalIntentId(Context context) {
126         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
127         return prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1);
128     }
129 
updateGlobalIntentId(Context context)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      */
updateNextAlarm(Context context)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      */
updateParentAlarm(Context context, AlarmInstance instance)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      */
createStateChangeIntent(Context context, String tag, AlarmInstance instance, Integer state)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      */
scheduleInstanceStateChange(Context context, Calendar time, AlarmInstance instance, int newState)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      */
cancelScheduledInstance(Context context, AlarmInstance instance)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      */
setSilentState(Context context, AlarmInstance instance)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      */
setLowNotificationState(Context context, AlarmInstance instance)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      */
setHideNotificationState(Context context, AlarmInstance instance)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      */
setHighNotificationState(Context context, AlarmInstance instance)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      */
setFiredState(Context context, AlarmInstance instance)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      */
setSnoozeState(Context context, AlarmInstance instance, boolean showToast)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 
getSnoozedMinutes(Context context)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      */
setMissedState(Context context, AlarmInstance instance)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      */
setDismissState(Context context, AlarmInstance instance)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      */
unregisterInstance(Context context, AlarmInstance instance)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      */
registerInstance(Context context, AlarmInstance instance, boolean updateNextAlarm)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      */
deleteAllInstances(Context context, long alarmId)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      */
fixAlarmInstances(Context context)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      */
setAlarmState(Context context, AlarmInstance instance, int state)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
onReceive(final Context context, final Intent intent)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 
handleIntent(Context context, Intent intent)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      */
createIndicatorIntent(Context context)747     public static Intent createIndicatorIntent(Context context) {
748         return new Intent(context, AlarmStateManager.class).setAction(INDICATOR_ACTION);
749     }
750 }
751