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