Home | History | Annotate | Download | only in deskclock
      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 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