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