1 /* 2 * Copyright (C) 2020 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 17 package com.android.deskclock.data 18 19 import android.annotation.SuppressLint 20 import android.app.AlarmManager 21 import android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP 22 import android.app.Notification 23 import android.app.PendingIntent 24 import android.app.Service 25 import android.content.BroadcastReceiver 26 import android.content.Context 27 import android.content.Intent 28 import android.content.IntentFilter 29 import android.content.SharedPreferences 30 import android.content.SharedPreferences.OnSharedPreferenceChangeListener 31 import android.net.Uri 32 import android.text.format.DateUtils.MINUTE_IN_MILLIS 33 import androidx.annotation.StringRes 34 import androidx.core.app.NotificationManagerCompat 35 36 import com.android.deskclock.AlarmAlertWakeLock 37 import com.android.deskclock.LogUtils 38 import com.android.deskclock.R 39 import com.android.deskclock.Utils 40 import com.android.deskclock.events.Events 41 import com.android.deskclock.settings.SettingsActivity 42 import com.android.deskclock.timer.TimerKlaxon 43 import com.android.deskclock.timer.TimerService 44 45 /** 46 * All [Timer] data is accessed via this model. 47 */ 48 internal class TimerModel( 49 private val mContext: Context, 50 private val mPrefs: SharedPreferences, 51 /** The model from which settings are fetched. */ 52 private val mSettingsModel: SettingsModel, 53 /** The model from which ringtone data are fetched. */ 54 private val mRingtoneModel: RingtoneModel, 55 /** The model from which notification data are fetched. */ 56 private val mNotificationModel: NotificationModel 57 ) { 58 /** The alarm manager system service that calls back when timers expire. */ 59 private val mAlarmManager = mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager 60 61 /** Used to create and destroy system notifications related to timers. */ 62 private val mNotificationManager = NotificationManagerCompat.from(mContext) 63 64 /** Update timer notification when locale changes. */ 65 private val mLocaleChangedReceiver: BroadcastReceiver = LocaleChangedReceiver() 66 67 /** 68 * Retain a hard reference to the shared preference observer to prevent it from being garbage 69 * collected. See [SharedPreferences.registerOnSharedPreferenceChangeListener] for detail. 70 */ 71 private val mPreferenceListener: OnSharedPreferenceChangeListener = PreferenceListener() 72 73 /** The listeners to notify when a timer is added, updated or removed. */ 74 private val mTimerListeners: MutableList<TimerListener> = mutableListOf() 75 76 /** Delegate that builds platform-specific timer notifications. */ 77 private val mNotificationBuilder = TimerNotificationBuilder() 78 79 /** 80 * The ids of expired timers for which the ringer is ringing. Not all expired timers have their 81 * ids in this collection. If a timer was already expired when the app was started its id will 82 * be absent from this collection. 83 */ 84 @SuppressLint("NewApi") 85 private val mRingingIds: MutableSet<Int> = mutableSetOf() 86 87 /** The uri of the ringtone to play for timers. */ 88 private var mTimerRingtoneUri: Uri? = null 89 90 /** The title of the ringtone to play for timers. */ 91 private var mTimerRingtoneTitle: String? = null 92 93 /** A mutable copy of the timers. */ 94 private var mTimers: MutableList<Timer>? = null 95 96 /** A mutable copy of the expired timers. */ 97 private var mExpiredTimers: MutableList<Timer>? = null 98 99 /** A mutable copy of the missed timers. */ 100 private var mMissedTimers: MutableList<Timer>? = null 101 102 /** 103 * The service that keeps this application in the foreground while a heads-up timer 104 * notification is displayed. Marking the service as foreground prevents the operating system 105 * from killing this application while expired timers are actively firing. 106 */ 107 private var mService: Service? = null 108 109 init { 110 // Clear caches affected by preferences when preferences change. 111 mPrefs.registerOnSharedPreferenceChangeListener(mPreferenceListener) 112 113 // Update timer notification when locale changes. 114 val localeBroadcastFilter = IntentFilter(Intent.ACTION_LOCALE_CHANGED) 115 mContext.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter) 116 } 117 118 /** 119 * @param timerListener to be notified when timers are added, updated and removed 120 */ addTimerListenernull121 fun addTimerListener(timerListener: TimerListener) { 122 mTimerListeners.add(timerListener) 123 } 124 125 /** 126 * @param timerListener to no longer be notified when timers are added, updated and removed 127 */ removeTimerListenernull128 fun removeTimerListener(timerListener: TimerListener) { 129 mTimerListeners.remove(timerListener) 130 } 131 132 /** 133 * @return all defined timers in their creation order 134 */ 135 val timers: List<Timer> 136 get() = mutableTimers 137 138 /** 139 * @return all expired timers in their expiration order 140 */ 141 val expiredTimers: List<Timer> 142 get() = mutableExpiredTimers 143 144 /** 145 * @return all missed timers in their expiration order 146 */ 147 private val missedTimers: List<Timer> 148 get() = mutableMissedTimers 149 150 /** 151 * @param timerId identifies the timer to return 152 * @return the timer with the given `timerId` 153 */ getTimernull154 fun getTimer(timerId: Int): Timer? { 155 for (timer in mutableTimers) { 156 if (timer.id == timerId) { 157 return timer 158 } 159 } 160 161 return null 162 } 163 164 /** 165 * @return the timer that last expired and is still expired now; `null` if no timers are 166 * expired 167 */ 168 val mostRecentExpiredTimer: Timer? 169 get() { 170 val timers = mutableExpiredTimers 171 return if (timers.isEmpty()) null else timers[timers.size - 1] 172 } 173 174 /** 175 * @param length the length of the timer in milliseconds 176 * @param label describes the purpose of the timer 177 * @param deleteAfterUse `true` indicates the timer should be deleted when it is reset 178 * @return the newly added timer 179 */ addTimernull180 fun addTimer(length: Long, label: String?, deleteAfterUse: Boolean): Timer { 181 // Create the timer instance. 182 var timer = 183 Timer(-1, Timer.State.RESET, length, length, Timer.UNUSED, Timer.UNUSED, length, 184 label, deleteAfterUse) 185 186 // Add the timer to permanent storage. 187 timer = TimerDAO.addTimer(mPrefs, timer) 188 189 // Add the timer to the cache. 190 mutableTimers.add(0, timer) 191 192 // Update the timer notification. 193 updateNotification() 194 // Heads-Up notification is unaffected by this change 195 196 // Notify listeners of the change. 197 for (timerListener in mTimerListeners) { 198 timerListener.timerAdded(timer) 199 } 200 201 return timer 202 } 203 204 /** 205 * @param service used to start foreground notifications related to expired timers 206 * @param timer the timer to be expired 207 */ expireTimernull208 fun expireTimer(service: Service?, timer: Timer) { 209 if (mService == null) { 210 // If this is the first expired timer, retain the service that will be used to start 211 // the heads-up notification in the foreground. 212 mService = service 213 } else if (mService != service) { 214 // If this is not the first expired timer, the service should match the one given when 215 // the first timer expired. 216 LogUtils.wtf("Expected TimerServices to be identical") 217 } 218 219 updateTimer(timer.expire()) 220 } 221 222 /** 223 * @param timer an updated timer to store 224 */ updateTimernull225 fun updateTimer(timer: Timer) { 226 val before = doUpdateTimer(timer) 227 228 // Update the notification after updating the timer data. 229 updateNotification() 230 231 // If the timer started or stopped being expired, update the heads-up notification. 232 if (before.state != timer.state) { 233 if (before.isExpired || timer.isExpired) { 234 updateHeadsUpNotification() 235 } 236 } 237 } 238 239 /** 240 * @param timer an existing timer to be removed 241 */ removeTimernull242 fun removeTimer(timer: Timer) { 243 doRemoveTimer(timer) 244 245 // Update the timer notifications after removing the timer data. 246 if (timer.isExpired) { 247 updateHeadsUpNotification() 248 } else { 249 updateNotification() 250 } 251 } 252 253 /** 254 * If the given `timer` is expired and marked for deletion after use then this method 255 * removes the timer. The timer is otherwise transitioned to the reset state and continues 256 * to exist. 257 * 258 * @param timer the timer to be reset 259 * @param allowDelete `true` if the timer is allowed to be deleted instead of reset 260 * (e.g. one use timers) 261 * @param eventLabelId the label of the timer event to send; 0 if no event should be sent 262 * @return the reset `timer` or `null` if the timer was deleted 263 */ resetTimernull264 fun resetTimer(timer: Timer, allowDelete: Boolean, @StringRes eventLabelId: Int): Timer? { 265 val result = doResetOrDeleteTimer(timer, allowDelete, eventLabelId) 266 267 // Update the notification after updating the timer data. 268 when { 269 timer.isMissed -> updateMissedNotification() 270 timer.isExpired -> updateHeadsUpNotification() 271 else -> updateNotification() 272 } 273 274 return result 275 } 276 277 /** 278 * Update timers after system reboot. 279 */ updateTimersAfterRebootnull280 fun updateTimersAfterReboot() { 281 for (timer in timers) { 282 doUpdateAfterRebootTimer(timer) 283 } 284 285 // Update the notifications once after all timers are updated. 286 updateNotification() 287 updateMissedNotification() 288 updateHeadsUpNotification() 289 } 290 291 /** 292 * Update timers after time set. 293 */ updateTimersAfterTimeSetnull294 fun updateTimersAfterTimeSet() { 295 for (timer in timers) { 296 doUpdateAfterTimeSetTimer(timer) 297 } 298 299 // Update the notifications once after all timers are updated. 300 updateNotification() 301 updateMissedNotification() 302 updateHeadsUpNotification() 303 } 304 305 /** 306 * Reset all expired timers. Exactly one parameter should be filled, with preference given to 307 * eventLabelId. 308 * 309 * @param eventLabelId the label of the timer event to send; 0 if no event should be sent 310 */ resetOrDeleteExpiredTimersnull311 fun resetOrDeleteExpiredTimers(@StringRes eventLabelId: Int) { 312 for (timer in timers) { 313 if (timer.isExpired) { 314 doResetOrDeleteTimer(timer, true /* allowDelete */, eventLabelId) 315 } 316 } 317 318 // Update the notifications once after all timers are updated. 319 updateHeadsUpNotification() 320 } 321 322 /** 323 * Reset all missed timers. 324 * 325 * @param eventLabelId the label of the timer event to send; 0 if no event should be sent 326 */ resetMissedTimersnull327 fun resetMissedTimers(@StringRes eventLabelId: Int) { 328 for (timer in timers) { 329 if (timer.isMissed) { 330 doResetOrDeleteTimer(timer, true /* allowDelete */, eventLabelId) 331 } 332 } 333 334 // Update the notifications once after all timers are updated. 335 updateMissedNotification() 336 } 337 338 /** 339 * Reset all unexpired timers. 340 * 341 * @param eventLabelId the label of the timer event to send; 0 if no event should be sent 342 */ resetUnexpiredTimersnull343 fun resetUnexpiredTimers(@StringRes eventLabelId: Int) { 344 for (timer in timers) { 345 if (timer.isRunning || timer.isPaused) { 346 doResetOrDeleteTimer(timer, true /* allowDelete */, eventLabelId) 347 } 348 } 349 350 // Update the notification once after all timers are updated. 351 updateNotification() 352 // Heads-Up notification is unaffected by this change 353 } 354 355 /** 356 * @return the uri of the default ringtone to play for all timers when no user selection exists 357 */ 358 val defaultTimerRingtoneUri: Uri 359 get() = mSettingsModel.defaultTimerRingtoneUri 360 361 /** 362 * @return `true` iff the ringtone to play for all timers is the silent ringtone 363 */ 364 val isTimerRingtoneSilent: Boolean 365 get() = Uri.EMPTY.equals(timerRingtoneUri) 366 367 var timerRingtoneUri: Uri 368 /** 369 * @return the uri of the ringtone to play for all timers 370 */ 371 get() { 372 if (mTimerRingtoneUri == null) { 373 mTimerRingtoneUri = mSettingsModel.timerRingtoneUri 374 } 375 376 return mTimerRingtoneUri!! 377 } 378 /** 379 * @param uri the uri of the ringtone to play for all timers 380 */ 381 set(uri) { 382 mSettingsModel.timerRingtoneUri = uri 383 } 384 385 /** 386 * @return the title of the ringtone that is played for all timers 387 */ 388 val timerRingtoneTitle: String 389 get() { 390 if (mTimerRingtoneTitle == null) { 391 mTimerRingtoneTitle = if (isTimerRingtoneSilent) { 392 // Special case: no ringtone has a title of "Silent". 393 mContext.getString(R.string.silent_ringtone_title) 394 } else { 395 val defaultUri: Uri = defaultTimerRingtoneUri 396 val uri: Uri = timerRingtoneUri 397 if (defaultUri.equals(uri)) { 398 // Special case: default ringtone has a title of "Timer Expired". 399 mContext.getString(R.string.default_timer_ringtone_title) 400 } else { 401 mRingtoneModel.getRingtoneTitle(uri) 402 } 403 } 404 } 405 406 return mTimerRingtoneTitle!! 407 } 408 409 /** 410 * @return the duration, in milliseconds, of the crescendo to apply to timer ringtone playback; 411 * `0` implies no crescendo should be applied 412 */ 413 val timerCrescendoDuration: Long 414 get() = mSettingsModel.timerCrescendoDuration 415 416 var timerVibrate: Boolean 417 /** 418 * @return `true` if the device vibrates when timers expire 419 */ 420 get() = mSettingsModel.timerVibrate 421 /** 422 * @param enabled `true` if the device should vibrate when timers expire 423 */ 424 set(enabled) { 425 mSettingsModel.timerVibrate = enabled 426 } 427 428 private val mutableTimers: MutableList<Timer> 429 get() { 430 if (mTimers == null) { 431 mTimers = TimerDAO.getTimers(mPrefs) 432 mTimers!!.sortWith(Timer.ID_COMPARATOR) 433 } 434 435 return mTimers!! 436 } 437 438 private val mutableExpiredTimers: List<Timer> 439 get() { 440 if (mExpiredTimers == null) { 441 mExpiredTimers = mutableListOf() 442 for (timer in mutableTimers) { 443 if (timer.isExpired) { 444 mExpiredTimers!!.add(timer) 445 } 446 } 447 mExpiredTimers!!.sortWith(Timer.EXPIRY_COMPARATOR) 448 } 449 450 return mExpiredTimers!! 451 } 452 453 private val mutableMissedTimers: List<Timer> 454 get() { 455 if (mMissedTimers == null) { 456 mMissedTimers = mutableListOf() 457 for (timer in mutableTimers) { 458 if (timer.isMissed) { 459 mMissedTimers!!.add(timer) 460 } 461 } 462 mMissedTimers!!.sortWith(Timer.EXPIRY_COMPARATOR) 463 } 464 465 return mMissedTimers!! 466 } 467 468 /** 469 * This method updates timer data without updating notifications. This is useful in bulk-update 470 * scenarios so the notifications are only rebuilt once. 471 * 472 * @param timer an updated timer to store 473 * @return the state of the timer prior to the update 474 */ doUpdateTimernull475 private fun doUpdateTimer(timer: Timer): Timer { 476 // Retrieve the cached form of the timer. 477 val timers = mutableTimers 478 val index = timers.indexOf(timer) 479 val before = timers[index] 480 481 // If no change occurred, ignore this update. 482 if (timer === before) { 483 return timer 484 } 485 486 // Update the timer in permanent storage. 487 TimerDAO.updateTimer(mPrefs, timer) 488 489 // Update the timer in the cache. 490 val oldTimer = timers.set(index, timer) 491 492 // Clear the cache of expired timers if the timer changed to/from expired. 493 if (before.isExpired || timer.isExpired) { 494 mExpiredTimers = null 495 } 496 // Clear the cache of missed timers if the timer changed to/from missed. 497 if (before.isMissed || timer.isMissed) { 498 mMissedTimers = null 499 } 500 501 // Update the timer expiration callback. 502 updateAlarmManager() 503 504 // Update the timer ringer. 505 updateRinger(before, timer) 506 507 // Notify listeners of the change. 508 for (timerListener in mTimerListeners) { 509 timerListener.timerUpdated(before, timer) 510 } 511 512 return oldTimer 513 } 514 515 /** 516 * This method removes timer data without updating notifications. This is useful in bulk-remove 517 * scenarios so the notifications are only rebuilt once. 518 * 519 * @param timer an existing timer to be removed 520 */ doRemoveTimernull521 private fun doRemoveTimer(timer: Timer) { 522 // Remove the timer from permanent storage. 523 var timerVar = timer 524 TimerDAO.removeTimer(mPrefs, timerVar) 525 526 // Remove the timer from the cache. 527 val timers: MutableList<Timer> = mutableTimers 528 val index = timers.indexOf(timerVar) 529 530 // If the timer cannot be located there is nothing to remove. 531 if (index == -1) { 532 return 533 } 534 timerVar = timers.removeAt(index) 535 536 // Clear the cache of expired timers if a new expired timer was added. 537 if (timerVar.isExpired) { 538 mExpiredTimers = null 539 } 540 541 // Clear the cache of missed timers if a new missed timer was added. 542 if (timerVar.isMissed) { 543 mMissedTimers = null 544 } 545 546 // Update the timer expiration callback. 547 updateAlarmManager() 548 549 // Update the timer ringer. 550 updateRinger(timerVar, null) 551 552 // Notify listeners of the change. 553 for (timerListener in mTimerListeners) { 554 timerListener.timerRemoved(timerVar) 555 } 556 } 557 558 /** 559 * This method updates/removes timer data without updating notifications. This is useful in 560 * bulk-update scenarios so the notifications are only rebuilt once. 561 * 562 * If the given `timer` is expired and marked for deletion after use then this method 563 * removes the timer. The timer is otherwise transitioned to the reset state and continues 564 * to exist. 565 * 566 * @param timer the timer to be reset 567 * @param allowDelete `true` if the timer is allowed to be deleted instead of reset 568 * (e.g. one use timers) 569 * @param eventLabelId the label of the timer event to send; 0 if no event should be sent 570 * @return the reset `timer` or `null` if the timer was deleted 571 */ doResetOrDeleteTimernull572 private fun doResetOrDeleteTimer( 573 timer: Timer, 574 allowDelete: Boolean, 575 @StringRes eventLabelId: Int 576 ): Timer? { 577 if (allowDelete && 578 (timer.isExpired || timer.isMissed) && 579 timer.deleteAfterUse) { 580 doRemoveTimer(timer) 581 if (eventLabelId != 0) { 582 Events.sendTimerEvent(R.string.action_delete, eventLabelId) 583 } 584 return null 585 } else if (!timer.isReset) { 586 val reset = timer.reset() 587 doUpdateTimer(reset) 588 if (eventLabelId != 0) { 589 Events.sendTimerEvent(R.string.action_reset, eventLabelId) 590 } 591 return reset 592 } 593 return timer 594 } 595 596 /** 597 * This method updates/removes timer data after a reboot without updating notifications. 598 * 599 * @param timer the timer to be updated 600 */ doUpdateAfterRebootTimernull601 private fun doUpdateAfterRebootTimer(timer: Timer) { 602 var updated = timer.updateAfterReboot() 603 if (updated.remainingTime < MISSED_THRESHOLD && updated.isRunning) { 604 updated = updated.miss() 605 } 606 doUpdateTimer(updated) 607 } 608 doUpdateAfterTimeSetTimernull609 private fun doUpdateAfterTimeSetTimer(timer: Timer) { 610 val updated = timer.updateAfterTimeSet() 611 doUpdateTimer(updated) 612 } 613 614 /** 615 * Updates the callback given to this application from the [AlarmManager] that signals the 616 * expiration of the next timer. If no timers are currently set to expire (i.e. no running 617 * timers exist) then this method clears the expiration callback from AlarmManager. 618 */ updateAlarmManagernull619 private fun updateAlarmManager() { 620 // Locate the next firing timer if one exists. 621 var nextExpiringTimer: Timer? = null 622 for (timer in mutableTimers) { 623 if (timer.isRunning) { 624 if (nextExpiringTimer == null) { 625 nextExpiringTimer = timer 626 } else if (timer.expirationTime < nextExpiringTimer.expirationTime) { 627 nextExpiringTimer = timer 628 } 629 } 630 } 631 632 // Build the intent that signals the timer expiration. 633 val intent: Intent = TimerService.createTimerExpiredIntent(mContext, nextExpiringTimer) 634 if (nextExpiringTimer == null) { 635 // Cancel the existing timer expiration callback. 636 val pi: PendingIntent? = PendingIntent.getService(mContext, 637 0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_NO_CREATE) 638 if (pi != null) { 639 mAlarmManager.cancel(pi) 640 pi.cancel() 641 } 642 } else { 643 // Update the existing timer expiration callback. 644 val pi: PendingIntent = PendingIntent.getService(mContext, 645 0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT) 646 schedulePendingIntent(mAlarmManager, nextExpiringTimer.expirationTime, pi) 647 } 648 } 649 650 /** 651 * Starts and stops the ringer for timers if the change to the timer demands it. 652 * 653 * @param before the state of the timer before the change; `null` indicates added 654 * @param after the state of the timer after the change; `null` indicates delete 655 */ updateRingernull656 private fun updateRinger(before: Timer?, after: Timer?) { 657 // Retrieve the states before and after the change. 658 val beforeState = before?.state 659 val afterState = after?.state 660 661 // If the timer state did not change, the ringer state is unchanged. 662 if (beforeState == afterState) { 663 return 664 } 665 666 // If the timer is the first to expire, start ringing. 667 if (afterState == Timer.State.EXPIRED && mRingingIds.add(after.id) && 668 mRingingIds.size == 1) { 669 AlarmAlertWakeLock.acquireScreenCpuWakeLock(mContext) 670 TimerKlaxon.start(mContext) 671 } 672 673 // If the expired timer was the last to reset, stop ringing. 674 if (beforeState == Timer.State.EXPIRED && mRingingIds.remove(before.id) && 675 mRingingIds.isEmpty()) { 676 TimerKlaxon.stop(mContext) 677 AlarmAlertWakeLock.releaseCpuLock() 678 } 679 } 680 681 /** 682 * Updates the notification controlling unexpired timers. This notification is only displayed 683 * when the application is not open. 684 */ updateNotificationnull685 fun updateNotification() { 686 // Filter the timers to just include unexpired ones. 687 val unexpired: MutableList<Timer> = mutableListOf() 688 for (timer in mutableTimers) { 689 if (timer.isRunning || timer.isPaused) { 690 unexpired.add(timer) 691 } 692 } 693 694 // If no unexpired timers exist, cancel the notification. 695 if (unexpired.isEmpty()) { 696 if(mNotificationBuilder.isChannelCreated(mNotificationManager)) { 697 LogUtils.i("Cancelling Notifications when list is empty") 698 mNotificationManager.cancel(mNotificationModel.unexpiredTimerNotificationId) 699 } 700 return 701 } 702 703 // Sort the unexpired timers to locate the next one scheduled to expire. 704 unexpired.sortWith(Timer.EXPIRY_COMPARATOR) 705 706 //Build and setup a channel for notifications 707 LogUtils.i("Channel being setup!!!!") 708 // Otherwise build and post a notification reflecting the latest unexpired timers. 709 val notification: Notification = 710 mNotificationBuilder.build(mContext, mNotificationModel, unexpired) 711 val notificationId = mNotificationModel.unexpiredTimerNotificationId 712 mNotificationBuilder.buildChannel(mContext, mNotificationManager) 713 714 // Notifications should be hidden if the app is open. 715 if (mNotificationModel.isApplicationInForeground) { 716 if(mNotificationBuilder.isChannelCreated(mNotificationManager)) { 717 LogUtils.i("Cancelling notifications when the app is in foreground") 718 mNotificationManager.cancel(mNotificationModel.unexpiredTimerNotificationId) 719 } 720 return 721 } 722 723 mNotificationManager.notify(notificationId, notification) 724 } 725 726 /** 727 * Updates the notification controlling missed timers. This notification is only displayed when 728 * the application is not open. 729 */ updateMissedNotificationnull730 fun updateMissedNotification() { 731 // Notifications should be hidden if the app is open. 732 if (mNotificationModel.isApplicationInForeground) { 733 mNotificationManager.cancel(mNotificationModel.missedTimerNotificationId) 734 return 735 } 736 737 val missed = missedTimers 738 739 if (missed.isEmpty()) { 740 mNotificationManager.cancel(mNotificationModel.missedTimerNotificationId) 741 return 742 } 743 744 val notification: Notification = mNotificationBuilder.buildMissed(mContext, 745 mNotificationModel, missed) 746 val notificationId = mNotificationModel.missedTimerNotificationId 747 mNotificationManager.notify(notificationId, notification) 748 } 749 750 /** 751 * Updates the heads-up notification controlling expired timers. This heads-up notification is 752 * displayed whether the application is open or not. 753 */ updateHeadsUpNotificationnull754 private fun updateHeadsUpNotification() { 755 // Nothing can be done with the heads-up notification without a valid service reference. 756 if (mService == null) { 757 return 758 } 759 760 val expired = expiredTimers 761 762 // If no expired timers exist, stop the service (which cancels the foreground notification). 763 if (expired.isEmpty()) { 764 mService!!.stopSelf() 765 mService = null 766 return 767 } 768 769 // Otherwise build and post a foreground notification reflecting the latest expired timers. 770 val notification: Notification = mNotificationBuilder.buildHeadsUp(mContext, expired) 771 val notificationId = mNotificationModel.expiredTimerNotificationId 772 mService!!.startForeground(notificationId, notification) 773 } 774 775 /** 776 * Update the timer notification in response to a locale change. 777 */ 778 private inner class LocaleChangedReceiver : BroadcastReceiver() { onReceivenull779 override fun onReceive(context: Context?, intent: Intent?) { 780 mTimerRingtoneTitle = null 781 updateNotification() 782 updateMissedNotification() 783 updateHeadsUpNotification() 784 } 785 } 786 787 /** 788 * This receiver is notified when shared preferences change. Cached information built on 789 * preferences must be cleared. 790 */ 791 private inner class PreferenceListener : OnSharedPreferenceChangeListener { onSharedPreferenceChangednull792 override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) { 793 when (key) { 794 SettingsActivity.KEY_TIMER_RINGTONE -> { 795 mTimerRingtoneUri = null 796 mTimerRingtoneTitle = null 797 } 798 } 799 } 800 } 801 802 companion object { 803 /** 804 * Running timers less than this threshold are left running/expired; greater than this 805 * threshold are considered missed. 806 */ 807 private val MISSED_THRESHOLD: Long = -MINUTE_IN_MILLIS 808 schedulePendingIntentnull809 fun schedulePendingIntent(am: AlarmManager, triggerTime: Long, pi: PendingIntent) { 810 if (Utils.isMOrLater) { 811 // Ensure the timer fires even if the device is dozing. 812 am.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, triggerTime, pi) 813 } else { 814 am.setExact(ELAPSED_REALTIME_WAKEUP, triggerTime, pi) 815 } 816 } 817 } 818 } 819