1 /*
2  * Copyright (C) 2016 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 com.android.deskclock.widget;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.net.Uri;
23 import android.os.Handler;
24 import android.provider.Settings;
25 import androidx.annotation.VisibleForTesting;
26 import android.text.format.DateFormat;
27 import android.util.AttributeSet;
28 import android.widget.TextView;
29 
30 import com.android.deskclock.Utils;
31 import com.android.deskclock.data.DataModel;
32 
33 import java.util.Calendar;
34 import java.util.TimeZone;
35 
36 import static java.util.Calendar.HOUR_OF_DAY;
37 import static java.util.Calendar.MINUTE;
38 
39 /**
40  * Based on {@link android.widget.TextClock}, This widget displays a constant time of day using
41  * format specifiers. {@link android.widget.TextClock} doesn't support a non-ticking clock.
42  */
43 public class TextTime extends TextView {
44 
45     /** UTC does not have DST rules and will not alter the {@link #mHour} and {@link #mMinute}. */
46     private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
47 
48     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
49     static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
50     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
51     static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm";
52 
53     private CharSequence mFormat12;
54     private CharSequence mFormat24;
55     private CharSequence mFormat;
56 
57     private boolean mAttached;
58 
59     private int mHour;
60     private int mMinute;
61 
62     private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
63         @Override
64         public void onChange(boolean selfChange) {
65             chooseFormat();
66             updateTime();
67         }
68 
69         @Override
70         public void onChange(boolean selfChange, Uri uri) {
71             chooseFormat();
72             updateTime();
73         }
74     };
75 
76     @SuppressWarnings("UnusedDeclaration")
TextTime(Context context)77     public TextTime(Context context) {
78         this(context, null);
79     }
80 
81     @SuppressWarnings("UnusedDeclaration")
TextTime(Context context, AttributeSet attrs)82     public TextTime(Context context, AttributeSet attrs) {
83         this(context, attrs, 0);
84     }
85 
TextTime(Context context, AttributeSet attrs, int defStyle)86     public TextTime(Context context, AttributeSet attrs, int defStyle) {
87         super(context, attrs, defStyle);
88 
89         setFormat12Hour(Utils.get12ModeFormat(0.3f /* amPmRatio */, false));
90         setFormat24Hour(Utils.get24ModeFormat(false));
91 
92         chooseFormat();
93     }
94 
95     @SuppressWarnings("UnusedDeclaration")
getFormat12Hour()96     public CharSequence getFormat12Hour() {
97         return mFormat12;
98     }
99 
100     @SuppressWarnings("UnusedDeclaration")
setFormat12Hour(CharSequence format)101     public void setFormat12Hour(CharSequence format) {
102         mFormat12 = format;
103 
104         chooseFormat();
105         updateTime();
106     }
107 
108     @SuppressWarnings("UnusedDeclaration")
getFormat24Hour()109     public CharSequence getFormat24Hour() {
110         return mFormat24;
111     }
112 
113     @SuppressWarnings("UnusedDeclaration")
setFormat24Hour(CharSequence format)114     public void setFormat24Hour(CharSequence format) {
115         mFormat24 = format;
116 
117         chooseFormat();
118         updateTime();
119     }
120 
chooseFormat()121     private void chooseFormat() {
122         final boolean format24Requested = DataModel.getDataModel().is24HourFormat();
123         if (format24Requested) {
124             mFormat = mFormat24 == null ? DEFAULT_FORMAT_24_HOUR : mFormat24;
125         } else {
126             mFormat = mFormat12 == null ? DEFAULT_FORMAT_12_HOUR : mFormat12;
127         }
128     }
129 
130     @Override
onAttachedToWindow()131     protected void onAttachedToWindow() {
132         super.onAttachedToWindow();
133         if (!mAttached) {
134             mAttached = true;
135             registerObserver();
136             updateTime();
137         }
138     }
139 
140     @Override
onDetachedFromWindow()141     protected void onDetachedFromWindow() {
142         super.onDetachedFromWindow();
143         if (mAttached) {
144             unregisterObserver();
145             mAttached = false;
146         }
147     }
148 
registerObserver()149     private void registerObserver() {
150         final ContentResolver resolver = getContext().getContentResolver();
151         resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
152     }
153 
unregisterObserver()154     private void unregisterObserver() {
155         final ContentResolver resolver = getContext().getContentResolver();
156         resolver.unregisterContentObserver(mFormatChangeObserver);
157     }
158 
setTime(int hour, int minute)159     public void setTime(int hour, int minute) {
160         mHour = hour;
161         mMinute = minute;
162         updateTime();
163     }
164 
updateTime()165     private void updateTime() {
166         // Format the time relative to UTC to ensure hour and minute are not adjusted for DST.
167         final Calendar calendar = DataModel.getDataModel().getCalendar();
168         calendar.setTimeZone(UTC);
169         calendar.set(HOUR_OF_DAY, mHour);
170         calendar.set(MINUTE, mMinute);
171         final CharSequence text = DateFormat.format(mFormat, calendar);
172         setText(text);
173         // Strip away the spans from text so talkback is not confused
174         setContentDescription(text.toString());
175     }
176 }