134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux/*
234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * Copyright (C) 2015 The Android Open Source Project
334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux *
434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * Licensed under the Apache License, Version 2.0 (the "License");
534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * you may not use this file except in compliance with the License.
634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * You may obtain a copy of the License at
734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux *
834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux *      http://www.apache.org/licenses/LICENSE-2.0
934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux *
1034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * Unless required by applicable law or agreed to in writing, software
1134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * distributed under the License is distributed on an "AS IS" BASIS,
1234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * See the License for the specific language governing permissions and
1434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * limitations under the License.
1534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux */
1634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
1734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuxpackage com.android.deskclock.data;
1834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
190dd0cac610cd59762c8b604da6c437b18a29246bJames Lemieuximport android.app.Service;
2034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuximport android.content.Context;
2125384711e3c2894a84642c49953594930dd078abJames Lemieuximport android.content.Intent;
2223629266834a251cb937a885e5223e5ae37cc6faChristine Franksimport android.content.SharedPreferences;
2325384711e3c2894a84642c49953594930dd078abJames Lemieuximport android.media.AudioManager;
24856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieuximport android.net.Uri;
25d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phanimport android.os.Handler;
26d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phanimport android.os.Looper;
276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.support.annotation.StringRes;
2825384711e3c2894a84642c49953594930dd078abJames Lemieuximport android.view.View;
2934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
302a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieuximport com.android.deskclock.Predicate;
3125384711e3c2894a84642c49953594930dd078abJames Lemieuximport com.android.deskclock.R;
3225384711e3c2894a84642c49953594930dd078abJames Lemieuximport com.android.deskclock.Utils;
330a3313e231702cc9944e9a17e52aea62eb25afabSean Stoutimport com.android.deskclock.timer.TimerService;
340a3313e231702cc9944e9a17e52aea62eb25afabSean Stout
35458aa8b4ebb8b7c6fdc0680a1b687ea21a61bf35James Lemieuximport java.util.Calendar;
3634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuximport java.util.Collection;
3734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuximport java.util.Comparator;
3834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuximport java.util.List;
3934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
4025384711e3c2894a84642c49953594930dd078abJames Lemieuximport static android.content.Context.AUDIO_SERVICE;
4125384711e3c2894a84642c49953594930dd078abJames Lemieuximport static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
4225384711e3c2894a84642c49953594930dd078abJames Lemieuximport static android.media.AudioManager.FLAG_SHOW_UI;
4325384711e3c2894a84642c49953594930dd078abJames Lemieuximport static android.media.AudioManager.STREAM_ALARM;
4425384711e3c2894a84642c49953594930dd078abJames Lemieuximport static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
4525384711e3c2894a84642c49953594930dd078abJames Lemieuximport static android.provider.Settings.ACTION_SOUND_SETTINGS;
4634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuximport static com.android.deskclock.Utils.enforceMainLooper;
47c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieuximport static com.android.deskclock.Utils.enforceNotMainLooper;
4834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
4934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux/**
5034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux * All application-wide data is accessible through this singleton.
5134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux */
5234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieuxpublic final class DataModel {
5334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
5434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /** Indicates the display style of clocks. */
5534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public enum ClockStyle {ANALOG, DIGITAL}
5634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
5734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /** Indicates the preferred sort order of cities. */
5834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public enum CitySort {NAME, UTC_OFFSET}
5934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
600777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /** Indicates the preferred behavior of hardware volume buttons when firing alarms. */
610777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public enum AlarmVolumeButtonBehavior {NOTHING, SNOOZE, DISMISS}
620777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
6325384711e3c2894a84642c49953594930dd078abJames Lemieux    /** Indicates the reason alarms may not fire or may fire silently. */
6425384711e3c2894a84642c49953594930dd078abJames Lemieux    public enum SilentSetting {
6563669870c5677e73c61ae81dd7469abccb51d629James Lemieux        @SuppressWarnings("unchecked")
662a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        DO_NOT_DISTURB(R.string.alarms_blocked_by_dnd, 0, Predicate.FALSE, null),
6763669870c5677e73c61ae81dd7469abccb51d629James Lemieux        @SuppressWarnings("unchecked")
6825384711e3c2894a84642c49953594930dd078abJames Lemieux        MUTED_VOLUME(R.string.alarm_volume_muted,
6925384711e3c2894a84642c49953594930dd078abJames Lemieux                R.string.unmute_alarm_volume,
702a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux                Predicate.TRUE,
7125384711e3c2894a84642c49953594930dd078abJames Lemieux                new UnmuteAlarmVolumeListener()),
7225384711e3c2894a84642c49953594930dd078abJames Lemieux        SILENT_RINGTONE(R.string.silent_default_alarm_ringtone,
7325384711e3c2894a84642c49953594930dd078abJames Lemieux                R.string.change_setting_action,
742a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux                new ChangeSoundActionPredicate(),
7525384711e3c2894a84642c49953594930dd078abJames Lemieux                new ChangeSoundSettingsListener()),
7663669870c5677e73c61ae81dd7469abccb51d629James Lemieux        @SuppressWarnings("unchecked")
7725384711e3c2894a84642c49953594930dd078abJames Lemieux        BLOCKED_NOTIFICATIONS(R.string.app_notifications_blocked,
7825384711e3c2894a84642c49953594930dd078abJames Lemieux                R.string.change_setting_action,
792a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux                Predicate.TRUE,
8025384711e3c2894a84642c49953594930dd078abJames Lemieux                new ChangeAppNotificationSettingsListener());
8125384711e3c2894a84642c49953594930dd078abJames Lemieux
8225384711e3c2894a84642c49953594930dd078abJames Lemieux        private final @StringRes int mLabelResId;
8325384711e3c2894a84642c49953594930dd078abJames Lemieux        private final @StringRes int mActionResId;
842a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        private final Predicate<Context> mActionEnabled;
8525384711e3c2894a84642c49953594930dd078abJames Lemieux        private final View.OnClickListener mActionListener;
8625384711e3c2894a84642c49953594930dd078abJames Lemieux
872a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        SilentSetting(int labelResId, int actionResId, Predicate<Context> actionEnabled,
882a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux                View.OnClickListener actionListener) {
8925384711e3c2894a84642c49953594930dd078abJames Lemieux            mLabelResId = labelResId;
9025384711e3c2894a84642c49953594930dd078abJames Lemieux            mActionResId = actionResId;
912a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux            mActionEnabled = actionEnabled;
9225384711e3c2894a84642c49953594930dd078abJames Lemieux            mActionListener = actionListener;
9325384711e3c2894a84642c49953594930dd078abJames Lemieux        }
9425384711e3c2894a84642c49953594930dd078abJames Lemieux
9525384711e3c2894a84642c49953594930dd078abJames Lemieux        public @StringRes int getLabelResId() { return mLabelResId; }
9625384711e3c2894a84642c49953594930dd078abJames Lemieux        public @StringRes int getActionResId() { return mActionResId; }
9725384711e3c2894a84642c49953594930dd078abJames Lemieux        public View.OnClickListener getActionListener() { return mActionListener; }
982a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        public boolean isActionEnabled(Context context) {
992a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux            return mLabelResId != 0 && mActionEnabled.apply(context);
1002a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        }
10125384711e3c2894a84642c49953594930dd078abJames Lemieux
10225384711e3c2894a84642c49953594930dd078abJames Lemieux        private static class UnmuteAlarmVolumeListener implements View.OnClickListener {
10325384711e3c2894a84642c49953594930dd078abJames Lemieux            @Override
10425384711e3c2894a84642c49953594930dd078abJames Lemieux            public void onClick(View v) {
105df466cea0899b6dd2efd3ebd961c614486b639d0James Lemieux                // Set the alarm volume to 11/16th of max and show the slider UI.
106df466cea0899b6dd2efd3ebd961c614486b639d0James Lemieux                // 11/16th of max is the initial volume of the alarm stream on a fresh install.
10725384711e3c2894a84642c49953594930dd078abJames Lemieux                final Context context = v.getContext();
10825384711e3c2894a84642c49953594930dd078abJames Lemieux                final AudioManager am = (AudioManager) context.getSystemService(AUDIO_SERVICE);
109df466cea0899b6dd2efd3ebd961c614486b639d0James Lemieux                final int index = Math.round(am.getStreamMaxVolume(STREAM_ALARM) * 11f / 16f);
11025384711e3c2894a84642c49953594930dd078abJames Lemieux                am.setStreamVolume(STREAM_ALARM, index, FLAG_SHOW_UI);
11125384711e3c2894a84642c49953594930dd078abJames Lemieux            }
112ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux        }
11325384711e3c2894a84642c49953594930dd078abJames Lemieux
11425384711e3c2894a84642c49953594930dd078abJames Lemieux        private static class ChangeSoundSettingsListener implements View.OnClickListener {
11525384711e3c2894a84642c49953594930dd078abJames Lemieux            @Override
11625384711e3c2894a84642c49953594930dd078abJames Lemieux            public void onClick(View v) {
11725384711e3c2894a84642c49953594930dd078abJames Lemieux                final Context context = v.getContext();
11825384711e3c2894a84642c49953594930dd078abJames Lemieux                context.startActivity(new Intent(ACTION_SOUND_SETTINGS)
11925384711e3c2894a84642c49953594930dd078abJames Lemieux                        .addFlags(FLAG_ACTIVITY_NEW_TASK));
12025384711e3c2894a84642c49953594930dd078abJames Lemieux            }
121ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux        }
12225384711e3c2894a84642c49953594930dd078abJames Lemieux
1232a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        private static class ChangeSoundActionPredicate implements Predicate<Context> {
1242a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux            @Override
1252a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux            public boolean apply(Context context) {
1262a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux                final Intent intent = new Intent(ACTION_SOUND_SETTINGS);
1272a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux                return intent.resolveActivity(context.getPackageManager()) != null;
1282a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux            }
1292a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux        }
1302a138d4a772ad8cf1d3e9cd9682d69f4f8d49db6James Lemieux
13125384711e3c2894a84642c49953594930dd078abJames Lemieux        private static class ChangeAppNotificationSettingsListener implements View.OnClickListener {
13225384711e3c2894a84642c49953594930dd078abJames Lemieux            @Override
13325384711e3c2894a84642c49953594930dd078abJames Lemieux            public void onClick(View v) {
13425384711e3c2894a84642c49953594930dd078abJames Lemieux                final Context context = v.getContext();
13525384711e3c2894a84642c49953594930dd078abJames Lemieux                if (Utils.isLOrLater()) {
13625384711e3c2894a84642c49953594930dd078abJames Lemieux                    try {
13725384711e3c2894a84642c49953594930dd078abJames Lemieux                        // Attempt to open the notification settings for this app.
13825384711e3c2894a84642c49953594930dd078abJames Lemieux                        context.startActivity(
13925384711e3c2894a84642c49953594930dd078abJames Lemieux                                new Intent("android.settings.APP_NOTIFICATION_SETTINGS")
14025384711e3c2894a84642c49953594930dd078abJames Lemieux                                .putExtra("app_package", context.getPackageName())
14125384711e3c2894a84642c49953594930dd078abJames Lemieux                                .putExtra("app_uid", context.getApplicationInfo().uid)
14225384711e3c2894a84642c49953594930dd078abJames Lemieux                                .addFlags(FLAG_ACTIVITY_NEW_TASK));
14325384711e3c2894a84642c49953594930dd078abJames Lemieux                        return;
14425384711e3c2894a84642c49953594930dd078abJames Lemieux                    } catch (Exception ignored) {
14525384711e3c2894a84642c49953594930dd078abJames Lemieux                        // best attempt only; recovery code below
14625384711e3c2894a84642c49953594930dd078abJames Lemieux                    }
14725384711e3c2894a84642c49953594930dd078abJames Lemieux                }
14825384711e3c2894a84642c49953594930dd078abJames Lemieux
14925384711e3c2894a84642c49953594930dd078abJames Lemieux                // Fall back to opening the app settings page.
15025384711e3c2894a84642c49953594930dd078abJames Lemieux                context.startActivity(new Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
15125384711e3c2894a84642c49953594930dd078abJames Lemieux                        .setData(Uri.fromParts("package", context.getPackageName(), null))
15225384711e3c2894a84642c49953594930dd078abJames Lemieux                        .addFlags(FLAG_ACTIVITY_NEW_TASK));
15325384711e3c2894a84642c49953594930dd078abJames Lemieux            }
154ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux        }
15525384711e3c2894a84642c49953594930dd078abJames Lemieux    }
15625384711e3c2894a84642c49953594930dd078abJames Lemieux
1578bf1c3ba34724773b92242effec8330e1852bb7bJames Lemieux    public static final String ACTION_WORLD_CITIES_CHANGED =
1588bf1c3ba34724773b92242effec8330e1852bb7bJames Lemieux            "com.android.deskclock.WORLD_CITIES_CHANGED";
1598bf1c3ba34724773b92242effec8330e1852bb7bJames Lemieux
16034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /** The single instance of this data model that exists for the life of the application. */
16134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    private static final DataModel sDataModel = new DataModel();
16234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
163d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    private Handler mHandler;
164d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
16534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    private Context mContext;
16634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
16734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /** The model from which settings are fetched. */
16834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    private SettingsModel mSettingsModel;
16934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
170856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    /** The model from which city data are fetched. */
17134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    private CityModel mCityModel;
17234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
173856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    /** The model from which timer data are fetched. */
174856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    private TimerModel mTimerModel;
175856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux
1766a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    /** The model from which alarm data are fetched. */
1776a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    private AlarmModel mAlarmModel;
1786a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux
179592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    /** The model from which widget data are fetched. */
180592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    private WidgetModel mWidgetModel;
181592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux
18225384711e3c2894a84642c49953594930dd078abJames Lemieux    /** The model from which data about settings that silence alarms are fetched. */
18325384711e3c2894a84642c49953594930dd078abJames Lemieux    private SilentSettingsModel mSilentSettingsModel;
18425384711e3c2894a84642c49953594930dd078abJames Lemieux
18524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /** The model from which stopwatch data are fetched. */
18624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    private StopwatchModel mStopwatchModel;
18724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
18824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /** The model from which notification data are fetched. */
18924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    private NotificationModel mNotificationModel;
19024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
191be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    /** The model from which time data are fetched. */
192be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    private TimeModel mTimeModel;
193be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout
19433781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    /** The model from which ringtone data are fetched. */
19533781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    private RingtoneModel mRingtoneModel;
19633781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux
19734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public static DataModel getDataModel() {
19834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return sDataModel;
19934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
20034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
20134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    private DataModel() {}
20234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
20334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
204ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux     * Initializes the data model with the context and shared preferences to be used.
20534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
206ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux    public void init(Context context, SharedPreferences prefs) {
207310f96ed002219067d2e652e27d64d256e315832James Lemieux        if (mContext != context) {
208310f96ed002219067d2e652e27d64d256e315832James Lemieux            mContext = context.getApplicationContext();
209310f96ed002219067d2e652e27d64d256e315832James Lemieux
2100b19985ecfa0f43acb8ededc173f643843098a95James Lemieux            mTimeModel = new TimeModel(mContext);
211ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux            mWidgetModel = new WidgetModel(prefs);
212310f96ed002219067d2e652e27d64d256e315832James Lemieux            mNotificationModel = new NotificationModel();
21384b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux            mRingtoneModel = new RingtoneModel(mContext, prefs);
21484b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux            mSettingsModel = new SettingsModel(mContext, prefs, mTimeModel);
215ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux            mCityModel = new CityModel(mContext, prefs, mSettingsModel);
216310f96ed002219067d2e652e27d64d256e315832James Lemieux            mAlarmModel = new AlarmModel(mContext, mSettingsModel);
21725384711e3c2894a84642c49953594930dd078abJames Lemieux            mSilentSettingsModel = new SilentSettingsModel(mContext, mNotificationModel);
218ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux            mStopwatchModel = new StopwatchModel(mContext, prefs, mNotificationModel);
219ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux            mTimerModel = new TimerModel(mContext, prefs, mSettingsModel, mRingtoneModel,
220ff62e7fa903e3b6b11d0443543725c1351ab289dJames Lemieux                    mNotificationModel);
22123629266834a251cb937a885e5223e5ae37cc6faChristine Franks        }
22223629266834a251cb937a885e5223e5ae37cc6faChristine Franks    }
22323629266834a251cb937a885e5223e5ae37cc6faChristine Franks
22423629266834a251cb937a885e5223e5ae37cc6faChristine Franks    /**
225a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen     * Convenience for {@code run(runnable, 0)}, i.e. waits indefinitely.
226a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen     */
227a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen    public void run(Runnable runnable) {
228a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen        try {
229a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen            run(runnable, 0 /* waitMillis */);
230a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen        } catch (InterruptedException ignored) {
231a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen        }
232a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen    }
233a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen
234a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen    /**
2352a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     * Updates all timers and the stopwatch after the device has shutdown and restarted.
2362a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     */
2372a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    public void updateAfterReboot() {
2382a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        enforceMainLooper();
2392a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mTimerModel.updateTimersAfterReboot();
2402a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mStopwatchModel.setStopwatch(getStopwatch().updateAfterReboot());
2412a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    }
2422a07ae3286fd5c76f71546890e0f02af99065825Sean Stout
2432a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    /**
2442a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     * Updates all timers and the stopwatch after the device's time has changed.
2452a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     */
2462a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    public void updateAfterTimeSet() {
2472a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        enforceMainLooper();
2482a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mTimerModel.updateTimersAfterTimeSet();
2492a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mStopwatchModel.setStopwatch(getStopwatch().updateAfterTimeSet());
2502a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    }
2512a07ae3286fd5c76f71546890e0f02af99065825Sean Stout
2522a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    /**
253d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     * Posts a runnable to the main thread and blocks until the runnable executes. Used to access
254d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     * the data model from the main thread.
255d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     */
256a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen    public void run(Runnable runnable, long waitMillis) throws InterruptedException {
257d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        if (Looper.myLooper() == Looper.getMainLooper()) {
258d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            runnable.run();
259d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            return;
260d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        }
261d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
262d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        final ExecutedRunnable er = new ExecutedRunnable(runnable);
263d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        getHandler().post(er);
264d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
265d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        // Wait for the data to arrive, if it has not.
266d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        synchronized (er) {
267d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            if (!er.isExecuted()) {
268a5c107781dc8f8239bc980d19e1e31ed262e8829Justin Klaassen                er.wait(waitMillis);
269d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            }
270d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        }
271d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    }
272d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
273d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    /**
274d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     * @return a handler associated with the main thread
275d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     */
276d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    private synchronized Handler getHandler() {
277d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        if (mHandler == null) {
278d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            mHandler = new Handler(Looper.getMainLooper());
279d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        }
280d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        return mHandler;
281d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    }
282d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
28324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    //
28424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    // Application
28524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    //
28624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
28724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
28824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @param inForeground {@code true} to indicate the application is open in the foreground
28924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
29024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public void setApplicationInForeground(boolean inForeground) {
29124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
29224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
29324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        if (mNotificationModel.isApplicationInForeground() != inForeground) {
29424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux            mNotificationModel.setApplicationInForeground(inForeground);
29524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
29624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux            // Refresh all notifications in response to a change in app open state.
2976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mTimerModel.updateNotification();
2982a07ae3286fd5c76f71546890e0f02af99065825Sean Stout            mTimerModel.updateMissedNotification();
29924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux            mStopwatchModel.updateNotification();
30025384711e3c2894a84642c49953594930dd078abJames Lemieux            mSilentSettingsModel.updateSilentState();
30124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        }
30224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
30324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
30424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
30524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return {@code true} when the application is open in the foreground; {@code false} otherwise
30624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
30724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public boolean isApplicationInForeground() {
3083101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan        enforceMainLooper();
30924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mNotificationModel.isApplicationInForeground();
31034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
31134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
312ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux    /**
313ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux     * Called when the notifications may be stale or absent from the notification manager and must
314ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux     * be rebuilt. e.g. after upgrading the application
315ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux     */
316ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux    public void updateAllNotifications() {
317376b0f51995803a93d7a6907f8ed30aff06e2aaeJames Lemieux        enforceMainLooper();
318ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux        mTimerModel.updateNotification();
3192a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mTimerModel.updateMissedNotification();
320ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux        mStopwatchModel.updateNotification();
321ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux    }
322ac1506f175631e2b2f52ff9142cbdc6c725ef447James Lemieux
32334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    //
32434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    // Cities
32534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    //
32634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
32734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
32834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return a list of all cities in their display order
32934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
33034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public List<City> getAllCities() {
33134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
33234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mCityModel.getAllCities();
33334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
33434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
33534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
33634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return a city representing the user's home timezone
33734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
33834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public City getHomeCity() {
33934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
34034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mCityModel.getHomeCity();
34134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
34234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
34334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
34434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return a list of cities not selected for display
34534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
34634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public List<City> getUnselectedCities() {
34734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
34834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mCityModel.getUnselectedCities();
34934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
35034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
35134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
35234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return a list of cities selected for display
35334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
35434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public List<City> getSelectedCities() {
35534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
35634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mCityModel.getSelectedCities();
35734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
35834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
35934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
36034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @param cities the new collection of cities selected for display by the user
36134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
36234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public void setSelectedCities(Collection<City> cities) {
36334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
36434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        mCityModel.setSelectedCities(cities);
36534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
36634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
36734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
36834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return a comparator used to locate index positions
36934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
37034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public Comparator<City> getCityIndexComparator() {
37134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
37234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mCityModel.getCityIndexComparator();
37334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
37434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
37534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
37634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return the order in which cities are sorted
37734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
37834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public CitySort getCitySort() {
37934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
38034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mCityModel.getCitySort();
38134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
38234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
38334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
38434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * Adjust the order in which cities are sorted.
38534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
38634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public void toggleCitySort() {
38734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
38834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        mCityModel.toggleCitySort();
38934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
39034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
391a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout    /**
392a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout     * @param cityListener listener to be notified when the world city list changes
393a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout     */
394a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout    public void addCityListener(CityListener cityListener) {
395a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout        enforceMainLooper();
396a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout        mCityModel.addCityListener(cityListener);
397a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout    }
398a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout
399a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout    /**
400a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout     * @param cityListener listener that no longer needs to be notified of world city list changes
401a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout     */
402a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout    public void removeCityListener(CityListener cityListener) {
403a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout        enforceMainLooper();
404a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout        mCityModel.removeCityListener(cityListener);
405a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout    }
406a10158df7b7e056ec5c63df9cfaacd88d0e79c7fSean Stout
40734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    //
408856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    // Timers
409856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    //
410856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux
411856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    /**
4126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timerListener to be notified when timers are added, updated and removed
4136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void addTimerListener(TimerListener timerListener) {
4156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.addTimerListener(timerListener);
4176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timerListener to no longer be notified when timers are added, updated and removed
4216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void removeTimerListener(TimerListener timerListener) {
4236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.removeTimerListener(timerListener);
4256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @return a list of timers for display
4296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public List<Timer> getTimers() {
4316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        return mTimerModel.getTimers();
4336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @return a list of expired timers for display
4376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public List<Timer> getExpiredTimers() {
4396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        return mTimerModel.getExpiredTimers();
4416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timerId identifies the timer to return
4456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @return the timer with the given {@code timerId}
4466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public Timer getTimer(int timerId) {
4486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        return mTimerModel.getTimer(timerId);
4506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @return the timer that last expired and is still expired now; {@code null} if no timers are
4546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     *      expired
4556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public Timer getMostRecentExpiredTimer() {
4576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        return mTimerModel.getMostRecentExpiredTimer();
4596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param length the length of the timer in milliseconds
4636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param label describes the purpose of the timer
464437da3b08ce9ce1b32f4e544816cb3431ceb8d4eJames Lemieux     * @param deleteAfterUse {@code true} indicates the timer should be deleted when it is reset
4656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @return the newly added timer
4666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
467437da3b08ce9ce1b32f4e544816cb3431ceb8d4eJames Lemieux    public Timer addTimer(long length, String label, boolean deleteAfterUse) {
4686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
469437da3b08ce9ce1b32f4e544816cb3431ceb8d4eJames Lemieux        return mTimerModel.addTimer(length, label, deleteAfterUse);
4706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4736d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to be removed
4746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void removeTimer(Timer timer) {
4766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.removeTimer(timer);
4786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
4796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
4806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
4816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to be started
4826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
4836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void startTimer(Timer timer) {
4840a3313e231702cc9944e9a17e52aea62eb25afabSean Stout        startTimer(null, timer);
4850a3313e231702cc9944e9a17e52aea62eb25afabSean Stout    }
4860a3313e231702cc9944e9a17e52aea62eb25afabSean Stout
4870a3313e231702cc9944e9a17e52aea62eb25afabSean Stout    /**
4880a3313e231702cc9944e9a17e52aea62eb25afabSean Stout     * @param service used to start foreground notifications for expired timers
4890a3313e231702cc9944e9a17e52aea62eb25afabSean Stout     * @param timer the timer to be started
4900a3313e231702cc9944e9a17e52aea62eb25afabSean Stout     */
4910a3313e231702cc9944e9a17e52aea62eb25afabSean Stout    public void startTimer(Service service, Timer timer) {
4926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
4930a3313e231702cc9944e9a17e52aea62eb25afabSean Stout        final Timer started = timer.start();
4940a3313e231702cc9944e9a17e52aea62eb25afabSean Stout        mTimerModel.updateTimer(started);
4950a3313e231702cc9944e9a17e52aea62eb25afabSean Stout        if (timer.getRemainingTime() <= 0) {
4960a3313e231702cc9944e9a17e52aea62eb25afabSean Stout            if (service != null) {
4970a3313e231702cc9944e9a17e52aea62eb25afabSean Stout                expireTimer(service, started);
4980a3313e231702cc9944e9a17e52aea62eb25afabSean Stout            } else {
4990a3313e231702cc9944e9a17e52aea62eb25afabSean Stout                mContext.startService(TimerService.createTimerExpiredIntent(mContext, started));
5000a3313e231702cc9944e9a17e52aea62eb25afabSean Stout            }
5010a3313e231702cc9944e9a17e52aea62eb25afabSean Stout        }
5026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5036d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to be paused
5066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void pauseTimer(Timer timer) {
5086d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5096d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.updateTimer(timer.pause());
5106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5130dd0cac610cd59762c8b604da6c437b18a29246bJames Lemieux     * @param service used to start foreground notifications for expired timers
5146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to be expired
5156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5160dd0cac610cd59762c8b604da6c437b18a29246bJames Lemieux    public void expireTimer(Service service, Timer timer) {
5176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5180dd0cac610cd59762c8b604da6c437b18a29246bJames Lemieux        mTimerModel.expireTimer(service, timer);
5196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
52212540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen     * @param timer the timer to be reset
52312540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen     * @return the reset {@code timer}
52412540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen     */
52512540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen    public Timer resetTimer(Timer timer) {
52612540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen        enforceMainLooper();
52712540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen        return mTimerModel.resetTimer(timer, false /* allowDelete */, 0 /* eventLabelId */);
52812540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen    }
52912540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen
53012540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen    /**
5316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * If the given {@code timer} is expired and marked for deletion after use then this method
5326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * removes the the timer. The timer is otherwise transitioned to the reset state and continues
5336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * to exist.
5346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     *
5356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to be reset
5366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
537b10e1abe917de457e9cb3be12ffd5d2634d8f79cJames Lemieux     * @return the reset {@code timer} or {@code null} if the timer was deleted
5386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
539b10e1abe917de457e9cb3be12ffd5d2634d8f79cJames Lemieux    public Timer resetOrDeleteTimer(Timer timer, @StringRes int eventLabelId) {
5406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
54112540ba3d8802d36c3893c6b88300c6a391d7d22Justin Klaassen        return mTimerModel.resetTimer(timer, true /* allowDelete */, eventLabelId);
5426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5452a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     * Resets all expired timers.
5466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     *
5476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
5486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5492cfca77929657240f985a26ee59c389998cf3ab6Christine Franks    public void resetOrDeleteExpiredTimers(@StringRes int eventLabelId) {
5506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5512cfca77929657240f985a26ee59c389998cf3ab6Christine Franks        mTimerModel.resetOrDeleteExpiredTimers(eventLabelId);
5526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5552a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     * Resets all unexpired timers.
5566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     *
5576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
5586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5592a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    public void resetUnexpiredTimers(@StringRes int eventLabelId) {
5606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5612a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mTimerModel.resetUnexpiredTimers(eventLabelId);
5626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5652a07ae3286fd5c76f71546890e0f02af99065825Sean Stout     * Resets all missed timers.
5666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     *
5676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
5686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5692a07ae3286fd5c76f71546890e0f02af99065825Sean Stout    public void resetMissedTimers(@StringRes int eventLabelId) {
5706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5712a07ae3286fd5c76f71546890e0f02af99065825Sean Stout        mTimerModel.resetMissedTimers(eventLabelId);
5726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5736d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to which a minute should be added to the remaining time
5766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void addTimerMinute(Timer timer) {
5786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.updateTimer(timer.addMinute());
5806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
5836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param timer the timer to which the new {@code label} belongs
5846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * @param label the new label to store for the {@code timer}
5856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
5866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void setTimerLabel(Timer timer, String label) {
5876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
5886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.updateTimer(timer.setLabel(label));
5896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
5906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
5916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
59258d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux     * @param timer the timer whose {@code length} to change
593705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen     * @param length the new length of the timer in milliseconds
594705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen     */
595705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen    public void setTimerLength(Timer timer, long length) {
596705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        enforceMainLooper();
597705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        mTimerModel.updateTimer(timer.setLength(length));
598705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen    }
599705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen
600705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen    /**
60158d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux     * @param timer the timer whose {@code remainingTime} to change
602705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen     * @param remainingTime the new remaining time of the timer in milliseconds
603705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen     */
604705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen    public void setRemainingTime(Timer timer, long remainingTime) {
605705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        enforceMainLooper();
606705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen
607705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        final Timer updated = timer.setRemainingTime(remainingTime);
608705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        mTimerModel.updateTimer(updated);
609705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        if (timer.isRunning() && timer.getRemainingTime() <= 0) {
610705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen            mContext.startService(TimerService.createTimerExpiredIntent(mContext, updated));
611705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen        }
612705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen    }
613705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen
614705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen    /**
6156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Updates the timer notifications to be current.
6166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
6176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void updateTimerNotification() {
6186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        enforceMainLooper();
6196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mTimerModel.updateNotification();
6206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
6216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
6226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
623bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux     * @return the uri of the default ringtone to play for all timers when no user selection exists
624bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux     */
625bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux    public Uri getDefaultTimerRingtoneUri() {
626bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux        enforceMainLooper();
627bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux        return mTimerModel.getDefaultTimerRingtoneUri();
628bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux    }
629bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux
630bd9eae10b13e015d1997d06f13e9abe06a7f306bJames Lemieux    /**
631f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux     * @return {@code true} iff the ringtone to play for all timers is the silent ringtone
632f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux     */
633f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux    public boolean isTimerRingtoneSilent() {
634f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux        enforceMainLooper();
635f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux        return mTimerModel.isTimerRingtoneSilent();
636f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux    }
637f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux
638f8faca1961278db2797d122351885ce6e32e4f3dJames Lemieux    /**
639856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux     * @return the uri of the ringtone to play for all timers
640856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux     */
641856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    public Uri getTimerRingtoneUri() {
642856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux        enforceMainLooper();
643856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux        return mTimerModel.getTimerRingtoneUri();
644856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    }
645856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux
646856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    /**
6473101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan     * @param uri the uri of the ringtone to play for all timers
6483101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan     */
6493101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan    public void setTimerRingtoneUri(Uri uri) {
6503101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan        enforceMainLooper();
6513101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan        mTimerModel.setTimerRingtoneUri(uri);
6523101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan    }
6533101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan
6543101fcf76ff5c228d2e643395be03d6cbf97d47eDylan Phan    /**
655856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux     * @return the title of the ringtone that is played for all timers
656856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux     */
657856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    public String getTimerRingtoneTitle() {
658856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux        enforceMainLooper();
659856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux        return mTimerModel.getTimerRingtoneTitle();
660856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    }
661856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux
66209c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin    /**
6630777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return the duration, in milliseconds, of the crescendo to apply to timer ringtone playback;
6640777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     *      {@code 0} implies no crescendo should be applied
6650777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
6660777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public long getTimerCrescendoDuration() {
6670777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        enforceMainLooper();
6680777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mTimerModel.getTimerCrescendoDuration();
6690777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
6700777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
6710777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
67209c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin     * @return whether vibrate is enabled for all timers.
67309c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin     */
67409c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin    public boolean getTimerVibrate() {
67509c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin        enforceMainLooper();
67609c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin        return mTimerModel.getTimerVibrate();
67709c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin    }
67809c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin
67909c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin    /**
68009c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin     * @param enabled whether vibrate is enabled for all timers.
68109c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin     */
68209c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin    public void setTimerVibrate(boolean enabled) {
68309c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin        enforceMainLooper();
68409c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin        mTimerModel.setTimerVibrate(enabled);
68509c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin    }
68609c1f0e96743fcde110ecd1160ab55cdfaec12c0Annie Chin
687856483e7e18d5f042a338f7b3d472e28a386c4adJames Lemieux    //
6886a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    // Alarms
6896a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    //
6906a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux
6916a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    /**
6926a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux     * @return the uri of the ringtone to which all new alarms default
6936a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux     */
6946a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    public Uri getDefaultAlarmRingtoneUri() {
6956a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux        enforceMainLooper();
6966a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux        return mAlarmModel.getDefaultAlarmRingtoneUri();
6976a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    }
6986a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux
6996a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    /**
7006a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux     * @param uri the uri of the ringtone to which future new alarms will default
7016a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux     */
7026a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    public void setDefaultAlarmRingtoneUri(Uri uri) {
7036a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux        enforceMainLooper();
7046a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux        mAlarmModel.setDefaultAlarmRingtoneUri(uri);
7056a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    }
7066a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux
7070777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
7080777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return the duration, in milliseconds, of the crescendo to apply to alarm ringtone playback;
7090777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     *      {@code 0} implies no crescendo should be applied
7100777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
7110777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public long getAlarmCrescendoDuration() {
7120777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        enforceMainLooper();
7130777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mAlarmModel.getAlarmCrescendoDuration();
7140777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
7150777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
7160777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
7170777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return the behavior to execute when volume buttons are pressed while firing an alarm
7180777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
7190777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public AlarmVolumeButtonBehavior getAlarmVolumeButtonBehavior() {
7200777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        enforceMainLooper();
7210777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mAlarmModel.getAlarmVolumeButtonBehavior();
7220777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
7230777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
7240777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
7250777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return the number of minutes an alarm may ring before it has timed out and becomes missed
7260777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
7270777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public int getAlarmTimeout() {
7280777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mAlarmModel.getAlarmTimeout();
7290777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
7300777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
7310777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
7320777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return the number of minutes an alarm will remain snoozed before it rings again
7330777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
7340777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public int getSnoozeLength() {
7350777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mAlarmModel.getSnoozeLength();
7360777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
7370777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
7386a59a7b2c034557bc8bc7481544db5cd1105a891James Lemieux    //
73924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    // Stopwatch
74024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    //
74124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
74224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
7439042b0b548db623f7f465767008a27d305299aa9James Lemieux     * @param stopwatchListener to be notified when stopwatch changes or laps are added
7449042b0b548db623f7f465767008a27d305299aa9James Lemieux     */
7459042b0b548db623f7f465767008a27d305299aa9James Lemieux    public void addStopwatchListener(StopwatchListener stopwatchListener) {
7469042b0b548db623f7f465767008a27d305299aa9James Lemieux        enforceMainLooper();
7479042b0b548db623f7f465767008a27d305299aa9James Lemieux        mStopwatchModel.addStopwatchListener(stopwatchListener);
7489042b0b548db623f7f465767008a27d305299aa9James Lemieux    }
7499042b0b548db623f7f465767008a27d305299aa9James Lemieux
7509042b0b548db623f7f465767008a27d305299aa9James Lemieux    /**
7519042b0b548db623f7f465767008a27d305299aa9James Lemieux     * @param stopwatchListener to no longer be notified when stopwatch changes or laps are added
7529042b0b548db623f7f465767008a27d305299aa9James Lemieux     */
7539042b0b548db623f7f465767008a27d305299aa9James Lemieux    public void removeStopwatchListener(StopwatchListener stopwatchListener) {
7549042b0b548db623f7f465767008a27d305299aa9James Lemieux        enforceMainLooper();
7559042b0b548db623f7f465767008a27d305299aa9James Lemieux        mStopwatchModel.removeStopwatchListener(stopwatchListener);
7569042b0b548db623f7f465767008a27d305299aa9James Lemieux    }
7579042b0b548db623f7f465767008a27d305299aa9James Lemieux
7589042b0b548db623f7f465767008a27d305299aa9James Lemieux    /**
75924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the current state of the stopwatch
76024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
76124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public Stopwatch getStopwatch() {
76224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
76324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.getStopwatch();
76424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
76524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
76624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
76724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the stopwatch after being started
76824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
76924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public Stopwatch startStopwatch() {
77024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
77124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.setStopwatch(getStopwatch().start());
77224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
77324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
77424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
77524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the stopwatch after being paused
77624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
77724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public Stopwatch pauseStopwatch() {
77824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
77924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.setStopwatch(getStopwatch().pause());
78024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
78124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
78224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
78324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the stopwatch after being reset
78424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
78524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public Stopwatch resetStopwatch() {
78624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
78724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.setStopwatch(getStopwatch().reset());
78824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
78924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
79024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
79124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the laps recorded for this stopwatch
79224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
79324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public List<Lap> getLaps() {
79424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
79524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.getLaps();
79624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
79724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
79824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
79924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return a newly recorded lap completed now; {@code null} if no more laps can be added
80024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
80124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public Lap addLap() {
80224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
80324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.addLap();
80424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
80524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
80624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
80724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return {@code true} iff more laps can be recorded
80824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
80924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public boolean canAddMoreLaps() {
81024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
81124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.canAddMoreLaps();
81224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
81324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
81424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
81524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the longest lap time of all recorded laps and the current lap
81624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
81724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public long getLongestLapTime() {
81824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
81924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.getLongestLapTime();
82024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
82124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
82224a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    /**
82324a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @param time a point in time after the end of the last lap
82424a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     * @return the elapsed time between the given {@code time} and the end of the previous lap
82524a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux     */
82624a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    public long getCurrentLapTime(long time) {
82724a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        enforceMainLooper();
82824a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux        return mStopwatchModel.getCurrentLapTime(time);
82924a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    }
83024a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux
83124a54fc16fdf95ee3f76ab99978c3401473dc516James Lemieux    //
832be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    // Time
8330b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    // (Time settings/values are accessible from any Thread so no Thread-enforcement exists.)
834be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    //
835be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout
836be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    /**
8370b19985ecfa0f43acb8ededc173f643843098a95James Lemieux     * @return the current time in milliseconds
838be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout     */
839be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    public long currentTimeMillis() {
840be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout        return mTimeModel.currentTimeMillis();
841be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    }
842be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout
843be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    /**
8440b19985ecfa0f43acb8ededc173f643843098a95James Lemieux     * @return milliseconds since boot, including time spent in sleep
845be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout     */
846be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    public long elapsedRealtime() {
847be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout        return mTimeModel.elapsedRealtime();
848be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    }
849be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout
8500b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    /**
8510b19985ecfa0f43acb8ededc173f643843098a95James Lemieux     * @return {@code true} if 24 hour time format is selected; {@code false} otherwise
8520b19985ecfa0f43acb8ededc173f643843098a95James Lemieux     */
8530b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    public boolean is24HourFormat() {
8540b19985ecfa0f43acb8ededc173f643843098a95James Lemieux        return mTimeModel.is24HourFormat();
8550b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    }
8560b19985ecfa0f43acb8ededc173f643843098a95James Lemieux
8570b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    /**
8580b19985ecfa0f43acb8ededc173f643843098a95James Lemieux     * @return a new calendar object initialized to the {@link #currentTimeMillis()}
8590b19985ecfa0f43acb8ededc173f643843098a95James Lemieux     */
8600b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    public Calendar getCalendar() {
8610b19985ecfa0f43acb8ededc173f643843098a95James Lemieux        return mTimeModel.getCalendar();
8620b19985ecfa0f43acb8ededc173f643843098a95James Lemieux    }
8630b19985ecfa0f43acb8ededc173f643843098a95James Lemieux
864be74d5fefb57de5ec6bb986bb3ef6f2874da5a00Sean Stout    //
86533781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    // Ringtones
86633781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    //
86733781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux
86833781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    /**
869c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux     * Ringtone titles are cached because loading them is expensive. This method
870c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux     * <strong>must</strong> be called on a background thread and is responsible for priming the
871c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux     * cache of ringtone titles to avoid later fetching titles on the main thread.
872c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux     */
873c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux    public void loadRingtoneTitles() {
874c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux        enforceNotMainLooper();
875c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux        mRingtoneModel.loadRingtoneTitles();
876c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux    }
877c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux
878c9446b52d9391e859e65d11e5342d84cfe3e781cJames Lemieux    /**
87995eed3212011413e1cb717c281e2157859a5adf4Sean Stout     * Recheck the permission to read each custom ringtone.
88095eed3212011413e1cb717c281e2157859a5adf4Sean Stout     */
88195eed3212011413e1cb717c281e2157859a5adf4Sean Stout    public void loadRingtonePermissions() {
88295eed3212011413e1cb717c281e2157859a5adf4Sean Stout        enforceNotMainLooper();
88395eed3212011413e1cb717c281e2157859a5adf4Sean Stout        mRingtoneModel.loadRingtonePermissions();
88495eed3212011413e1cb717c281e2157859a5adf4Sean Stout    }
88595eed3212011413e1cb717c281e2157859a5adf4Sean Stout
88695eed3212011413e1cb717c281e2157859a5adf4Sean Stout    /**
88733781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @param uri the uri of a ringtone
88833781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @return the title of the ringtone with the {@code uri}; {@code null} if it cannot be fetched
88933781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     */
89033781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    public String getRingtoneTitle(Uri uri) {
89133781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        enforceMainLooper();
89233781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        return mRingtoneModel.getRingtoneTitle(uri);
89333781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    }
89433781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux
89533781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    /**
89633781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @param uri the uri of an audio file to use as a ringtone
89733781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @param title the title of the audio content at the given {@code uri}
89833781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @return the ringtone instance created for the audio file
89933781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     */
90033781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    public CustomRingtone addCustomRingtone(Uri uri, String title) {
90133781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        enforceMainLooper();
90233781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        return mRingtoneModel.addCustomRingtone(uri, title);
90333781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    }
90433781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux
90533781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    /**
90633781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @param uri identifies the ringtone to remove
90733781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     */
90833781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    public void removeCustomRingtone(Uri uri) {
90933781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        enforceMainLooper();
91033781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        mRingtoneModel.removeCustomRingtone(uri);
91133781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    }
91233781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux
91333781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    /**
91433781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     * @return all available custom ringtones
91533781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux     */
91633781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    public List<CustomRingtone> getCustomRingtones() {
91733781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        enforceMainLooper();
91833781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux        return mRingtoneModel.getCustomRingtones();
91933781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    }
92033781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux
92133781c882e4229f4ec1a8fafbabb9d4b8b8e2932James Lemieux    //
922592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    // Widgets
923592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    //
924592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux
925592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    /**
926592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux     * @param widgetClass indicates the type of widget being counted
927592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux     * @param count the number of widgets of the given type
928592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux     * @param eventCategoryId identifies the category of event to send
929592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux     */
930592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    public void updateWidgetCount(Class widgetClass, int count, @StringRes int eventCategoryId) {
931592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux        enforceMainLooper();
932592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux        mWidgetModel.updateWidgetCount(widgetClass, count, eventCategoryId);
933592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    }
934592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux
935592c66b3eb497d24d1528fb2597059c6e4ec6620James Lemieux    //
93634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    // Settings
93734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    //
93834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
93934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
94025384711e3c2894a84642c49953594930dd078abJames Lemieux     * @param silentSettingsListener to be notified when alarm-silencing settings change
94125384711e3c2894a84642c49953594930dd078abJames Lemieux     */
94225384711e3c2894a84642c49953594930dd078abJames Lemieux    public void addSilentSettingsListener(OnSilentSettingsListener silentSettingsListener) {
94325384711e3c2894a84642c49953594930dd078abJames Lemieux        enforceMainLooper();
94425384711e3c2894a84642c49953594930dd078abJames Lemieux        mSilentSettingsModel.addSilentSettingsListener(silentSettingsListener);
94525384711e3c2894a84642c49953594930dd078abJames Lemieux    }
94625384711e3c2894a84642c49953594930dd078abJames Lemieux
94725384711e3c2894a84642c49953594930dd078abJames Lemieux    /**
94825384711e3c2894a84642c49953594930dd078abJames Lemieux     * @param silentSettingsListener to no longer be notified when alarm-silencing settings change
94925384711e3c2894a84642c49953594930dd078abJames Lemieux     */
95025384711e3c2894a84642c49953594930dd078abJames Lemieux    public void removeSilentSettingsListener(OnSilentSettingsListener silentSettingsListener) {
95125384711e3c2894a84642c49953594930dd078abJames Lemieux        enforceMainLooper();
95225384711e3c2894a84642c49953594930dd078abJames Lemieux        mSilentSettingsModel.removeSilentSettingsListener(silentSettingsListener);
95325384711e3c2894a84642c49953594930dd078abJames Lemieux    }
95425384711e3c2894a84642c49953594930dd078abJames Lemieux
95525384711e3c2894a84642c49953594930dd078abJames Lemieux    /**
9560777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return the id used to discriminate relevant AlarmManager callbacks from defunct ones
9570777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
9580777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public int getGlobalIntentId() {
9590777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mSettingsModel.getGlobalIntentId();
9600777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
9610777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
9620777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
9630777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * Update the id used to discriminate relevant AlarmManager callbacks from defunct ones
9640777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
9650777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public void updateGlobalIntentId() {
9660777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        enforceMainLooper();
9670777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        mSettingsModel.updateGlobalIntentId();
9680777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
9690777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
9700777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
97134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return the style of clock to display in the clock application
97234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
97334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public ClockStyle getClockStyle() {
97434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
97534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mSettingsModel.getClockStyle();
97634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
97734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
97834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
9797b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks     * @return the style of clock to display in the clock application
9807b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks     */
9817b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks    public boolean getDisplayClockSeconds() {
9827b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks        enforceMainLooper();
9837b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks        return mSettingsModel.getDisplayClockSeconds();
9847b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks    }
9857b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks
9867b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks    /**
9877b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks     * @param displaySeconds whether or not to display seconds for main clock
9887b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks     */
9897b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks    public void setDisplayClockSeconds(boolean displaySeconds) {
9907b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks        enforceMainLooper();
9917b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks        mSettingsModel.setDisplayClockSeconds(displaySeconds);
9927b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks    }
9937b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks
9947b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks    /**
99534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return the style of clock to display in the clock screensaver
99634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
99734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public ClockStyle getScreensaverClockStyle() {
99834142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
99934142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mSettingsModel.getScreensaverClockStyle();
100034142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
100134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux
100234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    /**
10033af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     * @return {@code true} if the screen saver should be dimmed for lower contrast at night
10043af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux     */
10053af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    public boolean getScreensaverNightModeOn() {
10063af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        enforceMainLooper();
10073af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux        return mSettingsModel.getScreensaverNightModeOn();
10083af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    }
10093af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux
10103af168c834d73487f8f614f0aaafbf6f9a850f0fJames Lemieux    /**
101134142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     * @return {@code true} if the users wants to automatically show a clock for their home timezone
101234142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     *      when they have travelled outside of that timezone
101334142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux     */
101434142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    public boolean getShowHomeClock() {
101534142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        enforceMainLooper();
101634142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux        return mSettingsModel.getShowHomeClock();
101734142b1d0f2445bbd606bb490dfef6c078c630eaJames Lemieux    }
1018d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
1019d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    /**
1020ff17acface9b98eba868fff0e2d70ddc85c5e4dbJames Lemieux     * @return the display order of the weekdays, which can start with {@link Calendar#SATURDAY},
1021ff17acface9b98eba868fff0e2d70ddc85c5e4dbJames Lemieux     *      {@link Calendar#SUNDAY} or {@link Calendar#MONDAY}
1022458aa8b4ebb8b7c6fdc0680a1b687ea21a61bf35James Lemieux     */
1023ff17acface9b98eba868fff0e2d70ddc85c5e4dbJames Lemieux    public Weekdays.Order getWeekdayOrder() {
1024458aa8b4ebb8b7c6fdc0680a1b687ea21a61bf35James Lemieux        enforceMainLooper();
1025ff17acface9b98eba868fff0e2d70ddc85c5e4dbJames Lemieux        return mSettingsModel.getWeekdayOrder();
1026458aa8b4ebb8b7c6fdc0680a1b687ea21a61bf35James Lemieux    }
1027458aa8b4ebb8b7c6fdc0680a1b687ea21a61bf35James Lemieux
1028458aa8b4ebb8b7c6fdc0680a1b687ea21a61bf35James Lemieux    /**
10290777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @return {@code true} if the restore process (of backup and restore) has completed
10300777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
10310777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public boolean isRestoreBackupFinished() {
10320777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        return mSettingsModel.isRestoreBackupFinished();
10330777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
10340777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
10350777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
10360777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     * @param finished {@code true} means the restore process (of backup and restore) has completed
10370777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux     */
10380777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    public void setRestoreBackupFinished(boolean finished) {
10390777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux        mSettingsModel.setRestoreBackupFinished(finished);
10400777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    }
10410777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux
10420777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux    /**
104384b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux     * @return a description of the time zones available for selection
104484b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux     */
104584b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux    public TimeZones getTimeZones() {
104684b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux        enforceMainLooper();
104784b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux        return mSettingsModel.getTimeZones();
104884b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux    }
104984b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux
105084b60fe3a95f07ee793d880ca754389159b4929eJames Lemieux    /**
1051d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     * Used to execute a delegate runnable and track its completion.
1052d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan     */
1053d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    private static class ExecutedRunnable implements Runnable {
1054d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
1055d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        private final Runnable mDelegate;
1056d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        private boolean mExecuted;
1057d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
1058d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        private ExecutedRunnable(Runnable delegate) {
1059d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            this.mDelegate = delegate;
1060d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        }
1061d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
1062d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        @Override
1063d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        public void run() {
1064d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            mDelegate.run();
1065d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
1066d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            synchronized (this) {
1067d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan                mExecuted = true;
1068d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan                notifyAll();
1069d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            }
1070d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        }
1071d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan
1072d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        private boolean isExecuted() {
1073d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan            return mExecuted;
1074d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan        }
1075d6c134979a65c717aa5e667c39d9f9a788b32a45Dylan Phan    }
1076ae9a055482865a70028fc9940c5d871775f9620cJustin Klaassen}
1077