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