• 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      * @deprecated Let the system use locale-appropriate defaults instead.
99      */
100     public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
101 
102     /**
103      * The default formatting pattern in 24-hour mode. This pattern is used
104      * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern
105      * or if no pattern was specified when creating an instance of this class.
106      *
107      * This default pattern shows only the time, hours and minutes.
108      *
109      * @see #setFormat24Hour(CharSequence)
110      * @see #getFormat24Hour()
111      * @deprecated Let the system use locale-appropriate defaults instead.
112      */
113     public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm";
114 
115     private CharSequence mFormat12;
116     private CharSequence mFormat24;
117 
118     @ExportedProperty
119     private CharSequence mFormat;
120     @ExportedProperty
121     private boolean mHasSeconds;
122 
123     private boolean mAttached;
124 
125     private Calendar mTime;
126     private String mTimeZone;
127 
128     private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
129         @Override
130         public void onChange(boolean selfChange) {
131             chooseFormat();
132             onTimeChanged();
133         }
134 
135         @Override
136         public void onChange(boolean selfChange, Uri uri) {
137             chooseFormat();
138             onTimeChanged();
139         }
140     };
141 
142     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
143         @Override
144         public void onReceive(Context context, Intent intent) {
145             if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
146                 final String timeZone = intent.getStringExtra("time-zone");
147                 createTime(timeZone);
148             }
149             onTimeChanged();
150         }
151     };
152 
153     private final Runnable mTicker = new Runnable() {
154         public void run() {
155             onTimeChanged();
156 
157             long now = SystemClock.uptimeMillis();
158             long next = now + (1000 - now % 1000);
159 
160             getHandler().postAtTime(mTicker, next);
161         }
162     };
163 
164     /**
165      * Creates a new clock using the default patterns
166      * {@link #DEFAULT_FORMAT_24_HOUR} and {@link #DEFAULT_FORMAT_12_HOUR}
167      * respectively for the 24-hour and 12-hour modes.
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 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      */
TextClock(Context context, AttributeSet attrs, int defStyle)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 
init()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 
createTime(String timeZone)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
getFormat12Hour()256     public CharSequence getFormat12Hour() {
257         return mFormat12;
258     }
259 
260     /**
261      * 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}.
264      *
265      * 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, {@link #DEFAULT_FORMAT_24_HOUR} and
268      * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead.
269      *
270      * @param format A date/time formatting pattern as described in {@link DateFormat}
271      *
272      * @see #getFormat12Hour()
273      * @see #is24HourModeEnabled()
274      * @see #DEFAULT_FORMAT_12_HOUR
275      * @see DateFormat
276      *
277      * @attr ref android.R.styleable#TextClock_format12Hour
278      */
279     @RemotableViewMethod
setFormat12Hour(CharSequence format)280     public void setFormat12Hour(CharSequence format) {
281         mFormat12 = format;
282 
283         chooseFormat();
284         onTimeChanged();
285     }
286 
287     /**
288      * Returns the formatting pattern used to display the date and/or time
289      * in 24-hour mode. The formatting pattern syntax is described in
290      * {@link DateFormat}.
291      *
292      * @return A {@link CharSequence} or null.
293      *
294      * @see #setFormat24Hour(CharSequence)
295      * @see #is24HourModeEnabled()
296      */
297     @ExportedProperty
getFormat24Hour()298     public CharSequence getFormat24Hour() {
299         return mFormat24;
300     }
301 
302     /**
303      * Specifies the formatting pattern used to display the date and/or time
304      * in 24-hour mode. The formatting pattern syntax is described in
305      * {@link DateFormat}.
306      *
307      * If this pattern is set to null, {@link #getFormat12Hour()} will be used
308      * even in 24-hour mode. If both 24-hour and 12-hour formatting patterns
309      * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and
310      * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead.
311      *
312      * @param format A date/time formatting pattern as described in {@link DateFormat}
313      *
314      * @see #getFormat24Hour()
315      * @see #is24HourModeEnabled()
316      * @see #DEFAULT_FORMAT_24_HOUR
317      * @see DateFormat
318      *
319      * @attr ref android.R.styleable#TextClock_format24Hour
320      */
321     @RemotableViewMethod
setFormat24Hour(CharSequence format)322     public void setFormat24Hour(CharSequence format) {
323         mFormat24 = format;
324 
325         chooseFormat();
326         onTimeChanged();
327     }
328 
329     /**
330      * Indicates whether the system is currently using the 24-hour mode.
331      *
332      * When the system is in 24-hour mode, this view will use the pattern
333      * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern
334      * returned by {@link #getFormat12Hour()} is used instead.
335      *
336      * If either one of the formats is null, the other format is used. If
337      * both formats are null, the default values {@link #DEFAULT_FORMAT_12_HOUR}
338      * and {@link #DEFAULT_FORMAT_24_HOUR} are used instead.
339      *
340      * @return true if time should be displayed in 24-hour format, false if it
341      *         should be displayed in 12-hour format.
342      *
343      * @see #setFormat12Hour(CharSequence)
344      * @see #getFormat12Hour()
345      * @see #setFormat24Hour(CharSequence)
346      * @see #getFormat24Hour()
347      */
is24HourModeEnabled()348     public boolean is24HourModeEnabled() {
349         return DateFormat.is24HourFormat(getContext());
350     }
351 
352     /**
353      * Indicates which time zone is currently used by this view.
354      *
355      * @return The ID of the current time zone or null if the default time zone,
356      *         as set by the user, must be used
357      *
358      * @see TimeZone
359      * @see java.util.TimeZone#getAvailableIDs()
360      * @see #setTimeZone(String)
361      */
getTimeZone()362     public String getTimeZone() {
363         return mTimeZone;
364     }
365 
366     /**
367      * Sets the specified time zone to use in this clock. When the time zone
368      * is set through this method, system time zone changes (when the user
369      * sets the time zone in settings for instance) will be ignored.
370      *
371      * @param timeZone The desired time zone's ID as specified in {@link TimeZone}
372      *                 or null to user the time zone specified by the user
373      *                 (system time zone)
374      *
375      * @see #getTimeZone()
376      * @see java.util.TimeZone#getAvailableIDs()
377      * @see TimeZone#getTimeZone(String)
378      *
379      * @attr ref android.R.styleable#TextClock_timeZone
380      */
381     @RemotableViewMethod
setTimeZone(String timeZone)382     public void setTimeZone(String timeZone) {
383         mTimeZone = timeZone;
384 
385         createTime(timeZone);
386         onTimeChanged();
387     }
388 
389     /**
390      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
391      * depending on whether the user has selected 24-hour format.
392      *
393      * Calling this method does not schedule or unschedule the time ticker.
394      */
chooseFormat()395     private void chooseFormat() {
396         chooseFormat(true);
397     }
398 
399     /**
400      * Returns the current format string. Always valid after constructor has
401      * finished, and will never be {@code null}.
402      *
403      * @hide
404      */
getFormat()405     public CharSequence getFormat() {
406         return mFormat;
407     }
408 
409     /**
410      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
411      * depending on whether the user has selected 24-hour format.
412      *
413      * @param handleTicker true if calling this method should schedule/unschedule the
414      *                     time ticker, false otherwise
415      */
chooseFormat(boolean handleTicker)416     private void chooseFormat(boolean handleTicker) {
417         final boolean format24Requested = is24HourModeEnabled();
418 
419         LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale);
420 
421         if (format24Requested) {
422             mFormat = abc(mFormat24, mFormat12, ld.timeFormat24);
423         } else {
424             mFormat = abc(mFormat12, mFormat24, ld.timeFormat12);
425         }
426 
427         boolean hadSeconds = mHasSeconds;
428         mHasSeconds = DateFormat.hasSeconds(mFormat);
429 
430         if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
431             if (hadSeconds) getHandler().removeCallbacks(mTicker);
432             else mTicker.run();
433         }
434     }
435 
436     /**
437      * Returns a if not null, else return b if not null, else return c.
438      */
abc(CharSequence a, CharSequence b, CharSequence c)439     private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) {
440         return a == null ? (b == null ? c : b) : a;
441     }
442 
443     @Override
onAttachedToWindow()444     protected void onAttachedToWindow() {
445         super.onAttachedToWindow();
446 
447         if (!mAttached) {
448             mAttached = true;
449 
450             registerReceiver();
451             registerObserver();
452 
453             createTime(mTimeZone);
454 
455             if (mHasSeconds) {
456                 mTicker.run();
457             } else {
458                 onTimeChanged();
459             }
460         }
461     }
462 
463     @Override
onDetachedFromWindow()464     protected void onDetachedFromWindow() {
465         super.onDetachedFromWindow();
466 
467         if (mAttached) {
468             unregisterReceiver();
469             unregisterObserver();
470 
471             getHandler().removeCallbacks(mTicker);
472 
473             mAttached = false;
474         }
475     }
476 
registerReceiver()477     private void registerReceiver() {
478         final IntentFilter filter = new IntentFilter();
479 
480         filter.addAction(Intent.ACTION_TIME_TICK);
481         filter.addAction(Intent.ACTION_TIME_CHANGED);
482         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
483 
484         getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
485     }
486 
registerObserver()487     private void registerObserver() {
488         final ContentResolver resolver = getContext().getContentResolver();
489         resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
490     }
491 
unregisterReceiver()492     private void unregisterReceiver() {
493         getContext().unregisterReceiver(mIntentReceiver);
494     }
495 
unregisterObserver()496     private void unregisterObserver() {
497         final ContentResolver resolver = getContext().getContentResolver();
498         resolver.unregisterContentObserver(mFormatChangeObserver);
499     }
500 
onTimeChanged()501     private void onTimeChanged() {
502         mTime.setTimeInMillis(System.currentTimeMillis());
503         setText(DateFormat.format(mFormat, mTime));
504     }
505 }
506