Merge "AOSP/DeskClock - Add Kotlin file for DataModel"
diff --git a/Android.bp b/Android.bp
index ddb4de6..fccb977 100644
--- a/Android.bp
+++ b/Android.bp
@@ -46,36 +46,7 @@
         "src/**/deskclock/actionbarmenu/*.java",
         "src/**/deskclock/alarms/dataadapter/*.java",
         "src/**/deskclock/controller/*.java",
-        "src/**/deskclock/data/AlarmModel.java",
-        "src/**/deskclock/data/City.java",
-        "src/**/deskclock/data/CityDAO.java",
-        "src/**/deskclock/data/CityListener.java",
-        "src/**/deskclock/data/CityModel.java",
-        "src/**/deskclock/data/CustomRingtone.java",
-        "src/**/deskclock/data/CustomRingtoneDAO.java",
-        "src/**/deskclock/data/Lap.java",
-        "src/**/deskclock/data/NotificationModel.java",
-        "src/**/deskclock/data/OnSilentSettingsListener.java",
-        "src/**/deskclock/data/RingtoneModel.java",
-        "src/**/deskclock/data/SettingsDAO.java",
-        "src/**/deskclock/data/SettingsModel.java",
-        "src/**/deskclock/data/SilentSettingsModel.java",
-        "src/**/deskclock/data/Stopwatch.java",
-        "src/**/deskclock/data/StopwatchDAO.java",
-        "src/**/deskclock/data/StopwatchListener.java",
-        "src/**/deskclock/data/StopwatchModel.java",
-        "src/**/deskclock/data/StopwatchNotificationBuilder.java",
-        "src/**/deskclock/data/TimeModel.java",
-        "src/**/deskclock/data/Timer.java",
-        "src/**/deskclock/data/TimerDAO.java",
-        "src/**/deskclock/data/TimerListener.java",
-        "src/**/deskclock/data/TimerModel.java",
-        "src/**/deskclock/data/TimerNotificationBuilder.java",
-        "src/**/deskclock/data/TimerStringFormatter.java",
-        "src/**/deskclock/data/TimeZones.java",
-        "src/**/deskclock/data/Weekdays.java",
-        "src/**/deskclock/data/WidgetDAO.java",
-        "src/**/deskclock/data/WidgetModel.java",
+        "src/**/deskclock/data/*.java",
         "src/**/deskclock/events/*.java",
         "src/**/deskclock/provider/*.java",
         "src/**/deskclock/settings/*.java",
diff --git a/src/com/android/alarmclock/AnalogAppWidgetProvider.kt b/src/com/android/alarmclock/AnalogAppWidgetProvider.kt
index e3753db..743e441 100644
--- a/src/com/android/alarmclock/AnalogAppWidgetProvider.kt
+++ b/src/com/android/alarmclock/AnalogAppWidgetProvider.kt
@@ -39,7 +39,7 @@
         // Send events for newly created/deleted widgets.
         val provider = ComponentName(context, javaClass)
         val widgetCount: Int = wm.getAppWidgetIds(provider).size
-        val dm = DataModel.getDataModel()
+        val dm = DataModel.dataModel
         dm.updateWidgetCount(javaClass, widgetCount, R.string.category_analog_widget)
     }
 
diff --git a/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.kt b/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.kt
index 4eb42d1..a4e55ad 100644
--- a/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.kt
+++ b/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.kt
@@ -148,7 +148,7 @@
     override fun onDataSetChanged() {
         // Fetch the data on the main Looper.
         val refreshRunnable = RefreshRunnable()
-        DataModel.getDataModel().run(refreshRunnable)
+        DataModel.dataModel.run(refreshRunnable)
 
         // Store the data in local variables.
         mHomeCity = refreshRunnable.mHomeCity
@@ -201,9 +201,9 @@
         var mShowHomeClock = false
 
         override fun run() {
-            mHomeCity = DataModel.getDataModel().homeCity
-            mCities = ArrayList(DataModel.getDataModel().selectedCities)
-            mShowHomeClock = DataModel.getDataModel().showHomeClock
+            mHomeCity = DataModel.dataModel.homeCity
+            mCities = ArrayList(DataModel.dataModel.selectedCities)
+            mShowHomeClock = DataModel.dataModel.showHomeClock
         }
     }
 
diff --git a/src/com/android/alarmclock/DigitalAppWidgetProvider.kt b/src/com/android/alarmclock/DigitalAppWidgetProvider.kt
index a4427d1..8e7ec2d 100644
--- a/src/com/android/alarmclock/DigitalAppWidgetProvider.kt
+++ b/src/com/android/alarmclock/DigitalAppWidgetProvider.kt
@@ -125,7 +125,7 @@
             }
         }
 
