OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / widget / Chronometer.java
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.widget;
18
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.os.Handler;
23 import android.os.Message;
24 import android.os.SystemClock;
25 import android.text.format.DateUtils;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.widget.RemoteViews.RemoteView;
29
30 import java.util.Formatter;
31 import java.util.IllegalFormatException;
32 import java.util.Locale;
33
34 /**
35  * Class that implements a simple timer.
36  * <p>
37  * You can give it a start time in the {@link SystemClock#elapsedRealtime} timebase,
38  * and it counts up from that, or if you don't give it a base time, it will use the
39  * time at which you call {@link #start}.  By default it will display the current
40  * timer value in the form "MM:SS" or "H:MM:SS", or you can use {@link #setFormat}
41  * to format the timer value into an arbitrary string.
42  *
43  * @attr ref android.R.styleable#Chronometer_format
44  */
45 @RemoteView
46 public class Chronometer extends TextView {
47     private static final String TAG = "Chronometer";
48
49     /**
50      * A callback that notifies when the chronometer has incremented on its own.
51      */
52     public interface OnChronometerTickListener {
53
54         /**
55          * Notification that the chronometer has changed.
56          */
57         void onChronometerTick(Chronometer chronometer);
58
59     }
60
61     private long mBase;
62     private boolean mVisible;
63     private boolean mStarted;
64     private boolean mRunning;
65     private boolean mLogged;
66     private String mFormat;
67     private Formatter mFormatter;
68     private Locale mFormatterLocale;
69     private Object[] mFormatterArgs = new Object[1];
70     private StringBuilder mFormatBuilder;
71     private OnChronometerTickListener mOnChronometerTickListener;
72     private StringBuilder mRecycle = new StringBuilder(8);
73     
74     private static final int TICK_WHAT = 2;
75     
76     /**
77      * Initialize this Chronometer object.
78      * Sets the base to the current time.
79      */
80     public Chronometer(Context context) {
81         this(context, null, 0);
82     }
83
84     /**
85      * Initialize with standard view layout information.
86      * Sets the base to the current time.
87      */
88     public Chronometer(Context context, AttributeSet attrs) {
89         this(context, attrs, 0);
90     }
91
92     /**
93      * Initialize with standard view layout information and style.
94      * Sets the base to the current time.
95      */
96     public Chronometer(Context context, AttributeSet attrs, int defStyle) {
97         super(context, attrs, defStyle);
98
99         TypedArray a = context.obtainStyledAttributes(
100                 attrs,
101                 com.android.internal.R.styleable.Chronometer, defStyle, 0);
102         setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format));
103         a.recycle();
104
105         init();
106     }
107
108     private void init() {
109         mBase = SystemClock.elapsedRealtime();
110         updateText(mBase);
111     }
112
113     /**
114      * Set the time that the count-up timer is in reference to.
115      *
116      * @param base Use the {@link SystemClock#elapsedRealtime} time base.
117      */
118     @android.view.RemotableViewMethod
119     public void setBase(long base) {
120         mBase = base;
121         dispatchChronometerTick();
122         updateText(SystemClock.elapsedRealtime());
123     }
124
125     /**
126      * Return the base time as set through {@link #setBase}.
127      */
128     public long getBase() {
129         return mBase;
130     }
131
132     /**
133      * Sets the format string used for display.  The Chronometer will display
134      * this string, with the first "%s" replaced by the current timer value in
135      * "MM:SS" or "H:MM:SS" form.
136      *
137      * If the format string is null, or if you never call setFormat(), the
138      * Chronometer will simply display the timer value in "MM:SS" or "H:MM:SS"
139      * form.
140      *
141      * @param format the format string.
142      */
143     @android.view.RemotableViewMethod
144     public void setFormat(String format) {
145         mFormat = format;
146         if (format != null && mFormatBuilder == null) {
147             mFormatBuilder = new StringBuilder(format.length() * 2);
148         }
149     }
150
151     /**
152      * Returns the current format string as set through {@link #setFormat}.
153      */
154     public String getFormat() {
155         return mFormat;
156     }
157
158     /**
159      * Sets the listener to be called when the chronometer changes.
160      * 
161      * @param listener The listener.
162      */
163     public void setOnChronometerTickListener(OnChronometerTickListener listener) {
164         mOnChronometerTickListener = listener;
165     }
166
167     /**
168      * @return The listener (may be null) that is listening for chronometer change
169      *         events.
170      */
171     public OnChronometerTickListener getOnChronometerTickListener() {
172         return mOnChronometerTickListener;
173     }
174
175     /**
176      * Start counting up.  This does not affect the base as set from {@link #setBase}, just
177      * the view display.
178      * 
179      * Chronometer works by regularly scheduling messages to the handler, even when the 
180      * Widget is not visible.  To make sure resource leaks do not occur, the user should 
181      * make sure that each start() call has a reciprocal call to {@link #stop}. 
182      */
183     public void start() {
184         mStarted = true;
185         updateRunning();
186     }
187
188     /**
189      * Stop counting up.  This does not affect the base as set from {@link #setBase}, just
190      * the view display.
191      * 
192      * This stops the messages to the handler, effectively releasing resources that would
193      * be held as the chronometer is running, via {@link #start}. 
194      */
195     public void stop() {
196         mStarted = false;
197         updateRunning();
198     }
199
200     /**
201      * The same as calling {@link #start} or {@link #stop}.
202      * @hide pending API council approval
203      */
204     @android.view.RemotableViewMethod
205     public void setStarted(boolean started) {
206         mStarted = started;
207         updateRunning();
208     }
209
210     @Override
211     protected void onDetachedFromWindow() {
212         super.onDetachedFromWindow();
213         mVisible = false;
214         updateRunning();
215     }
216
217     @Override
218     protected void onWindowVisibilityChanged(int visibility) {
219         super.onWindowVisibilityChanged(visibility);
220         mVisible = visibility == VISIBLE;
221         updateRunning();
222     }
223
224     private synchronized void updateText(long now) {
225         long seconds = now - mBase;
226         seconds /= 1000;
227         String text = DateUtils.formatElapsedTime(mRecycle, seconds);
228
229         if (mFormat != null) {
230             Locale loc = Locale.getDefault();
231             if (mFormatter == null || !loc.equals(mFormatterLocale)) {
232                 mFormatterLocale = loc;
233                 mFormatter = new Formatter(mFormatBuilder, loc);
234             }
235             mFormatBuilder.setLength(0);
236             mFormatterArgs[0] = text;
237             try {
238                 mFormatter.format(mFormat, mFormatterArgs);
239                 text = mFormatBuilder.toString();
240             } catch (IllegalFormatException ex) {
241                 if (!mLogged) {
242                     Log.w(TAG, "Illegal format string: " + mFormat);
243                     mLogged = true;
244                 }
245             }
246         }
247         setText(text);
248     }
249
250     private void updateRunning() {
251         boolean running = mVisible && mStarted;
252         if (running != mRunning) {
253             if (running) {
254                 updateText(SystemClock.elapsedRealtime());
255                 dispatchChronometerTick();
256                 mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
257             } else {
258                 mHandler.removeMessages(TICK_WHAT);
259             }
260             mRunning = running;
261         }
262     }
263     
264     private Handler mHandler = new Handler() {
265         public void handleMessage(Message m) {
266             if (mRunning) {
267                 updateText(SystemClock.elapsedRealtime());
268                 dispatchChronometerTick();
269                 sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
270             }
271         }
272     };
273
274     void dispatchChronometerTick() {
275         if (mOnChronometerTickListener != null) {
276             mOnChronometerTickListener.onChronometerTick(this);
277         }
278     }
279 }