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