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 17package com.android.deskclock; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.IntentFilter; 22import android.content.BroadcastReceiver; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.Color; 27import android.graphics.Paint; 28import android.graphics.drawable.Drawable; 29import android.os.Handler; 30import android.text.format.DateUtils; 31import android.text.format.Time; 32import android.util.AttributeSet; 33import android.view.View; 34import android.widget.RemoteViews.RemoteView; 35 36import java.util.TimeZone; 37 38/** 39 * This widget display an analogic clock with two hands for hours and 40 * minutes. 41 */ 42public 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 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_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 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 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 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 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 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 283 public void setTimeZone(String id) { 284 mTimeZoneId = id; 285 onTimeChanged(); 286 } 287 288 public void enableSeconds(boolean enable) { 289 mNoSeconds = !enable; 290 } 291 292} 293 294