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