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 float mDotRadius; 65 private float mDotOffset; 66 private Paint mDotPaint; 67 68 public AnalogClock(Context context) { 69 this(context, null); 70 } 71 72 public AnalogClock(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 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); 83 mHourHand = r.getDrawable(R.drawable.clock_analog_hour); 84 mMinuteHand = r.getDrawable(R.drawable.clock_analog_minute); 85 mSecondHand = r.getDrawable(R.drawable.clock_analog_second); 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 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 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 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 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 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 canvas.save(); 211 canvas.rotate(mHour / 12.0f * 360.0f, x, y); 212 final Drawable hourHand = mHourHand; 213 if (changed) { 214 w = hourHand.getIntrinsicWidth(); 215 h = hourHand.getIntrinsicHeight(); 216 hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)); 217 } 218 hourHand.draw(canvas); 219 canvas.restore(); 220 221 if (!mNoSeconds) { 222 canvas.save(); 223 canvas.rotate(mSeconds / 60.0f * 360.0f, x, y); 224 225 final Drawable secondHand = mSecondHand; 226 if (changed) { 227 w = secondHand.getIntrinsicWidth(); 228 h = secondHand.getIntrinsicHeight(); 229 secondHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)); 230 } 231 secondHand.draw(canvas); 232 canvas.restore(); 233 } 234 canvas.save(); 235 canvas.rotate(mMinutes / 60.0f * 360.0f, x, y); 236 237 final Drawable minuteHand = mMinuteHand; 238 if (changed) { 239 w = minuteHand.getIntrinsicWidth(); 240 h = minuteHand.getIntrinsicHeight(); 241 minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)); 242 } 243 minuteHand.draw(canvas); 244 canvas.restore(); 245 246 if (scaled) { 247 canvas.restore(); 248 } 249 } 250 251 private void onTimeChanged() { 252 mCalendar.setToNow(); 253 254 if (mTimeZoneId != null) { 255 mCalendar.switchTimezone(mTimeZoneId); 256 } 257 258 int hour = mCalendar.hour; 259 int minute = mCalendar.minute; 260 int second = mCalendar.second; 261 // long millis = System.currentTimeMillis() % 1000; 262 263 mSeconds = second;//(float) ((second * 1000 + millis) / 166.666); 264 mMinutes = minute + second / 60.0f; 265 mHour = hour + mMinutes / 60.0f; 266 mChanged = true; 267 268 updateContentDescription(mCalendar); 269 } 270 271 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 272 @Override 273 public void onReceive(Context context, Intent intent) { 274 if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) { 275 String tz = intent.getStringExtra("time-zone"); 276 mCalendar = new Time(TimeZone.getTimeZone(tz).getID()); 277 } 278 onTimeChanged(); 279 invalidate(); 280 } 281 }; 282 283 private final Runnable mClockTick = new Runnable () { 284 285 @Override 286 public void run() { 287 onTimeChanged(); 288 invalidate(); 289 AnalogClock.this.postDelayed(mClockTick, 1000); 290 } 291 }; 292 293 private void updateContentDescription(Time time) { 294 final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR; 295 String contentDescription = DateUtils.formatDateTime(mContext, 296 time.toMillis(false), flags); 297 setContentDescription(contentDescription); 298 } 299 300 public void setTimeZone(String id) { 301 mTimeZoneId = id; 302 onTimeChanged(); 303 } 304 305 public void enableSeconds(boolean enable) { 306 mNoSeconds = !enable; 307 } 308 309 } 310 311