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