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