1 /*
2  * Copyright (C) 2006 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.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.BroadcastReceiver;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.Paint;
28 import android.graphics.drawable.Drawable;
29 import android.os.Handler;
30 import android.text.format.DateUtils;
31 import android.text.format.Time;
32 import android.util.AttributeSet;
33 import android.view.View;
34 import android.widget.RemoteViews.RemoteView;
35 
36 import java.util.TimeZone;
37 
38 /**
39  * This widget display an analogic clock with two hands for hours and
40  * minutes.
41  */
42 public class AnalogClock extends View {
43     private Time mCalendar;
44 
45     private final Drawable mHourHand;
46     private final Drawable mMinuteHand;
47     private final Drawable mSecondHand;
48     private final Drawable mDial;
49 
50     private final int mDialWidth;
51     private final int mDialHeight;
52 
53     private boolean mAttached;
54 
55     private final Handler mHandler = new Handler();
56     private float mSeconds;
57     private float mMinutes;
58     private float mHour;
59     private boolean mChanged;
60     private final Context mContext;
61     private String mTimeZoneId;
62     private boolean mNoSeconds = false;
63 
64     private final float mDotRadius;
65     private final float mDotOffset;
66     private Paint mDotPaint;
67 
AnalogClock(Context context)68     public AnalogClock(Context context) {
69         this(context, null);
70     }
71 
AnalogClock(Context context, AttributeSet attrs)72     public AnalogClock(Context context, AttributeSet attrs) {
73         this(context, attrs, 0);
74     }
75 
AnalogClock(Context context, AttributeSet attrs, int defStyle)76     public AnalogClock(Context context, AttributeSet attrs,
77                        int defStyle) {
78         super(context, attrs, defStyle);
79         mContext = context;
80         Resources r = mContext.getResources();
81 
82         mDial = r.getDrawable(R.drawable.clock_analog_dial_mipmap);
83         mHourHand = r.getDrawable(R.drawable.clock_analog_hour_mipmap);
84         mMinuteHand = r.getDrawable(R.drawable.clock_analog_minute_mipmap);
85         mSecondHand = r.getDrawable(R.drawable.clock_analog_second_mipmap);
86 
87         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AnalogClock);
88         mDotRadius = a.getDimension(R.styleable.AnalogClock_jewelRadius, 0);
89         mDotOffset = a.getDimension(R.styleable.AnalogClock_jewelOffset, 0);
90         final int dotColor = a.getColor(R.styleable.AnalogClock_jewelColor, Color.WHITE);
91         if (dotColor != 0) {
92             mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
93             mDotPaint.setColor(dotColor);
94         }
95 
96         mCalendar = new Time();
97 
98         mDialWidth = mDial.getIntrinsicWidth();
99         mDialHeight = mDial.getIntrinsicHeight();
100     }
101 
102     @Override
onAttachedToWindow()103     protected void onAttachedToWindow() {
104         super.onAttachedToWindow();
105 
106         if (!mAttached) {
107             mAttached = true;
108             IntentFilter filter = new IntentFilter();
109 
110             filter.addAction(Intent.ACTION_TIME_TICK);
111             filter.addAction(Intent.ACTION_TIME_CHANGED);
112             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
113 
114             getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
115         }
116 
117         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
118         // in the main thread, therefore the receiver can't run before this method returns.
119 
120         // The time zone may have changed while the receiver wasn't registered, so update the Time
121         mCalendar = new Time();
122 
123         // Make sure we update to the current time
124         onTimeChanged();
125 
126         // tick the seconds
127         post(mClockTick);
128 
129     }
130 
131     @Override
onDetachedFromWindow()132     protected void onDetachedFromWindow() {
133         super.onDetachedFromWindow();
134         if (mAttached) {
135             getContext().unregisterReceiver(mIntentReceiver);
136             removeCallbacks(mClockTick);
137             mAttached = false;
138         }
139     }
140 
141     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)142     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
143 
144         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
145         int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
146         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
147         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
148 
149         float hScale = 1.0f;
150         float vScale = 1.0f;
151 
152         if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
153             hScale = (float) widthSize / (float) mDialWidth;
154         }
155 
156         if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
157             vScale = (float )heightSize / (float) mDialHeight;
158         }
159 
160         float scale = Math.min(hScale, vScale);
161 
162         setMeasuredDimension(resolveSizeAndState((int) (mDialWidth * scale), widthMeasureSpec, 0),
163                 resolveSizeAndState((int) (mDialHeight * scale), heightMeasureSpec, 0));
164     }
165 
166     @Override
onSizeChanged(int w, int h, int oldw, int oldh)167     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
168         super.onSizeChanged(w, h, oldw, oldh);
169         mChanged = true;
170     }
171 
172     @Override
onDraw(Canvas canvas)173     protected void onDraw(Canvas canvas) {
174         super.onDraw(canvas);
175 
176         boolean changed = mChanged;
177         if (changed) {
178             mChanged = false;
179         }
180 
181         int availableWidth = getWidth();
182         int availableHeight = getHeight();
183 
184         int x = availableWidth / 2;
185         int y = availableHeight / 2;
186 
187         final Drawable dial = mDial;
188         int w = dial.getIntrinsicWidth();
189         int h = dial.getIntrinsicHeight();
190 
191         boolean scaled = false;
192 
193         if (availableWidth < w || availableHeight < h) {
194             scaled = true;
195             float scale = Math.min((float) availableWidth / (float) w,
196                                    (float) availableHeight / (float) h);
197             canvas.save();
198             canvas.scale(scale, scale, x, y);
199         }
200 
201         if (changed) {
202             dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
203         }
204         dial.draw(canvas);
205 
206         if (mDotRadius > 0f && mDotPaint != null) {
207             canvas.drawCircle(x, y - (h / 2) + mDotOffset, mDotRadius, mDotPaint);
208         }
209 
210         drawHand(canvas, mHourHand, x, y, mHour / 12.0f * 360.0f, changed);
211         drawHand(canvas, mMinuteHand, x, y, mMinutes / 60.0f * 360.0f, changed);
212         if (!mNoSeconds) {
213             drawHand(canvas, mSecondHand, x, y, mSeconds / 60.0f * 360.0f, changed);
214         }
215 
216         if (scaled) {
217             canvas.restore();
218         }
219     }
220 
drawHand(Canvas canvas, Drawable hand, int x, int y, float angle, boolean changed)221     private void drawHand(Canvas canvas, Drawable hand, int x, int y, float angle,
222           boolean changed) {
223       canvas.save();
224       canvas.rotate(angle, x, y);
225       if (changed) {
226           final int w = hand.getIntrinsicWidth();
227           final int h = hand.getIntrinsicHeight();
228           hand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
229       }
230       hand.draw(canvas);
231       canvas.restore();
232     }
233 
onTimeChanged()234     private void onTimeChanged() {
235         mCalendar.setToNow();
236 
237         if (mTimeZoneId != null) {
238             mCalendar.switchTimezone(mTimeZoneId);
239         }
240 
241         int hour = mCalendar.hour;
242         int minute = mCalendar.minute;
243         int second = mCalendar.second;
244   //      long millis = System.currentTimeMillis() % 1000;
245 
246         mSeconds = second;//(float) ((second * 1000 + millis) / 166.666);
247         mMinutes = minute + second / 60.0f;
248         mHour = hour + mMinutes / 60.0f;
249         mChanged = true;
250 
251         updateContentDescription(mCalendar);
252     }
253 
254     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
255         @Override
256         public void onReceive(Context context, Intent intent) {
257             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
258                 String tz = intent.getStringExtra("time-zone");
259                 mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
260             }
261             onTimeChanged();
262             invalidate();
263         }
264     };
265 
266     private final Runnable mClockTick = new Runnable () {
267 
268         @Override
269         public void run() {
270             onTimeChanged();
271             invalidate();
272             AnalogClock.this.postDelayed(mClockTick, 1000);
273         }
274     };
275 
updateContentDescription(Time time)276     private void updateContentDescription(Time time) {
277         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
278         String contentDescription = DateUtils.formatDateTime(mContext,
279                 time.toMillis(false), flags);
280         setContentDescription(contentDescription);
281     }
282 
setTimeZone(String id)283     public void setTimeZone(String id) {
284         mTimeZoneId = id;
285         onTimeChanged();
286     }
287 
enableSeconds(boolean enable)288     public void enableSeconds(boolean enable) {
289         mNoSeconds = !enable;
290     }
291 
292 }
293 
294