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