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 */ addQuarterHourCallback(Runnable runnable)342 public void addQuarterHourCallback(Runnable runnable) { 343 enforceMainLooper(); 344 mPeriodicCallbackModel.addQuarterHourCallback(runnable); 345 } 346 347 /** 348 * @param runnable to be called every hour 349 */ addHourCallback(Runnable runnable)350 public void addHourCallback(Runnable runnable) { 351 enforceMainLooper(); 352 mPeriodicCallbackModel.addHourCallback(runnable); 353 } 354 355 /** 356 * @param runnable to be called every midnight 357 */ addMidnightCallback(Runnable runnable)358 public void addMidnightCallback(Runnable runnable) { 359 enforceMainLooper(); 360 mPeriodicCallbackModel.addMidnightCallback(runnable); 361 } 362 363 /** 364 * @param runnable to no longer be called periodically 365 */ removePeriodicCallback(Runnable runnable)366 public void removePeriodicCallback(Runnable runnable) { 367 enforceMainLooper(); 368 mPeriodicCallbackModel.removePeriodicCallback(runnable); 369 } 370 } 371