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 62 public AnalogClock(Context context) { 63 this(context, null); 64 } 65 66 public AnalogClock(Context context, AttributeSet attrs) { 67 this(context, attrs, 0); 68 } 69 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 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 125 protected void onDetachedFromWindow() { 126 super.onDetachedFromWindow(); 127 if (mAttached) { 128 getContext().unregisterReceiver(mIntentReceiver); 129 mAttached = false; 130 } 131 } 132 133 @Override 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 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 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 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 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