-        val dm = DataModel.getDataModel()
+        val dm = DataModel.dataModel
         dm.updateWidgetCount(javaClass, widgetIds.size, R.string.category_digital_widget)
 
         if (widgetIds.size > 0) {
@@ -164,7 +164,7 @@
      * Add the day-change callback if it is needed (selected cities exist).
      */
     private fun updateDayChangeCallback(context: Context) {
-        val dm = DataModel.getDataModel()
+        val dm = DataModel.dataModel
         val selectedCities = dm.selectedCities
         val showHomeClock = dm.showHomeClock
         if (selectedCities.isEmpty() && !showHomeClock) {
diff --git a/src/com/android/deskclock/ClockFragment.java b/src/com/android/deskclock/ClockFragment.java
index 1536b55..db6cbcf 100644
--- a/src/com/android/deskclock/ClockFragment.java
+++ b/src/com/android/deskclock/ClockFragment.java
@@ -416,7 +416,7 @@
         }
 
         private List<City> getCities() {
-            return DataModel.getDataModel().getSelectedCities();
+            return (List<City>) DataModel.getDataModel().getSelectedCities();
         }
 
         private void refreshAlarm() {
diff --git a/src/com/android/deskclock/alarms/AlarmActivity.kt b/src/com/android/deskclock/alarms/AlarmActivity.kt
index a1bd876..c68d2eb 100644
--- a/src/com/android/deskclock/alarms/AlarmActivity.kt
+++ b/src/com/android/deskclock/alarms/AlarmActivity.kt
@@ -146,7 +146,7 @@
         LOGGER.i("Displaying alarm for instance: %s", mAlarmInstance)
 
         // Get the volume/camera button behavior setting
-        mVolumeBehavior = DataModel.getDataModel().alarmVolumeButtonBehavior
+        mVolumeBehavior = DataModel.dataModel.alarmVolumeButtonBehavior
 
         // TODO(b/157255731) Replace deprecated LayoutParams flags on Android versions above O
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
@@ -467,7 +467,7 @@
         val colorAccent = ThemeUtils.resolveColor(this, R.attr.colorAccent)
         setAnimatedFractions(1.0f /* snoozeFraction */, 0.0f /* dismissFraction */)
 
-        val snoozeMinutes = DataModel.getDataModel().snoozeLength
+        val snoozeMinutes = DataModel.dataModel.snoozeLength
         val infoText: String = getResources().getQuantityString(
                 R.plurals.alarm_alert_snooze_duration, snoozeMinutes, snoozeMinutes)
         val accessibilityText: String = getResources().getQuantityString(
diff --git a/src/com/android/deskclock/alarms/AlarmKlaxon.kt b/src/com/android/deskclock/alarms/AlarmKlaxon.kt
index cc3b6f9..bc37af7 100644
--- a/src/com/android/deskclock/alarms/AlarmKlaxon.kt
+++ b/src/com/android/deskclock/alarms/AlarmKlaxon.kt
@@ -56,7 +56,7 @@
         LogUtils.v("AlarmKlaxon.start()")
 
         if (!AlarmSettingColumns.NO_RINGTONE_URI.equals(instance.mRingtone)) {
-            val crescendoDuration = DataModel.getDataModel().alarmCrescendoDuration
+            val crescendoDuration = DataModel.dataModel.alarmCrescendoDuration
             getAsyncRingtonePlayer(context)!!.play(instance.mRingtone, crescendoDuration)
         }
 
diff --git a/src/com/android/deskclock/alarms/AlarmStateManager.kt b/src/com/android/deskclock/alarms/AlarmStateManager.kt
index d50157b..d4cc370 100644
--- a/src/com/android/deskclock/alarms/AlarmStateManager.kt
+++ b/src/com/android/deskclock/alarms/AlarmStateManager.kt
@@ -190,7 +190,7 @@
 
         private val currentTime: Calendar
             get() = (if (sCurrentTimeFactory == null) {
-                DataModel.getDataModel().calendar
+                DataModel.dataModel.calendar
             } else {
                 sCurrentTimeFactory!!.currentTime
             })
@@ -363,7 +363,7 @@
                     AlarmInstance.createIntent(context, AlarmService::class.java, instance.mId)
             intent.setAction(CHANGE_STATE_ACTION)
             intent.addCategory(tag)
-            intent.putExtra(ALARM_GLOBAL_ID_EXTRA, DataModel.getDataModel().globalIntentId)
+            intent.putExtra(ALARM_GLOBAL_ID_EXTRA, DataModel.dataModel.globalIntentId)
             if (state != null) {
                 intent.putExtra(ALARM_STATE_EXTRA, state.toInt())
             }
@@ -536,7 +536,7 @@
             AlarmService.stopAlarm(context, instance)
 
             // Calculate the new snooze alarm time
-            val snoozeMinutes = DataModel.getDataModel().snoozeLength
+            val snoozeMinutes = DataModel.dataModel.snoozeLength
             val newAlarmTime = Calendar.getInstance()
             newAlarmTime.add(Calendar.MINUTE, snoozeMinutes)
 
@@ -945,7 +945,7 @@
                     return
                 }
 
-                val globalId = DataModel.getDataModel().globalIntentId
+                val globalId = DataModel.dataModel.globalIntentId
                 val intentId: Int = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1)
                 val alarmState: Int = intent.getIntExtra(ALARM_STATE_EXTRA, -1)
                 if (intentId != globalId) {
diff --git a/src/com/android/deskclock/alarms/AlarmTimeClickHandler.kt b/src/com/android/deskclock/alarms/AlarmTimeClickHandler.kt
index 975a895..f9350ba 100644
--- a/src/com/android/deskclock/alarms/AlarmTimeClickHandler.kt
+++ b/src/com/android/deskclock/alarms/AlarmTimeClickHandler.kt
@@ -129,7 +129,7 @@
         val now = Calendar.getInstance()
         val oldNextAlarmTime = alarm.getNextAlarmTime(now)
 
-        val weekday = DataModel.getDataModel().weekdayOrder.calendarDays[index]
+        val weekday = DataModel.dataModel.weekdayOrder.calendarDays[index]
         alarm.daysOfWeek = alarm.daysOfWeek.setBit(weekday, checked)
 
         // if the change altered the next scheduled alarm time, tell the user
diff --git a/src/com/android/deskclock/alarms/dataadapter/CollapsedAlarmViewHolder.kt b/src/com/android/deskclock/alarms/dataadapter/CollapsedAlarmViewHolder.kt
index 33217b6..5b7b1ce 100644
--- a/src/com/android/deskclock/alarms/dataadapter/CollapsedAlarmViewHolder.kt
+++ b/src/com/android/deskclock/alarms/dataadapter/CollapsedAlarmViewHolder.kt
@@ -95,7 +95,7 @@
 
     private fun bindRepeatText(context: Context, alarm: Alarm) {
         if (alarm.daysOfWeek.isRepeating) {
-            val weekdayOrder = DataModel.getDataModel().weekdayOrder
+            val weekdayOrder = DataModel.dataModel.weekdayOrder
             val daysOfWeekText = alarm.daysOfWeek.toString(context, weekdayOrder)
             daysOfWeek.text = daysOfWeekText
 
diff --git a/src/com/android/deskclock/alarms/dataadapter/ExpandedAlarmViewHolder.kt b/src/com/android/deskclock/alarms/dataadapter/ExpandedAlarmViewHolder.kt
index 8fa4ca4..e071c99 100644
--- a/src/com/android/deskclock/alarms/dataadapter/ExpandedAlarmViewHolder.kt
+++ b/src/com/android/deskclock/alarms/dataadapter/ExpandedAlarmViewHolder.kt
@@ -73,7 +73,7 @@
 
         // Build button for each day.
         val inflater: LayoutInflater = LayoutInflater.from(context)
-        val weekdays = DataModel.getDataModel().weekdayOrder.calendarDays
+        val weekdays = DataModel.dataModel.weekdayOrder.calendarDays
         for (i in 0..6) {
             val dayButtonFrame: View = inflater.inflate(R.layout.day_button, repeatDays,
                     false /* attachToRoot */)
@@ -153,7 +153,7 @@
     }
 
     private fun bindRingtone(context: Context, alarm: Alarm) {
-        val title = DataModel.getDataModel().getRingtoneTitle(alarm.alert)
+        val title = DataModel.dataModel.getRingtoneTitle(alarm.alert!!)
         ringtone.text = title
 
         val description: String = context.getString(R.string.ringtone_description)
@@ -166,7 +166,7 @@
     }
 
     private fun bindDaysOfWeekButtons(alarm: Alarm, context: Context) {
-        val weekdays = DataModel.getDataModel().weekdayOrder.calendarDays
+        val weekdays = DataModel.dataModel.weekdayOrder.calendarDays
         for (i in weekdays.indices) {
             val dayButton: CompoundButton? = dayButtons[i]
             dayButton?.let {
diff --git a/src/com/android/deskclock/controller/ShortcutController.kt b/src/com/android/deskclock/controller/ShortcutController.kt
index 6b1e762..ee13111 100644
--- a/src/com/android/deskclock/controller/ShortcutController.kt
+++ b/src/com/android/deskclock/controller/ShortcutController.kt
@@ -51,7 +51,7 @@
 
     init {
         Controller.getController().addEventTracker(ShortcutEventTracker(context))
-        DataModel.getDataModel().addStopwatchListener(StopwatchWatcher())
+        DataModel.dataModel.addStopwatchListener(StopwatchWatcher())
     }
 
     fun updateShortcuts() {
@@ -105,7 +105,7 @@
     }
 
     private fun createStopwatchShortcut(): ShortcutInfo {
-        @StringRes val action: Int = if (DataModel.getDataModel().stopwatch.isRunning) {
+        @StringRes val action: Int = if (DataModel.dataModel.stopwatch.isRunning) {
             R.string.action_pause
         } else {
             R.string.action_start
@@ -117,7 +117,7 @@
                 .setActivity(mComponentName)
                 .setRank(2)
         val intent: Intent
-        if (DataModel.getDataModel().stopwatch.isRunning) {
+        if (DataModel.dataModel.stopwatch.isRunning) {
             intent = Intent(StopwatchService.ACTION_PAUSE_STOPWATCH)
                     .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_shortcut)
             shortcut.setShortLabel(context.getString(R.string.shortcut_pause_stopwatch_short))
diff --git a/src/com/android/deskclock/data/DataModel.kt b/src/com/android/deskclock/data/DataModel.kt
new file mode 100644
index 0000000..27b3fac
--- /dev/null
+++ b/src/com/android/deskclock/data/DataModel.kt
@@ -0,0 +1,1073 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.deskclock.data
+
+import android.app.Service
+import android.content.Context
+import android.content.Context.AUDIO_SERVICE
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.SharedPreferences
+import android.media.AudioManager
+import android.media.AudioManager.FLAG_SHOW_UI
+import android.media.AudioManager.STREAM_ALARM
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+import android.provider.Settings.ACTION_SOUND_SETTINGS
+import android.view.View
+import androidx.annotation.StringRes
+
+import com.android.deskclock.Predicate
+import com.android.deskclock.R
+import com.android.deskclock.Utils
+import com.android.deskclock.timer.TimerService
+
+import java.util.Calendar
+
+import kotlin.Comparator
+import kotlin.math.roundToInt
+
+/**
+ * All application-wide data is accessible through this singleton.
+ */
+// TODO(b/157255731) DataModel can be converted to an object after no Java code depends on it
+class DataModel private constructor() {
+
+    /** Indicates the display style of clocks.  */
+    enum class ClockStyle {
+        ANALOG, DIGITAL
+    }
+
+    /** Indicates the preferred sort order of cities.  */
+    enum class CitySort {
+        NAME, UTC_OFFSET
+    }
+
+    /** Indicates the preferred behavior of hardware volume buttons when firing alarms.  */
+    enum class AlarmVolumeButtonBehavior {
+        NOTHING, SNOOZE, DISMISS
+    }
+
+    /** Indicates the reason alarms may not fire or may fire silently.  */
+    enum class SilentSetting(
+        @field:StringRes @get:StringRes val labelResId: Int,
+        @field:StringRes @get:StringRes val actionResId: Int,
+        private val mActionEnabled: Predicate<Context>,
+        private val mActionListener: View.OnClickListener?
+    ) {
+
+        DO_NOT_DISTURB(R.string.alarms_blocked_by_dnd,
+                0,
+                Predicate.FALSE as Predicate<Context>,
+                mActionListener = null),
+        MUTED_VOLUME(R.string.alarm_volume_muted,
+                R.string.unmute_alarm_volume,
+                Predicate.TRUE as Predicate<Context>,
+                UnmuteAlarmVolumeListener()),
+        SILENT_RINGTONE(R.string.silent_default_alarm_ringtone,
+                R.string.change_setting_action,
+                ChangeSoundActionPredicate(),
+                ChangeSoundSettingsListener()),
+        BLOCKED_NOTIFICATIONS(R.string.app_notifications_blocked,
+                R.string.change_setting_action,
+                Predicate.TRUE as Predicate<Context>,
+                ChangeAppNotificationSettingsListener());
+
+        val actionListener: View.OnClickListener?
+            get() = mActionListener
+
+        fun isActionEnabled(context: Context?): Boolean {
+            return labelResId != 0 && mActionEnabled.apply(context)
+        }
+
+        private class UnmuteAlarmVolumeListener : View.OnClickListener {
+            override fun onClick(v: View) {
+                // Set the alarm volume to 11/16th of max and show the slider UI.
+                // 11/16th of max is the initial volume of the alarm stream on a fresh install.
+                val context: Context = v.context
+                val am: AudioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
+                val index = (am.getStreamMaxVolume(STREAM_ALARM) * 11f / 16f).roundToInt()
+                am.setStreamVolume(STREAM_ALARM, index, FLAG_SHOW_UI)
+            }
+        }
+
+        private class ChangeSoundSettingsListener : View.OnClickListener {
+            override fun onClick(v: View) {
+                val context: Context = v.context
+                context.startActivity(Intent(ACTION_SOUND_SETTINGS)
+                        .addFlags(FLAG_ACTIVITY_NEW_TASK))
+            }
+        }
+
+        private class ChangeSoundActionPredicate : Predicate<Context> {
+            override fun apply(context: Context): Boolean {
+                val intent = Intent(ACTION_SOUND_SETTINGS)
+                return intent.resolveActivity(context.packageManager) != null
+            }
+        }
+
+        private class ChangeAppNotificationSettingsListener : View.OnClickListener {
+            override fun onClick(v: View) {
+                val context: Context = v.context
+                if (Utils.isLOrLater()) {
+                    try {
+                        // Attempt to open the notification settings for this app.
+                        context.startActivity(
+                                Intent("android.settings.APP_NOTIFICATION_SETTINGS")
+                                        .putExtra("app_package", context.packageName)
+                                        .putExtra("app_uid", context.applicationInfo.uid)
+                                        .addFlags(FLAG_ACTIVITY_NEW_TASK))
+                        return
+                    } catch (ignored: Exception) {
+                        // best attempt only; recovery code below
+                    }
+                }
+
+                // Fall back to opening the app settings page.
+                context.startActivity(Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
+                        .setData(Uri.fromParts("package", context.packageName, null))
+                        .addFlags(FLAG_ACTIVITY_NEW_TASK))
+            }
+        }
+    }
+
+    private var mHandler: Handler? = null
+    private var mContext: Context? = null
+
+    /** The model from which settings are fetched.  */
+    private var mSettingsModel: SettingsModel? = null
+
+    /** The model from which city data are fetched.  */
+    private var mCityModel: CityModel? = null
+
+    /** The model from which timer data are fetched.  */
+    private var mTimerModel: TimerModel? = null
+
+    /** The model from which alarm data are fetched.  */
+    private var mAlarmModel: AlarmModel? = null
+
+    /** The model from which widget data are fetched.  */
+    private var mWidgetModel: WidgetModel? = null
+
+    /** The model from which data about settings that silence alarms are fetched.  */
+    private var mSilentSettingsModel: SilentSettingsModel? = null
+
+    /** The model from which stopwatch data are fetched.  */
+    private var mStopwatchModel: StopwatchModel? = null
+
+    /** The model from which notification data are fetched.  */
+    private var mNotificationModel: NotificationModel? = null
+
+    /** The model from which time data are fetched.  */
+    private var mTimeModel: TimeModel? = null
+
+    /** The model from which ringtone data are fetched.  */
+    private var mRingtoneModel: RingtoneModel? = null
+
+    /**
+     * Initializes the data model with the context and shared preferences to be used.
+     */
+    fun init(context: Context, prefs: SharedPreferences) {
+        if (mContext !== context) {
+            mContext = context.applicationContext
+            mTimeModel = TimeModel(mContext!!)
+            mWidgetModel = WidgetModel(prefs)
+            mNotificationModel = NotificationModel()
+            mRingtoneModel = RingtoneModel(mContext!!, prefs)
+            mSettingsModel = SettingsModel(mContext!!, prefs, mTimeModel!!)
+            mCityModel = CityModel(mContext!!, prefs, mSettingsModel!!)
+            mAlarmModel = AlarmModel(mContext!!, mSettingsModel!!)
+            mSilentSettingsModel = SilentSettingsModel(mContext!!, mNotificationModel!!)
+            mStopwatchModel = StopwatchModel(mContext!!, prefs, mNotificationModel!!)
+            mTimerModel = TimerModel(mContext!!, prefs, mSettingsModel!!, mRingtoneModel!!,
+                    mNotificationModel!!)
+        }
+    }
+
+    /**
+     * Convenience for `run(runnable, 0)`, i.e. waits indefinitely.
+     */
+    fun run(runnable: Runnable) {
+        try {
+            run(runnable, 0 /* waitMillis */)
+        } catch (ignored: InterruptedException) {
+        }
+    }
+
+    /**
+     * Updates all timers and the stopwatch after the device has shutdown and restarted.
+     */
+    fun updateAfterReboot() {
+        Utils.enforceMainLooper()
+        mTimerModel!!.updateTimersAfterReboot()
+        mStopwatchModel!!.setStopwatch(stopwatch.updateAfterReboot())
+    }
+
+    /**
+     * Updates all timers and the stopwatch after the device's time has changed.
+     */
+    fun updateAfterTimeSet() {
+        Utils.enforceMainLooper()
+        mTimerModel!!.updateTimersAfterTimeSet()
+        mStopwatchModel!!.setStopwatch(stopwatch.updateAfterTimeSet())
+    }
+
+    /**
+     * Posts a runnable to the main thread and blocks until the runnable executes. Used to access
+     * the data model from the main thread.
+     */
+    @Throws(InterruptedException::class)
+    fun run(runnable: Runnable, waitMillis: Long) {
+        if (Looper.myLooper() === Looper.getMainLooper()) {
+            runnable.run()
+            return
+        }
+
+        val er = ExecutedRunnable(runnable)
+        handler.post(er)
+
+        // Wait for the data to arrive, if it has not.
+        synchronized(er) {
+            if (!er.isExecuted) {
+                er.wait(waitMillis)
+            }
+        }
+    }
+
+    /**
+     * @return a handler associated with the main thread
+     */
+    @get:Synchronized
+    private val handler: Handler
+        get() {
+            if (mHandler == null) {
+                mHandler = Handler(Looper.getMainLooper())
+            }
+            return mHandler!!
+        }
+
+    //
+    // Application
+    //
+
+    var isApplicationInForeground: Boolean
+        /**
+         * @return `true` when the application is open in the foreground; `false` otherwise
+         */
+        get() {
+            Utils.enforceMainLooper()
+            return mNotificationModel!!.isApplicationInForeground
+        }
+        /**
+         * @param inForeground `true` to indicate the application is open in the foreground
+         */
+        set(inForeground) {
+            Utils.enforceMainLooper()
+            if (mNotificationModel!!.isApplicationInForeground != inForeground) {
+                mNotificationModel!!.isApplicationInForeground = inForeground
+
+                // Refresh all notifications in response to a change in app open state.
+                mTimerModel!!.updateNotification()
+                mTimerModel!!.updateMissedNotification()
+                mStopwatchModel!!.updateNotification()
+                mSilentSettingsModel!!.updateSilentState()
+            }
+        }
+
+    /**
+     * Called when the notifications may be stale or absent from the notification manager and must
+     * be rebuilt. e.g. after upgrading the application
+     */
+    fun updateAllNotifications() {
+        Utils.enforceMainLooper()
+        mTimerModel!!.updateNotification()
+        mTimerModel!!.updateMissedNotification()
+        mStopwatchModel!!.updateNotification()
+    }
+
+    //
+    // Cities
+    //
+
+    /**
+     * @return a list of all cities in their display order
+     */
+    val allCities: List<City>
+        get() {
+            Utils.enforceMainLooper()
+            return mCityModel!!.allCities
+        }
+
+    /**
+     * @return a city representing the user's home timezone
+     */
+    val homeCity: City
+        get() {
+            Utils.enforceMainLooper()
+            return mCityModel!!.homeCity
+        }
+
+    /**
+     * @return a list of cities not selected for display
+     */
+    val unselectedCities: List<City>
+        get() {
+            Utils.enforceMainLooper()
+            return mCityModel!!.unselectedCities
+        }
+
+    var selectedCities: Collection<City>
+        /**
+         * @return a list of cities selected for display
+         */
+        get() {
+            Utils.enforceMainLooper()
+            return mCityModel!!.selectedCities
+        }
+        /**
+         * @param cities the new collection of cities selected for display by the user
+         */
+        set(cities) {
+            Utils.enforceMainLooper()
+            mCityModel?.setSelectedCities(cities)
+        }
+
+    /**
+     * @return a comparator used to locate index positions
+     */
+    val cityIndexComparator: Comparator<City>
+        get() {
+            Utils.enforceMainLooper()
+            return mCityModel!!.cityIndexComparator
+        }
+
+    /**
+     * @return the order in which cities are sorted
+     */
+    val citySort: CitySort
+        get() {
+            Utils.enforceMainLooper()
+            return mCityModel!!.citySort
+        }
+
+    /**
+     * Adjust the order in which cities are sorted.
+     */
+    fun toggleCitySort() {
+        Utils.enforceMainLooper()
+        mCityModel?.toggleCitySort()
+    }
+
+    /**
+     * @param cityListener listener to be notified when the world city list changes
+     */
+    fun addCityListener(cityListener: CityListener) {
+        Utils.enforceMainLooper()
+        mCityModel?.addCityListener(cityListener)
+    }
+
+    /**
+     * @param cityListener listener that no longer needs to be notified of world city list changes
+     */
+    fun removeCityListener(cityListener: CityListener) {
+        Utils.enforceMainLooper()
+        mCityModel?.removeCityListener(cityListener)
+    }
+
+    //
+    // Timers
+    //
+
+    /**
+     * @param timerListener to be notified when timers are added, updated and removed
+     */
+    fun addTimerListener(timerListener: TimerListener) {
+        Utils.enforceMainLooper()
+        mTimerModel?.addTimerListener(timerListener)
+    }
+
+    /**
+     * @param timerListener to no longer be notified when timers are added, updated and removed
+     */
+    fun removeTimerListener(timerListener: TimerListener) {
+        Utils.enforceMainLooper()
+        mTimerModel?.removeTimerListener(timerListener)
+    }
+
+    /**
+     * @return a list of timers for display
+     */
+    val timers: List<Timer>
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.timers
+        }
+
+    /**
+     * @return a list of expired timers for display
+     */
+    val expiredTimers: List<Timer>
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.expiredTimers
+        }
+
+    /**
+     * @param timerId identifies the timer to return
+     * @return the timer with the given `timerId`
+     */
+    fun getTimer(timerId: Int): Timer? {
+        Utils.enforceMainLooper()
+        return mTimerModel?.getTimer(timerId)
+    }
+
+    /**
+     * @return the timer that last expired and is still expired now; `null` if no timers are
+     * expired
+     */
+    val mostRecentExpiredTimer: Timer?
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel?.mostRecentExpiredTimer
+        }
+
+    /**
+     * @param length the length of the timer in milliseconds
+     * @param label describes the purpose of the timer
+     * @param deleteAfterUse `true` indicates the timer should be deleted when it is reset
+     * @return the newly added timer
+     */
+    fun addTimer(length: Long, label: String?, deleteAfterUse: Boolean): Timer {
+        Utils.enforceMainLooper()
+        return mTimerModel!!.addTimer(length, label, deleteAfterUse)
+    }
+
+    /**
+     * @param timer the timer to be removed
+     */
+    fun removeTimer(timer: Timer) {
+        Utils.enforceMainLooper()
+        mTimerModel?.removeTimer(timer)
+    }
+
+    /**
+     * @param timer the timer to be started
+     */
+    fun startTimer(timer: Timer) {
+        startTimer(null, timer)
+    }
+
+    /**
+     * @param service used to start foreground notifications for expired timers
+     * @param timer the timer to be started
+     */
+    fun startTimer(service: Service?, timer: Timer) {
+        Utils.enforceMainLooper()
+        val started = timer.start()
+        mTimerModel?.updateTimer(started)
+        if (timer.remainingTime <= 0) {
+            if (service != null) {
+                expireTimer(service, started)
+            } else {
+                mContext!!.startService(TimerService.createTimerExpiredIntent(mContext, started))
+            }
+        }
+    }
+
+    /**
+     * @param timer the timer to be paused
+     */
+    fun pauseTimer(timer: Timer) {
+        Utils.enforceMainLooper()
+        mTimerModel?.updateTimer(timer.pause())
+    }
+
+    /**
+     * @param service used to start foreground notifications for expired timers
+     * @param timer the timer to be expired
+     */
+    fun expireTimer(service: Service, timer: Timer) {
+        Utils.enforceMainLooper()
+        mTimerModel?.expireTimer(service, timer)
+    }
+
+    /**
+     * @param timer the timer to be reset
+     * @return the reset `timer`
+     */
+    fun resetTimer(timer: Timer): Timer? {
+        Utils.enforceMainLooper()
+        return mTimerModel?.resetTimer(timer, false /* allowDelete */, 0 /* eventLabelId */)
+    }
+
+    /**
+     * If the given `timer` is expired and marked for deletion after use then this method
+     * removes the timer. The timer is otherwise transitioned to the reset state and continues
+     * to exist.
+     *
+     * @param timer the timer to be reset
+     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     * @return the reset `timer` or `null` if the timer was deleted
+     */
+    fun resetOrDeleteTimer(timer: Timer, @StringRes eventLabelId: Int): Timer? {
+        Utils.enforceMainLooper()
+        return mTimerModel?.resetTimer(timer, true /* allowDelete */, eventLabelId)
+    }
+
+    /**
+     * Resets all expired timers.
+     *
+     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     */
+    fun resetOrDeleteExpiredTimers(@StringRes eventLabelId: Int) {
+        Utils.enforceMainLooper()
+        mTimerModel?.resetOrDeleteExpiredTimers(eventLabelId)
+    }
+
+    /**
+     * Resets all unexpired timers.
+     *
+     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     */
+    fun resetUnexpiredTimers(@StringRes eventLabelId: Int) {
+        Utils.enforceMainLooper()
+        mTimerModel?.resetUnexpiredTimers(eventLabelId)
+    }
+
+    /**
+     * Resets all missed timers.
+     *
+     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     */
+    fun resetMissedTimers(@StringRes eventLabelId: Int) {
+        Utils.enforceMainLooper()
+        mTimerModel?.resetMissedTimers(eventLabelId)
+    }
+
+    /**
+     * @param timer the timer to which a minute should be added to the remaining time
+     */
+    fun addTimerMinute(timer: Timer) {
+        Utils.enforceMainLooper()
+        mTimerModel?.updateTimer(timer.addMinute())
+    }
+
+    /**
+     * @param timer the timer to which the new `label` belongs
+     * @param label the new label to store for the `timer`
+     */
+    fun setTimerLabel(timer: Timer, label: String?) {
+        Utils.enforceMainLooper()
+        mTimerModel?.updateTimer(timer.setLabel(label))
+    }
+
+    /**
+     * @param timer the timer whose `length` to change
+     * @param length the new length of the timer in milliseconds
+     */
+    fun setTimerLength(timer: Timer, length: Long) {
+        Utils.enforceMainLooper()
+        mTimerModel?.updateTimer(timer.setLength(length))
+    }
+
+    /**
+     * @param timer the timer whose `remainingTime` to change
+     * @param remainingTime the new remaining time of the timer in milliseconds
+     */
+    fun setRemainingTime(timer: Timer, remainingTime: Long) {
+        Utils.enforceMainLooper()
+
+        val updated = timer.setRemainingTime(remainingTime)
+        mTimerModel?.updateTimer(updated)
+        if (timer.isRunning && timer.remainingTime <= 0) {
+            mContext?.startService(TimerService.createTimerExpiredIntent(mContext, updated))
+        }
+    }
+
+    /**
+     * Updates the timer notifications to be current.
+     */
+    fun updateTimerNotification() {
+        Utils.enforceMainLooper()
+        mTimerModel?.updateNotification()
+    }
+
+    /**
+     * @return the uri of the default ringtone to play for all timers when no user selection exists
+     */
+    val defaultTimerRingtoneUri: Uri
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.defaultTimerRingtoneUri
+        }
+
+    /**
+     * @return `true` iff the ringtone to play for all timers is the silent ringtone
+     */
+    val isTimerRingtoneSilent: Boolean
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.isTimerRingtoneSilent
+        }
+
+    var timerRingtoneUri: Uri
+        /**
+         * @return the uri of the ringtone to play for all timers
+         */
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.timerRingtoneUri
+        }
+        /**
+         * @param uri the uri of the ringtone to play for all timers
+         */
+        set(uri) {
+            Utils.enforceMainLooper()
+            mTimerModel!!.timerRingtoneUri = uri
+        }
+
+    /**
+     * @return the title of the ringtone that is played for all timers
+     */
+    val timerRingtoneTitle: String
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.timerRingtoneTitle
+        }
+
+    /**
+     * @return the duration, in milliseconds, of the crescendo to apply to timer ringtone playback;
+     * `0` implies no crescendo should be applied
+     */
+    val timerCrescendoDuration: Long
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.timerCrescendoDuration
+        }
+
+    var timerVibrate: Boolean
+        /**
+         * @return whether vibrate is enabled for all timers.
+         */
+        get() {
+            Utils.enforceMainLooper()
+            return mTimerModel!!.timerVibrate
+        }
+        /**
+         * @param enabled whether vibrate is enabled for all timers.
+         */
+        set(enabled) {
+            Utils.enforceMainLooper()
+            mTimerModel!!.timerVibrate = enabled
+        }
+
+    //
+    // Alarms
+    //
+
+    var defaultAlarmRingtoneUri: Uri
+        /**
+         * @return the uri of the ringtone to which all new alarms default
+         */
+        get() {
+            Utils.enforceMainLooper()
+            return mAlarmModel!!.defaultAlarmRingtoneUri
+        }
+        /**
+         * @param uri the uri of the ringtone to which future new alarms will default
+         */
+        set(uri) {
+            Utils.enforceMainLooper()
+            mAlarmModel!!.defaultAlarmRingtoneUri = uri
+        }
+
+    /**
+     * @return the duration, in milliseconds, of the crescendo to apply to alarm ringtone playback;
+     * `0` implies no crescendo should be applied
+     */
+    val alarmCrescendoDuration: Long
+        get() {
+            Utils.enforceMainLooper()
+            return mAlarmModel!!.alarmCrescendoDuration
+        }
+
+    /**
+     * @return the behavior to execute when volume buttons are pressed while firing an alarm
+     */
+    val alarmVolumeButtonBehavior: AlarmVolumeButtonBehavior
+        get() {
+            Utils.enforceMainLooper()
+            return mAlarmModel!!.alarmVolumeButtonBehavior
+        }
+
+    /**
+     * @return the number of minutes an alarm may ring before it has timed out and becomes missed
+     */
+    val alarmTimeout: Int
+        get() = mAlarmModel!!.alarmTimeout
+
+    /**
+     * @return the number of minutes an alarm will remain snoozed before it rings again
+     */
+    val snoozeLength: Int
+        get() = mAlarmModel!!.snoozeLength
+
+    //
+    // Stopwatch
+    //
+
+    /**
+     * @param stopwatchListener to be notified when stopwatch changes or laps are added
+     */
+    fun addStopwatchListener(stopwatchListener: StopwatchListener) {
+        Utils.enforceMainLooper()
+        mStopwatchModel?.addStopwatchListener(stopwatchListener)
+    }
+
+    /**
+     * @param stopwatchListener to no longer be notified when stopwatch changes or laps are added
+     */
+    fun removeStopwatchListener(stopwatchListener: StopwatchListener) {
+        Utils.enforceMainLooper()
+        mStopwatchModel?.removeStopwatchListener(stopwatchListener)
+    }
+
+    /**
+     * @return the current state of the stopwatch
+     */
+    val stopwatch: Stopwatch
+        get() {
+            Utils.enforceMainLooper()
+            return mStopwatchModel!!.stopwatch
+        }
+
+    /**
+     * @return the stopwatch after being started
+     */
+    fun startStopwatch(): Stopwatch {
+        Utils.enforceMainLooper()
+        return mStopwatchModel!!.setStopwatch(stopwatch.start())
+    }
+
+    /**
+     * @return the stopwatch after being paused
+     */
+    fun pauseStopwatch(): Stopwatch {
+        Utils.enforceMainLooper()
+        return mStopwatchModel!!.setStopwatch(stopwatch.pause())
+    }
+
+    /**
+     * @return the stopwatch after being reset
+     */
+    fun resetStopwatch(): Stopwatch {
+        Utils.enforceMainLooper()
+        return mStopwatchModel!!.setStopwatch(stopwatch.reset())
+    }
+
+    /**
+     * @return the laps recorded for this stopwatch
+     */
+    val laps: List<Lap>
+        get() {
+            Utils.enforceMainLooper()
+            return mStopwatchModel!!.laps
+        }
+
+    /**
+     * @return a newly recorded lap completed now; `null` if no more laps can be added
+     */
+    fun addLap(): Lap? {
+        Utils.enforceMainLooper()
+        return mStopwatchModel!!.addLap()
+    }
+
+    /**
+     * @return `true` iff more laps can be recorded
+     */
+    fun canAddMoreLaps(): Boolean {
+        Utils.enforceMainLooper()
+        return mStopwatchModel!!.canAddMoreLaps()
+    }
+
+    /**
+     * @return the longest lap time of all recorded laps and the current lap
+     */
+    val longestLapTime: Long
+        get() {
+            Utils.enforceMainLooper()
+            return mStopwatchModel!!.longestLapTime
+        }
+
+    /**
+     * @param time a point in time after the end of the last lap
+     * @return the elapsed time between the given `time` and the end of the previous lap
+     */
+    fun getCurrentLapTime(time: Long): Long {
+        Utils.enforceMainLooper()
+        return mStopwatchModel!!.getCurrentLapTime(time)
+    }
+
+    //
+    // Time
+    // (Time settings/values are accessible from any Thread so no Thread-enforcement exists.)
+    //
+
+    /**
+     * @return the current time in milliseconds
+     */
+    fun currentTimeMillis(): Long {
+        return mTimeModel!!.currentTimeMillis()
+    }
+
+    /**
+     * @return milliseconds since boot, including time spent in sleep
+     */
+    fun elapsedRealtime(): Long {
+        return mTimeModel!!.elapsedRealtime()
+    }
+
+    /**
+     * @return `true` if 24 hour time format is selected; `false` otherwise
+     */
+    fun is24HourFormat(): Boolean {
+        return mTimeModel!!.is24HourFormat()
+    }
+
+    /**
+     * @return a new calendar object initialized to the [.currentTimeMillis]
+     */
+    val calendar: Calendar
+        get() = mTimeModel!!.calendar
+
+    //
+    // Ringtones
+    //
+
+    /**
+     * Ringtone titles are cached because loading them is expensive. This method
+     * **must** be called on a background thread and is responsible for priming the
+     * cache of ringtone titles to avoid later fetching titles on the main thread.
+     */
+    fun loadRingtoneTitles() {
+        Utils.enforceNotMainLooper()
+        mRingtoneModel?.loadRingtoneTitles()
+    }
+
+    /**
+     * Recheck the permission to read each custom ringtone.
+     */
+    fun loadRingtonePermissions() {
+        Utils.enforceNotMainLooper()
+        mRingtoneModel?.loadRingtonePermissions()
+    }
+
+    /**
+     * @param uri the uri of a ringtone
+     * @return the title of the ringtone with the `uri`; `null` if it cannot be fetched
+     */
+    fun getRingtoneTitle(uri: Uri): String? {
+        Utils.enforceMainLooper()
+        return mRingtoneModel?.getRingtoneTitle(uri)
+    }
+
+    /**
+     * @param uri the uri of an audio file to use as a ringtone
+     * @param title the title of the audio content at the given `uri`
+     * @return the ringtone instance created for the audio file
+     */
+    fun addCustomRingtone(uri: Uri, title: String?): CustomRingtone? {
+        Utils.enforceMainLooper()
+        return mRingtoneModel?.addCustomRingtone(uri, title)
+    }
+
+    /**
+     * @param uri identifies the ringtone to remove
+     */
+    fun removeCustomRingtone(uri: Uri) {
+        Utils.enforceMainLooper()
+        mRingtoneModel?.removeCustomRingtone(uri)
+    }
+
+    /**
+     * @return all available custom ringtones
+     */
+    val customRingtones: List<CustomRingtone>
+        get() {
+            Utils.enforceMainLooper()
+            return mRingtoneModel!!.customRingtones
+        }
+
+    //
+    // Widgets
+    //
+
+    /**
+     * @param widgetClass indicates the type of widget being counted
+     * @param count the number of widgets of the given type
+     * @param eventCategoryId identifies the category of event to send
+     */
+    fun updateWidgetCount(widgetClass: Class<*>?, count: Int, @StringRes eventCategoryId: Int) {
+        Utils.enforceMainLooper()
+        mWidgetModel!!.updateWidgetCount(widgetClass!!, count, eventCategoryId)
+    }
+
+    //
+    // Settings
+    //
+
+    /**
+     * @param silentSettingsListener to be notified when alarm-silencing settings change
+     */
+    fun addSilentSettingsListener(silentSettingsListener: OnSilentSettingsListener) {
+        Utils.enforceMainLooper()
+        mSilentSettingsModel?.addSilentSettingsListener(silentSettingsListener)
+    }
+
+    /**
+     * @param silentSettingsListener to no longer be notified when alarm-silencing settings change
+     */
+    fun removeSilentSettingsListener(silentSettingsListener: OnSilentSettingsListener) {
+        Utils.enforceMainLooper()
+        mSilentSettingsModel?.removeSilentSettingsListener(silentSettingsListener)
+    }
+
+    /**
+     * @return the id used to discriminate relevant AlarmManager callbacks from defunct ones
+     */
+    val globalIntentId: Int
+        get() = mSettingsModel!!.globalIntentId
+
+    /**
+     * Update the id used to discriminate relevant AlarmManager callbacks from defunct ones
+     */
+    fun updateGlobalIntentId() {
+        Utils.enforceMainLooper()
+        mSettingsModel!!.updateGlobalIntentId()
+    }
+
+    /**
+     * @return the style of clock to display in the clock application
+     */
+    val clockStyle: ClockStyle
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.clockStyle
+        }
+
+    var displayClockSeconds: Boolean
+        /**
+         * @return the style of clock to display in the clock application
+         */
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.displayClockSeconds
+        }
+        /**
+         * @param displaySeconds whether or not to display seconds for main clock
+         */
+        set(displaySeconds) {
+            Utils.enforceMainLooper()
+            mSettingsModel!!.displayClockSeconds = displaySeconds
+        }
+
+    /**
+     * @return the style of clock to display in the clock screensaver
+     */
+    val screensaverClockStyle: ClockStyle
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.screensaverClockStyle
+        }
+
+    /**
+     * @return `true` if the screen saver should be dimmed for lower contrast at night
+     */
+    val screensaverNightModeOn: Boolean
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.screensaverNightModeOn
+        }
+
+    /**
+     * @return `true` if the users wants to automatically show a clock for their home timezone
+     * when they have travelled outside of that timezone
+     */
+    val showHomeClock: Boolean
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.showHomeClock
+        }
+
+    /**
+     * @return the display order of the weekdays, which can start with [Calendar.SATURDAY],
+     * [Calendar.SUNDAY] or [Calendar.MONDAY]
+     */
+    val weekdayOrder: Weekdays.Order
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.weekdayOrder
+        }
+
+    var isRestoreBackupFinished: Boolean
+        /**
+         * @return `true` if the restore process (of backup and restore) has completed
+         */
+        get() = mSettingsModel!!.isRestoreBackupFinished
+        /**
+         * @param finished `true` means the restore process (of backup and restore) has completed
+         */
+        set(finished) {
+            mSettingsModel!!.isRestoreBackupFinished = finished
+        }
+
+    /**
+     * @return a description of the time zones available for selection
+     */
+    val timeZones: TimeZones
+        get() {
+            Utils.enforceMainLooper()
+            return mSettingsModel!!.timeZones
+        }
+
+    /**
+     * Used to execute a delegate runnable and track its completion.
+     */
+    private class ExecutedRunnable(private val mDelegate: Runnable) : Runnable, java.lang.Object() {
+        var isExecuted = false
+        override fun run() {
+            mDelegate.run()
+            synchronized(this) {
+                isExecuted = true
+                notifyAll()
+            }
+        }
+    }
+
+    companion object {
+        const val ACTION_WORLD_CITIES_CHANGED = "com.android.deskclock.WORLD_CITIES_CHANGED"
+
+        /** The single instance of this data model that exists for the life of the application.  */
+        val sDataModel = DataModel()
+
+        @get:JvmStatic
+        val dataModel
+            get() = sDataModel
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/SettingsModel.kt b/src/com/android/deskclock/data/SettingsModel.kt
index bfbb8d7..040c791 100644
--- a/src/com/android/deskclock/data/SettingsModel.kt
+++ b/src/com/android/deskclock/data/SettingsModel.kt
@@ -60,7 +60,7 @@
     val homeTimeZone: TimeZone
         get() = SettingsDAO.getHomeTimeZone(mContext, mPrefs, TimeZone.getDefault())
 
-    val clockStyle: DataModel.ClockStyle?
+    val clockStyle: DataModel.ClockStyle
         get() = SettingsDAO.getClockStyle(mContext, mPrefs)
 
     var displayClockSeconds: Boolean
@@ -69,7 +69,7 @@
             SettingsDAO.setDisplayClockSeconds(mPrefs, shouldDisplaySeconds)
         }
 
