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