1/*
2 * Copyright (C) 2008 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
17package android.os;
18
19/**
20 * Schedule a countdown until a time in the future, with
21 * regular notifications on intervals along the way.
22 *
23 * Example of showing a 30 second countdown in a text field:
24 *
25 * <pre class="prettyprint">
26 * new CountDownTimer(30000, 1000) {
27 *
28 *     public void onTick(long millisUntilFinished) {
29 *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
30 *     }
31 *
32 *     public void onFinish() {
33 *         mTextField.setText("done!");
34 *     }
35 *  }.start();
36 * </pre>
37 *
38 * The calls to {@link #onTick(long)} are synchronized to this object so that
39 * one call to {@link #onTick(long)} won't ever occur before the previous
40 * callback is complete.  This is only relevant when the implementation of
41 * {@link #onTick(long)} takes an amount of time to execute that is significant
42 * compared to the countdown interval.
43 */
44public abstract class CountDownTimer {
45
46    /**
47     * Millis since epoch when alarm should stop.
48     */
49    private final long mMillisInFuture;
50
51    /**
52     * The interval in millis that the user receives callbacks
53     */
54    private final long mCountdownInterval;
55
56    private long mStopTimeInFuture;
57
58    /**
59    * boolean representing if the timer was cancelled
60    */
61    private boolean mCancelled = false;
62
63    /**
64     * @param millisInFuture The number of millis in the future from the call
65     *   to {@link #start()} until the countdown is done and {@link #onFinish()}
66     *   is called.
67     * @param countDownInterval The interval along the way to receive
68     *   {@link #onTick(long)} callbacks.
69     */
70    public CountDownTimer(long millisInFuture, long countDownInterval) {
71        mMillisInFuture = millisInFuture;
72        mCountdownInterval = countDownInterval;
73    }
74
75    /**
76     * Cancel the countdown.
77     */
78    public synchronized final void cancel() {
79        mCancelled = true;
80        mHandler.removeMessages(MSG);
81    }
82
83    /**
84     * Start the countdown.
85     */
86    public synchronized final CountDownTimer start() {
87        mCancelled = false;
88        if (mMillisInFuture <= 0) {
89            onFinish();
90            return this;
91        }
92        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
93        mHandler.sendMessage(mHandler.obtainMessage(MSG));
94        return this;
95    }
96
97
98    /**
99     * Callback fired on regular interval.
100     * @param millisUntilFinished The amount of time until finished.
101     */
102    public abstract void onTick(long millisUntilFinished);
103
104    /**
105     * Callback fired when the time is up.
106     */
107    public abstract void onFinish();
108
109
110    private static final int MSG = 1;
111
112
113    // handles counting down
114    private Handler mHandler = new Handler() {
115
116        @Override
117        public void handleMessage(Message msg) {
118
119            synchronized (CountDownTimer.this) {
120                if (mCancelled) {
121                    return;
122                }
123
124                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
125
126                if (millisLeft <= 0) {
127                    onFinish();
128                } else if (millisLeft < mCountdownInterval) {
129                    // no tick, just delay until done
130                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
131                } else {
132                    long lastTickStart = SystemClock.elapsedRealtime();
133                    onTick(millisLeft);
134
135                    // take into account user's onTick taking time to execute
136                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
137
138                    // special case: user's onTick took more than interval to
139                    // complete, skip to next interval
140                    while (delay < 0) delay += mCountdownInterval;
141
142                    sendMessageDelayed(obtainMessage(MSG), delay);
143                }
144            }
145        }
146    };
147}
148