Home | History | Annotate | Download | only in data
      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.net.Uri;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.support.annotation.StringRes;
     25 
     26 import java.util.Collection;
     27 import java.util.Comparator;
     28 import java.util.List;
     29 
     30 import static com.android.deskclock.Utils.enforceMainLooper;
     31 
     32 /**
     33  * All application-wide data is accessible through this singleton.
     34  */
     35 public final class DataModel {
     36 
     37     /** Indicates the display style of clocks. */
     38     public enum ClockStyle {ANALOG, DIGITAL}
     39 
     40     /** Indicates the preferred sort order of cities. */
     41     public enum CitySort {NAME, UTC_OFFSET}
     42 
     43     public static final String ACTION_DIGITAL_WIDGET_CHANGED =
     44             "com.android.deskclock.DIGITAL_WIDGET_CHANGED";
     45 
     46     /** The single instance of this data model that exists for the life of the application. */
     47     private static final DataModel sDataModel = new DataModel();
     48 
     49     private Handler mHandler;
     50 
     51     private Context mContext;
     52 
     53     /** The model from which settings are fetched. */
     54     private SettingsModel mSettingsModel;
     55 
     56     /** The model from which city data are fetched. */
     57     private CityModel mCityModel;
     58 
     59     /** The model from which timer data are fetched. */
     60     private TimerModel mTimerModel;
     61 
     62     /** The model from which alarm data are fetched. */
     63     private AlarmModel mAlarmModel;
     64 
     65     /** The model from which stopwatch data are fetched. */
     66     private StopwatchModel mStopwatchModel;
     67 
     68     /** The model from which notification data are fetched. */
     69     private NotificationModel mNotificationModel;
     70 
     71     public static DataModel getDataModel() {
     72         return sDataModel;
     73     }
     74 
     75     private DataModel() {}
     76 
     77     /**
     78      * The context may be set precisely once during the application life.
     79      */
     80     public void setContext(Context context) {
     81         if (mContext != null) {
     82             throw new IllegalStateException("context has already been set");
     83         }
     84         mContext = context.getApplicationContext();
     85 
     86         mSettingsModel = new SettingsModel(mContext);
     87         mNotificationModel = new NotificationModel();
     88         mCityModel = new CityModel(mContext, mSettingsModel);
     89         mAlarmModel = new AlarmModel(mContext, mSettingsModel);
     90         mStopwatchModel = new StopwatchModel(mContext, mNotificationModel);
     91         mTimerModel = new TimerModel(mContext, mSettingsModel, mNotificationModel);
     92     }
     93 
     94     /**
     95      * Posts a runnable to the main thread and blocks until the runnable executes. Used to access
     96      * the data model from the main thread.
     97      */
     98     public void run(Runnable runnable) {
     99         if (Looper.myLooper() == Looper.getMainLooper()) {
    100             runnable.run();
    101             return;
    102         }
    103 
    104         final ExecutedRunnable er = new ExecutedRunnable(runnable);
    105         getHandler().post(er);
    106 
    107         // Wait for the data to arrive, if it has not.
    108         synchronized (er) {
    109             if (!er.isExecuted()) {
    110                 try {
    111                     er.wait();
    112                 } catch (InterruptedException ignored) {
    113                     // ignore
    114                 }
    115             }
    116         }
    117     }
    118 
    119     /**
    120      * @return a handler associated with the main thread
    121      */
    122     private synchronized Handler getHandler() {
    123         if (mHandler == null) {
    124             mHandler = new Handler(Looper.getMainLooper());
    125         }
    126         return mHandler;
    127     }
    128 
    129     //
    130     // Application
    131     //
    132 
    133     /**
    134      * @param inForeground {@code true} to indicate the application is open in the foreground
    135      */
    136     public void setApplicationInForeground(boolean inForeground) {
    137         enforceMainLooper();
    138 
    139         if (mNotificationModel.isApplicationInForeground() != inForeground) {
    140             mNotificationModel.setApplicationInForeground(inForeground);
    141 
    142             // Refresh all notifications in response to a change in app open state.
    143             mTimerModel.updateNotification();
    144             mStopwatchModel.updateNotification();
    145         }
    146     }
    147 
    148     /**
    149      * @return {@code true} when the application is open in the foreground; {@code false} otherwise
    150      */
    151     public boolean isApplicationInForeground() {
    152         return mNotificationModel.isApplicationInForeground();
    153     }
    154 
    155     /**
    156      * Called when the notifications may be stale or absent from the notification manager and must
    157      * be rebuilt. e.g. after upgrading the application
    158      */
    159     public void updateAllNotifications() {
    160         mTimerModel.updateNotification();
    161         mStopwatchModel.updateNotification();
    162     }
    163 
    164     //
    165     // Cities
    166     //
    167 
    168     /**
    169      * @return a list of all cities in their display order
    170      */
    171     public List<City> getAllCities() {
    172         enforceMainLooper();
    173         return mCityModel.getAllCities();
    174     }
    175 
    176     /**
    177      * @param cityName the case-insensitive city name to search for
    178      * @return the city with the given {@code cityName}; {@code null} if no such city exists
    179      */
    180     public City getCity(String cityName) {
    181         enforceMainLooper();
    182         return mCityModel.getCity(cityName);
    183     }
    184 
    185     /**
    186      * @return a city representing the user's home timezone
    187      */
    188     public City getHomeCity() {
    189         enforceMainLooper();
    190         return mCityModel.getHomeCity();
    191     }
    192 
    193     /**
    194      * @return a list of cities not selected for display
    195      */
    196     public List<City> getUnselectedCities() {
    197         enforceMainLooper();
    198         return mCityModel.getUnselectedCities();
    199     }
    200 
    201     /**
    202      * @return a list of cities selected for display
    203      */
    204     public List<City> getSelectedCities() {
    205         enforceMainLooper();
    206         return mCityModel.getSelectedCities();
    207     }
    208 
    209     /**
    210      * @param cities the new collection of cities selected for display by the user
    211      */
    212     public void setSelectedCities(Collection<City> cities) {
    213         enforceMainLooper();
    214         mCityModel.setSelectedCities(cities);
    215     }
    216 
    217     /**
    218      * @return a comparator used to locate index positions
    219      */
    220     public Comparator<City> getCityIndexComparator() {
    221         enforceMainLooper();
    222         return mCityModel.getCityIndexComparator();
    223     }
    224 
    225     /**
    226      * @return the order in which cities are sorted
    227      */
    228     public CitySort getCitySort() {
    229         enforceMainLooper();
    230         return mCityModel.getCitySort();
    231     }
    232 
    233     /**
    234      * Adjust the order in which cities are sorted.
    235      */
    236     public void toggleCitySort() {
    237         enforceMainLooper();
    238         mCityModel.toggleCitySort();
    239     }
    240 
    241     //
    242     // Timers
    243     //
    244 
    245     /**
    246      * @param timerListener to be notified when timers are added, updated and removed
    247      */
    248     public void addTimerListener(TimerListener timerListener) {
    249         enforceMainLooper();
    250         mTimerModel.addTimerListener(timerListener);
    251     }
    252 
    253     /**
    254      * @param timerListener to no longer be notified when timers are added, updated and removed
    255      */
    256     public void removeTimerListener(TimerListener timerListener) {
    257         enforceMainLooper();
    258         mTimerModel.removeTimerListener(timerListener);
    259     }
    260 
    261     /**
    262      * @return a list of timers for display
    263      */
    264     public List<Timer> getTimers() {
    265         enforceMainLooper();
    266         return mTimerModel.getTimers();
    267     }
    268 
    269     /**
    270      * @return a list of expired timers for display
    271      */
    272     public List<Timer> getExpiredTimers() {
    273         enforceMainLooper();
    274         return mTimerModel.getExpiredTimers();
    275     }
    276 
    277     /**
    278      * @param timerId identifies the timer to return
    279      * @return the timer with the given {@code timerId}
    280      */
    281     public Timer getTimer(int timerId) {
    282         enforceMainLooper();
    283         return mTimerModel.getTimer(timerId);
    284     }
    285 
    286     /**
    287      * @return the timer that last expired and is still expired now; {@code null} if no timers are
    288      *      expired
    289      */
    290     public Timer getMostRecentExpiredTimer() {
    291         enforceMainLooper();
    292         return mTimerModel.getMostRecentExpiredTimer();
    293     }
    294 
    295     /**
    296      * @param length the length of the timer in milliseconds
    297      * @param label describes the purpose of the timer
    298      * @param deleteAfterUse {@code true} indicates the timer should be deleted when it is reset
    299      * @return the newly added timer
    300      */
    301     public Timer addTimer(long length, String label, boolean deleteAfterUse) {
    302         enforceMainLooper();
    303         return mTimerModel.addTimer(length, label, deleteAfterUse);
    304     }
    305 
    306     /**
    307      * @param timer the timer to be removed
    308      */
    309     public void removeTimer(Timer timer) {
    310         enforceMainLooper();
    311         mTimerModel.removeTimer(timer);
    312     }
    313 
    314     /**
    315      * @param timer the timer to be started
    316      */
    317     public void startTimer(Timer timer) {
    318         enforceMainLooper();
    319         mTimerModel.updateTimer(timer.start());
    320     }
    321 
    322     /**
    323      * @param timer the timer to be paused
    324      */
    325     public void pauseTimer(Timer timer) {
    326         enforceMainLooper();
    327         mTimerModel.updateTimer(timer.pause());
    328     }
    329 
    330     /**
    331      * @param service used to start foreground notifications for expired timers
    332      * @param timer the timer to be expired
    333      */
    334     public void expireTimer(Service service, Timer timer) {
    335         enforceMainLooper();
    336         mTimerModel.expireTimer(service, timer);
    337     }
    338 
    339     /**
    340      * If the given {@code timer} is expired and marked for deletion after use then this method
    341      * removes the the timer. The timer is otherwise transitioned to the reset state and continues
    342      * to exist.
    343      *
    344      * @param timer the timer to be reset
    345      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
    346      */
    347     public void resetOrDeleteTimer(Timer timer, @StringRes int eventLabelId) {
    348         enforceMainLooper();
    349         mTimerModel.resetOrDeleteTimer(timer, eventLabelId);
    350     }
    351 
    352     /**
    353      * Resets all timers.
    354      *
    355      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
    356      */
    357     public void resetTimers(@StringRes int eventLabelId) {
    358         enforceMainLooper();
    359         mTimerModel.resetTimers(eventLabelId);
    360     }
    361 
    362     /**
    363      * Resets all expired timers.
    364      *
    365      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
    366      */
    367     public void resetExpiredTimers(@StringRes int eventLabelId) {
    368         enforceMainLooper();
    369         mTimerModel.resetExpiredTimers(eventLabelId);
    370     }
    371 
    372     /**
    373      * Resets all unexpired timers.
    374      *
    375      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
    376      */
    377     public void resetUnexpiredTimers(@StringRes int eventLabelId) {
    378         enforceMainLooper();
    379         mTimerModel.resetUnexpiredTimers(eventLabelId);
    380     }
    381 
    382     /**
    383      * @param timer the timer to which a minute should be added to the remaining time
    384      */
    385     public void addTimerMinute(Timer timer) {
    386         enforceMainLooper();
    387         mTimerModel.updateTimer(timer.addMinute());
    388     }
    389 
    390     /**
    391      * @param timer the timer to which the new {@code label} belongs
    392      * @param label the new label to store for the {@code timer}
    393      */
    394     public void setTimerLabel(Timer timer, String label) {
    395         enforceMainLooper();
    396         mTimerModel.updateTimer(timer.setLabel(label));
    397     }
    398 
    399     /**
    400      * Updates the timer notifications to be current.
    401      */
    402     public void updateTimerNotification() {
    403         enforceMainLooper();
    404         mTimerModel.updateNotification();
    405     }
    406 
    407     /**
    408      * @return the uri of the default ringtone to play for all timers when no user selection exists
    409      */
    410     public Uri getDefaultTimerRingtoneUri() {
    411         enforceMainLooper();
    412         return mTimerModel.getDefaultTimerRingtoneUri();
    413     }
    414 
    415     /**
    416      * @return {@code true} iff the ringtone to play for all timers is the silent ringtone
    417      */
    418     public boolean isTimerRingtoneSilent() {
    419         enforceMainLooper();
    420         return mTimerModel.isTimerRingtoneSilent();
    421     }
    422 
    423     /**
    424      * @return the uri of the ringtone to play for all timers
    425      */
    426     public Uri getTimerRingtoneUri() {
    427         enforceMainLooper();
    428         return mTimerModel.getTimerRingtoneUri();
    429     }
    430 
    431     /**
    432      * @return the title of the ringtone that is played for all timers
    433      */
    434     public String getTimerRingtoneTitle() {
    435         enforceMainLooper();
    436         return mTimerModel.getTimerRingtoneTitle();
    437     }
    438 
    439     //
    440     // Alarms
    441     //
    442 
    443     /**
    444      * @return the uri of the ringtone to which all new alarms default
    445      */
    446     public Uri getDefaultAlarmRingtoneUri() {
    447         enforceMainLooper();
    448         return mAlarmModel.getDefaultAlarmRingtoneUri();
    449     }
    450 
    451     /**
    452      * @param uri the uri of the ringtone to which future new alarms will default
    453      */
    454     public void setDefaultAlarmRingtoneUri(Uri uri) {
    455         enforceMainLooper();
    456         mAlarmModel.setDefaultAlarmRingtoneUri(uri);
    457     }
    458 
    459     /**
    460      * @param uri the uri of a ringtone
    461      * @return the title of the ringtone with the {@code uri}; {@code null} if it cannot be fetched
    462      */
    463     public String getAlarmRingtoneTitle(Uri uri) {
    464         enforceMainLooper();
    465         return mAlarmModel.getAlarmRingtoneTitle(uri);
    466     }
    467 
    468     //
    469     // Stopwatch
    470     //
    471 
    472     /**
    473      * @return the current state of the stopwatch
    474      */
    475     public Stopwatch getStopwatch() {
    476         enforceMainLooper();
    477         return mStopwatchModel.getStopwatch();
    478     }
    479 
    480     /**
    481      * @return the stopwatch after being started
    482      */
    483     public Stopwatch startStopwatch() {
    484         enforceMainLooper();
    485         return mStopwatchModel.setStopwatch(getStopwatch().start());
    486     }
    487 
    488     /**
    489      * @return the stopwatch after being paused
    490      */
    491     public Stopwatch pauseStopwatch() {
    492         enforceMainLooper();
    493         return mStopwatchModel.setStopwatch(getStopwatch().pause());
    494     }
    495 
    496     /**
    497      * @return the stopwatch after being reset
    498      */
    499     public Stopwatch resetStopwatch() {
    500         enforceMainLooper();
    501         return mStopwatchModel.setStopwatch(getStopwatch().reset());
    502     }
    503 
    504     /**
    505      * @return the laps recorded for this stopwatch
    506      */
    507     public List<Lap> getLaps() {
    508         enforceMainLooper();
    509         return mStopwatchModel.getLaps();
    510     }
    511 
    512     /**
    513      * @return a newly recorded lap completed now; {@code null} if no more laps can be added
    514      */
    515     public Lap addLap() {
    516         enforceMainLooper();
    517         return mStopwatchModel.addLap();
    518     }
    519 
    520     /**
    521      * Clears the laps recorded for this stopwatch.
    522      */
    523     public void clearLaps() {
    524         enforceMainLooper();
    525         mStopwatchModel.clearLaps();
    526     }
    527 
    528     /**
    529      * @return {@code true} iff more laps can be recorded
    530      */
    531     public boolean canAddMoreLaps() {
    532         enforceMainLooper();
    533         return mStopwatchModel.canAddMoreLaps();
    534     }
    535 
    536     /**
    537      * @return the longest lap time of all recorded laps and the current lap
    538      */
    539     public long getLongestLapTime() {
    540         enforceMainLooper();
    541         return mStopwatchModel.getLongestLapTime();
    542     }
    543 
    544     /**
    545      * @param time a point in time after the end of the last lap
    546      * @return the elapsed time between the given {@code time} and the end of the previous lap
    547      */
    548     public long getCurrentLapTime(long time) {
    549         enforceMainLooper();
    550         return mStopwatchModel.getCurrentLapTime(time);
    551     }
    552 
    553     //
    554     // Settings
    555     //
    556 
    557     /**
    558      * @return the style of clock to display in the clock application
    559      */
    560     public ClockStyle getClockStyle() {
    561         enforceMainLooper();
    562         return mSettingsModel.getClockStyle();
    563     }
    564 
    565     /**
    566      * @return the style of clock to display in the clock screensaver
    567      */
    568     public ClockStyle getScreensaverClockStyle() {
    569         enforceMainLooper();
    570         return mSettingsModel.getScreensaverClockStyle();
    571     }
    572 
    573     /**
    574      * @return {@code true} if the users wants to automatically show a clock for their home timezone
    575      *      when they have travelled outside of that timezone
    576      */
    577     public boolean getShowHomeClock() {
    578         enforceMainLooper();
    579         return mSettingsModel.getShowHomeClock();
    580     }
    581 
    582     /**
    583      * Used to execute a delegate runnable and track its completion.
    584      */
    585     private static class ExecutedRunnable implements Runnable {
    586 
    587         private final Runnable mDelegate;
    588         private boolean mExecuted;
    589 
    590         private ExecutedRunnable(Runnable delegate) {
    591             this.mDelegate = delegate;
    592         }
    593 
    594         @Override
    595         public void run() {
    596             mDelegate.run();
    597 
    598             synchronized (this) {
    599                 mExecuted = true;
    600                 notifyAll();
    601             }
    602         }
    603 
    604         private boolean isExecuted() {
    605             return mExecuted;
    606         }
    607     }
    608 }