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
17package com.android.deskclock.uidata;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.graphics.Typeface;
22import android.support.annotation.DrawableRes;
23import android.support.annotation.StringRes;
24
25import com.android.deskclock.AlarmClockFragment;
26import com.android.deskclock.ClockFragment;
27import com.android.deskclock.R;
28import com.android.deskclock.stopwatch.StopwatchFragment;
29import com.android.deskclock.timer.TimerFragment;
30
31import java.util.Calendar;
32
33import static com.android.deskclock.Utils.enforceMainLooper;
34
35/**
36 * All application-wide user interface data is accessible through this singleton.
37 */
38public 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
51        Tab(Class fragmentClass, @DrawableRes int iconResId, @StringRes int labelResId) {
52            mFragmentClassName = fragmentClass.getName();
53            mIconResId = iconResId;
54            mLabelResId = labelResId;
55        }
56
57        public String getFragmentClassName() { return mFragmentClassName; }
58        public @DrawableRes int getIconResId() { return mIconResId; }
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
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
80    private UiDataModel() {}
81
82    /**
83     * The context may be set precisely once during the application life.
84     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
228    public void removeTabListener(TabListener tabListener) {
229        enforceMainLooper();
230        mTabModel.removeTabListener(tabListener);
231    }
232
233    /**
234     * @return the number of tabs
235     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
369    public void removePeriodicCallback(Runnable runnable) {
370        enforceMainLooper();
371        mPeriodicCallbackModel.removePeriodicCallback(runnable);
372    }
373}
374