• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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")
TextClock(Context context)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")
TextClock(Context context, AttributeSet attrs)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 defStyleAttr An attribute in the current theme that contains a
202      *        reference to a style resource that supplies default values for
203      *        the view. Can be 0 to not look for defaults.
204      */
TextClock(Context context, AttributeSet attrs, int defStyleAttr)205     public TextClock(Context context, AttributeSet attrs, int defStyleAttr) {
206         this(context, attrs, defStyleAttr, 0);
207     }
208 
TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)209     public TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
210         super(context, attrs, defStyleAttr, defStyleRes);
211 
212         final TypedArray a = context.obtainStyledAttributes(
213                 attrs, R.styleable.TextClock, defStyleAttr, defStyleRes);
214         try {
215             mFormat12 = a.getText(R.styleable.TextClock_format12Hour);
216             mFormat24 = a.getText(R.styleable.TextClock_format24Hour);
217             mTimeZone = a.getString(R.styleable.TextClock_timeZone);
218         } finally {
219             a.recycle();
220         }
221 
222         init();
223     }
224 
init()225     private void init() {
226         if (mFormat12 == null || mFormat24 == null) {
227             LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale);
228             if (mFormat12 == null) {
229                 mFormat12 = ld.timeFormat12;
230             }
231             if (mFormat24 == null) {
232                 mFormat24 = ld.timeFormat24;
233             }
234         }
235 
236         createTime(mTimeZone);
237         // Wait until onAttachedToWindow() to handle the ticker
238         chooseFormat(false);
239     }
240 
createTime(String timeZone)241     private void createTime(String timeZone) {
242         if (timeZone != null) {
243             mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
244         } else {
245             mTime = Calendar.getInstance();
246         }
247     }
248 
249     /**
250      * Returns the formatting pattern used to display the date and/or time
251      * in 12-hour mode. The formatting pattern syntax is described in
252      * {@link DateFormat}.
253      *
254      * @return A {@link CharSequence} or null.
255      *
256      * @see #setFormat12Hour(CharSequence)
257      * @see #is24HourModeEnabled()
258      */
259     @ExportedProperty
getFormat12Hour()260     public CharSequence getFormat12Hour() {
261         return mFormat12;
262     }
263 
264     /**
265      * <p>Specifies the formatting pattern used to display the date and/or time
266      * in 12-hour mode. The formatting pattern syntax is described in
267      * {@link DateFormat}.</p>
268      *
269      * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
270      * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
271      * are set to null, the default pattern for the current locale will be used
272      * instead.</p>
273      *
274      * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
275      * you supply a format string generated by
276      * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
277      * takes care of generating a format string adapted to the desired locale.</p>
278      *
279      *
280      * @param format A date/time formatting pattern as described in {@link DateFormat}
281      *
282      * @see #getFormat12Hour()
283      * @see #is24HourModeEnabled()
284      * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
285      * @see DateFormat
286      *
287      * @attr ref android.R.styleable#TextClock_format12Hour
288      */
289     @RemotableViewMethod
setFormat12Hour(CharSequence format)290     public void setFormat12Hour(CharSequence format) {
291         mFormat12 = format;
292 
293         chooseFormat();
294         onTimeChanged();
295     }
296 
297     /**
298      * Returns the formatting pattern used to display the date and/or time
299      * in 24-hour mode. The formatting pattern syntax is described in
300      * {@link DateFormat}.
301      *
302      * @return A {@link CharSequence} or null.
303      *
304      * @see #setFormat24Hour(CharSequence)
305      * @see #is24HourModeEnabled()
306      */
307     @ExportedProperty
getFormat24Hour()308     public CharSequence getFormat24Hour() {
309         return mFormat24;
310     }
311 
312     /**
313      * <p>Specifies the formatting pattern used to display the date and/or time
314      * in 24-hour mode. The formatting pattern syntax is described in
315      * {@link DateFormat}.</p>
316      *
317      * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
318      * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
319      * are set to null, the default pattern for the current locale will be used
320      * instead.</p>
321      *
322      * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
323      * you supply a format string generated by
324      * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
325      * takes care of generating a format string adapted to the desired locale.</p>
326      *
327      * @param format A date/time formatting pattern as described in {@link DateFormat}
328      *
329      * @see #getFormat24Hour()
330      * @see #is24HourModeEnabled()
331      * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
332      * @see DateFormat
333      *
334      * @attr ref android.R.styleable#TextClock_format24Hour
335      */
336     @RemotableViewMethod
setFormat24Hour(CharSequence format)337     public void setFormat24Hour(CharSequence format) {
338         mFormat24 = format;
339 
340         chooseFormat();
341         onTimeChanged();
342     }
343 
344     /**
345      * Indicates whether the system is currently using the 24-hour mode.
346      *
347      * When the system is in 24-hour mode, this view will use the pattern
348      * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern
349      * returned by {@link #getFormat12Hour()} is used instead.
350      *
351      * If either one of the formats is null, the other format is used. If
352      * both formats are null, the default formats for the current locale are used.
353      *
354      * @return true if time should be displayed in 24-hour format, false if it
355      *         should be displayed in 12-hour format.
356      *
357      * @see #setFormat12Hour(CharSequence)
358      * @see #getFormat12Hour()
359      * @see #setFormat24Hour(CharSequence)
360      * @see #getFormat24Hour()
361      */
is24HourModeEnabled()362     public boolean is24HourModeEnabled() {
363         return DateFormat.is24HourFormat(getContext());
364     }
365 
366     /**
367      * Indicates which time zone is currently used by this view.
368      *
369      * @return The ID of the current time zone or null if the default time zone,
370      *         as set by the user, must be used
371      *
372      * @see TimeZone
373      * @see java.util.TimeZone#getAvailableIDs()
374      * @see #setTimeZone(String)
375      */
getTimeZone()376     public String getTimeZone() {
377         return mTimeZone;
378     }
379 
380     /**
381      * Sets the specified time zone to use in this clock. When the time zone
382      * is set through this method, system time zone changes (when the user
383      * sets the time zone in settings for instance) will be ignored.
384      *
385      * @param timeZone The desired time zone's ID as specified in {@link TimeZone}
386      *                 or null to user the time zone specified by the user
387      *                 (system time zone)
388      *
389      * @see #getTimeZone()
390      * @see java.util.TimeZone#getAvailableIDs()
391      * @see TimeZone#getTimeZone(String)
392      *
393      * @attr ref android.R.styleable#TextClock_timeZone
394      */
395     @RemotableViewMethod
setTimeZone(String timeZone)396     public void setTimeZone(String timeZone) {
397         mTimeZone = timeZone;
398 
399         createTime(timeZone);
400         onTimeChanged();
401     }
402 
403     /**
404      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
405      * depending on whether the user has selected 24-hour format.
406      *
407      * Calling this method does not schedule or unschedule the time ticker.
408      */
chooseFormat()409     private void chooseFormat() {
410         chooseFormat(true);
411     }
412 
413     /**
414      * Returns the current format string. Always valid after constructor has
415      * finished, and will never be {@code null}.
416      *
417      * @hide
418      */
getFormat()419     public CharSequence getFormat() {
420         return mFormat;
421     }
422 
423     /**
424      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
425      * depending on whether the user has selected 24-hour format.
426      *
427      * @param handleTicker true if calling this method should schedule/unschedule the
428      *                     time ticker, false otherwise
429      */
chooseFormat(boolean handleTicker)430     private void chooseFormat(boolean handleTicker) {
431         final boolean format24Requested = is24HourModeEnabled();
432 
433         LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale);
434 
435         if (format24Requested) {
436             mFormat = abc(mFormat24, mFormat12, ld.timeFormat24);
437         } else {
438             mFormat = abc(mFormat12, mFormat24, ld.timeFormat12);
439         }
440 
441         boolean hadSeconds = mHasSeconds;
442         mHasSeconds = DateFormat.hasSeconds(mFormat);
443 
444         if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
445             if (hadSeconds) getHandler().removeCallbacks(mTicker);
446             else mTicker.run();
447         }
448     }
449 
450     /**
451      * Returns a if not null, else return b if not null, else return c.
452      */
abc(CharSequence a, CharSequence b, CharSequence c)453     private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) {
454         return a == null ? (b == null ? c : b) : a;
455     }
456 
457     @Override
onAttachedToWindow()458     protected void onAttachedToWindow() {
459         super.onAttachedToWindow();
460 
461         if (!mAttached) {
462             mAttached = true;
463 
464             registerReceiver();
465             registerObserver();
466 
467             createTime(mTimeZone);
468 
469             if (mHasSeconds) {
470                 mTicker.run();
471             } else {
472                 onTimeChanged();
473             }
474         }
475     }
476 
477     @Override
onDetachedFromWindow()478     protected void onDetachedFromWindow() {
479         super.onDetachedFromWindow();
480 
481         if (mAttached) {
482             unregisterReceiver();
483             unregisterObserver();
484 
485             getHandler().removeCallbacks(mTicker);
486 
487             mAttached = false;
488         }
489     }
490 
registerReceiver()491     private void registerReceiver() {
492         final IntentFilter filter = new IntentFilter();
493 
494         filter.addAction(Intent.ACTION_TIME_TICK);
495         filter.addAction(Intent.ACTION_TIME_CHANGED);
496         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
497 
498         getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
499     }
500 
registerObserver()501     private void registerObserver() {
502         final ContentResolver resolver = getContext().getContentResolver();
503         resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
504     }
505 
unregisterReceiver()506     private void unregisterReceiver() {
507         getContext().unregisterReceiver(mIntentReceiver);
508     }
509 
unregisterObserver()510     private void unregisterObserver() {
511         final ContentResolver resolver = getContext().getContentResolver();
512         resolver.unregisterContentObserver(mFormatChangeObserver);
513     }
514 
onTimeChanged()515     private void onTimeChanged() {
516         mTime.setTimeInMillis(System.currentTimeMillis());
517         setText(DateFormat.format(mFormat, mTime));
518     }
519 }
520