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 
17 package 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  */
44 public 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      */
CountDownTimer(long millisInFuture, long countDownInterval)70     public CountDownTimer(long millisInFuture, long countDownInterval) {
71         mMillisInFuture = millisInFuture;
72         mCountdownInterval = countDownInterval;
73     }
74 
75     /**
76      * Cancel the countdown.
77      */
cancel()78     public synchronized final void cancel() {
79         mCancelled = true;
80         mHandler.removeMessages(MSG);
81     }
82 
83     /**
84      * Start the countdown.
85      */
start()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      */
onTick(long millisUntilFinished)102     public abstract void onTick(long millisUntilFinished);
103 
104     /**
105      * Callback fired when the time is up.
106      */
onFinish()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 {
129                     long lastTickStart = SystemClock.elapsedRealtime();
130                     onTick(millisLeft);
131 
132                     // take into account user's onTick taking time to execute
133                     long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
134                     long delay;
135 
136                     if (millisLeft < mCountdownInterval) {
137                         // just delay until done
138                         delay = millisLeft - lastTickDuration;
139 
140                         // special case: user's onTick took more than interval to
141                         // complete, trigger onFinish without delay
142                         if (delay < 0) delay = 0;
143                     } else {
144                         delay = mCountdownInterval - lastTickDuration;
145 
146                         // special case: user's onTick took more than interval to
147                         // complete, skip to next interval
148                         while (delay < 0) delay += mCountdownInterval;
149                     }
150 
151                     sendMessageDelayed(obtainMessage(MSG), delay);
152                 }
153             }
154         }
155     };
156 }
157