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