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.uidata;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.graphics.Typeface;
22 import androidx.annotation.DrawableRes;
23 import androidx.annotation.StringRes;
24 
25 import com.android.deskclock.AlarmClockFragment;
26 import com.android.deskclock.ClockFragment;
27 import com.android.deskclock.R;
28 import com.android.deskclock.stopwatch.StopwatchFragment;
29 import com.android.deskclock.timer.TimerFragment;
30 
31 import java.util.Calendar;
32 
33 import static com.android.deskclock.Utils.enforceMainLooper;
34 
35 /**
36  * All application-wide user interface data is accessible through this singleton.
37  */
38 public final class UiDataModel {
39 
40     /** Identifies each of the primary tabs within the application. */
41     public enum Tab {
42         ALARMS(AlarmClockFragment.class, R.drawable.ic_tab_alarm, R.string.menu_alarm),
43         CLOCKS(ClockFragment.class, R.drawable.ic_tab_clock, R.string.menu_clock),
44         TIMERS(TimerFragment.class, R.drawable.ic_tab_timer, R.string.menu_timer),
45         STOPWATCH(StopwatchFragment.class, R.drawable.ic_tab_stopwatch, R.string.menu_stopwatch);
46 
47         private final String mFragmentClassName;
48         private final @DrawableRes int mIconResId;
49         private final @StringRes int mLabelResId;
50 
Tab(Class fragmentClass, @DrawableRes int iconResId, @StringRes int labelResId)51         Tab(Class fragmentClass, @DrawableRes int iconResId, @StringRes int labelResId) {
52             mFragmentClassName = fragmentClass.getName();
53             mIconResId = iconResId;
54             mLabelResId = labelResId;
55         }
56 
getFragmentClassName()57         public String getFragmentClassName() { return mFragmentClassName; }
getIconResId()58         public @DrawableRes int getIconResId() { return mIconResId; }
getLabelResId()59         public @StringRes int getLabelResId() { return mLabelResId; }
60     }
61 
62     /** The single instance of this data model that exists for the life of the application. */
63     private static final UiDataModel sUiDataModel = new UiDataModel();
64 
getUiDataModel()65     public static UiDataModel getUiDataModel() {
66         return sUiDataModel;
67     }
68 
69     private Context mContext;
70 
71     /** The model from which tab data are fetched. */
72     private TabModel mTabModel;
73 
74     /** The model from which formatted strings are fetched. */
75     private FormattedStringModel mFormattedStringModel;
76 
77     /** The model from which timed callbacks originate. */
78     private PeriodicCallbackModel mPeriodicCallbackModel;
79 
UiDataModel()80     private UiDataModel() {}
81 
82     /**
83      * The context may be set precisely once during the application life.
84      */
init(Context context, SharedPreferences prefs)85     public void init(Context context, SharedPreferences prefs) {
86         if (mContext != context) {
87             mContext = context.getApplicationContext();
88 
89             mPeriodicCallbackModel = new PeriodicCallbackModel(mContext);
90             mFormattedStringModel = new FormattedStringModel(mContext);
91             mTabModel = new TabModel(prefs);
92         }
93     }
94 
95     /**
96      * To display the alarm clock in this font, use the character {@link R.string#clock_emoji}.
97      *
98      * @return a special font containing a glyph that draws an alarm clock
99      */
getAlarmIconTypeface()100     public Typeface getAlarmIconTypeface() {
101         return Typeface.createFromAsset(mContext.getAssets(), "fonts/clock.ttf");
102     }
103 
104     //
105     // Formatted Strings
106     //
107 
108     /**
109      * This method is intended to be used when formatting numbers occurs in a hotspot such as the
110      * update loop of a timer or stopwatch. It returns cached results when possible in order to
111      * provide speed and limit garbage to be collected by the virtual machine.
112      *
113      * @param value a positive integer to format as a String
114      * @return the {@code value} formatted as a String in the current locale
115      * @throws IllegalArgumentException if {@code value} is negative
116      */
getFormattedNumber(int value)117     public String getFormattedNumber(int value) {
118         enforceMainLooper();
119         return mFormattedStringModel.getFormattedNumber(value);
120     }
121 
122     /**
123      * This method is intended to be used when formatting numbers occurs in a hotspot such as the
124      * update loop of a timer or stopwatch. It returns cached results when possible in order to
125      * provide speed and limit garbage to be collected by the virtual machine.
126      *
127      * @param value a positive integer to format as a String
128      * @param length the length of the String; zeroes are padded to match this length
129      * @return the {@code value} formatted as a String in the current locale and padded to the
130      *      requested {@code length}
131      * @throws IllegalArgumentException if {@code value} is negative
132      */
getFormattedNumber(int value, int length)133     public String getFormattedNumber(int value, int length) {
134         enforceMainLooper();
135         return mFormattedStringModel.getFormattedNumber(value, length);
136     }
137 
138     /**
139      * This method is intended to be used when formatting numbers occurs in a hotspot such as the
140      * update loop of a timer or stopwatch. It returns cached results when possible in order to
141      * provide speed and limit garbage to be collected by the virtual machine.
142      *
143      * @param negative force a minus sign (-) onto the display, even if {@code value} is {@code 0}
144      * @param value a positive integer to format as a String
145      * @param length the length of the String; zeroes are padded to match this length. If
146      *      {@code negative} is {@code true} the return value will contain a minus sign and a total
147      *      length of {@code length + 1}.
148      * @return the {@code value} formatted as a String in the current locale and padded to the
149      *      requested {@code length}
150      * @throws IllegalArgumentException if {@code value} is negative
151      */
getFormattedNumber(boolean negative, int value, int length)152     public String getFormattedNumber(boolean negative, int value, int length) {
153         enforceMainLooper();
154         return mFormattedStringModel.getFormattedNumber(negative, value, length);
155     }
156 
157     /**
158      * @param calendarDay any of the following values
159      *                     <ul>
160      *                     <li>{@link Calendar#SUNDAY}</li>
161      *                     <li>{@link Calendar#MONDAY}</li>
162      *                     <li>{@link Calendar#TUESDAY}</li>
163      *                     <li>{@link Calendar#WEDNESDAY}</li>
164      *                     <li>{@link Calendar#THURSDAY}</li>
165      *                     <li>{@link Calendar#FRIDAY}</li>
166      *                     <li>{@link Calendar#SATURDAY}</li>
167      *                     </ul>
168      * @return single-character version of weekday name; e.g.: 'S', 'M', 'T', 'W', 'T', 'F', 'S'
169      */
getShortWeekday(int calendarDay)170     public String getShortWeekday(int calendarDay) {
171         enforceMainLooper();
172         return mFormattedStringModel.getShortWeekday(calendarDay);
173     }
174 
175     /**
176      * @param calendarDay any of the following values
177      *                     <ul>
178      *                     <li>{@link Calendar#SUNDAY}</li>
179      *                     <li>{@link Calendar#MONDAY}</li>
180      *                     <li>{@link Calendar#TUESDAY}</li>
181      *                     <li>{@link Calendar#WEDNESDAY}</li>
182      *                     <li>{@link Calendar#THURSDAY}</li>
183      *                     <li>{@link Calendar#FRIDAY}</li>
184      *                     <li>{@link Calendar#SATURDAY}</li>
185      *                     </ul>
186      * @return full weekday name; e.g.: 'Sunday', 'Monday', 'Tuesday', etc.
187      */
getLongWeekday(int calendarDay)188     public String getLongWeekday(int calendarDay) {
189         enforceMainLooper();
190         return mFormattedStringModel.getLongWeekday(calendarDay);
191     }
192 
193     //
194     // Animations
195     //
196 
197     /**
198      * @return the duration in milliseconds of short animations
199      */
getShortAnimationDuration()200     public long getShortAnimationDuration() {
201         enforceMainLooper();
202         return mContext.getResources().getInteger(android.R.integer.config_shortAnimTime);
203     }
204 
205     /**
206      * @return the duration in milliseconds of long animations
207      */
getLongAnimationDuration()208     public long getLongAnimationDuration() {
209         enforceMainLooper();
210         return mContext.getResources().getInteger(android.R.integer.config_longAnimTime);
211     }
212 
213     //
214     // Tabs
215     //
216 
217     /**
218      * @param tabListener to be notified when the selected tab changes
219      */
addTabListener(TabListener tabListener)220     public void addTabListener(TabListener tabListener) {
221         enforceMainLooper();
222         mTabModel.addTabListener(tabListener);
223     }
224 
225     /**
226      * @param tabListener to no longer be notified when the selected tab changes
227      */
removeTabListener(TabListener tabListener)228     public void removeTabListener(TabListener tabListener) {
229         enforceMainLooper();
230         mTabModel.removeTabListener(tabListener);
231     }
232 
233     /**
234      * @return the number of tabs
235      */
getTabCount()236     public int getTabCount() {
237         enforceMainLooper();
238         return mTabModel.getTabCount();
239     }
240 
241     /**
242      * @param ordinal the ordinal of the tab
243      * @return the tab at the given {@code ordinal}
244      */
getTab(int ordinal)245     public Tab getTab(int ordinal) {
246         enforceMainLooper();
247         return mTabModel.getTab(ordinal);
248     }
249 
250     /**
251      * @param position the position of the tab in the user interface
252      * @return the tab at the given {@code ordinal}
253      */
getTabAt(int position)254     public Tab getTabAt(int position) {
255         enforceMainLooper();
256         return mTabModel.getTabAt(position);
257     }
258 
259     /**
260      * @return an enumerated value indicating the currently selected primary tab
261      */
getSelectedTab()262     public Tab getSelectedTab() {
263         enforceMainLooper();
264         return mTabModel.getSelectedTab();
265     }
266 
267     /**
268      * @param tab an enumerated value indicating the newly selected primary tab
269      */
setSelectedTab(Tab tab)270     public void setSelectedTab(Tab tab) {
271         enforceMainLooper();
272         mTabModel.setSelectedTab(tab);
273     }
274 
275     /**
276      * @param tabScrollListener to be notified when the scroll position of the selected tab changes
277      */
addTabScrollListener(TabScrollListener tabScrollListener)278     public void addTabScrollListener(TabScrollListener tabScrollListener) {
279         enforceMainLooper();
280         mTabModel.addTabScrollListener(tabScrollListener);
281     }
282 
283     /**
284      * @param tabScrollListener to be notified when the scroll position of the selected tab changes
285      */
removeTabScrollListener(TabScrollListener tabScrollListener)286     public void removeTabScrollListener(TabScrollListener tabScrollListener) {
287         enforceMainLooper();
288         mTabModel.removeTabScrollListener(tabScrollListener);
289     }
290 
291     /**
292      * Updates the scrolling state in the {@link UiDataModel} for this tab.
293      *
294      * @param tab an enumerated value indicating the tab reporting its vertical scroll position
295      * @param scrolledToTop {@code true} iff the vertical scroll position of the tab is at the top
296      */
setTabScrolledToTop(Tab tab, boolean scrolledToTop)297     public void setTabScrolledToTop(Tab tab, boolean scrolledToTop) {
298         enforceMainLooper();
299         mTabModel.setTabScrolledToTop(tab, scrolledToTop);
300     }
301 
302     /**
303      * @return {@code true} iff the content in the selected tab is currently scrolled to the top
304      */
isSelectedTabScrolledToTop()305     public boolean isSelectedTabScrolledToTop() {
306         enforceMainLooper();
307         return mTabModel.isTabScrolledToTop(getSelectedTab());
308     }
309 
310     //
311     // Shortcut Ids
312     //
313 
314     /**
315      * @param category which category of shortcut of which to get the id
316      * @param action the desired action to perform
317      * @return the id of the shortcut
318      */
getShortcutId(@tringRes int category, @StringRes int action)319     public String getShortcutId(@StringRes int category, @StringRes int action) {
320         if (category == R.string.category_stopwatch) {
321             return mContext.getString(category);
322         }
323         return mContext.getString(category) + "_" + mContext.getString(action);
324     }
325 
326     //
327     // Timed Callbacks
328     //
329 
330     /**
331      * @param runnable to be called every minute
332      * @param offset an offset applied to the minute to control when the callback occurs
333      */
addMinuteCallback(Runnable runnable, long offset)334     public void addMinuteCallback(Runnable runnable, long offset) {
335         enforceMainLooper();
336         mPeriodicCallbackModel.addMinuteCallback(runnable, offset);
337     }
338 
339     /**
340      * @param runnable to be called every quarter-hour
341      * @param offset an offset applied to the quarter-hour to control when the callback occurs
342      */
addQuarterHourCallback(Runnable runnable, long offset)343     public void addQuarterHourCallback(Runnable runnable, long offset) {
344         enforceMainLooper();
345         mPeriodicCallbackModel.addQuarterHourCallback(runnable, offset);
346     }
347 
348     /**
349      * @param runnable to be called every hour
350      * @param offset an offset applied to the hour to control when the callback occurs
351      */
addHourCallback(Runnable runnable, long offset)352     public void addHourCallback(Runnable runnable, long offset) {
353         enforceMainLooper();
354         mPeriodicCallbackModel.addHourCallback(runnable, offset);
355     }
356 
357     /**
358      * @param runnable to be called every midnight
359      * @param offset an offset applied to the midnight to control when the callback occurs
360      */
addMidnightCallback(Runnable runnable, long offset)361     public void addMidnightCallback(Runnable runnable, long offset) {
362         enforceMainLooper();
363         mPeriodicCallbackModel.addMidnightCallback(runnable, offset);
364     }
365 
366     /**
367      * @param runnable to no longer be called periodically
368      */
removePeriodicCallback(Runnable runnable)369     public void removePeriodicCallback(Runnable runnable) {
370         enforceMainLooper();
371         mPeriodicCallbackModel.removePeriodicCallback(runnable);
372     }
373 }
374