1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.deskclock.data; 18 19 import android.os.SystemClock; 20 import android.text.TextUtils; 21 22 import java.util.Arrays; 23 import java.util.Comparator; 24 import java.util.List; 25 26 import static android.text.format.DateUtils.HOUR_IN_MILLIS; 27 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 28 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 29 import static com.android.deskclock.data.Timer.State.EXPIRED; 30 import static com.android.deskclock.data.Timer.State.PAUSED; 31 import static com.android.deskclock.data.Timer.State.RESET; 32 import static com.android.deskclock.data.Timer.State.RUNNING; 33 34 /** 35 * A read-only domain object representing a countdown timer. 36 */ 37 public final class Timer { 38 39 public enum State { 40 RUNNING(1), PAUSED(2), EXPIRED(3), RESET(4); 41 42 /** The value assigned to this State in prior releases. */ 43 private final int mValue; 44 State(int value)45 State(int value) { 46 mValue = value; 47 } 48 49 /** 50 * @return the numeric value assigned to this state 51 */ getValue()52 public int getValue() { 53 return mValue; 54 } 55 56 /** 57 * @return the state corresponding to the given {@code value} 58 */ fromValue(int value)59 public static State fromValue(int value) { 60 for (State state : values()) { 61 if (state.getValue() == value) { 62 return state; 63 } 64 } 65 66 return null; 67 } 68 } 69 70 /** The minimum duration of a timer. */ 71 public static final long MIN_LENGTH = SECOND_IN_MILLIS; 72 73 /** The maximum duration of a timer. */ 74 public static final long MAX_LENGTH = 75 99 * HOUR_IN_MILLIS + 99 * MINUTE_IN_MILLIS + 99 * SECOND_IN_MILLIS; 76 77 /** A unique identifier for the city. */ 78 private final int mId; 79 80 /** The current state of the timer. */ 81 private final State mState; 82 83 /** The original length of the timer in milliseconds when it was created. */ 84 private final long mLength; 85 86 /** The length of the timer in milliseconds including additional time added by the user. */ 87 private final long mTotalLength; 88 89 /** The time at which the timer was last started; {@link Long#MIN_VALUE} when not running. */ 90 private final long mLastStartTime; 91 92 /** The time at which the timer is scheduled to expire; negative if it is already expired. */ 93 private final long mRemainingTime; 94 95 /** A message describing the meaning of the timer. */ 96 private final String mLabel; 97 98 /** A flag indicating the timer should be deleted when it is reset. */ 99 private final boolean mDeleteAfterUse; 100 Timer(int id, State state, long length, long totalLength, long lastStartTime, long remainingTime, String label, boolean deleteAfterUse)101 Timer(int id, State state, long length, long totalLength, long lastStartTime, 102 long remainingTime, String label, boolean deleteAfterUse) { 103 mId = id; 104 mState = state; 105 mLength = length; 106 mTotalLength = totalLength; 107 mLastStartTime = lastStartTime; 108 mRemainingTime = remainingTime; 109 mLabel = label; 110 mDeleteAfterUse = deleteAfterUse; 111 } 112 getId()113 public int getId() { return mId; } getState()114 public State getState() { return mState; } getLabel()115 public String getLabel() { return mLabel; } getLength()116 public long getLength() { return mLength; } getTotalLength()117 public long getTotalLength() { return mTotalLength; } getDeleteAfterUse()118 public boolean getDeleteAfterUse() { return mDeleteAfterUse; } isReset()119 public boolean isReset() { return mState == RESET; } isRunning()120 public boolean isRunning() { return mState == RUNNING; } isPaused()121 public boolean isPaused() { return mState == PAUSED; } isExpired()122 public boolean isExpired() { return mState == EXPIRED; } 123 124 /** 125 * @return the total amount of time remaining up to this moment; expired timers will return a 126 * negative amount 127 */ getRemainingTime()128 public long getRemainingTime() { 129 if (mState == RUNNING || mState == EXPIRED) { 130 return mRemainingTime - (now() - mLastStartTime); 131 } 132 133 return mRemainingTime; 134 } 135 136 /** 137 * @return the time at which this timer will or did expire 138 */ getExpirationTime()139 public long getExpirationTime() { 140 if (mState != RUNNING && mState != EXPIRED) { 141 throw new IllegalStateException("cannot compute expiration time in state " + mState); 142 } 143 144 return mLastStartTime + mRemainingTime; 145 } 146 147 /** 148 * 149 * @return the total amount of time elapsed up to this moment; expired timers will report more 150 * than the {@link #getTotalLength() total length} 151 */ getElapsedTime()152 public long getElapsedTime() { 153 return getTotalLength() - getRemainingTime(); 154 } 155 getLastStartTime()156 long getLastStartTime() { return mLastStartTime; } 157 158 /** 159 * @return a copy of this timer that is running or expired 160 */ start()161 Timer start() { 162 if (mState == RUNNING || mState == EXPIRED) { 163 return this; 164 } 165 166 return new Timer(mId, RUNNING, mLength, mTotalLength, now(), mRemainingTime, mLabel, 167 mDeleteAfterUse); 168 } 169 170 /** 171 * @return a copy of this timer that is paused or reset 172 */ pause()173 Timer pause() { 174 if (mState == PAUSED || mState == RESET) { 175 return this; 176 } else if (mState == EXPIRED) { 177 return reset(); 178 } 179 180 final long remainingTime = getRemainingTime(); 181 return new Timer(mId, PAUSED, mLength, mTotalLength, Long.MIN_VALUE, remainingTime, mLabel, 182 mDeleteAfterUse); 183 } 184 185 /** 186 * @return a copy of this timer that is expired or reset 187 */ expire()188 Timer expire() { 189 if (mState == EXPIRED || mState == RESET) { 190 return this; 191 } 192 193 return new Timer(mId, EXPIRED, mLength, mTotalLength, mLastStartTime, mRemainingTime, 194 mLabel, mDeleteAfterUse); 195 } 196 197 /** 198 * @return a copy of this timer that is reset 199 */ reset()200 Timer reset() { 201 if (mState == RESET) { 202 return this; 203 } 204 205 return new Timer(mId, RESET, mLength, mLength, Long.MIN_VALUE, mLength, mLabel, 206 mDeleteAfterUse); 207 } 208 209 /** 210 * @return a copy of this timer with the given {@code label} 211 */ setLabel(String label)212 Timer setLabel(String label) { 213 if (TextUtils.equals(mLabel, label)) { 214 return this; 215 } 216 217 return new Timer(mId, mState, mLength, mTotalLength, mLastStartTime, mRemainingTime, label, 218 mDeleteAfterUse); 219 } 220 221 /** 222 * @return a copy of this timer with an additional minute added to the remaining time and total 223 * length, or this Timer if adding a minute would exceed the maximum timer duration 224 */ addMinute()225 Timer addMinute() { 226 final long lastStartTime; 227 final long remainingTime; 228 final long totalLength; 229 final State state; 230 if (mState == EXPIRED) { 231 state = RUNNING; 232 lastStartTime = now(); 233 totalLength = MINUTE_IN_MILLIS; 234 remainingTime = MINUTE_IN_MILLIS; 235 } else { 236 state = mState; 237 lastStartTime = mLastStartTime; 238 totalLength = mRemainingTime + MINUTE_IN_MILLIS; 239 remainingTime = mRemainingTime + MINUTE_IN_MILLIS; 240 } 241 242 // Do not allow the remaining time to exceed the maximum. 243 if (remainingTime > MAX_LENGTH) { 244 return this; 245 } 246 247 return new Timer(mId, state, mLength, totalLength, lastStartTime, remainingTime, mLabel, 248 mDeleteAfterUse); 249 } 250 251 @Override equals(Object o)252 public boolean equals(Object o) { 253 if (this == o) return true; 254 if (o == null || getClass() != o.getClass()) return false; 255 256 final Timer timer = (Timer) o; 257 258 return mId == timer.mId; 259 260 } 261 262 @Override hashCode()263 public int hashCode() { 264 return mId; 265 } 266 now()267 private static long now() { 268 return SystemClock.elapsedRealtime(); 269 } 270 271 /** 272 * Orders timers by their IDs. Oldest timers are at the bottom. Newest timers are at the top. 273 */ 274 public static Comparator<Timer> ID_COMPARATOR = new Comparator<Timer>() { 275 @Override 276 public int compare(Timer timer1, Timer timer2) { 277 return Integer.compare(timer2.getId(), timer1.getId()); 278 } 279 }; 280 281 /** 282 * Orders timers by their expected/actual expiration time. The general order is: 283 * 284 * <ol> 285 * <li>{@link State#EXPIRED EXPIRED} timers; ties broken by {@link #getRemainingTime()}</li> 286 * <li>{@link State#RUNNING RUNNING} timers; ties broken by {@link #getRemainingTime()}</li> 287 * <li>{@link State#PAUSED PAUSED} timers; ties broken by {@link #getRemainingTime()}</li> 288 * <li>{@link State#RESET RESET} timers; ties broken by {@link #getLength()}</li> 289 * </ol> 290 */ 291 public static Comparator<Timer> EXPIRY_COMPARATOR = new Comparator<Timer>() { 292 293 private final List<State> stateExpiryOrder = Arrays.asList(EXPIRED, RUNNING, PAUSED, RESET); 294 295 @Override 296 public int compare(Timer timer1, Timer timer2) { 297 final int stateIndex1 = stateExpiryOrder.indexOf(timer1.getState()); 298 final int stateIndex2 = stateExpiryOrder.indexOf(timer2.getState()); 299 300 int order = Integer.compare(stateIndex1, stateIndex2); 301 if (order == 0) { 302 final State state = timer1.getState(); 303 if (state == RESET) { 304 order = Long.compare(timer1.getLength(), timer2.getLength()); 305 } else { 306 order = Long.compare(timer1.getRemainingTime(), timer2.getRemainingTime()); 307 } 308 } 309 310 return order; 311 } 312 }; 313 }