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