1 /*
2  * Copyright (C) 2020 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.Context
20 import android.database.ContentObserver
21 import android.net.Uri
22 import android.os.Handler
23 import android.provider.Settings
24 import android.text.format.DateFormat
25 import android.util.AttributeSet
26 import android.widget.TextView
27 import androidx.annotation.VisibleForTesting
28 
29 import com.android.deskclock.Utils
30 import com.android.deskclock.data.DataModel
31 
32 import java.util.Calendar
33 import java.util.TimeZone
34 
35 /**
36  * Based on [android.widget.TextClock], This widget displays a constant time of day using
37  * format specifiers. [android.widget.TextClock] doesn't support a non-ticking clock.
38  */
39 class TextTime @JvmOverloads constructor(
40     context: Context?,
41     attrs: AttributeSet? = null,
42     defStyle: Int = 0
43 ) : TextView(context, attrs, defStyle) {
44     private var mFormat12: CharSequence? = Utils.get12ModeFormat(0.3f, false)
45     private var mFormat24: CharSequence? = Utils.get24ModeFormat(false)
46     private var mFormat: CharSequence? = null
47 
48     private var mAttached = false
49 
50     private var mHour = 0
51     private var mMinute = 0
52 
53     private val mFormatChangeObserver: ContentObserver = object : ContentObserver(Handler()) {
onChangenull54         override fun onChange(selfChange: Boolean) {
55             chooseFormat()
56             updateTime()
57         }
58 
onChangenull59         override fun onChange(selfChange: Boolean, uri: Uri?) {
60             chooseFormat()
61             updateTime()
62         }
63     }
64 
65     var format12Hour: CharSequence?
66         get() = mFormat12
67         set(format) {
68             mFormat12 = format
69             chooseFormat()
70             updateTime()
71         }
72 
73     var format24Hour: CharSequence?
74         get() = mFormat24
75         set(format) {
76             mFormat24 = format
77             chooseFormat()
78             updateTime()
79         }
80 
81     init {
82         chooseFormat()
83     }
84 
chooseFormatnull85     private fun chooseFormat() {
86         val format24Requested: Boolean = DataModel.dataModel.is24HourFormat()
87         mFormat = if (format24Requested) {
88             mFormat24 ?: DEFAULT_FORMAT_24_HOUR
89         } else {
90             mFormat12 ?: DEFAULT_FORMAT_12_HOUR
91         }
92     }
93 
onAttachedToWindownull94     override fun onAttachedToWindow() {
95         super.onAttachedToWindow()
96         if (!mAttached) {
97             mAttached = true
98             registerObserver()
99             updateTime()
100         }
101     }
102 
onDetachedFromWindownull103     override fun onDetachedFromWindow() {
104         super.onDetachedFromWindow()
105         if (mAttached) {
106             unregisterObserver()
107             mAttached = false
108         }
109     }
110 
registerObservernull111     private fun registerObserver() {
112         val resolver = context.contentResolver
113         resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver)
114     }
115 
unregisterObservernull116     private fun unregisterObserver() {
117         val resolver = context.contentResolver
118         resolver.unregisterContentObserver(mFormatChangeObserver)
119     }
120 
setTimenull121     fun setTime(hour: Int, minute: Int) {
122         mHour = hour
123         mMinute = minute
124         updateTime()
125     }
126 
updateTimenull127     private fun updateTime() {
128         // Format the time relative to UTC to ensure hour and minute are not adjusted for DST.
129         val calendar: Calendar = DataModel.dataModel.calendar
130         calendar.timeZone = UTC
131         calendar[Calendar.HOUR_OF_DAY] = mHour
132         calendar[Calendar.MINUTE] = mMinute
133         val text = DateFormat.format(mFormat, calendar)
134         setText(text)
135         // Strip away the spans from text so talkback is not confused
136         contentDescription = text.toString()
137     }
138 
139     companion object {
140         /** UTC does not have DST rules and will not alter the [.mHour] and [.mMinute].  */
141         private val UTC = TimeZone.getTimeZone("UTC")
142 
143         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
144         val DEFAULT_FORMAT_12_HOUR: CharSequence = "h:mm a"
145 
146         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
147         val DEFAULT_FORMAT_24_HOUR: CharSequence = "H:mm"
148     }
149 }