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