Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2012 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.BroadcastReceiver;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.content.res.TypedArray;
     25 import android.database.ContentObserver;
     26 import android.net.Uri;
     27 import android.os.Handler;
     28 import android.os.SystemClock;
     29 import android.provider.Settings;
     30 import android.text.format.DateFormat;
     31 import android.util.AttributeSet;
     32 import android.view.RemotableViewMethod;
     33 
     34 import com.android.internal.R;
     35 
     36 import java.util.Calendar;
     37 import java.util.TimeZone;
     38 
     39 import libcore.icu.LocaleData;
     40 
     41 import static android.view.ViewDebug.ExportedProperty;
     42 import static android.widget.RemoteViews.*;
     43 
     44 /**
     45  * <p><code>TextClock</code> can display the current date and/or time as
     46  * a formatted string.</p>
     47  *
     48  * <p>This view honors the 24-hour format system setting. As such, it is
     49  * possible and recommended to provide two different formatting patterns:
     50  * one to display the date/time in 24-hour mode and one to display the
     51  * date/time in 12-hour mode. Most callers will want to use the defaults,
     52  * though, which will be appropriate for the user's locale.</p>
     53  *
     54  * <p>It is possible to determine whether the system is currently in
     55  * 24-hour mode by calling {@link #is24HourModeEnabled()}.</p>
     56  *
     57  * <p>The rules used by this widget to decide how to format the date and
     58  * time are the following:</p>
     59  * <ul>
     60  *     <li>In 24-hour mode:
     61  *         <ul>
     62  *             <li>Use the value returned by {@link #getFormat24Hour()} when non-null</li>
     63  *             <li>Otherwise, use the value returned by {@link #getFormat12Hour()} when non-null</li>
     64  *             <li>Otherwise, use a default value appropriate for the user's locale, such as {@code h:mm a}</li>
     65  *         </ul>
     66  *     </li>
     67  *     <li>In 12-hour mode:
     68  *         <ul>
     69  *             <li>Use the value returned by {@link #getFormat12Hour()} when non-null</li>
     70  *             <li>Otherwise, use the value returned by {@link #getFormat24Hour()} when non-null</li>
     71  *             <li>Otherwise, use a default value appropriate for the user's locale, such as {@code HH:mm}</li>
     72  *         </ul>
     73  *     </li>
     74  * </ul>
     75  *
     76  * <p>The {@link CharSequence} instances used as formatting patterns when calling either
     77  * {@link #setFormat24Hour(CharSequence)} or {@link #setFormat12Hour(CharSequence)} can
     78  * contain styling information. To do so, use a {@link android.text.Spanned} object.
     79  * Note that if you customize these strings, it is your responsibility to supply strings
     80  * appropriate for formatting dates and/or times in the user's locale.</p>
     81  *
     82  * @attr ref android.R.styleable#TextClock_format12Hour
     83  * @attr ref android.R.styleable#TextClock_format24Hour
     84  * @attr ref android.R.styleable#TextClock_timeZone
     85  */
     86 @RemoteView
     87 public class TextClock extends TextView {
     88     /**
     89      * The default formatting pattern in 12-hour mode. This pattern is used
     90      * if {@link #setFormat12Hour(CharSequence)} is called with a null pattern
     91      * or if no pattern was specified when creating an instance of this class.
     92      *
     93      * This default pattern shows only the time, hours and minutes, and an am/pm
     94      * indicator.
     95      *
     96      * @see #setFormat12Hour(CharSequence)
     97      * @see #getFormat12Hour()
     98      *
     99      * @deprecated Let the system use locale-appropriate defaults instead.
    100      */
    101     public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
    102 
    103     /**
    104      * The default formatting pattern in 24-hour mode. This pattern is used
    105      * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern
    106      * or if no pattern was specified when creating an instance of this class.
    107      *
    108      * This default pattern shows only the time, hours and minutes.
    109      *
    110      * @see #setFormat24Hour(CharSequence)
    111      * @see #getFormat24Hour()
    112      *
    113      * @deprecated Let the system use locale-appropriate defaults instead.
    114      */
    115     public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm";
    116 
    117     private CharSequence mFormat12;
    118     private CharSequence mFormat24;
    119 
    120     @ExportedProperty
    121     private CharSequence mFormat;
    122     @ExportedProperty
    123     private boolean mHasSeconds;
    124 
    125     private boolean mAttached;
    126 
    127     private Calendar mTime;
    128     private String mTimeZone;
    129 
    130     private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
    131         @Override
    132         public void onChange(boolean selfChange) {
    133             chooseFormat();
    134             onTimeChanged();
    135         }
    136 
    137         @Override
    138         public void onChange(boolean selfChange, Uri uri) {
    139             chooseFormat();
    140             onTimeChanged();
    141         }
    142     };
    143 
    144     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    145         @Override
    146         public void onReceive(Context context, Intent intent) {
    147             if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
    148                 final String timeZone = intent.getStringExtra("time-zone");
    149                 createTime(timeZone);
    150             }
    151             onTimeChanged();
    152         }
    153     };
    154 
    155     private final Runnable mTicker = new Runnable() {
    156         public void run() {
    157             onTimeChanged();
    158 
    159             long now = SystemClock.uptimeMillis();
    160             long next = now + (1000 - now % 1000);
    161 
    162             getHandler().postAtTime(mTicker, next);
    163         }
    164     };
    165 
    166     /**
    167      * Creates a new clock using the default patterns for the current locale.
    168      *
    169      * @param context The Context the view is running in, through which it can
    170      *        access the current theme, resources, etc.
    171      */
    172     @SuppressWarnings("UnusedDeclaration")
    173     public TextClock(Context context) {
    174         super(context);
    175         init();
    176     }
    177 
    178     /**
    179      * Creates a new clock inflated from XML. This object's properties are
    180      * intialized from the attributes specified in XML.
    181      *
    182      * This constructor uses a default style of 0, so the only attribute values
    183      * applied are those in the Context's Theme and the given AttributeSet.
    184      *
    185      * @param context The Context the view is running in, through which it can
    186      *        access the current theme, resources, etc.
    187      * @param attrs The attributes of the XML tag that is inflating the view
    188      */
    189     @SuppressWarnings("UnusedDeclaration")
    190     public TextClock(Context context, AttributeSet attrs) {
    191         this(context, attrs, 0);
    192     }
    193 
    194     /**
    195      * Creates a new clock inflated from XML. This object's properties are
    196      * intialized from the attributes specified in XML.
    197      *
    198      * @param context The Context the view is running in, through which it can
    199      *        access the current theme, resources, etc.
    200      * @param attrs The attributes of the XML tag that is inflating the view
    201      * @param defStyle The default style to apply to this view. If 0, no style
    202      *        will be applied (beyond what is included in the theme). This may
    203      *        either be an attribute resource, whose value will be retrieved
    204      *        from the current theme, or an explicit style resource
    205      */
    206     public TextClock(Context context, AttributeSet attrs, int defStyle) {
    207         super(context, attrs, defStyle);
    208 
    209         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextClock, defStyle, 0);
    210         try {
    211             mFormat12 = a.getText(R.styleable.TextClock_format12Hour);
    212             mFormat24 = a.getText(R.styleable.TextClock_format24Hour);
    213             mTimeZone = a.getString(R.styleable.TextClock_timeZone);
    214         } finally {
    215             a.recycle();
    216         }
    217 
    218         init();
    219     }
    220 
    221     private void init() {
    222         if (mFormat12 == null || mFormat24 == null) {
    223             LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale);
    224             if (mFormat12 == null) {
    225                 mFormat12 = ld.timeFormat12;
    226             }
    227             if (mFormat24 == null) {
    228                 mFormat24 = ld.timeFormat24;
    229             }
    230         }
    231 
    232         createTime(mTimeZone);
    233         // Wait until onAttachedToWindow() to handle the ticker
    234         chooseFormat(false);
    235     }
    236 
    237     private void createTime(String timeZone) {
    238         if (timeZone != null) {
    239             mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
    240         } else {
    241             mTime = Calendar.getInstance();
    242         }
    243     }
    244 
    245     /**
    246      * Returns the formatting pattern used to display the date and/or time
    247      * in 12-hour mode. The formatting pattern syntax is described in
    248      * {@link DateFormat}.
    249      *
    250      * @return A {@link CharSequence} or null.
    251      *
    252      * @see #setFormat12Hour(CharSequence)
    253      * @see #is24HourModeEnabled()
    254      */
    255     @ExportedProperty
    256     public CharSequence getFormat12Hour() {
    257         return mFormat12;
    258     }
    259 
    260     /**
    261      * <p>Specifies the formatting pattern used to display the date and/or time
    262      * in 12-hour mode. The formatting pattern syntax is described in
    263      * {@link DateFormat}.</p>
    264      *
    265      * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
    266      * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
    267      * are set to null, the default pattern for the current locale will be used
    268      * instead.</p>
    269      *
    270      * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
    271      * you supply a format string generated by
    272      * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
    273      * takes care of generating a format string adapted to the desired locale.</p>
    274      *
    275      *
    276      * @param format A date/time formatting pattern as described in {@link DateFormat}
    277      *
    278      * @see #getFormat12Hour()
    279      * @see #is24HourModeEnabled()
    280      * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
    281      * @see DateFormat
    282      *
    283      * @attr ref android.R.styleable#TextClock_format12Hour
    284      */
    285     @RemotableViewMethod
    286     public void setFormat12Hour(CharSequence format) {
    287         mFormat12 = format;
    288 
    289         chooseFormat();
    290         onTimeChanged();
    291     }
    292 
    293     /**
    294      * Returns the formatting pattern used to display the date and/or time
    295      * in 24-hour mode. The formatting pattern syntax is described in
    296      * {@link DateFormat}.
    297      *
    298      * @return A {@link CharSequence} or null.
    299      *
    300      * @see #setFormat24Hour(CharSequence)
    301      * @see #is24HourModeEnabled()
    302      */
    303     @ExportedProperty
    304     public CharSequence getFormat24Hour() {
    305         return mFormat24;
    306     }
    307 
    308     /**
    309      * <p>Specifies the formatting pattern used to display the date and/or time
    310      * in 24-hour mode. The formatting pattern syntax is described in
    311      * {@link DateFormat}.</p>
    312      *
    313      * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
    314      * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
    315      * are set to null, the default pattern for the current locale will be used
    316      * instead.</p>
    317      *
    318      * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
    319      * you supply a format string generated by
    320      * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
    321      * takes care of generating a format string adapted to the desired locale.</p>
    322      *
    323      * @param format A date/time formatting pattern as described in {@link DateFormat}
    324      *
    325      * @see #getFormat24Hour()
    326      * @see #is24HourModeEnabled()
    327      * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
    328      * @see DateFormat
    329      *
    330      * @attr ref android.R.styleable#TextClock_format24Hour
    331      */
    332     @RemotableViewMethod
    333     public void setFormat24Hour(CharSequence format) {
    334         mFormat24 = format;
    335 
    336         chooseFormat();
    337         onTimeChanged();
    338     }
    339 
    340     /**
    341      * Indicates whether the system is currently using the 24-hour mode.
    342      *
    343      * When the system is in 24-hour mode, this view will use the pattern
    344      * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern
    345      * returned by {@link #getFormat12Hour()} is used instead.
    346      *
    347      * If either one of the formats is null, the other format is used. If
    348      * both formats are null, the default formats for the current locale are used.
    349      *
    350      * @return true if time should be displayed in 24-hour format, false if it
    351      *         should be displayed in 12-hour format.
    352      *
    353      * @see #setFormat12Hour(CharSequence)
    354      * @see #getFormat12Hour()
    355      * @see #setFormat24Hour(CharSequence)
    356      * @see #getFormat24Hour()
    357      */
    358     public boolean is24HourModeEnabled() {
    359         return DateFormat.is24HourFormat(getContext());
    360     }
    361 
    362     /**
    363      * Indicates which time zone is currently used by this view.
    364      *
    365      * @return The ID of the current time zone or null if the default time zone,
    366      *         as set by the user, must be used
    367      *
    368      * @see TimeZone
    369      * @see java.util.TimeZone#getAvailableIDs()
    370      * @see #setTimeZone(String)
    371      */
    372     public String getTimeZone() {
    373         return mTimeZone;
    374     }
    375 
    376     /**
    377      * Sets the specified time zone to use in this clock. When the time zone
    378      * is set through this method, system time zone changes (when the user
    379      * sets the time zone in settings for instance) will be ignored.
    380      *
    381      * @param timeZone The desired time zone's ID as specified in {@link TimeZone}
    382      *                 or null to user the time zone specified by the user
    383      *                 (system time zone)
    384      *
    385      * @see #getTimeZone()
    386      * @see java.util.TimeZone#getAvailableIDs()
    387      * @see TimeZone#getTimeZone(String)
    388      *
    389      * @attr ref android.R.styleable#TextClock_timeZone
    390      */
    391     @RemotableViewMethod
    392     public void setTimeZone(String timeZone) {
    393         mTimeZone = timeZone;
    394 
    395         createTime(timeZone);
    396         onTimeChanged();
    397     }
    398 
    399     /**
    400      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
    401      * depending on whether the user has selected 24-hour format.
    402      *
    403      * Calling this method does not schedule or unschedule the time ticker.
    404      */
    405     private void chooseFormat() {
    406         chooseFormat(true);
    407     }
    408 
    409     /**
    410      * Returns the current format string. Always valid after constructor has
    411      * finished, and will never be {@code null}.
    412      *
    413      * @hide
    414      */
    415     public CharSequence getFormat() {
    416         return mFormat;
    417     }
    418 
    419     /**
    420      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
    421      * depending on whether the user has selected 24-hour format.
    422      *
    423      * @param handleTicker true if calling this method should schedule/unschedule the
    424      *                     time ticker, false otherwise
    425      */
    426     private void chooseFormat(boolean handleTicker) {
    427         final boolean format24Requested = is24HourModeEnabled();
    428 
    429         LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale);
    430 
    431         if (format24Requested) {
    432             mFormat = abc(mFormat24, mFormat12, ld.timeFormat24);
    433         } else {
    434             mFormat = abc(mFormat12, mFormat24, ld.timeFormat12);
    435         }
    436 
    437         boolean hadSeconds = mHasSeconds;
    438         mHasSeconds = DateFormat.hasSeconds(mFormat);
    439 
    440         if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
    441             if (hadSeconds) getHandler().removeCallbacks(mTicker);
    442             else mTicker.run();
    443         }
    444     }
    445 
    446     /**
    447      * Returns a if not null, else return b if not null, else return c.
    448      */
    449     private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) {
    450         return a == null ? (b == null ? c : b) : a;
    451     }
    452 
    453     @Override
    454     protected void onAttachedToWindow() {
    455         super.onAttachedToWindow();
    456 
    457         if (!mAttached) {
    458             mAttached = true;
    459 
    460             registerReceiver();
    461             registerObserver();
    462 
    463             createTime(mTimeZone);
    464 
    465             if (mHasSeconds) {
    466                 mTicker.run();
    467             } else {
    468                 onTimeChanged();
    469             }
    470         }
    471     }
    472 
    473     @Override
    474     protected void onDetachedFromWindow() {
    475         super.onDetachedFromWindow();
    476 
    477         if (mAttached) {
    478             unregisterReceiver();
    479             unregisterObserver();
    480 
    481             getHandler().removeCallbacks(mTicker);
    482 
    483             mAttached = false;
    484         }
    485     }
    486 
    487     private void registerReceiver() {
    488         final IntentFilter filter = new IntentFilter();
    489 
    490         filter.addAction(Intent.ACTION_TIME_TICK);
    491         filter.addAction(Intent.ACTION_TIME_CHANGED);
    492         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    493 
    494         getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
    495     }
    496 
    497     private void registerObserver() {
    498         final ContentResolver resolver = getContext().getContentResolver();
    499         resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
    500     }
    501 
    502     private void unregisterReceiver() {
    503         getContext().unregisterReceiver(mIntentReceiver);
    504     }
    505 
    506     private void unregisterObserver() {
    507         final ContentResolver resolver = getContext().getContentResolver();
    508         resolver.unregisterContentObserver(mFormatChangeObserver);
    509     }
    510 
    511     private void onTimeChanged() {
    512         mTime.setTimeInMillis(System.currentTimeMillis());
    513         setText(DateFormat.format(mFormat, mTime));
    514     }
    515 }
    516