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 }