• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.widget;
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.drawable.Drawable;
27 import android.os.Handler;
28 import android.text.format.DateUtils;
29 import android.text.format.Time;
30 import android.util.AttributeSet;
31 import android.view.View;
32 import android.widget.RemoteViews.RemoteView;
33 
34 import java.util.TimeZone;
35 
36 /**
37  * This widget display an analogic clock with two hands for hours and
38  * minutes.
39  *
40  * @attr ref android.R.styleable#AnalogClock_dial
41  * @attr ref android.R.styleable#AnalogClock_hand_hour
42  * @attr ref android.R.styleable#AnalogClock_hand_minute
43  */
44 @RemoteView
45 public class AnalogClock extends View {
46     private Time mCalendar;
47 
48     private Drawable mHourHand;
49     private Drawable mMinuteHand;
50     private Drawable mDial;
51 
52     private int mDialWidth;
53     private int mDialHeight;
54 
55     private boolean mAttached;
56 
57     private final Handler mHandler = new Handler();
58     private float mMinutes;
59     private float mHour;
60     private boolean mChanged;
61 
AnalogClock(Context context)62     public AnalogClock(Context context) {
63         this(context, null);
64     }
65 
AnalogClock(Context context, AttributeSet attrs)66     public AnalogClock(Context context, AttributeSet attrs) {
67         this(context, attrs, 0);
68     }
69 
AnalogClock(Context context, AttributeSet attrs, int defStyle)70     public AnalogClock(Context context, AttributeSet attrs,
71                        int defStyle) {
72         super(context, attrs, defStyle);
73         Resources r = mContext.getResources();
74         TypedArray a =
75                 context.obtainStyledAttributes(
76                         attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);
77 
78         mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial);
79         if (mDial == null) {
80             mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
81         }
82 
83         mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour);
84         if (mHourHand == null) {
85             mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
86         }
87 
88         mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute);
89         if (mMinuteHand == null) {
90             mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
91         }
92 
93         mCalendar = new Time();
94 
95         mDialWidth = mDial.getIntrinsicWidth();
96         mDialHeight = mDial.getIntrinsicHeight();
97     }
98 
99     @Override
onAttachedToWindow()100     protected void onAttachedToWindow() {
101         super.onAttachedToWindow();
102 
103         if (!mAttached) {
104             mAttached = true;
105             IntentFilter filter = new IntentFilter();
106 
107             filter.addAction(Intent.ACTION_TIME_TICK);
108             filter.addAction(Intent.ACTION_TIME_CHANGED);
109             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
110 
111             getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
112         }
113 
114         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
115         // in the main thread, therefore the receiver can't run before this method returns.
116 
117         // The time zone may have changed while the receiver wasn't registered, so update the Time
118         mCalendar = new Time();
119 
120         // Make sure we update to the current time
121         onTimeChanged();
122     }
123 
124     @Override
onDetachedFromWindow()125     protected void onDetachedFromWindow() {
126         super.onDetachedFromWindow();
127         if (mAttached) {
128             getContext().unregisterReceiver(mIntentReceiver);
129             mAttached = false;
130         }
131     }
132 
133     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)134     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
135 
136         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
137         int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
138         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
139         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
140 
141         float hScale = 1.0f;
142         float vScale = 1.0f;
143 
144         if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
145             hScale = (float) widthSize / (float) mDialWidth;
146         }
147 
148         if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
149             vScale = (float )heightSize / (float) mDialHeight;
150         }
151 
152         float scale = Math.min(hScale, vScale);
153 
154         setMeasuredDimension(resolveSizeAndState((int) (mDialWidth * scale), widthMeasureSpec, 0),
155                 resolveSizeAndState((int) (mDialHeight * scale), heightMeasureSpec, 0));
156     }
157 
158     @Override
onSizeChanged(int w, int h, int oldw, int oldh)159     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
160         super.onSizeChanged(w, h, oldw, oldh);
161         mChanged = true;
162     }
163 
164     @Override
onDraw(Canvas canvas)165     protected void onDraw(Canvas canvas) {
166         super.onDraw(canvas);
167 
168         boolean changed = mChanged;
169         if (changed) {
170             mChanged = false;
171         }
172 
173         int availableWidth = mRight - mLeft;
174         int availableHeight = mBottom - mTop;
175 
176         int x = availableWidth / 2;
177         int y = availableHeight / 2;
178 
179         final Drawable dial = mDial;
180         int w = dial.getIntrinsicWidth();
181         int h = dial.getIntrinsicHeight();
182 
183         boolean scaled = false;
184 
185         if (availableWidth < w || availableHeight < h) {
186             scaled = true;
187             float scale = Math.min((float) availableWidth / (float) w,
188                                    (float) availableHeight / (float) h);
189             canvas.save();
190             canvas.scale(scale, scale, x, y);
191         }
192 
193         if (changed) {
194             dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
195         }
196         dial.draw(canvas);
197 
198         canvas.save();
199         canvas.rotate(mHour / 12.0f * 360.0f, x, y);
200         final Drawable hourHand = mHourHand;
201         if (changed) {
202             w = hourHand.getIntrinsicWidth();
203             h = hourHand.getIntrinsicHeight();
204             hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
205         }
206         hourHand.draw(canvas);
207         canvas.restore();
208 
209         canvas.save();
210         canvas.rotate(mMinutes / 60.0f * 360.0f, x, y);
211 
212         final Drawable minuteHand = mMinuteHand;
213         if (changed) {
214             w = minuteHand.getIntrinsicWidth();
215             h = minuteHand.getIntrinsicHeight();
216             minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
217         }
218         minuteHand.draw(canvas);
219         canvas.restore();
220 
221         if (scaled) {
222             canvas.restore();
223         }
224     }
225 
onTimeChanged()226     private void onTimeChanged() {
227         mCalendar.setToNow();
228 
229         int hour = mCalendar.hour;
230         int minute = mCalendar.minute;
231         int second = mCalendar.second;
232 
233         mMinutes = minute + second / 60.0f;
234         mHour = hour + mMinutes / 60.0f;
235         mChanged = true;
236 
237         updateContentDescription(mCalendar);
238     }
239 
240     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
241         @Override
242         public void onReceive(Context context, Intent intent) {
243             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
244                 String tz = intent.getStringExtra("time-zone");
245                 mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
246             }
247 
248             onTimeChanged();
249 
250             invalidate();
251         }
252     };
253 
updateContentDescription(Time time)254     private void updateContentDescription(Time time) {
255         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
256         String contentDescription = DateUtils.formatDateTime(mContext,
257                 time.toMillis(false), flags);
258         setContentDescription(contentDescription);
259     }
260 }
261