1 /* 2 * Copyright (C) 2024 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.systemui.shared.clocks 18 19 import android.icu.text.DateFormat 20 import android.icu.text.SimpleDateFormat 21 import android.icu.util.TimeZone as IcuTimeZone 22 import android.icu.util.ULocale 23 import androidx.annotation.VisibleForTesting 24 import java.util.Calendar 25 import java.util.Locale 26 import java.util.TimeZone 27 28 open class TimespecHandler(val cal: Calendar) { 29 var timeZone: TimeZone 30 get() = cal.timeZone 31 set(value) { 32 cal.timeZone = value 33 onTimeZoneChanged() 34 } 35 36 @VisibleForTesting var fakeTimeMills: Long? = null 37 updateTimenull38 fun updateTime() { 39 var timeMs = fakeTimeMills ?: System.currentTimeMillis() 40 cal.timeInMillis = (timeMs * TIME_TRAVEL_SCALE).toLong() 41 } 42 onTimeZoneChangednull43 protected open fun onTimeZoneChanged() {} 44 45 companion object { 46 // Modifying this will cause the clock to run faster or slower. This is a useful way of 47 // manually checking that clocks are correctly animating through time. 48 private const val TIME_TRAVEL_SCALE = 1.0 49 } 50 } 51 52 class DigitalTimespecHandler( 53 val timespec: DigitalTimespec, 54 private val timeFormat: String, 55 cal: Calendar = Calendar.getInstance(), 56 ) : TimespecHandler(cal) { 57 var is24Hr = false 58 set(value) { 59 field = value 60 applyPattern() 61 } 62 63 private var dateFormat = updateSimpleDateFormat(Locale.getDefault()) 64 private var contentDescriptionFormat = getContentDescriptionFormat(Locale.getDefault()) 65 66 init { 67 applyPattern() 68 } 69 onTimeZoneChangednull70 override fun onTimeZoneChanged() { 71 dateFormat.timeZone = IcuTimeZone.getTimeZone(cal.timeZone.id) 72 contentDescriptionFormat?.timeZone = IcuTimeZone.getTimeZone(cal.timeZone.id) 73 applyPattern() 74 } 75 updateLocalenull76 fun updateLocale(locale: Locale) { 77 dateFormat = updateSimpleDateFormat(locale) 78 contentDescriptionFormat = getContentDescriptionFormat(locale) 79 onTimeZoneChanged() 80 } 81 updateSimpleDateFormatnull82 private fun updateSimpleDateFormat(locale: Locale): DateFormat { 83 if (locale.language.equals(Locale.ENGLISH.language)) { 84 // force date format in English, and time format to use format defined in json 85 return SimpleDateFormat(timeFormat, timeFormat, ULocale.forLocale(locale)) 86 } else { 87 return SimpleDateFormat.getInstanceForSkeleton(timeFormat, locale) 88 } 89 } 90 getContentDescriptionFormatnull91 private fun getContentDescriptionFormat(locale: Locale): DateFormat? { 92 return when (timespec) { 93 DigitalTimespec.TIME_FULL_FORMAT -> 94 SimpleDateFormat.getInstanceForSkeleton("hh:mm", locale) 95 else -> null 96 } 97 } 98 applyPatternnull99 private fun applyPattern() { 100 val timeFormat24Hour = timeFormat.replace("hh", "h").replace("h", "HH") 101 val format = if (is24Hr) timeFormat24Hour else timeFormat 102 (dateFormat as SimpleDateFormat).applyPattern(format) 103 (contentDescriptionFormat as? SimpleDateFormat)?.applyPattern( 104 if (is24Hr) CONTENT_DESCRIPTION_TIME_FORMAT_24_HOUR 105 else CONTENT_DESCRIPTION_TIME_FORMAT_12_HOUR 106 ) 107 } 108 getSingleDigitnull109 private fun getSingleDigit(offset: Int): String { 110 val text = dateFormat.format(cal.time).toString() 111 return text.substring(offset, offset + 1) 112 } 113 getDigitStringnull114 fun getDigitString(): String { 115 return when (timespec) { 116 DigitalTimespec.FIRST_DIGIT -> getSingleDigit(0) 117 DigitalTimespec.SECOND_DIGIT -> getSingleDigit(1) 118 DigitalTimespec.DIGIT_PAIR -> dateFormat.format(cal.time).toString() 119 DigitalTimespec.TIME_FULL_FORMAT -> dateFormat.format(cal.time).toString() 120 } 121 } 122 getContentDescriptionnull123 fun getContentDescription(): String? { 124 return when (timespec) { 125 DigitalTimespec.TIME_FULL_FORMAT -> { 126 contentDescriptionFormat?.format(cal.time).toString() 127 } 128 else -> return null 129 } 130 } 131 132 companion object { 133 const val CONTENT_DESCRIPTION_TIME_FORMAT_12_HOUR = "hh:mm" 134 const val CONTENT_DESCRIPTION_TIME_FORMAT_24_HOUR = "HH:mm" 135 } 136 } 137