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