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 
21 import static com.android.deskclock.data.Stopwatch.State.PAUSED;
22 import static com.android.deskclock.data.Stopwatch.State.RESET;
23 import static com.android.deskclock.data.Stopwatch.State.RUNNING;
24 
25 /**
26  * A read-only domain object representing a stopwatch.
27  */
28 public final class Stopwatch {
29 
30     public enum State { RESET, RUNNING, PAUSED }
31 
32     /** The single, immutable instance of a reset stopwatch. */
33     private static final Stopwatch RESET_STOPWATCH = new Stopwatch(RESET, Long.MIN_VALUE, 0);
34 
35     /** Current state of this stopwatch. */
36     private final State mState;
37 
38     /** Elapsed time in ms the stopwatch was last started; {@link Long#MIN_VALUE} if not running. */
39     private final long mLastStartTime;
40 
41     /** Elapsed time in ms this stopwatch has accumulated while running. */
42     private final long mAccumulatedTime;
43 
Stopwatch(State state, long lastStartTime, long accumulatedTime)44     Stopwatch(State state, long lastStartTime, long accumulatedTime) {
45         mState = state;
46         mLastStartTime = lastStartTime;
47         mAccumulatedTime = accumulatedTime;
48     }
49 
getState()50     public State getState() { return mState; }
getLastStartTime()51     public long getLastStartTime() { return mLastStartTime; }
isReset()52     public boolean isReset() { return mState == RESET; }
isPaused()53     public boolean isPaused() { return mState == PAUSED; }
isRunning()54     public boolean isRunning() { return mState == RUNNING; }
55 
56     /**
57      * @return the total amount of time accumulated up to this moment
58      */
getTotalTime()59     public long getTotalTime() {
60         if (mState != RUNNING) {
61             return mAccumulatedTime;
62         }
63 
64         // In practice, "now" can be any value due to device reboots. When the real-time clock
65         // is reset, there is no more guarantee that "now" falls after the last start time. To
66         // ensure the stopwatch is monotonically increasing, normalize negative time segments to 0,
67         final long timeSinceStart = now() - mLastStartTime;
68         return mAccumulatedTime + Math.max(0, timeSinceStart);
69     }
70 
71     /**
72      * @return the amount of time accumulated up to the last time the stopwatch was started
73      */
getAccumulatedTime()74     long getAccumulatedTime() {
75         return mAccumulatedTime;
76     }
77 
78     /**
79      * @return a copy of this stopwatch that is running
80      */
start()81     Stopwatch start() {
82         if (mState == RUNNING) {
83             return this;
84         }
85 
86         return new Stopwatch(RUNNING, now(), getTotalTime());
87     }
88 
89     /**
90      * @return a copy of this stopwatch that is paused
91      */
pause()92     Stopwatch pause() {
93         if (mState != RUNNING) {
94             return this;
95         }
96 
97         return new Stopwatch(PAUSED, Long.MIN_VALUE, getTotalTime());
98     }
99 
100     /**
101      * @return a copy of this stopwatch that is reset
102      */
reset()103     Stopwatch reset() {
104         return RESET_STOPWATCH;
105     }
106 
now()107     private static long now() {
108         return SystemClock.elapsedRealtime();
109     }
110 }