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