• 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.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")
TextClock(Context context)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")
TextClock(Context context, AttributeSet attrs)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      */
TextClock(Context context, AttributeSet attrs, int defStyleAttr)209     public TextClock(Context context, AttributeSet attrs, int defStyleAttr) {
210         this(context, attrs, defStyleAttr, 0);
211     }
212 
TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 
init()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 
createTime(String timeZone)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
getFormat12Hour()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
setFormat12Hour(CharSequence format)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
getFormat24Hour()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
setFormat24Hour(CharSequence format)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      */
setShowCurrentUserTime(boolean showCurrentUserTime)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      */
is24HourModeEnabled()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      */
getTimeZone()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
setTimeZone(String timeZone)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      */
chooseFormat()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      */
getFormat()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      */
chooseFormat(boolean handleTicker)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      */
abc(CharSequence a, CharSequence b, CharSequence c)477     private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) {
478         return a == null ? (b == null ? c : b) : a;
479     }
480 
481     @Override
onAttachedToWindow()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
onDetachedFromWindow()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 
registerReceiver()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 
registerObserver()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 
unregisterReceiver()536     private void unregisterReceiver() {
537         getContext().unregisterReceiver(mIntentReceiver);
538     }
539 
unregisterObserver()540     private void unregisterObserver() {
541         final ContentResolver resolver = getContext().getContentResolver();
542         resolver.unregisterContentObserver(mFormatChangeObserver);
543     }
544 
onTimeChanged()545     private void onTimeChanged() {
546         mTime.setTimeInMillis(System.currentTimeMillis());
547         setText(DateFormat.format(mFormat, mTime));
548     }
549 }
550