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