Home | History | Annotate | Download | only in data
      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 static com.android.deskclock.Utils.now;
     20 import static com.android.deskclock.Utils.wallClock;
     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     static final long UNUSED = Long.MIN_VALUE;
     33 
     34     /** The single, immutable instance of a reset stopwatch. */
     35     private static final Stopwatch RESET_STOPWATCH = new Stopwatch(RESET, UNUSED, UNUSED, 0);
     36 
     37     /** Current state of this stopwatch. */
     38     private final State mState;
     39 
     40     /** Elapsed time in ms the stopwatch was last started; {@link #UNUSED} if not running. */
     41     private final long mLastStartTime;
     42 
     43     /** The time since epoch at which the stopwatch was last started. */
     44     private final long mLastStartWallClockTime;
     45 
     46     /** Elapsed time in ms this stopwatch has accumulated while running. */
     47     private final long mAccumulatedTime;
     48 
     49     Stopwatch(State state, long lastStartTime, long lastWallClockTime, long accumulatedTime) {
     50         mState = state;
     51         mLastStartTime = lastStartTime;
     52         mLastStartWallClockTime = lastWallClockTime;
     53         mAccumulatedTime = accumulatedTime;
     54     }
     55 
     56     public State getState() { return mState; }
     57     public long getLastStartTime() { return mLastStartTime; }
     58     public long getLastWallClockTime() { return mLastStartWallClockTime; }
     59     public boolean isReset() { return mState == RESET; }
     60     public boolean isPaused() { return mState == PAUSED; }
     61     public boolean isRunning() { return mState == RUNNING; }
     62 
     63     /**
     64      * @return the total amount of time accumulated up to this moment
     65      */
     66     public long getTotalTime() {
     67         if (mState != RUNNING) {
     68             return mAccumulatedTime;
     69         }
     70 
     71         // In practice, "now" can be any value due to device reboots. When the real-time clock
     72         // is reset, there is no more guarantee that "now" falls after the last start time. To
     73         // ensure the stopwatch is monotonically increasing, normalize negative time segments to 0,
     74         final long timeSinceStart = now() - mLastStartTime;
     75         return mAccumulatedTime + Math.max(0, timeSinceStart);
     76     }
     77 
     78     /**
     79      * @return the amount of time accumulated up to the last time the stopwatch was started
     80      */
     81     public long getAccumulatedTime() {
     82         return mAccumulatedTime;
     83     }
     84 
     85     /**
     86      * @return a copy of this stopwatch that is running
     87      */
     88     Stopwatch start() {
     89         if (mState == RUNNING) {
     90             return this;
     91         }
     92 
     93         return new Stopwatch(RUNNING, now(), wallClock(), getTotalTime());
     94     }
     95 
     96     /**
     97      * @return a copy of this stopwatch that is paused
     98      */
     99     Stopwatch pause() {
    100         if (mState != RUNNING) {
    101             return this;
    102         }
    103 
    104         return new Stopwatch(PAUSED, UNUSED, UNUSED, getTotalTime());
    105     }
    106 
    107     /**
    108      * @return a copy of this stopwatch that is reset
    109      */
    110     Stopwatch reset() {
    111         return RESET_STOPWATCH;
    112     }
    113 
    114     /**
    115      * @return this Stopwatch if it is not running or an updated version based on wallclock time.
    116      *      The internals of the stopwatch are updated using the wallclock time which is durable
    117      *      across reboots.
    118      */
    119     Stopwatch updateAfterReboot() {
    120         if (mState != RUNNING) {
    121             return this;
    122         }
    123         final long timeSinceBoot = now();
    124         final long wallClockTime = wallClock();
    125         // Avoid negative time deltas. They can happen in practice, but they can't be used. Simply
    126         // update the recorded times and proceed with no change in accumulated time.
    127         final long delta = Math.max(0, wallClockTime - mLastStartWallClockTime);
    128         return new Stopwatch(mState, timeSinceBoot, wallClockTime, mAccumulatedTime + delta);
    129     }
    130 
    131     /**
    132      * @return this Stopwatch if it is not running or an updated version based on the realtime.
    133      *      The internals of the stopwatch are updated using the realtime clock which is accurate
    134      *      across wallclock time adjustments.
    135      */
    136     Stopwatch updateAfterTimeSet() {
    137         if (mState != RUNNING) {
    138             return this;
    139         }
    140         final long timeSinceBoot = now();
    141         final long wallClockTime = wallClock();
    142         final long delta = timeSinceBoot - mLastStartTime;
    143         if (delta < 0) {
    144             // Avoid negative time deltas. They typically happen following reboots when TIME_SET is
    145             // broadcast before BOOT_COMPLETED. Simply ignore the time update and hope
    146             // updateAfterReboot() can successfully correct the data at a later time.
    147             return this;
    148         }
    149         return new Stopwatch(mState, timeSinceBoot, wallClockTime, mAccumulatedTime + delta);
    150     }
    151 }