Home | History | Annotate | Download | only in widget
      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 }
    280