1 /*
2  * Copyright (C) 2015 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 androidx.appcompat.widget.AppCompatImageView;
24 import android.text.format.DateFormat;
25 import android.util.AttributeSet;
26 import android.widget.FrameLayout;
27 import android.widget.ImageView;
28 
29 import java.text.SimpleDateFormat;
30 import java.util.Calendar;
31 import java.util.TimeZone;
32 
33 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
34 
35 /**
36  * This widget display an analog clock with two hands for hours and minutes.
37  */
38 public class AnalogClock extends FrameLayout {
39 
40     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
41         @Override
42         public void onReceive(Context context, Intent intent) {
43             if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
44                 final String tz = intent.getStringExtra("time-zone");
45                 mTime = Calendar.getInstance(TimeZone.getTimeZone(tz));
46             }
47             onTimeChanged();
48         }
49     };
50 
51     private final Runnable mClockTick = new Runnable() {
52         @Override
53         public void run() {
54             onTimeChanged();
55 
56             if (mEnableSeconds) {
57                 final long now = System.currentTimeMillis();
58                 final long delay = SECOND_IN_MILLIS - now % SECOND_IN_MILLIS;
59                 postDelayed(this, delay);
60             }
61         }
62     };
63 
64     private final ImageView mHourHand;
65     private final ImageView mMinuteHand;
66     private final ImageView mSecondHand;
67 
68     private Calendar mTime;
69     private String mDescFormat;
70     private TimeZone mTimeZone;
71     private boolean mEnableSeconds = true;
72 
AnalogClock(Context context)73     public AnalogClock(Context context) {
74         this(context, null /* attrs */);
75     }
76 
AnalogClock(Context context, AttributeSet attrs)77     public AnalogClock(Context context, AttributeSet attrs) {
78         this(context, attrs, 0 /* defStyleAttr */);
79     }
80 
AnalogClock(Context context, AttributeSet attrs, int defStyleAttr)81     public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
82         super(context, attrs, defStyleAttr);
83 
84         mTime = Calendar.getInstance();
85         mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
86 
87         // Must call mutate on these instances, otherwise the drawables will blur, because they're
88         // sharing their size characteristics with the (smaller) world cities analog clocks.
89         final ImageView dial = new AppCompatImageView(context);
90         dial.setImageResource(R.drawable.clock_analog_dial);
91         dial.getDrawable().mutate();
92         addView(dial);
93 
94         mHourHand = new AppCompatImageView(context);
95         mHourHand.setImageResource(R.drawable.clock_analog_hour);
96         mHourHand.getDrawable().mutate();
97         addView(mHourHand);
98 
99         mMinuteHand = new AppCompatImageView(context);
100         mMinuteHand.setImageResource(R.drawable.clock_analog_minute);
101         mMinuteHand.getDrawable().mutate();
102         addView(mMinuteHand);
103 
104         mSecondHand = new AppCompatImageView(context);
105         mSecondHand.setImageResource(R.drawable.clock_analog_second);
106         mSecondHand.getDrawable().mutate();
107         addView(mSecondHand);
108     }
109 
110     @Override
onAttachedToWindow()111     protected void onAttachedToWindow() {
112         super.onAttachedToWindow();
113 
114         final IntentFilter filter = new IntentFilter();
115         filter.addAction(Intent.ACTION_TIME_TICK);
116         filter.addAction(Intent.ACTION_TIME_CHANGED);
117         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
118         getContext().registerReceiver(mIntentReceiver, filter);
119 
120         // Refresh the calendar instance since the time zone may have changed while the receiver
121         // wasn't registered.
122         mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
123         onTimeChanged();
124 
125         // Tick every second.
126         if (mEnableSeconds) {
127             mClockTick.run();
128         }
129     }
130 
131     @Override
onDetachedFromWindow()132     protected void onDetachedFromWindow() {
133         super.onDetachedFromWindow();
134 
135         getContext().unregisterReceiver(mIntentReceiver);
136         removeCallbacks(mClockTick);
137     }
138 
onTimeChanged()139     private void onTimeChanged() {
140         mTime.setTimeInMillis(System.currentTimeMillis());
141         final float hourAngle = mTime.get(Calendar.HOUR) * 30f;
142         mHourHand.setRotation(hourAngle);
143         final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
144         mMinuteHand.setRotation(minuteAngle);
145         if (mEnableSeconds) {
146             final float secondAngle = mTime.get(Calendar.SECOND) * 6f;
147             mSecondHand.setRotation(secondAngle);
148         }
149         setContentDescription(DateFormat.format(mDescFormat, mTime));
150         invalidate();
151     }
152 
setTimeZone(String id)153     public void setTimeZone(String id) {
154         mTimeZone = TimeZone.getTimeZone(id);
155         mTime.setTimeZone(mTimeZone);
156         onTimeChanged();
157     }
158 
enableSeconds(boolean enable)159     public void enableSeconds(boolean enable) {
160         mEnableSeconds = enable;
161         if (mEnableSeconds) {
162             mSecondHand.setVisibility(VISIBLE);
163             mClockTick.run();
164         } else {
165             mSecondHand.setVisibility(GONE);
166         }
167     }
168 }
169