16d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/* 26d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Copyright (C) 2015 The Android Open Source Project 36d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 46d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Licensed under the Apache License, Version 2.0 (the "License"); 56d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * you may not use this file except in compliance with the License. 66d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * You may obtain a copy of the License at 76d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 86d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * http://www.apache.org/licenses/LICENSE-2.0 96d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Unless required by applicable law or agreed to in writing, software 116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * distributed under the License is distributed on an "AS IS" BASIS, 126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * See the License for the specific language governing permissions and 146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * limitations under the License. 156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpackage com.android.deskclock.data; 186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.text.TextUtils; 206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport java.util.Arrays; 226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport java.util.Comparator; 236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport java.util.List; 246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static android.text.format.DateUtils.HOUR_IN_MILLIS; 266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static android.text.format.DateUtils.MINUTE_IN_MILLIS; 276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static android.text.format.DateUtils.SECOND_IN_MILLIS; 282a07ae3286fd5c76f71546890e0f02af99065825Sean Stoutimport static com.android.deskclock.Utils.now; 292a07ae3286fd5c76f71546890e0f02af99065825Sean Stoutimport static com.android.deskclock.Utils.wallClock; 306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static com.android.deskclock.data.Timer.State.EXPIRED; 312a07ae3286fd5c76f71546890e0f02af99065825Sean Stoutimport static com.android.deskclock.data.Timer.State.MISSED; 326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static com.android.deskclock.data.Timer.State.PAUSED; 336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static com.android.deskclock.data.Timer.State.RESET; 346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport static com.android.deskclock.data.Timer.State.RUNNING; 356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/** 376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * A read-only domain object representing a countdown timer. 386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpublic final class Timer { 406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public enum State { 422a07ae3286fd5c76f71546890e0f02af99065825Sean Stout RUNNING(1), PAUSED(2), EXPIRED(3), RESET(4), MISSED(5); 436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The value assigned to this State in prior releases. */ 456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final int mValue; 466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux State(int value) { 486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mValue = value; 496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * @return the numeric value assigned to this state 536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public int getValue() { 556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return mValue; 566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * @return the state corresponding to the given {@code value} 606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public static State fromValue(int value) { 626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux for (State state : values()) { 636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (state.getValue() == value) { 646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return state; 656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 6841ad8d99d3531947b1f7016086cd0e296818b4bdJames Lemieux return null; 696d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The minimum duration of a timer. */ 736d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public static final long MIN_LENGTH = SECOND_IN_MILLIS; 746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 7558d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux /** The maximum duration of a new timer created via the user interface. */ 7658d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux static final long MAX_LENGTH = 776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 99 * HOUR_IN_MILLIS + 99 * MINUTE_IN_MILLIS + 99 * SECOND_IN_MILLIS; 786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 792a07ae3286fd5c76f71546890e0f02af99065825Sean Stout static final long UNUSED = Long.MIN_VALUE; 802a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 81310f96ed002219067d2e652e27d64d256e315832James Lemieux /** A unique identifier for the timer. */ 826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final int mId; 836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The current state of the timer. */ 856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final State mState; 866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The original length of the timer in milliseconds when it was created. */ 886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final long mLength; 896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The length of the timer in milliseconds including additional time added by the user. */ 916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final long mTotalLength; 926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 932a07ae3286fd5c76f71546890e0f02af99065825Sean Stout /** The time at which the timer was last started; {@link #UNUSED} when not running. */ 946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final long mLastStartTime; 956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 962a07ae3286fd5c76f71546890e0f02af99065825Sean Stout /** The time since epoch at which the timer was last started. */ 972a07ae3286fd5c76f71546890e0f02af99065825Sean Stout private final long mLastStartWallClockTime; 982a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The time at which the timer is scheduled to expire; negative if it is already expired. */ 1006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final long mRemainingTime; 1016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** A message describing the meaning of the timer. */ 1036d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final String mLabel; 1046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** A flag indicating the timer should be deleted when it is reset. */ 1066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private final boolean mDeleteAfterUse; 1076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1086d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux Timer(int id, State state, long length, long totalLength, long lastStartTime, 1092a07ae3286fd5c76f71546890e0f02af99065825Sean Stout long lastWallClockTime, long remainingTime, String label, boolean deleteAfterUse) { 1106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mId = id; 1116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mState = state; 1126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLength = length; 1136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mTotalLength = totalLength; 1146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLastStartTime = lastStartTime; 1152a07ae3286fd5c76f71546890e0f02af99065825Sean Stout mLastStartWallClockTime = lastWallClockTime; 1166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mRemainingTime = remainingTime; 1176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLabel = label; 1186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mDeleteAfterUse = deleteAfterUse; 1196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public int getId() { return mId; } 1226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public State getState() { return mState; } 1236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public String getLabel() { return mLabel; } 1246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public long getLength() { return mLength; } 1256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public long getTotalLength() { return mTotalLength; } 1266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public boolean getDeleteAfterUse() { return mDeleteAfterUse; } 1276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public boolean isReset() { return mState == RESET; } 1286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public boolean isRunning() { return mState == RUNNING; } 1296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public boolean isPaused() { return mState == PAUSED; } 1306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public boolean isExpired() { return mState == EXPIRED; } 1312a07ae3286fd5c76f71546890e0f02af99065825Sean Stout public boolean isMissed() { return mState == MISSED; } 1326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 1340b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen * @return the amount of remaining time when the timer was last started or paused. 1350b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen */ 1360b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen public long getLastRemainingTime() { 1370b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen return mRemainingTime; 1380b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen } 1390b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen 1400b7dd7c94ad010d5702d252c8b2d8a14d98886e2Justin Klaassen /** 1412a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * @return the total amount of time remaining up to this moment; expired and missed timers will 1424eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux * return a negative amount 1436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 1446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public long getRemainingTime() { 1454eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux if (mState == PAUSED || mState == RESET) { 1464eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux return mRemainingTime; 1476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1494eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux // In practice, "now" can be any value due to device reboots. When the real-time clock 1504eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux // is reset, there is no more guarantee that "now" falls after the last start time. To 1514eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux // ensure the timer is monotonically decreasing, normalize negative time segments to 0, 1524eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux final long timeSinceStart = now() - mLastStartTime; 1534eabf334b12b04ba941fa242d28a15a9ee8e172fJames Lemieux return mRemainingTime - Math.max(0, timeSinceStart); 1546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 1578d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen * @return the elapsed realtime at which this timer will or did expire 1586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 1596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public long getExpirationTime() { 1602a07ae3286fd5c76f71546890e0f02af99065825Sean Stout if (mState != RUNNING && mState != EXPIRED && mState != MISSED) { 1616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux throw new IllegalStateException("cannot compute expiration time in state " + mState); 1626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return mLastStartTime + mRemainingTime; 1656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 1688d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen * @return the wall clock time at which this timer will or did expire 1698d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen */ 1708d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen public long getWallClockExpirationTime() { 1718d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen if (mState != RUNNING && mState != EXPIRED && mState != MISSED) { 1728d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen throw new IllegalStateException("cannot compute expiration time in state " + mState); 1738d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen } 1748d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen 1758d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen return mLastStartWallClockTime + mRemainingTime; 1768d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen } 1778d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen 1788d751a7c017ee01b5a0ac897cfc4671e5f7193efJustin Klaassen /** 1796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 1806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * @return the total amount of time elapsed up to this moment; expired timers will report more 1816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * than the {@link #getTotalLength() total length} 1826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 1836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public long getElapsedTime() { 1846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return getTotalLength() - getRemainingTime(); 1856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux long getLastStartTime() { return mLastStartTime; } 1882a07ae3286fd5c76f71546890e0f02af99065825Sean Stout long getLastWallClockTime() { return mLastStartWallClockTime; } 1896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 1912a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * @return a copy of this timer that is running, expired or missed 1926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 1936d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux Timer start() { 1942a07ae3286fd5c76f71546890e0f02af99065825Sean Stout if (mState == RUNNING || mState == EXPIRED || mState == MISSED) { 1956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return this; 1966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1982a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, RUNNING, mLength, mTotalLength, now(), wallClock(), mRemainingTime, 1992a07ae3286fd5c76f71546890e0f02af99065825Sean Stout mLabel, mDeleteAfterUse); 2006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 2026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 2036d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * @return a copy of this timer that is paused or reset 2046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 2056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux Timer pause() { 2066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (mState == PAUSED || mState == RESET) { 2076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return this; 2082a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } else if (mState == EXPIRED || mState == MISSED) { 20906a5775de256f1238909789406ae7bec593b5b42James Lemieux return reset(); 2106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 2126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final long remainingTime = getRemainingTime(); 2132a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, PAUSED, mLength, mTotalLength, UNUSED, UNUSED, remainingTime, mLabel, 2146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mDeleteAfterUse); 2156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 2176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 2182a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * @return a copy of this timer that is expired, missed or reset 2196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 2206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux Timer expire() { 2212a07ae3286fd5c76f71546890e0f02af99065825Sean Stout if (mState == EXPIRED || mState == RESET || mState == MISSED) { 2226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return this; 2236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 225705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen final long remainingTime = Math.min(0L, getRemainingTime()); 226705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen return new Timer(mId, EXPIRED, mLength, 0L, now(), wallClock(), remainingTime, mLabel, 227705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen mDeleteAfterUse); 2282a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } 2292a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 2302a07ae3286fd5c76f71546890e0f02af99065825Sean Stout /** 2312a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * @return a copy of this timer that is missed or reset 2322a07ae3286fd5c76f71546890e0f02af99065825Sean Stout */ 2332a07ae3286fd5c76f71546890e0f02af99065825Sean Stout Timer miss() { 2342a07ae3286fd5c76f71546890e0f02af99065825Sean Stout if (mState == RESET || mState == MISSED) { 2352a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return this; 2362a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } 2372a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 238705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen final long remainingTime = Math.min(0L, getRemainingTime()); 239705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen return new Timer(mId, MISSED, mLength, 0L, now(), wallClock(), remainingTime, mLabel, 240705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen mDeleteAfterUse); 2416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 2436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 2446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * @return a copy of this timer that is reset 2456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 2466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux Timer reset() { 2476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (mState == RESET) { 2486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return this; 2496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 2512a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, RESET, mLength, mLength, UNUSED, UNUSED, mLength, mLabel, 2526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mDeleteAfterUse); 2536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 2546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 2556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 2562a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * @return a copy of this timer that has its times adjusted after a reboot 2572a07ae3286fd5c76f71546890e0f02af99065825Sean Stout */ 2582a07ae3286fd5c76f71546890e0f02af99065825Sean Stout Timer updateAfterReboot() { 2592a07ae3286fd5c76f71546890e0f02af99065825Sean Stout if (mState == RESET || mState == PAUSED) { 2602a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return this; 2612a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } 2622a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 2632a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long timeSinceBoot = now(); 2642a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long wallClockTime = wallClock(); 265a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux // Avoid negative time deltas. They can happen in practice, but they can't be used. Simply 266a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux // update the recorded times and proceed with no change in accumulated time. 267a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux final long delta = Math.max(0, wallClockTime - mLastStartWallClockTime); 2682a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long remainingTime = mRemainingTime - delta; 2692a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, mState, mLength, mTotalLength, timeSinceBoot, wallClockTime, 2702a07ae3286fd5c76f71546890e0f02af99065825Sean Stout remainingTime, mLabel, mDeleteAfterUse); 2712a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } 2722a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 2732a07ae3286fd5c76f71546890e0f02af99065825Sean Stout /** 2742a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * @return a copy of this timer that has its times adjusted after time has been set 2752a07ae3286fd5c76f71546890e0f02af99065825Sean Stout */ 2762a07ae3286fd5c76f71546890e0f02af99065825Sean Stout Timer updateAfterTimeSet() { 2772a07ae3286fd5c76f71546890e0f02af99065825Sean Stout if (mState == RESET || mState == PAUSED) { 2782a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return this; 2792a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } 2802a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 2812a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long timeSinceBoot = now(); 2822a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long wallClockTime = wallClock(); 2832a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long delta = timeSinceBoot - mLastStartTime; 2842a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long remainingTime = mRemainingTime - delta; 2852191a20c11e972ebe0906e163ec9df614e99daf3Sean Stout if (delta < 0) { 286a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux // Avoid negative time deltas. They typically happen following reboots when TIME_SET is 287a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux // broadcast before BOOT_COMPLETED. Simply ignore the time update and hope 288a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux // updateAfterReboot() can successfully correct the data at a later time. 2892bd7fb4d346a1a116f0f1a58f17722f2286cdcdcSean Stout return this; 2902bd7fb4d346a1a116f0f1a58f17722f2286cdcdcSean Stout } 2912a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, mState, mLength, mTotalLength, timeSinceBoot, wallClockTime, 2922a07ae3286fd5c76f71546890e0f02af99065825Sean Stout remainingTime, mLabel, mDeleteAfterUse); 2932a07ae3286fd5c76f71546890e0f02af99065825Sean Stout } 2942a07ae3286fd5c76f71546890e0f02af99065825Sean Stout 2952a07ae3286fd5c76f71546890e0f02af99065825Sean Stout /** 2966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * @return a copy of this timer with the given {@code label} 2976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 2986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux Timer setLabel(String label) { 2996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (TextUtils.equals(mLabel, label)) { 3006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return this; 3016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3032a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, mState, mLength, mTotalLength, mLastStartTime, 3042a07ae3286fd5c76f71546890e0f02af99065825Sean Stout mLastStartWallClockTime, mRemainingTime, label, mDeleteAfterUse); 3056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 30858d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux * @return a copy of this timer with the given {@code length} or this timer if the length could 30958d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux * not be legally adjusted 3106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 311705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen Timer setLength(long length) { 31258d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux if (mLength == length || length <= Timer.MIN_LENGTH) { 313705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen return this; 314705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen } 315705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen 316705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen final long totalLength; 317705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen final long remainingTime; 318705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen if (mState == RESET) { 319705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen totalLength = length; 320705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen remainingTime = length; 321705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen } else { 322705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen totalLength = mTotalLength; 323705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen remainingTime = mRemainingTime; 324705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen } 325705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen 326705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen return new Timer(mId, mState, length, totalLength, mLastStartTime, 327705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen mLastStartWallClockTime, remainingTime, mLabel, mDeleteAfterUse); 328705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen } 329705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen 330705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen /** 33158d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux * @return a copy of this timer with the given {@code remainingTime} or this timer if the 33258d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux * remaining time could not be legally adjusted 333705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen */ 334705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen Timer setRemainingTime(long remainingTime) { 33558d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux // Do not change the remaining time of a reset timer. 33658d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux if (mRemainingTime == remainingTime || mState == RESET) { 337705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen return this; 338705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen } 339705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen 340705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen final long delta = remainingTime - mRemainingTime; 341705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen final long totalLength = mTotalLength + delta; 342705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen 3436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final long lastStartTime; 3442a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final long lastWallClockTime; 3456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final State state; 346705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen if (remainingTime > 0 && (mState == EXPIRED || mState == MISSED)) { 3476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux state = RUNNING; 3486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux lastStartTime = now(); 3492a07ae3286fd5c76f71546890e0f02af99065825Sean Stout lastWallClockTime = wallClock(); 3506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } else { 3516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux state = mState; 3526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux lastStartTime = mLastStartTime; 3532a07ae3286fd5c76f71546890e0f02af99065825Sean Stout lastWallClockTime = mLastStartWallClockTime; 3546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3562a07ae3286fd5c76f71546890e0f02af99065825Sean Stout return new Timer(mId, state, mLength, totalLength, lastStartTime, 3572a07ae3286fd5c76f71546890e0f02af99065825Sean Stout lastWallClockTime, remainingTime, mLabel, mDeleteAfterUse); 3586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 360705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen /** 361705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen * @return a copy of this timer with an additional minute added to the remaining time and total 36258d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux * length, or this Timer if the minute could not be added 363705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen */ 364705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen Timer addMinute() { 36558d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux // Expired and missed timers restart with 60 seconds of remaining time. 36658d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux if (mState == EXPIRED || mState == MISSED) { 36758d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux return setRemainingTime(MINUTE_IN_MILLIS); 36858d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux } 36958d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux 37058d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux // Otherwise try to add a minute to the remaining time. 37158d9315aed4f645eb60c22be117b074e18c0982fJames Lemieux return setRemainingTime(mRemainingTime + MINUTE_IN_MILLIS); 372705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen } 373705928dc4b0ed61e28d7f2807d5af3aaa9075806Justin Klaassen 3746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux @Override 3756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public boolean equals(Object o) { 3766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (this == o) return true; 3776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (o == null || getClass() != o.getClass()) return false; 3786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final Timer timer = (Timer) o; 3806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return mId == timer.mId; 3826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux @Override 3856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public int hashCode() { 3866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return mId; 3876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 3906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Orders timers by their IDs. Oldest timers are at the bottom. Newest timers are at the top. 3916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 392a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux static Comparator<Timer> ID_COMPARATOR = new Comparator<Timer>() { 3936d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux @Override 3946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public int compare(Timer timer1, Timer timer2) { 3956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return Integer.compare(timer2.getId(), timer1.getId()); 3966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 3976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux }; 3986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 4006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Orders timers by their expected/actual expiration time. The general order is: 4016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 4026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * <ol> 4032a07ae3286fd5c76f71546890e0f02af99065825Sean Stout * <li>{@link State#MISSED MISSED} timers; ties broken by {@link #getRemainingTime()}</li> 4046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * <li>{@link State#EXPIRED EXPIRED} timers; ties broken by {@link #getRemainingTime()}</li> 4056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * <li>{@link State#RUNNING RUNNING} timers; ties broken by {@link #getRemainingTime()}</li> 4066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * <li>{@link State#PAUSED PAUSED} timers; ties broken by {@link #getRemainingTime()}</li> 4076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * <li>{@link State#RESET RESET} timers; ties broken by {@link #getLength()}</li> 4086d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * </ol> 4096d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 410a115910142e42d26076e4c77731cf10fbe571ad9James Lemieux static Comparator<Timer> EXPIRY_COMPARATOR = new Comparator<Timer>() { 4116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 4122a07ae3286fd5c76f71546890e0f02af99065825Sean Stout private final List<State> stateExpiryOrder = Arrays.asList(MISSED, EXPIRED, RUNNING, PAUSED, 4132a07ae3286fd5c76f71546890e0f02af99065825Sean Stout RESET); 4146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 4156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux @Override 4166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public int compare(Timer timer1, Timer timer2) { 4176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final int stateIndex1 = stateExpiryOrder.indexOf(timer1.getState()); 4186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final int stateIndex2 = stateExpiryOrder.indexOf(timer2.getState()); 4196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 4206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux int order = Integer.compare(stateIndex1, stateIndex2); 4216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (order == 0) { 4226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final State state = timer1.getState(); 4236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (state == RESET) { 4246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux order = Long.compare(timer1.getLength(), timer2.getLength()); 4256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } else { 4266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux order = Long.compare(timer1.getRemainingTime(), timer2.getRemainingTime()); 4276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 4286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 4296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 4306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux return order; 4316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 4326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux }; 4332bd7fb4d346a1a116f0f1a58f17722f2286cdcdcSean Stout} 434