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