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, int defStyleAttr) {
     71         this(context, attrs, defStyleAttr, 0);
     72     }
     73 
     74     public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
     75         super(context, attrs, defStyleAttr, defStyleRes);
     76 
     77         final Resources r = context.getResources();
     78         final TypedArray a = context.obtainStyledAttributes(
     79                 attrs, com.android.internal.R.styleable.AnalogClock, defStyleAttr, defStyleRes);
     80 
     81         mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial);
     82         if (mDial == null) {
     83             mDial = context.getDrawable(com.android.internal.R.drawable.clock_dial);
     84         }
     85 
     86         mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour);
     87         if (mHourHand == null) {
     88             mHourHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
     89         }
     90 
     91         mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute);
     92         if (mMinuteHand == null) {
     93             mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
     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             // OK, this is gross but needed. This class is supported by the
    115             // remote views machanism and as a part of that the remote views
    116             // can be inflated by a context for another user without the app
    117             // having interact users permission - just for loading resources.
    118             // For exmaple, when adding widgets from a user profile to the
    119             // home screen. Therefore, we register the receiver as the current
    120             // user not the one the context is for.
    121             getContext().registerReceiverAsUser(mIntentReceiver,
    122                     android.os.Process.myUserHandle(), filter, null, mHandler);
    123         }
    124 
    125         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
    126         // in the main thread, therefore the receiver can't run before this method returns.
    127 
    128         // The time zone may have changed while the receiver wasn't registered, so update the Time
    129         mCalendar = new Time();
    130 
    131         // Make sure we update to the current time
    132         onTimeChanged();
    133     }
    134 
    135     @Override
    136     protected void onDetachedFromWindow() {
    137         super.onDetachedFromWindow();
    138         if (mAttached) {
    139             getContext().unregisterReceiver(mIntentReceiver);
    140             mAttached = false;
    141         }
    142     }
    143 
    144     @Override
    145     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    146 
    147         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    148         int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
    149         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    150         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
    151 
    152         float hScale = 1.0f;
    153         float vScale = 1.0f;
    154 
    155         if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
    156             hScale = (float) widthSize / (float) mDialWidth;
    157         }
    158 
    159         if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
    160             vScale = (float )heightSize / (float) mDialHeight;
    161         }
    162 
    163         float scale = Math.min(hScale, vScale);
    164 
    165         setMeasuredDimension(resolveSizeAndState((int) (mDialWidth * scale), widthMeasureSpec, 0),
    166                 resolveSizeAndState((int) (mDialHeight * scale), heightMeasureSpec, 0));
    167     }
    168 
    169     @Override
    170     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    171         super.onSizeChanged(w, h, oldw, oldh);
    172         mChanged = true;
    173     }
    174 
    175     @Override
    176     protected void onDraw(Canvas canvas) {
    177         super.onDraw(canvas);
    178 
    179         boolean changed = mChanged;
    180         if (changed) {
    181             mChanged = false;
    182         }
    183 
    184         int availableWidth = mRight - mLeft;
    185         int availableHeight = mBottom - mTop;
    186 
    187         int x = availableWidth / 2;
    188         int y = availableHeight / 2;
    189 
    190         final Drawable dial = mDial;
    191         int w = dial.getIntrinsicWidth();
    192         int h = dial.getIntrinsicHeight();
    193 
    194         boolean scaled = false;
    195 
    196         if (availableWidth < w || availableHeight < h) {
    197             scaled = true;
    198             float scale = Math.min((float) availableWidth / (float) w,
    199                                    (float) availableHeight / (float) h);
    200             canvas.save();
    201             canvas.scale(scale, scale, x, y);
    202         }
    203 
    204         if (changed) {
    205             dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
    206         }
    207         dial.draw(canvas);
    208 
    209         canvas.save();
    210         canvas.rotate(mHour / 12.0f * 360.0f, x, y);
    211         final Drawable hourHand = mHourHand;
    212         if (changed) {
    213             w = hourHand.getIntrinsicWidth();
    214             h = hourHand.getIntrinsicHeight();
    215             hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
    216         }
    217         hourHand.draw(canvas);
    218         canvas.restore();
    219 
    220         canvas.save();
    221         canvas.rotate(mMinutes / 60.0f * 360.0f, x, y);
    222 
    223         final Drawable minuteHand = mMinuteHand;
    224         if (changed) {
    225             w = minuteHand.getIntrinsicWidth();
    226             h = minuteHand.getIntrinsicHeight();
    227             minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
    228         }
    229         minuteHand.draw(canvas);
    230         canvas.restore();
    231 
    232         if (scaled) {
    233             canvas.restore();
    234         }
    235     }
    236 
    237     private void onTimeChanged() {
    238         mCalendar.setToNow();
    239 
    240         int hour = mCalendar.hour;
    241         int minute = mCalendar.minute;
    242         int second = mCalendar.second;
    243 
    244         mMinutes = minute + second / 60.0f;
    245         mHour = hour + mMinutes / 60.0f;
    246         mChanged = true;
    247 
    248         updateContentDescription(mCalendar);
    249     }
    250 
    251     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    252         @Override
    253         public void onReceive(Context context, Intent intent) {
    254             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
    255                 String tz = intent.getStringExtra("time-zone");
    256                 mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
    257             }
    258 
    259             onTimeChanged();
    260 
    261             invalidate();
    262         }
    263     };
    264 
    265     private void updateContentDescription(Time time) {
    266         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
    267         String contentDescription = DateUtils.formatDateTime(mContext,
    268                 time.toMillis(false), flags);
    269         setContentDescription(contentDescription);
    270     }
    271 }
    272