• 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 static android.os.Process.myUserHandle;
20 import static android.view.ViewDebug.ExportedProperty;
21 import static android.widget.RemoteViews.RemoteView;
22 
23 import android.annotation.NonNull;
24 import android.annotation.TestApi;
25 import android.app.ActivityManager;
26 import android.compat.annotation.UnsupportedAppUsage;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.res.TypedArray;
32 import android.database.ContentObserver;
33 import android.icu.text.DateTimePatternGenerator;
34 import android.net.Uri;
35 import android.os.Build;
36 import android.os.Handler;
37 import android.os.UserHandle;
38 import android.provider.Settings;
39 import android.text.format.DateFormat;
40 import android.util.AttributeSet;
41 import android.view.RemotableViewMethod;
42 import android.view.ViewHierarchyEncoder;
43 import android.view.inspector.InspectableProperty;
44 
45 import com.android.internal.R;
46 import com.android.internal.util.Preconditions;
47 
48 import java.time.DateTimeException;
49 import java.time.Duration;
50 import java.time.Instant;
51 import java.time.ZoneId;
52 import java.time.ZonedDateTime;
53 import java.util.Calendar;
54 import java.util.TimeZone;
55 
56 /**
57  * <p><code>TextClock</code> can display the current date and/or time as
58  * a formatted string.</p>
59  *
60  * <p>This view honors the 24-hour format system setting. As such, it is
61  * possible and recommended to provide two different formatting patterns:
62  * one to display the date/time in 24-hour mode and one to display the
63  * date/time in 12-hour mode. Most callers will want to use the defaults,
64  * though, which will be appropriate for the user's locale.</p>
65  *
66  * <p>It is possible to determine whether the system is currently in
67  * 24-hour mode by calling {@link #is24HourModeEnabled()}.</p>
68  *
69  * <p>The rules used by this widget to decide how to format the date and
70  * time are the following:</p>
71  * <ul>
72  *     <li>In 24-hour mode:
73  *         <ul>
74  *             <li>Use the value returned by {@link #getFormat24Hour()} when non-null</li>
75  *             <li>Otherwise, use the value returned by {@link #getFormat12Hour()} when non-null</li>
76  *             <li>Otherwise, use a default value appropriate for the user's locale, such as {@code h:mm a}</li>
77  *         </ul>
78  *     </li>
79  *     <li>In 12-hour mode:
80  *         <ul>
81  *             <li>Use the value returned by {@link #getFormat12Hour()} when non-null</li>
82  *             <li>Otherwise, use the value returned by {@link #getFormat24Hour()} when non-null</li>
83  *             <li>Otherwise, use a default value appropriate for the user's locale, such as {@code HH:mm}</li>
84  *         </ul>
85  *     </li>
86  * </ul>
87  *
88  * <p>The {@link CharSequence} instances used as formatting patterns when calling either
89  * {@link #setFormat24Hour(CharSequence)} or {@link #setFormat12Hour(CharSequence)} can
90  * contain styling information. To do so, use a {@link android.text.Spanned} object.
91  * Note that if you customize these strings, it is your responsibility to supply strings
92  * appropriate for formatting dates and/or times in the user's locale.</p>
93  *
94  * @attr ref android.R.styleable#TextClock_format12Hour
95  * @attr ref android.R.styleable#TextClock_format24Hour
96  * @attr ref android.R.styleable#TextClock_timeZone
97  */
98 @RemoteView
99 public class TextClock extends TextView {
100     /**
101      * The default formatting pattern in 12-hour mode. This pattern is used
102      * if {@link #setFormat12Hour(CharSequence)} is called with a null pattern
103      * or if no pattern was specified when creating an instance of this class.
104      *
105      * This default pattern shows only the time, hours and minutes, and an am/pm
106      * indicator.
107      *
108      * @see #setFormat12Hour(CharSequence)
109      * @see #getFormat12Hour()
110      *
111      * @deprecated Let the system use locale-appropriate defaults instead.
112      */
113     @Deprecated
114     public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
115 
116     /**
117      * The default formatting pattern in 24-hour mode. This pattern is used
118      * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern
119      * or if no pattern was specified when creating an instance of this class.
120      *
121      * This default pattern shows only the time, hours and minutes.
122      *
123      * @see #setFormat24Hour(CharSequence)
124      * @see #getFormat24Hour()
125      *
126      * @deprecated Let the system use locale-appropriate defaults instead.
127      */
128     @Deprecated
129     public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm";
130 
131     private CharSequence mFormat12;
132     private CharSequence mFormat24;
133     private CharSequence mDescFormat12;
134     private CharSequence mDescFormat24;
135 
136     @ExportedProperty
137     private CharSequence mFormat;
138     @ExportedProperty
139     private boolean mHasSeconds;
140 
141     private CharSequence mDescFormat;
142 
143     private boolean mRegistered;
144     private boolean mShouldRunTicker;
145 
146     private ClockEventDelegate mClockEventDelegate;
147 
148     private Calendar mTime;
149     private String mTimeZone;
150 
151     private boolean mShowCurrentUserTime;
152 
153     private ContentObserver mFormatChangeObserver;
154     // Used by tests to stop time change events from triggering the text update
155     private boolean mStopTicking;
156 
157     private class FormatChangeObserver extends ContentObserver {
158 
FormatChangeObserver(Handler handler)159         public FormatChangeObserver(Handler handler) {
160             super(handler);
161         }
162 
163         @Override
onChange(boolean selfChange)164         public void onChange(boolean selfChange) {
165             chooseFormat();
166             onTimeChanged();
167         }
168 
169         @Override
onChange(boolean selfChange, Uri uri)170         public void onChange(boolean selfChange, Uri uri) {
171             chooseFormat();
172             onTimeChanged();
173         }
174     };
175 
176     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
177         @Override
178         public void onReceive(Context context, Intent intent) {
179             if (mStopTicking) {
180                 return; // Test disabled the clock ticks
181             }
182             if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
183                 final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
184                 createTime(timeZone);
185             } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
186                 return;
187             }
188             onTimeChanged();
189         }
190     };
191 
192     private final Runnable mTicker = new Runnable() {
193         public void run() {
194             removeCallbacks(this);
195             if (mStopTicking || !mShouldRunTicker) {
196                 return; // Test disabled the clock ticks
197             }
198             onTimeChanged();
199 
200             Instant now = mTime.toInstant();
201             ZoneId zone = mTime.getTimeZone().toZoneId();
202 
203             ZonedDateTime nextTick;
204             if (mHasSeconds) {
205                 nextTick = now.atZone(zone).plusSeconds(1).withNano(0);
206             } else {
207                 nextTick = now.atZone(zone).plusMinutes(1).withSecond(0).withNano(0);
208             }
209 
210             long millisUntilNextTick = Duration.between(now, nextTick.toInstant()).toMillis();
211             if (millisUntilNextTick <= 0) {
212                 // This should never happen, but if it does, then tick again in a second.
213                 millisUntilNextTick = 1000;
214             }
215 
216             postDelayed(this, millisUntilNextTick);
217         }
218     };
219 
220     /**
221      * Creates a new clock using the default patterns for the current locale.
222      *
223      * @param context The Context the view is running in, through which it can
224      *        access the current theme, resources, etc.
225      */
226     @SuppressWarnings("UnusedDeclaration")
TextClock(Context context)227     public TextClock(Context context) {
228         super(context);
229         init();
230     }
231 
232     /**
233      * Creates a new clock inflated from XML. This object's properties are
234      * intialized from the attributes specified in XML.
235      *
236      * This constructor uses a default style of 0, so the only attribute values
237      * applied are those in the Context's Theme and the given AttributeSet.
238      *
239      * @param context The Context the view is running in, through which it can
240      *        access the current theme, resources, etc.
241      * @param attrs The attributes of the XML tag that is inflating the view
242      */
243     @SuppressWarnings("UnusedDeclaration")
TextClock(Context context, AttributeSet attrs)244     public TextClock(Context context, AttributeSet attrs) {
245         this(context, attrs, 0);
246     }
247 
248     /**
249      * Creates a new clock inflated from XML. This object's properties are
250      * intialized from the attributes specified in XML.
251      *
252      * @param context The Context the view is running in, through which it can
253      *        access the current theme, resources, etc.
254      * @param attrs The attributes of the XML tag that is inflating the view
255      * @param defStyleAttr An attribute in the current theme that contains a
256      *        reference to a style resource that supplies default values for
257      *        the view. Can be 0 to not look for defaults.
258      */
TextClock(Context context, AttributeSet attrs, int defStyleAttr)259     public TextClock(Context context, AttributeSet attrs, int defStyleAttr) {
260         this(context, attrs, defStyleAttr, 0);
261     }
262 
TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)263     public TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
264         super(context, attrs, defStyleAttr, defStyleRes);
265 
266         final TypedArray a = context.obtainStyledAttributes(
267                 attrs, R.styleable.TextClock, defStyleAttr, defStyleRes);
268         saveAttributeDataForStyleable(context, R.styleable.TextClock,
269                 attrs, a, defStyleAttr, defStyleRes);
270         try {
271             mFormat12 = a.getText(R.styleable.TextClock_format12Hour);
272             mFormat24 = a.getText(R.styleable.TextClock_format24Hour);
273             mTimeZone = a.getString(R.styleable.TextClock_timeZone);
274         } finally {
275             a.recycle();
276         }
277 
278         init();
279     }
280 
init()281     private void init() {
282         if (mFormat12 == null) {
283             mFormat12 = getBestDateTimePattern("hm");
284         }
285         if (mFormat24 == null) {
286             mFormat24 = getBestDateTimePattern("Hm");
287         }
288         mClockEventDelegate = new ClockEventDelegate(getContext());
289 
290         createTime(mTimeZone);
291         chooseFormat();
292     }
293 
createTime(String timeZone)294     private void createTime(String timeZone) {
295         TimeZone tz = null;
296         if (timeZone == null) {
297             tz = TimeZone.getDefault();
298             // Note that mTimeZone should always be null if timeZone is.
299         } else {
300             tz = TimeZone.getTimeZone(timeZone);
301             try {
302                 // Try converting this TZ to a zoneId to make sure it's valid. This
303                 // performs a different set of checks than TimeZone.getTimeZone so
304                 // we can avoid exceptions later when we do need this conversion.
305                 tz.toZoneId();
306             } catch (DateTimeException ex) {
307                 // If we're here, the user supplied timezone is invalid, so reset
308                 // mTimeZone to something sane.
309                 tz = TimeZone.getDefault();
310                 mTimeZone = tz.getID();
311             }
312         }
313 
314         mTime = Calendar.getInstance(tz);
315     }
316 
317     /**
318      * Returns the formatting pattern used to display the date and/or time
319      * in 12-hour mode. The formatting pattern syntax is described in
320      * {@link DateFormat}.
321      *
322      * @return A {@link CharSequence} or null.
323      *
324      * @see #setFormat12Hour(CharSequence)
325      * @see #is24HourModeEnabled()
326      */
327     @InspectableProperty
328     @ExportedProperty
getFormat12Hour()329     public CharSequence getFormat12Hour() {
330         return mFormat12;
331     }
332 
333     /**
334      * <p>Specifies the formatting pattern used to display the date and/or time
335      * in 12-hour mode. The formatting pattern syntax is described in
336      * {@link DateFormat}.</p>
337      *
338      * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
339      * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
340      * are set to null, the default pattern for the current locale will be used
341      * instead.</p>
342      *
343      * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
344      * you supply a format string generated by
345      * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
346      * takes care of generating a format string adapted to the desired locale.</p>
347      *
348      *
349      * @param format A date/time formatting pattern as described in {@link DateFormat}
350      *
351      * @see #getFormat12Hour()
352      * @see #is24HourModeEnabled()
353      * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
354      * @see DateFormat
355      *
356      * @attr ref android.R.styleable#TextClock_format12Hour
357      */
358     @RemotableViewMethod
setFormat12Hour(CharSequence format)359     public void setFormat12Hour(CharSequence format) {
360         mFormat12 = format;
361 
362         chooseFormat();
363         onTimeChanged();
364     }
365 
366     /**
367      * Like setFormat12Hour, but for the content description.
368      * @hide
369      */
setContentDescriptionFormat12Hour(CharSequence format)370     public void setContentDescriptionFormat12Hour(CharSequence format) {
371         mDescFormat12 = format;
372 
373         chooseFormat();
374         onTimeChanged();
375     }
376 
377     /**
378      * Returns the formatting pattern used to display the date and/or time
379      * in 24-hour mode. The formatting pattern syntax is described in
380      * {@link DateFormat}.
381      *
382      * @return A {@link CharSequence} or null.
383      *
384      * @see #setFormat24Hour(CharSequence)
385      * @see #is24HourModeEnabled()
386      */
387     @InspectableProperty
388     @ExportedProperty
getFormat24Hour()389     public CharSequence getFormat24Hour() {
390         return mFormat24;
391     }
392 
393     /**
394      * <p>Specifies the formatting pattern used to display the date and/or time
395      * in 24-hour mode. The formatting pattern syntax is described in
396      * {@link DateFormat}.</p>
397      *
398      * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
399      * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
400      * are set to null, the default pattern for the current locale will be used
401      * instead.</p>
402      *
403      * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
404      * you supply a format string generated by
405      * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
406      * takes care of generating a format string adapted to the desired locale.</p>
407      *
408      * @param format A date/time formatting pattern as described in {@link DateFormat}
409      *
410      * @see #getFormat24Hour()
411      * @see #is24HourModeEnabled()
412      * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
413      * @see DateFormat
414      *
415      * @attr ref android.R.styleable#TextClock_format24Hour
416      */
417     @RemotableViewMethod
setFormat24Hour(CharSequence format)418     public void setFormat24Hour(CharSequence format) {
419         mFormat24 = format;
420 
421         chooseFormat();
422         onTimeChanged();
423     }
424 
425     /**
426      * Like setFormat24Hour, but for the content description.
427      * @hide
428      */
setContentDescriptionFormat24Hour(CharSequence format)429     public void setContentDescriptionFormat24Hour(CharSequence format) {
430         mDescFormat24 = format;
431 
432         chooseFormat();
433         onTimeChanged();
434     }
435 
436     /**
437      * Sets whether this clock should always track the current user and not the user of the
438      * current process. This is used for single instance processes like the systemUI who need
439      * to display time for different users.
440      *
441      * @hide
442      */
setShowCurrentUserTime(boolean showCurrentUserTime)443     public void setShowCurrentUserTime(boolean showCurrentUserTime) {
444         mShowCurrentUserTime = showCurrentUserTime;
445 
446         chooseFormat();
447         onTimeChanged();
448         unregisterObserver();
449         registerObserver();
450     }
451 
452     /**
453      * Sets a delegate to handle clock event registration. This must be called before the view is
454      * attached to the window
455      *
456      * @hide
457      */
setClockEventDelegate(ClockEventDelegate delegate)458     public void setClockEventDelegate(ClockEventDelegate delegate) {
459         Preconditions.checkState(!mRegistered, "Clock events already registered");
460         mClockEventDelegate = delegate;
461     }
462 
463     /**
464      * Update the displayed time if necessary and invalidate the view.
465      */
refreshTime()466     public void refreshTime() {
467         onTimeChanged();
468         invalidate();
469     }
470 
471     /**
472      * Indicates whether the system is currently using the 24-hour mode.
473      *
474      * When the system is in 24-hour mode, this view will use the pattern
475      * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern
476      * returned by {@link #getFormat12Hour()} is used instead.
477      *
478      * If either one of the formats is null, the other format is used. If
479      * both formats are null, the default formats for the current locale are used.
480      *
481      * @return true if time should be displayed in 24-hour format, false if it
482      *         should be displayed in 12-hour format.
483      *
484      * @see #setFormat12Hour(CharSequence)
485      * @see #getFormat12Hour()
486      * @see #setFormat24Hour(CharSequence)
487      * @see #getFormat24Hour()
488      */
489     @InspectableProperty(hasAttributeId = false)
is24HourModeEnabled()490     public boolean is24HourModeEnabled() {
491         if (mShowCurrentUserTime) {
492             return DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser());
493         } else {
494             return DateFormat.is24HourFormat(getContext());
495         }
496     }
497 
498     /**
499      * Indicates which time zone is currently used by this view.
500      *
501      * @return The ID of the current time zone or null if the default time zone,
502      *         as set by the user, must be used
503      *
504      * @see TimeZone
505      * @see java.util.TimeZone#getAvailableIDs()
506      * @see #setTimeZone(String)
507      */
508     @InspectableProperty
getTimeZone()509     public String getTimeZone() {
510         return mTimeZone;
511     }
512 
513     /**
514      * Sets the specified time zone to use in this clock. When the time zone
515      * is set through this method, system time zone changes (when the user
516      * sets the time zone in settings for instance) will be ignored.
517      *
518      * @param timeZone The desired time zone's ID as specified in {@link TimeZone}
519      *                 or null to user the time zone specified by the user
520      *                 (system time zone)
521      *
522      * @see #getTimeZone()
523      * @see java.util.TimeZone#getAvailableIDs()
524      * @see TimeZone#getTimeZone(String)
525      *
526      * @attr ref android.R.styleable#TextClock_timeZone
527      */
528     @RemotableViewMethod
setTimeZone(String timeZone)529     public void setTimeZone(String timeZone) {
530         mTimeZone = timeZone;
531 
532         createTime(timeZone);
533         onTimeChanged();
534     }
535 
536     /**
537      * Returns the current format string. Always valid after constructor has
538      * finished, and will never be {@code null}.
539      *
540      * @hide
541      */
542     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getFormat()543     public CharSequence getFormat() {
544         return mFormat;
545     }
546 
547     /**
548      * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
549      * depending on whether the user has selected 24-hour format.
550      */
chooseFormat()551     private void chooseFormat() {
552         final boolean format24Requested = is24HourModeEnabled();
553 
554         if (format24Requested) {
555             mFormat = abc(mFormat24, mFormat12, getBestDateTimePattern("Hm"));
556             mDescFormat = abc(mDescFormat24, mDescFormat12, mFormat);
557         } else {
558             mFormat = abc(mFormat12, mFormat24, getBestDateTimePattern("hm"));
559             mDescFormat = abc(mDescFormat12, mDescFormat24, mFormat);
560         }
561 
562         boolean hadSeconds = mHasSeconds;
563         mHasSeconds = DateFormat.hasSeconds(mFormat);
564 
565         if (mShouldRunTicker && hadSeconds != mHasSeconds) {
566             mTicker.run();
567         }
568     }
569 
getBestDateTimePattern(String skeleton)570     private String getBestDateTimePattern(String skeleton) {
571         DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(
572                 getContext().getResources().getConfiguration().locale);
573         return dtpg.getBestPattern(skeleton);
574     }
575 
576     /**
577      * Returns a if not null, else return b if not null, else return c.
578      */
abc(CharSequence a, CharSequence b, CharSequence c)579     private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) {
580         return a == null ? (b == null ? c : b) : a;
581     }
582 
583     @Override
onAttachedToWindow()584     protected void onAttachedToWindow() {
585         super.onAttachedToWindow();
586 
587         if (!mRegistered) {
588             mRegistered = true;
589 
590             mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
591             registerObserver();
592 
593             createTime(mTimeZone);
594         }
595     }
596 
597     @Override
onVisibilityAggregated(boolean isVisible)598     public void onVisibilityAggregated(boolean isVisible) {
599         super.onVisibilityAggregated(isVisible);
600 
601         if (!mShouldRunTicker && isVisible) {
602             mShouldRunTicker = true;
603             mTicker.run();
604         } else if (mShouldRunTicker && !isVisible) {
605             mShouldRunTicker = false;
606             removeCallbacks(mTicker);
607         }
608     }
609 
610     @Override
onDetachedFromWindow()611     protected void onDetachedFromWindow() {
612         super.onDetachedFromWindow();
613 
614         if (mRegistered) {
615             mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
616             unregisterObserver();
617 
618             mRegistered = false;
619         }
620     }
621 
622     /**
623      * Used by tests to stop the clock tick from updating the text.
624      * @hide
625      */
626     @TestApi
disableClockTick()627     public void disableClockTick() {
628         mStopTicking = true;
629     }
630 
registerObserver()631     private void registerObserver() {
632         if (mRegistered) {
633             if (mFormatChangeObserver == null) {
634                 mFormatChangeObserver = new FormatChangeObserver(getHandler());
635             }
636             // UserHandle.myUserId() is needed. This class is supported by the
637             // remote views mechanism and as a part of that the remote views
638             // can be inflated by a context for another user without the app
639             // having interact users permission - just for loading resources.
640             // For example, when adding widgets from a managed profile to the
641             // home screen. Therefore, we register the ContentObserver with the user
642             // the app is running (e.g. the launcher) and not the user of the
643             // context (e.g. the widget's profile).
644             int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId();
645             mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle);
646         }
647     }
648 
unregisterObserver()649     private void unregisterObserver() {
650         if (mFormatChangeObserver != null) {
651             mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver);
652         }
653     }
654 
655     /**
656      * Update the displayed time if this view and its ancestors and window is visible
657      */
658     @UnsupportedAppUsage
onTimeChanged()659     private void onTimeChanged() {
660         mTime.setTimeInMillis(System.currentTimeMillis());
661         setText(DateFormat.format(mFormat, mTime));
662         setContentDescription(DateFormat.format(mDescFormat, mTime));
663     }
664 
665     /** @hide */
666     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)667     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
668         super.encodeProperties(stream);
669 
670         CharSequence s = getFormat12Hour();
671         stream.addProperty("format12Hour", s == null ? null : s.toString());
672 
673         s = getFormat24Hour();
674         stream.addProperty("format24Hour", s == null ? null : s.toString());
675         stream.addProperty("format", mFormat == null ? null : mFormat.toString());
676         stream.addProperty("hasSeconds", mHasSeconds);
677     }
678 
679     /**
680      * Utility class to delegate some system event handling to allow overring the default behavior
681      *
682      * @hide
683      */
684     public static class ClockEventDelegate {
685 
686         private final Context mContext;
687 
ClockEventDelegate(Context context)688         public ClockEventDelegate(Context context) {
689             mContext = context;
690         }
691 
692         /**
693          * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and
694          * {@link Intent#ACTION_TIMEZONE_CHANGED}
695          *
696          * OK, this is gross but needed. This class is supported by the remote views mechanism and
697          * as a part of that the remote views can be inflated by a context for another user without
698          * the app having interact users permission - just for loading resources. For example,
699          * when adding widgets from a managed profile to the home screen. Therefore, we register
700          * the receiver as the user the app is running as not the one the context is for.
701          */
registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler)702         public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
703             final IntentFilter filter = new IntentFilter();
704 
705             filter.addAction(Intent.ACTION_TIME_CHANGED);
706             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
707 
708             mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler);
709         }
710 
711         /**
712          * Unregisters a previously registered receiver
713          */
unregisterTimeChangeReceiver(BroadcastReceiver receiver)714         public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
715             mContext.unregisterReceiver(receiver);
716         }
717 
718         /**
719          * Registers an observer for time format changes
720          */
registerFormatChangeObserver(ContentObserver observer, int userHandle)721         public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
722             Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
723             mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle);
724         }
725 
726         /**
727          * Unregisters a previously registered observer
728          */
unregisterFormatChangeObserver(ContentObserver observer)729         public void unregisterFormatChangeObserver(ContentObserver observer) {
730             mContext.getContentResolver().unregisterContentObserver(observer);
731         }
732     }
733 }
734