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.app.Service
20 import android.content.Context
21 import android.content.Context.AUDIO_SERVICE
22 import android.content.Intent
23 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
24 import android.content.SharedPreferences
25 import android.media.AudioManager
26 import android.media.AudioManager.FLAG_SHOW_UI
27 import android.media.AudioManager.STREAM_ALARM
28 import android.net.Uri
29 import android.os.Handler
30 import android.os.Looper
31 import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
32 import android.provider.Settings.ACTION_SOUND_SETTINGS
33 import android.provider.Settings.EXTRA_APP_PACKAGE
34 import android.view.View
35 import androidx.annotation.Keep
36 import androidx.annotation.StringRes
37 
38 import com.android.deskclock.Predicate
39 import com.android.deskclock.R
40 import com.android.deskclock.Utils
41 import com.android.deskclock.timer.TimerService
42 
43 import java.util.Calendar
44 
45 import kotlin.Comparator
46 import kotlin.math.roundToInt
47 
48 /**
49  * All application-wide data is accessible through this singleton.
50  */
51 class DataModel private constructor() {
52 
53     /** Indicates the display style of clocks.  */
54     enum class ClockStyle {
55         ANALOG, DIGITAL
56     }
57 
58     /** Indicates the preferred sort order of cities.  */
59     enum class CitySort {
60         NAME, UTC_OFFSET
61     }
62 
63     /** Indicates the preferred behavior of hardware volume buttons when firing alarms.  */
64     enum class AlarmVolumeButtonBehavior {
65         NOTHING, SNOOZE, DISMISS
66     }
67 
68     /** Indicates the reason alarms may not fire or may fire silently.  */
69     enum class SilentSetting(
70         @field:StringRes @get:StringRes val labelResId: Int,
71         @field:StringRes @get:StringRes val actionResId: Int,
72         private val mActionEnabled: Predicate<Context>,
73         private val mActionListener: View.OnClickListener?
74     ) {
75 
76         DO_NOT_DISTURB(R.string.alarms_blocked_by_dnd,
77                 0,
78                 Predicate.FALSE as Predicate<Context>,
79                 mActionListener = null),
80         MUTED_VOLUME(R.string.alarm_volume_muted,
81                 R.string.unmute_alarm_volume,
82                 Predicate.TRUE as Predicate<Context>,
83                 UnmuteAlarmVolumeListener()),
84         SILENT_RINGTONE(R.string.silent_default_alarm_ringtone,
85                 R.string.change_setting_action,
86                 ChangeSoundActionPredicate(),
87                 ChangeSoundSettingsListener()),
88         BLOCKED_NOTIFICATIONS(R.string.app_notifications_blocked,
89                 R.string.change_setting_action,
90                 Predicate.TRUE as Predicate<Context>,
91                 ChangeAppNotificationSettingsListener());
92 
93         val actionListener: View.OnClickListener?
94             get() = mActionListener
95 
isActionEnablednull96         fun isActionEnabled(context: Context): Boolean {
97             return labelResId != 0 && mActionEnabled.apply(context)
98         }
99 
100         private class UnmuteAlarmVolumeListener : View.OnClickListener {
onClicknull101             override fun onClick(v: View) {
102                 // Set the alarm volume to 11/16th of max and show the slider UI.
103                 // 11/16th of max is the initial volume of the alarm stream on a fresh install.
104                 val context: Context = v.context
105                 val am: AudioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
106                 val index = (am.getStreamMaxVolume(STREAM_ALARM) * 11f / 16f).roundToInt()
107                 am.setStreamVolume(STREAM_ALARM, index, FLAG_SHOW_UI)
108             }
109         }
110 
111         private class ChangeSoundSettingsListener : View.OnClickListener {
onClicknull112             override fun onClick(v: View) {
113                 val context: Context = v.context
114                 context.startActivity(Intent(ACTION_SOUND_SETTINGS)
115                         .addFlags(FLAG_ACTIVITY_NEW_TASK))
116             }
117         }
118 
119         private class ChangeSoundActionPredicate : Predicate<Context> {
applynull120             override fun apply(context: Context): Boolean {
121                 val intent = Intent(ACTION_SOUND_SETTINGS)
122                 return intent.resolveActivity(context.packageManager) != null
123             }
124         }
125 
126         private class ChangeAppNotificationSettingsListener : View.OnClickListener {
onClicknull127             override fun onClick(v: View) {
128                 val context: Context = v.context
129                 if (Utils.isLOrLater) {
130                     try {
131                         // Attempt to open the notification settings for this app.
132                         context.startActivity(
133                                 Intent("android.settings.APP_NOTIFICATION_SETTINGS")
134                                         .putExtra(EXTRA_APP_PACKAGE, context.packageName)
135                                         .putExtra("app_uid", context.applicationInfo.uid)
136                                         .addFlags(FLAG_ACTIVITY_NEW_TASK))
137                         return
138                     } catch (ignored: Exception) {
139                         // best attempt only; recovery code below
140                     }
141                 }
142 
143                 // Fall back to opening the app settings page.
144                 context.startActivity(Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
145                         .setData(Uri.fromParts("package", context.packageName, null))
146                         .addFlags(FLAG_ACTIVITY_NEW_TASK))
147             }
148         }
149     }
150 
151     private var mHandler: Handler? = null
152     private var mContext: Context? = null
153 
154     /** The model from which settings are fetched.  */
155     private var mSettingsModel: SettingsModel? = null
156 
157     /** The model from which city data are fetched.  */
158     private var mCityModel: CityModel? = null
159 
160     /** The model from which timer data are fetched.  */
161     private var mTimerModel: TimerModel? = null
162 
163     /** The model from which alarm data are fetched.  */
164     private var mAlarmModel: AlarmModel? = null
165 
166     /** The model from which widget data are fetched.  */
167     private var mWidgetModel: WidgetModel? = null
168 
169     /** The model from which data about settings that silence alarms are fetched.  */
170     private var mSilentSettingsModel: SilentSettingsModel? = null
171 
172     /** The model from which stopwatch data are fetched.  */
173     private var mStopwatchModel: StopwatchModel? = null
174 
175     /** The model from which notification data are fetched.  */
176     private var mNotificationModel: NotificationModel? = null
177 
178     /** The model from which time data are fetched.  */
179     private var mTimeModel: TimeModel? = null
180 
181     /** The model from which ringtone data are fetched.  */
182     private var mRingtoneModel: RingtoneModel? = null
183 
184     /**
185      * Initializes the data model with the context and shared preferences to be used.
186      */
initnull187     fun init(context: Context, prefs: SharedPreferences) {
188         if (mContext !== context) {
189             mContext = context.applicationContext
190             mTimeModel = TimeModel(mContext!!)
191             mWidgetModel = WidgetModel(prefs)
192             mNotificationModel = NotificationModel()
193             mRingtoneModel = RingtoneModel(mContext!!, prefs)
194             mSettingsModel = SettingsModel(mContext!!, prefs, mTimeModel!!)
195             mCityModel = CityModel(mContext!!, prefs, mSettingsModel!!)
196             mAlarmModel = AlarmModel(mContext!!, mSettingsModel!!)
197             mSilentSettingsModel = SilentSettingsModel(mContext!!, mNotificationModel!!)
198             mStopwatchModel = StopwatchModel(mContext!!, prefs, mNotificationModel!!)
199             mTimerModel = TimerModel(mContext!!, prefs, mSettingsModel!!, mRingtoneModel!!,
200                     mNotificationModel!!)
201         }
202     }
203 
204     /**
205      * Convenience for `run(runnable, 0)`, i.e. waits indefinitely.
206      */
runnull207     fun run(runnable: Runnable) {
208         try {
209             run(runnable, 0 /* waitMillis */)
210         } catch (ignored: InterruptedException) {
211         }
212     }
213 
214     /**
215      * Updates all timers and the stopwatch after the device has shutdown and restarted.
216      */
updateAfterRebootnull217     fun updateAfterReboot() {
218         Utils.enforceMainLooper()
219         mTimerModel!!.updateTimersAfterReboot()
220         mStopwatchModel!!.setStopwatch(stopwatch.updateAfterReboot())
221     }
222 
223     /**
224      * Updates all timers and the stopwatch after the device's time has changed.
225      */
updateAfterTimeSetnull226     fun updateAfterTimeSet() {
227         Utils.enforceMainLooper()
228         mTimerModel!!.updateTimersAfterTimeSet()
229         mStopwatchModel!!.setStopwatch(stopwatch.updateAfterTimeSet())
230     }
231 
232     /**
233      * Posts a runnable to the main thread and blocks until the runnable executes. Used to access
234      * the data model from the main thread.
235      */
236     @Throws(InterruptedException::class)
runnull237     fun run(runnable: Runnable, waitMillis: Long) {
238         if (Looper.myLooper() === Looper.getMainLooper()) {
239             runnable.run()
240             return
241         }
242 
243         val er = ExecutedRunnable(runnable)
244         handler.post(er)
245 
246         // Wait for the data to arrive, if it has not.
247         synchronized(er) {
248             if (!er.isExecuted) {
249                 er.wait(waitMillis)
250             }
251         }
252     }
253 
254     /**
255      * @return a handler associated with the main thread
256      */
257     @get:Synchronized
258     private val handler: Handler
259         get() {
260             if (mHandler == null) {
261                 mHandler = Handler(Looper.getMainLooper())
262             }
263             return mHandler!!
264         }
265 
266     //
267     // Application
268     //
269 
270     var isApplicationInForeground: Boolean
271         /**
272          * @return `true` when the application is open in the foreground; `false` otherwise
273          */
274         get() {
275             Utils.enforceMainLooper()
276             return mNotificationModel!!.isApplicationInForeground
277         }
278         /**
279          * @param inForeground `true` to indicate the application is open in the foreground
280          */
281         set(inForeground) {
282             Utils.enforceMainLooper()
283             if (mNotificationModel!!.isApplicationInForeground != inForeground) {
284                 mNotificationModel!!.isApplicationInForeground = inForeground
285 
286                 // Refresh all notifications in response to a change in app open state.
287                 mTimerModel!!.updateNotification()
288                 mTimerModel!!.updateMissedNotification()
289                 mStopwatchModel!!.updateNotification()
290                 mSilentSettingsModel!!.updateSilentState()
291             }
292         }
293 
294     /**
295      * Called when the notifications may be stale or absent from the notification manager and must
296      * be rebuilt. e.g. after upgrading the application
297      */
updateAllNotificationsnull298     fun updateAllNotifications() {
299         Utils.enforceMainLooper()
300         mTimerModel!!.updateNotification()
301         mTimerModel!!.updateMissedNotification()
302         mStopwatchModel!!.updateNotification()
303     }
304 
305     //
306     // Cities
307     //
308 
309     /**
310      * @return a list of all cities in their display order
311      */
312     val allCities: List<City>
313         get() {
314             Utils.enforceMainLooper()
315             return mCityModel!!.allCities
316         }
317 
318     /**
319      * @return a city representing the user's home timezone
320      */
321     val homeCity: City
322         get() {
323             Utils.enforceMainLooper()
324             return mCityModel!!.homeCity
325         }
326 
327     /**
328      * @return a list of cities not selected for display
329      */
330     val unselectedCities: List<City>
331         get() {
332             Utils.enforceMainLooper()
333             return mCityModel!!.unselectedCities
334         }
335 
336     var selectedCities: Collection<City>
337         /**
338          * @return a list of cities selected for display
339          */
340         get() {
341             Utils.enforceMainLooper()
342             return mCityModel!!.selectedCities
343         }
344         /**
345          * @param cities the new collection of cities selected for display by the user
346          */
347         set(cities) {
348             Utils.enforceMainLooper()
349             mCityModel?.setSelectedCities(cities)
350         }
351 
352     /**
353      * @return a comparator used to locate index positions
354      */
355     val cityIndexComparator: Comparator<City>
356         get() {
357             Utils.enforceMainLooper()
358             return mCityModel!!.cityIndexComparator
359         }
360 
361     /**
362      * @return the order in which cities are sorted
363      */
364     val citySort: CitySort
365         get() {
366             Utils.enforceMainLooper()
367             return mCityModel!!.citySort
368         }
369 
370     /**
371      * Adjust the order in which cities are sorted.
372      */
toggleCitySortnull373     fun toggleCitySort() {
374         Utils.enforceMainLooper()
375         mCityModel?.toggleCitySort()
376     }
377 
378     /**
379      * @param cityListener listener to be notified when the world city list changes
380      */
addCityListenernull381     fun addCityListener(cityListener: CityListener) {
382         Utils.enforceMainLooper()
383         mCityModel?.addCityListener(cityListener)
384     }
385 
386     /**
387      * @param cityListener listener that no longer needs to be notified of world city list changes
388      */
removeCityListenernull389     fun removeCityListener(cityListener: CityListener) {
390         Utils.enforceMainLooper()
391         mCityModel?.removeCityListener(cityListener)
392     }
393 
394     //
395     // Timers
396     //
397 
398     /**
399      * @param timerListener to be notified when timers are added, updated and removed
400      */
addTimerListenernull401     fun addTimerListener(timerListener: TimerListener) {
402         Utils.enforceMainLooper()
403         mTimerModel?.addTimerListener(timerListener)
404     }
405 
406     /**
407      * @param timerListener to no longer be notified when timers are added, updated and removed
408      */
removeTimerListenernull409     fun removeTimerListener(timerListener: TimerListener) {
410         Utils.enforceMainLooper()
411         mTimerModel?.removeTimerListener(timerListener)
412     }
413 
414     /**
415      * @return a list of timers for display
416      */
417     val timers: List<Timer>
418         get() {
419             Utils.enforceMainLooper()
420             return mTimerModel!!.timers
421         }
422 
423     /**
424      * @return a list of expired timers for display
425      */
426     val expiredTimers: List<Timer>
427         get() {
428             Utils.enforceMainLooper()
429             return mTimerModel!!.expiredTimers
430         }
431 
432     /**
433      * @param timerId identifies the timer to return
434      * @return the timer with the given `timerId`
435      */
getTimernull436     fun getTimer(timerId: Int): Timer? {
437         Utils.enforceMainLooper()
438         return mTimerModel?.getTimer(timerId)
439     }
440 
441     /**
442      * @return the timer that last expired and is still expired now; `null` if no timers are
443      * expired
444      */
445     val mostRecentExpiredTimer: Timer?
446         get() {
447             Utils.enforceMainLooper()
448             return mTimerModel?.mostRecentExpiredTimer
449         }
450 
451     /**
452      * @param length the length of the timer in milliseconds
453      * @param label describes the purpose of the timer
454      * @param deleteAfterUse `true` indicates the timer should be deleted when it is reset
455      * @return the newly added timer
456      */
addTimernull457     fun addTimer(length: Long, label: String?, deleteAfterUse: Boolean): Timer {
458         Utils.enforceMainLooper()
459         return mTimerModel!!.addTimer(length, label, deleteAfterUse)
460     }
461 
462     /**
463      * @param timer the timer to be removed
464      */
removeTimernull465     fun removeTimer(timer: Timer) {
466         Utils.enforceMainLooper()
467         mTimerModel?.removeTimer(timer)
468     }
469 
470     /**
471      * @param timer the timer to be started
472      */
startTimernull473     fun startTimer(timer: Timer) {
474         startTimer(null, timer)
475     }
476 
477     /**
478      * @param service used to start foreground notifications for expired timers
479      * @param timer the timer to be started
480      */
startTimernull481     fun startTimer(service: Service?, timer: Timer) {
482         Utils.enforceMainLooper()
483         val started = timer.start()
484         mTimerModel?.updateTimer(started)
485         if (timer.remainingTime <= 0) {
486             if (service != null) {
487                 expireTimer(service, started)
488             } else {
489                 mContext!!.startService(TimerService.createTimerExpiredIntent(mContext!!, started))
490             }
491         }
492     }
493 
494     /**
495      * @param timer the timer to be paused
496      */
pauseTimernull497     fun pauseTimer(timer: Timer) {
498         Utils.enforceMainLooper()
499         mTimerModel?.updateTimer(timer.pause())
500     }
501 
502     /**
503      * @param service used to start foreground notifications for expired timers
504      * @param timer the timer to be expired
505      */
expireTimernull506     fun expireTimer(service: Service?, timer: Timer) {
507         Utils.enforceMainLooper()
508         mTimerModel?.expireTimer(service, timer)
509     }
510 
511     /**
512      * @param timer the timer to be reset
513      * @return the reset `timer`
514      */
515     @Keep
resetTimernull516     fun resetTimer(timer: Timer): Timer? {
517         Utils.enforceMainLooper()
518         return mTimerModel?.resetTimer(timer, false /* allowDelete */, 0 /* eventLabelId */)
519     }
520 
521     /**
522      * If the given `timer` is expired and marked for deletion after use then this method
523      * removes the timer. The timer is otherwise transitioned to the reset state and continues
524      * to exist.
525      *
526      * @param timer the timer to be reset
527      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
528      * @return the reset `timer` or `null` if the timer was deleted
529      */
resetOrDeleteTimernull530     fun resetOrDeleteTimer(timer: Timer, @StringRes eventLabelId: Int): Timer? {
531         Utils.enforceMainLooper()
532         return mTimerModel?.resetTimer(timer, true /* allowDelete */, eventLabelId)
533     }
534 
535     /**
536      * Resets all expired timers.
537      *
538      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
539      */
resetOrDeleteExpiredTimersnull540     fun resetOrDeleteExpiredTimers(@StringRes eventLabelId: Int) {
541         Utils.enforceMainLooper()
542         mTimerModel?.resetOrDeleteExpiredTimers(eventLabelId)
543     }
544 
545     /**
546      * Resets all unexpired timers.
547      *
548      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
549      */
resetUnexpiredTimersnull550     fun resetUnexpiredTimers(@StringRes eventLabelId: Int) {
551         Utils.enforceMainLooper()
552         mTimerModel?.resetUnexpiredTimers(eventLabelId)
553     }
554 
555     /**
556      * Resets all missed timers.
557      *
558      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
559      */
resetMissedTimersnull560     fun resetMissedTimers(@StringRes eventLabelId: Int) {
561         Utils.enforceMainLooper()
562         mTimerModel?.resetMissedTimers(eventLabelId)
563     }
564 
565     /**
566      * @param timer the timer to which a minute should be added to the remaining time
567      */
addTimerMinutenull568     fun addTimerMinute(timer: Timer) {
569         Utils.enforceMainLooper()
570         mTimerModel?.updateTimer(timer.addMinute())
571     }
572 
573     /**
574      * @param timer the timer to which the new `label` belongs
575      * @param label the new label to store for the `timer`
576      */
setTimerLabelnull577     fun setTimerLabel(timer: Timer, label: String?) {
578         Utils.enforceMainLooper()
579         mTimerModel?.updateTimer(timer.setLabel(label))
580     }
581 
582     /**
583      * @param timer the timer whose `length` to change
584      * @param length the new length of the timer in milliseconds
585      */
setTimerLengthnull586     fun setTimerLength(timer: Timer, length: Long) {
587         Utils.enforceMainLooper()
588         mTimerModel?.updateTimer(timer.setLength(length))
589     }
590 
591     /**
592      * @param timer the timer whose `remainingTime` to change
593      * @param remainingTime the new remaining time of the timer in milliseconds
594      */
setRemainingTimenull595     fun setRemainingTime(timer: Timer, remainingTime: Long) {
596         Utils.enforceMainLooper()
597 
598         val updated = timer.setRemainingTime(remainingTime)
599         mTimerModel?.updateTimer(updated)
600         if (timer.isRunning && timer.remainingTime <= 0) {
601             mContext?.startService(TimerService.createTimerExpiredIntent(mContext!!, updated))
602         }
603     }
604 
605     /**
606      * Updates the timer notifications to be current.
607      */
updateTimerNotificationnull608     fun updateTimerNotification() {
609         Utils.enforceMainLooper()
610         mTimerModel?.updateNotification()
611     }
612 
613     /**
614      * @return the uri of the default ringtone to play for all timers when no user selection exists
615      */
616     val defaultTimerRingtoneUri: Uri
617         get() {
618             Utils.enforceMainLooper()
619             return mTimerModel!!.defaultTimerRingtoneUri
620         }
621 
622     /**
623      * @return `true` iff the ringtone to play for all timers is the silent ringtone
624      */
625     val isTimerRingtoneSilent: Boolean
626         get() {
627             Utils.enforceMainLooper()
628             return mTimerModel!!.isTimerRingtoneSilent
629         }
630 
631     var timerRingtoneUri: Uri
632         /**
633          * @return the uri of the ringtone to play for all timers
634          */
635         get() {
636             Utils.enforceMainLooper()
637             return mTimerModel!!.timerRingtoneUri
638         }
639         /**
640          * @param uri the uri of the ringtone to play for all timers
641          */
642         set(uri) {
643             Utils.enforceMainLooper()
644             mTimerModel!!.timerRingtoneUri = uri
645         }
646 
647     /**
648      * @return the title of the ringtone that is played for all timers
649      */
650     val timerRingtoneTitle: String
651         get() {
652             Utils.enforceMainLooper()
653             return mTimerModel!!.timerRingtoneTitle
654         }
655 
656     /**
657      * @return the duration, in milliseconds, of the crescendo to apply to timer ringtone playback;
658      * `0` implies no crescendo should be applied
659      */
660     val timerCrescendoDuration: Long
661         get() {
662             Utils.enforceMainLooper()
663             return mTimerModel!!.timerCrescendoDuration
664         }
665 
666     var timerVibrate: Boolean
667         /**
668          * @return whether vibrate is enabled for all timers.
669          */
670         get() {
671             Utils.enforceMainLooper()
672             return mTimerModel!!.timerVibrate
673         }
674         /**
675          * @param enabled whether vibrate is enabled for all timers.
676          */
677         set(enabled) {
678             Utils.enforceMainLooper()
679             mTimerModel!!.timerVibrate = enabled
680         }
681 
682     //
683     // Alarms
684     //
685 
686     var defaultAlarmRingtoneUri: Uri
687         /**
688          * @return the uri of the ringtone to which all new alarms default
689          */
690         get() {
691             Utils.enforceMainLooper()
692             return mAlarmModel!!.defaultAlarmRingtoneUri
693         }
694         /**
695          * @param uri the uri of the ringtone to which future new alarms will default
696          */
697         set(uri) {
698             Utils.enforceMainLooper()
699             mAlarmModel!!.defaultAlarmRingtoneUri = uri
700         }
701 
702     /**
703      * @return the duration, in milliseconds, of the crescendo to apply to alarm ringtone playback;
704      * `0` implies no crescendo should be applied
705      */
706     val alarmCrescendoDuration: Long
707         get() {
708             Utils.enforceMainLooper()
709             return mAlarmModel!!.alarmCrescendoDuration
710         }
711 
712     /**
713      * @return the behavior to execute when volume buttons are pressed while firing an alarm
714      */
715     val alarmVolumeButtonBehavior: AlarmVolumeButtonBehavior
716         get() {
717             Utils.enforceMainLooper()
718             return mAlarmModel!!.alarmVolumeButtonBehavior
719         }
720 
721     /**
722      * @return the number of minutes an alarm may ring before it has timed out and becomes missed
723      */
724     val alarmTimeout: Int
725         get() = mAlarmModel!!.alarmTimeout
726 
727     /**
728      * @return the number of minutes an alarm will remain snoozed before it rings again
729      */
730     val snoozeLength: Int
731         get() = mAlarmModel!!.snoozeLength
732 
733     //
734     // Stopwatch
735     //
736 
737     /**
738      * @param stopwatchListener to be notified when stopwatch changes or laps are added
739      */
addStopwatchListenernull740     fun addStopwatchListener(stopwatchListener: StopwatchListener) {
741         Utils.enforceMainLooper()
742         mStopwatchModel?.addStopwatchListener(stopwatchListener)
743     }
744 
745     /**
746      * @param stopwatchListener to no longer be notified when stopwatch changes or laps are added
747      */
removeStopwatchListenernull748     fun removeStopwatchListener(stopwatchListener: StopwatchListener) {
749         Utils.enforceMainLooper()
750         mStopwatchModel?.removeStopwatchListener(stopwatchListener)
751     }
752 
753     /**
754      * @return the current state of the stopwatch
755      */
756     val stopwatch: Stopwatch
757         get() {
758             Utils.enforceMainLooper()
759             return mStopwatchModel!!.stopwatch
760         }
761 
762     /**
763      * @return the stopwatch after being started
764      */
startStopwatchnull765     fun startStopwatch(): Stopwatch {
766         Utils.enforceMainLooper()
767         return mStopwatchModel!!.setStopwatch(stopwatch.start())
768     }
769 
770     /**
771      * @return the stopwatch after being paused
772      */
pauseStopwatchnull773     fun pauseStopwatch(): Stopwatch {
774         Utils.enforceMainLooper()
775         return mStopwatchModel!!.setStopwatch(stopwatch.pause())
776     }
777 
778     /**
779      * @return the stopwatch after being reset
780      */
resetStopwatchnull781     fun resetStopwatch(): Stopwatch {
782         Utils.enforceMainLooper()
783         return mStopwatchModel!!.setStopwatch(stopwatch.reset())
784     }
785 
786     /**
787      * @return the laps recorded for this stopwatch
788      */
789     val laps: List<Lap>
790         get() {
791             Utils.enforceMainLooper()
792             return mStopwatchModel!!.laps
793         }
794 
795     /**
796      * @return a newly recorded lap completed now; `null` if no more laps can be added
797      */
addLapnull798     fun addLap(): Lap? {
799         Utils.enforceMainLooper()
800         return mStopwatchModel!!.addLap()
801     }
802 
803     /**
804      * @return `true` iff more laps can be recorded
805      */
canAddMoreLapsnull806     fun canAddMoreLaps(): Boolean {
807         Utils.enforceMainLooper()
808         return mStopwatchModel!!.canAddMoreLaps()
809     }
810 
811     /**
812      * @return the longest lap time of all recorded laps and the current lap
813      */
814     val longestLapTime: Long
815         get() {
816             Utils.enforceMainLooper()
817             return mStopwatchModel!!.longestLapTime
818         }
819 
820     /**
821      * @param time a point in time after the end of the last lap
822      * @return the elapsed time between the given `time` and the end of the previous lap
823      */
getCurrentLapTimenull824     fun getCurrentLapTime(time: Long): Long {
825         Utils.enforceMainLooper()
826         return mStopwatchModel!!.getCurrentLapTime(time)
827     }
828 
829     //
830     // Time
831     // (Time settings/values are accessible from any Thread so no Thread-enforcement exists.)
832     //
833 
834     /**
835      * @return the current time in milliseconds
836      */
currentTimeMillisnull837     fun currentTimeMillis(): Long {
838         return mTimeModel!!.currentTimeMillis()
839     }
840 
841     /**
842      * @return milliseconds since boot, including time spent in sleep
843      */
elapsedRealtimenull844     fun elapsedRealtime(): Long {
845         return mTimeModel!!.elapsedRealtime()
846     }
847 
848     /**
849      * @return `true` if 24 hour time format is selected; `false` otherwise
850      */
is24HourFormatnull851     fun is24HourFormat(): Boolean {
852         return mTimeModel!!.is24HourFormat()
853     }
854 
855     /**
856      * @return a new calendar object initialized to the [.currentTimeMillis]
857      */
858     val calendar: Calendar
859         get() = mTimeModel!!.calendar
860 
861     //
862     // Ringtones
863     //
864 
865     /**
866      * Ringtone titles are cached because loading them is expensive. This method
867      * **must** be called on a background thread and is responsible for priming the
868      * cache of ringtone titles to avoid later fetching titles on the main thread.
869      */
loadRingtoneTitlesnull870     fun loadRingtoneTitles() {
871         Utils.enforceNotMainLooper()
872         mRingtoneModel?.loadRingtoneTitles()
873     }
874 
875     /**
876      * Recheck the permission to read each custom ringtone.
877      */
loadRingtonePermissionsnull878     fun loadRingtonePermissions() {
879         Utils.enforceNotMainLooper()
880         mRingtoneModel?.loadRingtonePermissions()
881     }
882 
883     /**
884      * @param uri the uri of a ringtone
885      * @return the title of the ringtone with the `uri`; `null` if it cannot be fetched
886      */
getRingtoneTitlenull887     fun getRingtoneTitle(uri: Uri): String? {
888         Utils.enforceMainLooper()
889         return mRingtoneModel?.getRingtoneTitle(uri)
890     }
891 
892     /**
893      * @param uri the uri of an audio file to use as a ringtone
894      * @param title the title of the audio content at the given `uri`
895      * @return the ringtone instance created for the audio file
896      */
addCustomRingtonenull897     fun addCustomRingtone(uri: Uri, title: String?): CustomRingtone? {
898         Utils.enforceMainLooper()
899         return mRingtoneModel?.addCustomRingtone(uri, title)
900     }
901 
902     /**
903      * @param uri identifies the ringtone to remove
904      */
removeCustomRingtonenull905     fun removeCustomRingtone(uri: Uri) {
906         Utils.enforceMainLooper()
907         mRingtoneModel?.removeCustomRingtone(uri)
908     }
909 
910     /**
911      * @return all available custom ringtones
912      */
913     val customRingtones: List<CustomRingtone>
914         get() {
915             Utils.enforceMainLooper()
916             return mRingtoneModel!!.customRingtones
917         }
918 
919     //
920     // Widgets
921     //
922 
923     /**
924      * @param widgetClass indicates the type of widget being counted
925      * @param count the number of widgets of the given type
926      * @param eventCategoryId identifies the category of event to send
927      */
updateWidgetCountnull928     fun updateWidgetCount(widgetClass: Class<*>?, count: Int, @StringRes eventCategoryId: Int) {
929         Utils.enforceMainLooper()
930         mWidgetModel!!.updateWidgetCount(widgetClass!!, count, eventCategoryId)
931     }
932 
933     //
934     // Settings
935     //
936 
937     /**
938      * @param silentSettingsListener to be notified when alarm-silencing settings change
939      */
addSilentSettingsListenernull940     fun addSilentSettingsListener(silentSettingsListener: OnSilentSettingsListener) {
941         Utils.enforceMainLooper()
942         mSilentSettingsModel?.addSilentSettingsListener(silentSettingsListener)
943     }
944 
945     /**
946      * @param silentSettingsListener to no longer be notified when alarm-silencing settings change
947      */
removeSilentSettingsListenernull948     fun removeSilentSettingsListener(silentSettingsListener: OnSilentSettingsListener) {
949         Utils.enforceMainLooper()
950         mSilentSettingsModel?.removeSilentSettingsListener(silentSettingsListener)
951     }
952 
953     /**
954      * @return the id used to discriminate relevant AlarmManager callbacks from defunct ones
955      */
956     val globalIntentId: Int
957         get() = mSettingsModel!!.globalIntentId
958 
959     /**
960      * Update the id used to discriminate relevant AlarmManager callbacks from defunct ones
961      */
updateGlobalIntentIdnull962     fun updateGlobalIntentId() {
963         Utils.enforceMainLooper()
964         mSettingsModel!!.updateGlobalIntentId()
965     }
966 
967     /**
968      * @return the style of clock to display in the clock application
969      */
970     val clockStyle: ClockStyle
971         get() {
972             Utils.enforceMainLooper()
973             return mSettingsModel!!.clockStyle
974         }
975 
976     var displayClockSeconds: Boolean
977         /**
978          * @return the style of clock to display in the clock application
979          */
980         get() {
981             Utils.enforceMainLooper()
982             return mSettingsModel!!.displayClockSeconds
983         }
984         /**
985          * @param displaySeconds whether or not to display seconds for main clock
986          */
987         set(displaySeconds) {
988             Utils.enforceMainLooper()
989             mSettingsModel!!.displayClockSeconds = displaySeconds
990         }
991 
992     /**
993      * @return the style of clock to display in the clock screensaver
994      */
995     val screensaverClockStyle: ClockStyle
996         get() {
997             Utils.enforceMainLooper()
998             return mSettingsModel!!.screensaverClockStyle
999         }
1000 
1001     /**
1002      * @return `true` if the screen saver should be dimmed for lower contrast at night
1003      */
1004     val screensaverNightModeOn: Boolean
1005         get() {
1006             Utils.enforceMainLooper()
1007             return mSettingsModel!!.screensaverNightModeOn
1008         }
1009 
1010     /**
1011      * @return `true` if the users wants to automatically show a clock for their home timezone
1012      * when they have travelled outside of that timezone
1013      */
1014     val showHomeClock: Boolean
1015         get() {
1016             Utils.enforceMainLooper()
1017             return mSettingsModel!!.showHomeClock
1018         }
1019 
1020     /**
1021      * @return the display order of the weekdays, which can start with [Calendar.SATURDAY],
1022      * [Calendar.SUNDAY] or [Calendar.MONDAY]
1023      */
1024     val weekdayOrder: Weekdays.Order
1025         get() {
1026             Utils.enforceMainLooper()
1027             return mSettingsModel!!.weekdayOrder
1028         }
1029 
1030     var isRestoreBackupFinished: Boolean
1031         /**
1032          * @return `true` if the restore process (of backup and restore) has completed
1033          */
1034         get() = mSettingsModel!!.isRestoreBackupFinished
1035         /**
1036          * @param finished `true` means the restore process (of backup and restore) has completed
1037          */
1038         set(finished) {
1039             mSettingsModel!!.isRestoreBackupFinished = finished
1040         }
1041 
1042     /**
1043      * @return a description of the time zones available for selection
1044      */
1045     val timeZones: TimeZones
1046         get() {
1047             Utils.enforceMainLooper()
1048             return mSettingsModel!!.timeZones
1049         }
1050 
1051     /**
1052      * Used to execute a delegate runnable and track its completion.
1053      */
1054     private class ExecutedRunnable(private val mDelegate: Runnable) : Runnable, java.lang.Object() {
1055         var isExecuted = false
runnull1056         override fun run() {
1057             mDelegate.run()
1058             synchronized(this) {
1059                 isExecuted = true
1060                 notifyAll()
1061             }
1062         }
1063     }
1064 
1065     companion object {
1066         const val ACTION_WORLD_CITIES_CHANGED = "com.android.deskclock.WORLD_CITIES_CHANGED"
1067 
1068         /** The single instance of this data model that exists for the life of the application.  */
1069         val sDataModel = DataModel()
1070 
1071         @get:JvmStatic
1072         @get:Keep
1073         val dataModel
1074             get() = sDataModel
1075     }
1076 }