-    val screensaverClockStyle: DataModel.ClockStyle?
+    val screensaverClockStyle: DataModel.ClockStyle
         get() = SettingsDAO.getScreensaverClockStyle(mContext, mPrefs)
 
     val screensaverNightModeOn: Boolean
diff --git a/src/com/android/deskclock/data/StopwatchNotificationBuilder.kt b/src/com/android/deskclock/data/StopwatchNotificationBuilder.kt
index 7671b05..a4bd9b8 100644
--- a/src/com/android/deskclock/data/StopwatchNotificationBuilder.kt
+++ b/src/com/android/deskclock/data/StopwatchNotificationBuilder.kt
@@ -87,7 +87,7 @@
             actions.add(Action.Builder(icon1, title1, intent1).build())
 
             // Right button: Add Lap
-            if (DataModel.getDataModel().canAddMoreLaps()) {
+            if (DataModel.dataModel.canAddMoreLaps()) {
                 val lap: Intent = Intent(context, StopwatchService::class.java)
                         .setAction(StopwatchService.ACTION_LAP_STOPWATCH)
                         .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel)
@@ -99,7 +99,7 @@
             }
 
             // Show the current lap number if any laps have been recorded.
-            val lapCount = DataModel.getDataModel().laps.size
+            val lapCount = DataModel.dataModel.laps.size
             if (lapCount > 0) {
                 val lapNumber = lapCount + 1
                 val lap: String = res.getString(R.string.sw_notification_lap_number, lapNumber)
diff --git a/src/com/android/deskclock/data/TimerModel.kt b/src/com/android/deskclock/data/TimerModel.kt
index 1ca630d..55ac14d 100644
--- a/src/com/android/deskclock/data/TimerModel.kt
+++ b/src/com/android/deskclock/data/TimerModel.kt
@@ -385,7 +385,7 @@
     /**
      * @return the title of the ringtone that is played for all timers
      */
-    val timerRingtoneTitle: String?
+    val timerRingtoneTitle: String
         get() {
             if (mTimerRingtoneTitle == null) {
                 mTimerRingtoneTitle = if (isTimerRingtoneSilent) {
@@ -403,7 +403,7 @@
                 }
             }
 
-            return mTimerRingtoneTitle
+            return mTimerRingtoneTitle!!
         }
 
     /**
diff --git a/src/com/android/deskclock/provider/Alarm.kt b/src/com/android/deskclock/provider/Alarm.kt
index 2e7c4ec..729d059 100644
--- a/src/com/android/deskclock/provider/Alarm.kt
+++ b/src/com/android/deskclock/provider/Alarm.kt
@@ -84,7 +84,7 @@
         vibrate = true
         daysOfWeek = Weekdays.NONE
         label = ""
-        alert = DataModel.getDataModel().defaultAlarmRingtoneUri
+        alert = DataModel.dataModel.defaultAlarmRingtoneUri
         deleteAfterUse = false
     }
 
@@ -381,7 +381,7 @@
                 override fun loadInBackground(): Cursor {
                     // Prime the ringtone title cache for later access. Most alarms will refer to
                     // system ringtones.
-                    DataModel.getDataModel().loadRingtoneTitles()
+                    DataModel.dataModel.loadRingtoneTitles()
                     return super.loadInBackground()
                 }
             }
diff --git a/src/com/android/deskclock/provider/AlarmInstance.kt b/src/com/android/deskclock/provider/AlarmInstance.kt
index 810930c..d69ae5b 100644
--- a/src/com/android/deskclock/provider/AlarmInstance.kt
+++ b/src/com/android/deskclock/provider/AlarmInstance.kt
@@ -201,7 +201,7 @@
      */
     val timeout: Calendar?
         get() {
-            val timeoutMinutes = DataModel.getDataModel().alarmTimeout
+            val timeoutMinutes = DataModel.dataModel.alarmTimeout
 
             // Alarm silence has been set to "None"
             if (timeoutMinutes < 0) {
diff --git a/src/com/android/deskclock/settings/AlarmVolumePreference.kt b/src/com/android/deskclock/settings/AlarmVolumePreference.kt
index 51f4ec6..790469f 100644
--- a/src/com/android/deskclock/settings/AlarmVolumePreference.kt
+++ b/src/com/android/deskclock/settings/AlarmVolumePreference.kt
@@ -90,7 +90,7 @@
                 if (!mPreviewPlaying && seekBar.getProgress() != 0) {
                     // If we are not currently playing and progress is set to non-zero, start.
                     RingtonePreviewKlaxon
-                            .start(context, DataModel.getDataModel().defaultAlarmRingtoneUri)
+                            .start(context, DataModel.dataModel.defaultAlarmRingtoneUri)
                     mPreviewPlaying = true
                     seekBar.postDelayed(Runnable {
                         RingtonePreviewKlaxon.stop(context)
diff --git a/src/com/android/deskclock/settings/SettingsActivity.kt b/src/com/android/deskclock/settings/SettingsActivity.kt
index dc854a1..8c7cbfc 100644
--- a/src/com/android/deskclock/settings/SettingsActivity.kt
+++ b/src/com/android/deskclock/settings/SettingsActivity.kt
@@ -139,7 +139,7 @@
                     pref.setSummary(simpleMenuPreference.getEntries().get(i))
                 }
                 KEY_CLOCK_DISPLAY_SECONDS -> {
-                    DataModel.getDataModel().displayClockSeconds = newValue as Boolean
+                    DataModel.dataModel.displayClockSeconds = newValue as Boolean
                 }
                 KEY_AUTO_SILENCE -> {
                     val delay = newValue as String
@@ -152,9 +152,9 @@
                 }
                 KEY_TIMER_VIBRATE -> {
                     val timerVibratePref: TwoStatePreference = pref as TwoStatePreference
-                    DataModel.getDataModel().timerVibrate = timerVibratePref.isChecked()
+                    DataModel.dataModel.timerVibrate = timerVibratePref.isChecked()
                 }
-                KEY_TIMER_RINGTONE -> pref.setSummary(DataModel.getDataModel().timerRingtoneTitle)
+                KEY_TIMER_RINGTONE -> pref.setSummary(DataModel.dataModel.timerRingtoneTitle)
             }
 
             // Set result so DeskClock knows to refresh itself
@@ -209,7 +209,7 @@
          * Reconstruct the timezone list.
          */
         private fun loadTimeZoneList() {
-            val timezones = DataModel.getDataModel().timeZones
+            val timezones = DataModel.dataModel.timeZones
             val homeTimezonePref: ListPreference? = findPreference(KEY_HOME_TZ)
             homeTimezonePref?.let {
                 it.setEntryValues(timezones.timeZoneIds)
@@ -260,7 +260,7 @@
 
             val weekStartPref: SimpleMenuPreference? = findPreference(KEY_WEEK_START)
             // Set the default value programmatically
-            val weekdayOrder = DataModel.getDataModel().weekdayOrder
+            val weekdayOrder = DataModel.dataModel.weekdayOrder
             val firstDay = weekdayOrder.calendarDays[0]
             val value = firstDay.toString()
             weekStartPref?.let {
@@ -273,7 +273,7 @@
             val timerRingtonePref: Preference? = findPreference(KEY_TIMER_RINGTONE)
             timerRingtonePref?.let {
                 it.setOnPreferenceClickListener(this)
-                it.setSummary(DataModel.getDataModel().timerRingtoneTitle)
+                it.setSummary(DataModel.dataModel.timerRingtoneTitle)
             }
         }
 
diff --git a/src/com/android/deskclock/stopwatch/LapsAdapter.kt b/src/com/android/deskclock/stopwatch/LapsAdapter.kt
index 4357748..798f7a6 100644
--- a/src/com/android/deskclock/stopwatch/LapsAdapter.kt
+++ b/src/com/android/deskclock/stopwatch/LapsAdapter.kt
@@ -87,7 +87,7 @@
         } else {
             // For the current lap, compute times relative to the stopwatch.
             totalTime = stopwatch.totalTime
-            lapTime = DataModel.getDataModel().getCurrentLapTime(totalTime)
+            lapTime = DataModel.dataModel.getCurrentLapTime(totalTime)
             lapNumber = laps.size + 1
         }
 
@@ -119,7 +119,7 @@
         val currentLapView: View? = rv.getChildAt(0)
         if (currentLapView != null) {
             // Compute the lap time using the total time.
-            val lapTime = DataModel.getDataModel().getCurrentLapTime(totalTime)
+            val lapTime = DataModel.dataModel.getCurrentLapTime(totalTime)
             val holder = rv.getChildViewHolder(currentLapView) as LapItemHolder
             holder.lapTime.setText(formatLapTime(lapTime, false))
             holder.accumulatedTime.setText(formatAccumulatedTime(totalTime, false))
@@ -132,7 +132,7 @@
      * @return a newly cleared lap
      */
     fun addLap(): Lap? {
-        val lap = DataModel.getDataModel().addLap()
+        val lap = DataModel.dataModel.addLap()
 
         if (itemCount == 10) {
             // 10 total laps indicates all items switch from 1 to 2 digit lap numbers.
@@ -195,7 +195,7 @@
                 // Append the final lap
                 builder.append(laps.size + 1)
                 builder.append(separator)
-                val lapTime = DataModel.getDataModel().getCurrentLapTime(totalTime)
+                val lapTime = DataModel.dataModel.getCurrentLapTime(totalTime)
                 builder.append(formatTime(lapTime, lapTime, " "))
                 builder.append("\n")
             }
@@ -224,7 +224,7 @@
      */
     private fun formatLapTime(lapTime: Long, isBinding: Boolean): String {
         // The longest lap dictates the way the given lapTime must be formatted.
-        val longestLapTime = max(DataModel.getDataModel().longestLapTime, lapTime)
+        val longestLapTime = max(DataModel.dataModel.longestLapTime, lapTime)
         val formattedTime = formatTime(longestLapTime, lapTime, LRM_SPACE)
 
         // If the newly formatted lap time has altered the format, refresh all laps.
@@ -259,10 +259,10 @@
     }
 
     private val stopwatch: Stopwatch
-        get() = DataModel.getDataModel().stopwatch
+        get() = DataModel.dataModel.stopwatch
 
     private val laps: List<Lap>
-        get() = DataModel.getDataModel().laps
+        get() = DataModel.dataModel.laps
 
     /**
      * Cache the child views of each lap item view.
diff --git a/src/com/android/deskclock/stopwatch/StopwatchCircleView.kt b/src/com/android/deskclock/stopwatch/StopwatchCircleView.kt
index 305b7b1..a7d8699 100644
--- a/src/com/android/deskclock/stopwatch/StopwatchCircleView.kt
+++ b/src/com/android/deskclock/stopwatch/StopwatchCircleView.kt
@@ -109,7 +109,7 @@
         val laps = laps
 
         // If a reference lap does not exist or should not be drawn, draw a simple white circle.
-        if (laps.isEmpty() || !DataModel.getDataModel().canAddMoreLaps()) {
+        if (laps.isEmpty() || !DataModel.dataModel.canAddMoreLaps()) {
             // Draw a complete white circle; no red arc required.
             canvas.drawCircle(xCenter.toFloat(), yCenter.toFloat(), radius, mPaint)
 
@@ -164,8 +164,8 @@
     }
 
     private val stopwatch: Stopwatch
-        get() = DataModel.getDataModel().stopwatch
+        get() = DataModel.dataModel.stopwatch
 
     private val laps: List<Lap>
-        get() = DataModel.getDataModel().laps
+        get() = DataModel.dataModel.laps
 }
\ No newline at end of file
diff --git a/src/com/android/deskclock/stopwatch/StopwatchFragment.kt b/src/com/android/deskclock/stopwatch/StopwatchFragment.kt
index 660ca44..d048817 100644
--- a/src/com/android/deskclock/stopwatch/StopwatchFragment.kt
+++ b/src/com/android/deskclock/stopwatch/StopwatchFragment.kt
@@ -144,7 +144,7 @@
         mStopwatchTextController = StopwatchTextController(mMainTimeText, mHundredthsTimeText)
         mStopwatchWrapper = v.findViewById(R.id.stopwatch_time_wrapper)
 
-        DataModel.getDataModel().addStopwatchListener(mStopwatchWatcher)
+        DataModel.dataModel.addStopwatchListener(mStopwatchWatcher)
 
         mStopwatchWrapper.setOnClickListener(TimeClickListener())
         if (mTime != null) {
@@ -173,11 +173,11 @@
         if (intent != null) {
             val action: String? = intent.getAction()
             if (StopwatchService.Companion.ACTION_START_STOPWATCH == action) {
-                DataModel.getDataModel().startStopwatch()
+                DataModel.dataModel.startStopwatch()
                 // Consume the intent
                 activity.setIntent(null)
             } else if (StopwatchService.Companion.ACTION_PAUSE_STOPWATCH == action) {
-                DataModel.getDataModel().pauseStopwatch()
+                DataModel.dataModel.pauseStopwatch()
                 // Consume the intent
                 activity.setIntent(null)
             }
@@ -209,7 +209,7 @@
     override fun onDestroyView() {
         super.onDestroyView()
 
-        DataModel.getDataModel().removeStopwatchListener(mStopwatchWatcher)
+        DataModel.dataModel.removeStopwatchListener(mStopwatchWatcher)
     }
 
     override fun onFabClick(fab: ImageView) {
@@ -306,7 +306,7 @@
      */
     private fun doStart() {
         Events.sendStopwatchEvent(R.string.action_start, R.string.label_deskclock)
-        DataModel.getDataModel().startStopwatch()
+        DataModel.dataModel.startStopwatch()
     }
 
     /**
@@ -314,7 +314,7 @@
      */
     private fun doPause() {
         Events.sendStopwatchEvent(R.string.action_pause, R.string.label_deskclock)
-        DataModel.getDataModel().pauseStopwatch()
+        DataModel.dataModel.pauseStopwatch()
     }
 
     /**
@@ -323,7 +323,7 @@
     private fun doReset() {
         val priorState = stopwatch.state
         Events.sendStopwatchEvent(R.string.action_reset, R.string.label_deskclock)
-        DataModel.getDataModel().resetStopwatch()
+        DataModel.dataModel.resetStopwatch()
         mMainTimeText.setAlpha(1f)
         mHundredthsTimeText.setAlpha(1f)
         if (priorState == Stopwatch.State.RUNNING) {
@@ -421,7 +421,7 @@
     }
 
     private fun adjustWakeLock() {
-        val appInForeground = DataModel.getDataModel().isApplicationInForeground
+        val appInForeground = DataModel.dataModel.isApplicationInForeground
         if (stopwatch.isRunning && isTabSelected && appInForeground) {
             getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
         } else {
@@ -445,9 +445,9 @@
     }
 
     private val stopwatch: Stopwatch
-        get() = DataModel.getDataModel().stopwatch
+        get() = DataModel.dataModel.stopwatch
 
-    private fun canRecordMoreLaps(): Boolean = DataModel.getDataModel().canAddMoreLaps()
+    private fun canRecordMoreLaps(): Boolean = DataModel.dataModel.canAddMoreLaps()
 
     /**
      * Post the first runnable to update times within the UI. It will reschedule itself as needed.
@@ -561,12 +561,12 @@
             if (after.isReset) {
                 // Ensure the drop shadow is hidden when the stopwatch is reset.
                 setTabScrolledToTop(true)
-                if (DataModel.getDataModel().isApplicationInForeground) {
+                if (DataModel.dataModel.isApplicationInForeground) {
                     updateUI(FabContainer.BUTTONS_IMMEDIATE)
                 }
                 return
             }
-            if (DataModel.getDataModel().isApplicationInForeground) {
+            if (DataModel.dataModel.isApplicationInForeground) {
                 updateUI(FabContainer.FAB_MORPH or FabContainer.BUTTONS_IMMEDIATE)
             }
         }
@@ -582,9 +582,9 @@
 
         override fun onClick(view: View?) {
             if (stopwatch.isRunning) {
-                DataModel.getDataModel().pauseStopwatch()
+                DataModel.dataModel.pauseStopwatch()
             } else {
-                DataModel.getDataModel().startStopwatch()
+                DataModel.dataModel.startStopwatch()
             }
         }
     }
diff --git a/src/com/android/deskclock/stopwatch/StopwatchService.kt b/src/com/android/deskclock/stopwatch/StopwatchService.kt
index 0ce936d..aba659a 100644
--- a/src/com/android/deskclock/stopwatch/StopwatchService.kt
+++ b/src/com/android/deskclock/stopwatch/StopwatchService.kt
@@ -52,19 +52,19 @@
             }
             ACTION_START_STOPWATCH -> {
                 Events.sendStopwatchEvent(R.string.action_start, label)
-                DataModel.getDataModel().startStopwatch()
+                DataModel.dataModel.startStopwatch()
             }
             ACTION_PAUSE_STOPWATCH -> {
                 Events.sendStopwatchEvent(R.string.action_pause, label)
-                DataModel.getDataModel().pauseStopwatch()
+                DataModel.dataModel.pauseStopwatch()
             }
             ACTION_RESET_STOPWATCH -> {
                 Events.sendStopwatchEvent(R.string.action_reset, label)
-                DataModel.getDataModel().resetStopwatch()
+                DataModel.dataModel.resetStopwatch()
             }
             ACTION_LAP_STOPWATCH -> {
                 Events.sendStopwatchEvent(R.string.action_lap, label)
-                DataModel.getDataModel().addLap()
+                DataModel.dataModel.addLap()
             }
         }
 
diff --git a/src/com/android/deskclock/worldclock/CitySelectionActivity.java b/src/com/android/deskclock/worldclock/CitySelectionActivity.java
index d8954d0..13e85c4 100644
--- a/src/com/android/deskclock/worldclock/CitySelectionActivity.java
+++ b/src/com/android/deskclock/worldclock/CitySelectionActivity.java
@@ -497,7 +497,7 @@
             mIs24HoursMode = DateFormat.is24HourFormat(mContext);
 
             // Refresh the user selections.
-            final List<City> selected = DataModel.getDataModel().getSelectedCities();
+            final List<City> selected = (List<City>) DataModel.getDataModel().getSelectedCities();
             mUserSelectedCities.clear();
             mUserSelectedCities.addAll(selected);
             mOriginalUserSelectionCount = selected.size();