Chronometer.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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.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.pim.DateUtils; 26import android.util.AttributeSet; 27import android.util.Log; 28import android.widget.RemoteViews.RemoteView; 29 30import java.util.Formatter; 31import java.util.IllegalFormatException; 32import 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 46public class Chronometer extends TextView { 47 private static final String TAG = "Chronometer"; 48 49 private long mBase; 50 private boolean mVisible; 51 private boolean mStarted; 52 private boolean mRunning; 53 private boolean mLogged; 54 private String mFormat; 55 private Formatter mFormatter; 56 private Locale mFormatterLocale; 57 private Object[] mFormatterArgs = new Object[1]; 58 private StringBuilder mFormatBuilder; 59 60 /** 61 * Initialize this Chronometer object. 62 * Sets the base to the current time. 63 */ 64 public Chronometer(Context context) { 65 this(context, null, 0); 66 } 67 68 /** 69 * Initialize with standard view layout information. 70 * Sets the base to the current time. 71 */ 72 public Chronometer(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 76 /** 77 * Initialize with standard view layout information and style. 78 * Sets the base to the current time. 79 */ 80 public Chronometer(Context context, AttributeSet attrs, int defStyle) { 81 super(context, attrs, defStyle); 82 83 TypedArray a = context.obtainStyledAttributes( 84 attrs, 85 com.android.internal.R.styleable.Chronometer, defStyle, 0); 86 setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format)); 87 a.recycle(); 88 89 init(); 90 } 91 92 private void init() { 93 mBase = SystemClock.elapsedRealtime(); 94 updateText(mBase); 95 } 96 97 /** 98 * Set the time that the count-up timer is in reference to. 99 * 100 * @param base Use the {@link SystemClock#elapsedRealtime} time base. 101 */ 102 public void setBase(long base) { 103 mBase = base; 104 updateText(SystemClock.elapsedRealtime()); 105 } 106 107 /** 108 * Return the base time as set through {@link #setBase}. 109 */ 110 public long getBase() { 111 return mBase; 112 } 113 114 /** 115 * Sets the format string used for display. The Chronometer will display 116 * this string, with the first "%s" replaced by the current timer value in 117 * "MM:SS" or "H:MM:SS" form. 118 * 119 * If the format string is null, or if you never call setFormat(), the 120 * Chronometer will simply display the timer value in "MM:SS" or "H:MM:SS" 121 * form. 122 * 123 * @param format the format string. 124 */ 125 public void setFormat(String format) { 126 mFormat = format; 127 if (format != null && mFormatBuilder == null) { 128 mFormatBuilder = new StringBuilder(format.length() * 2); 129 } 130 } 131 132 /** 133 * Returns the current format string as set through {@link #setFormat}. 134 */ 135 public String getFormat() { 136 return mFormat; 137 } 138 139 /** 140 * Start counting up. This does not affect the base as set from {@link #setBase}, just 141 * the view display. 142 * 143 * Chronometer works by regularly scheduling messages to the handler, even when the 144 * Widget is not visible. To make sure resource leaks do not occur, the user should 145 * make sure that each start() call has a reciprocal call to {@link #stop}. 146 */ 147 public void start() { 148 mStarted = true; 149 updateRunning(); 150 } 151 152 /** 153 * Stop counting up. This does not affect the base as set from {@link #setBase}, just 154 * the view display. 155 * 156 * This stops the messages to the handler, effectively releasing resources that would 157 * be held as the chronometer is running, via {@link #start}. 158 */ 159 public void stop() { 160 mStarted = false; 161 updateRunning(); 162 } 163 164 @Override 165 protected void onDetachedFromWindow() { 166 super.onDetachedFromWindow(); 167 mVisible = false; 168 updateRunning(); 169 } 170 171 @Override 172 protected void onWindowVisibilityChanged(int visibility) { 173 super.onWindowVisibilityChanged(visibility); 174 mVisible = visibility == VISIBLE; 175 updateRunning(); 176 } 177 178 private void updateText(long now) { 179 long seconds = now - mBase; 180 seconds /= 1000; 181 String text = DateUtils.formatElapsedTime(seconds); 182 183 if (mFormat != null) { 184 Locale loc = Locale.getDefault(); 185 if (mFormatter == null || !loc.equals(mFormatterLocale)) { 186 mFormatterLocale = loc; 187 mFormatter = new Formatter(mFormatBuilder, loc); 188 } 189 mFormatBuilder.setLength(0); 190 mFormatterArgs[0] = text; 191 try { 192 mFormatter.format(mFormat, mFormatterArgs); 193 text = mFormatBuilder.toString(); 194 } catch (IllegalFormatException ex) { 195 if (!mLogged) { 196 Log.w(TAG, "Illegal format string: " + mFormat); 197 mLogged = true; 198 } 199 } 200 } 201 setText(text); 202 } 203 204 private void updateRunning() { 205 boolean running = mVisible && mStarted; 206 if (running != mRunning) { 207 if (running) { 208 updateText(SystemClock.elapsedRealtime()); 209 mHandler.sendMessageDelayed(Message.obtain(), 1000); 210 } 211 mRunning = running; 212 } 213 } 214 215 private Handler mHandler = new Handler() { 216 public void handleMessage(Message m) { 217 if (mStarted) { 218 updateText(SystemClock.elapsedRealtime()); 219 sendMessageDelayed(Message.obtain(), 1000); 220 } 221 } 222 }; 223} 224