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 18 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.text.format.DateFormat 24 import android.text.format.DateUtils 25 import android.util.AttributeSet 26 import android.view.View 27 import android.widget.FrameLayout 28 import android.widget.ImageView 29 import androidx.appcompat.widget.AppCompatImageView 30 31 import java.text.SimpleDateFormat 32 import java.util.Calendar 33 import java.util.TimeZone 34 35 /** 36 * This widget display an analog clock with two hands for hours and minutes. 37 */ 38 class AnalogClock @JvmOverloads constructor( 39 context: Context, 40 attrs: AttributeSet? = null, 41 defStyleAttr: Int = 0 42 ) : FrameLayout(context, attrs, defStyleAttr) { 43 private val mIntentReceiver: BroadcastReceiver = object : BroadcastReceiver() { onReceivenull44 override fun onReceive(context: Context, intent: Intent) { 45 if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED == intent.action) { 46 val tz = intent.getStringExtra("time-zone") 47 mTime = Calendar.getInstance(TimeZone.getTimeZone(tz)) 48 } 49 onTimeChanged() 50 } 51 } 52 53 private val mClockTick: Runnable = object : Runnable { runnull54 override fun run() { 55 onTimeChanged() 56 57 if (mEnableSeconds) { 58 val now = System.currentTimeMillis() 59 val delay = DateUtils.SECOND_IN_MILLIS - now % DateUtils.SECOND_IN_MILLIS 60 postDelayed(this, delay) 61 } 62 } 63 } 64 65 private val mHourHand: ImageView 66 private val mMinuteHand: ImageView 67 private val mSecondHand: ImageView 68 69 private var mTime = Calendar.getInstance() 70 private val mDescFormat = 71 (DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern() 72 private var mTimeZone: TimeZone? = null 73 private var mEnableSeconds = true 74 75 init { 76 // Must call mutate on these instances, otherwise the drawables will blur, because they're 77 // sharing their size characteristics with the (smaller) world cities analog clocks. 78 val dial: ImageView = AppCompatImageView(context) 79 dial.setImageResource(R.drawable.clock_analog_dial) 80 dial.drawable.mutate() 81 addView(dial) 82 83 mHourHand = AppCompatImageView(context) 84 mHourHand.setImageResource(R.drawable.clock_analog_hour) 85 mHourHand.drawable.mutate() 86 addView(mHourHand) 87 88 mMinuteHand = AppCompatImageView(context) 89 mMinuteHand.setImageResource(R.drawable.clock_analog_minute) 90 mMinuteHand.drawable.mutate() 91 addView(mMinuteHand) 92 93 mSecondHand = AppCompatImageView(context) 94 mSecondHand.setImageResource(R.drawable.clock_analog_second) 95 mSecondHand.drawable.mutate() 96 addView(mSecondHand) 97 } 98 onAttachedToWindownull99 override fun onAttachedToWindow() { 100 super.onAttachedToWindow() 101 102 val filter = IntentFilter() 103 filter.addAction(Intent.ACTION_TIME_TICK) 104 filter.addAction(Intent.ACTION_TIME_CHANGED) 105 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED) 106 context.registerReceiver(mIntentReceiver, filter) 107 108 // Refresh the calendar instance since the time zone may have changed while the receiver 109 // wasn't registered. 110 mTime = Calendar.getInstance(mTimeZone ?: TimeZone.getDefault()) 111 onTimeChanged() 112 113 // Tick every second. 114 if (mEnableSeconds) { 115 mClockTick.run() 116 } 117 } 118 onDetachedFromWindownull119 override fun onDetachedFromWindow() { 120 super.onDetachedFromWindow() 121 122 context.unregisterReceiver(mIntentReceiver) 123 removeCallbacks(mClockTick) 124 } 125 onTimeChangednull126 private fun onTimeChanged() { 127 mTime.timeInMillis = System.currentTimeMillis() 128 val hourAngle = mTime[Calendar.HOUR] * 30f 129 mHourHand.rotation = hourAngle 130 val minuteAngle = mTime[Calendar.MINUTE] * 6f 131 mMinuteHand.rotation = minuteAngle 132 if (mEnableSeconds) { 133 val secondAngle = mTime[Calendar.SECOND] * 6f 134 mSecondHand.rotation = secondAngle 135 } 136 contentDescription = DateFormat.format(mDescFormat, mTime) 137 invalidate() 138 } 139 setTimeZonenull140 fun setTimeZone(id: String) { 141 mTimeZone = TimeZone.getTimeZone(id) 142 mTime.timeZone = mTimeZone!! 143 onTimeChanged() 144 } 145 enableSecondsnull146 fun enableSeconds(enable: Boolean) { 147 mEnableSeconds = enable 148 if (mEnableSeconds) { 149 mSecondHand.visibility = View.VISIBLE 150 mClockTick.run() 151 } else { 152 mSecondHand.visibility = View.GONE 153 } 154 } 155 }