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