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  * @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