• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.compat.annotation.UnsupportedAppUsage;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.graphics.Canvas;
27 import android.graphics.drawable.Drawable;
28 import android.text.format.DateUtils;
29 import android.util.AttributeSet;
30 import android.view.View;
31 import android.widget.RemoteViews.RemoteView;
32 
33 import java.time.Clock;
34 import java.time.Instant;
35 import java.time.LocalDateTime;
36 import java.time.ZoneId;
37 
38 /**
39  * This widget display an analogic clock with two hands for hours and
40  * minutes.
41  *
42  * @attr ref android.R.styleable#AnalogClock_dial
43  * @attr ref android.R.styleable#AnalogClock_hand_hour
44  * @attr ref android.R.styleable#AnalogClock_hand_minute
45  * @deprecated This widget is no longer supported.
46  */
47 @RemoteView
48 @Deprecated
49 public class AnalogClock extends View {
50     private Clock mClock;
51 
52     @UnsupportedAppUsage
53     private Drawable mHourHand;
54     @UnsupportedAppUsage
55     private Drawable mMinuteHand;
56     @UnsupportedAppUsage
57     private Drawable mDial;
58 
59     private int mDialWidth;
60     private int mDialHeight;
61 
62     private boolean mAttached;
63 
64     private float mMinutes;
65     private float mHour;
66     private boolean mChanged;
67 
AnalogClock(Context context)68     public AnalogClock(Context context) {
69         this(context, null);
70     }
71 
AnalogClock(Context context, AttributeSet attrs)72     public AnalogClock(Context context, AttributeSet attrs) {
73         this(context, attrs, 0);
74     }
75 
AnalogClock(Context context, AttributeSet attrs, int defStyleAttr)76     public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
77         this(context, attrs, defStyleAttr, 0);
78     }
79 
AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)80     public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
81         super(context, attrs, defStyleAttr, defStyleRes);
82 
83         final Resources r = context.getResources();
84         final TypedArray a = context.obtainStyledAttributes(
85                 attrs, com.android.internal.R.styleable.AnalogClock, defStyleAttr, defStyleRes);
86         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.AnalogClock,
87                 attrs, a, defStyleAttr, defStyleRes);
88 
89         mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial);
90         if (mDial == null) {
91             mDial = context.getDrawable(com.android.internal.R.drawable.clock_dial);
92         }
93 
94         mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour);
95         if (mHourHand == null) {
96             mHourHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
97         }
98 
99         mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute);
100         if (mMinuteHand == null) {
101             mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
102         }
103 
104         mClock = Clock.systemDefaultZone();
105 
106         mDialWidth = mDial.getIntrinsicWidth();
107         mDialHeight = mDial.getIntrinsicHeight();
108     }
109 
110     @Override
onAttachedToWindow()111     protected void onAttachedToWindow() {
112         super.onAttachedToWindow();
113 
114         if (!mAttached) {
115             mAttached = true;
116             IntentFilter filter = new IntentFilter();
117 
118             filter.addAction(Intent.ACTION_TIME_TICK);
119             filter.addAction(Intent.ACTION_TIME_CHANGED);
120             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
121 
122             // OK, this is gross but needed. This class is supported by the
123             // remote views machanism and as a part of that the remote views
124             // can be inflated by a context for another user without the app
125             // having interact users permission - just for loading resources.
126             // For exmaple, when adding widgets from a user profile to the
127             // home screen. Therefore, we register the receiver as the current
128             // user not the one the context is for.
129             getContext().registerReceiverAsUser(mIntentReceiver,
130                     android.os.Process.myUserHandle(), filter, null, getHandler());
131         }
132 
133         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
134         // in the main thread, therefore the receiver can't run before this method returns.
135 
136         // The time zone may have changed while the receiver wasn't registered, so update the Time
137         mClock = Clock.systemDefaultZone();
138 
139         // Make sure we update to the current time
140         onTimeChanged();
141     }
142 
143     @Override
onDetachedFromWindow()144     protected void onDetachedFromWindow() {
145         super.onDetachedFromWindow();
146         if (mAttached) {
147             getContext().unregisterReceiver(mIntentReceiver);
148             mAttached = false;
149         }
150     }
151 
152     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)153     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
154 
155         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
156         int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
157         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
158         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
159 
160         float hScale = 1.0f;
161         float vScale = 1.0f;
162 
163         if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
164             hScale = (float) widthSize / (float) mDialWidth;
165         }
166 
167         if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
168             vScale = (float )heightSize / (float) mDialHeight;
169         }
170 
171         float scale = Math.min(hScale, vScale);
172 
173         setMeasuredDimension(resolveSizeAndState((int) (mDialWidth * scale), widthMeasureSpec, 0),
174                 resolveSizeAndState((int) (mDialHeight * scale), heightMeasureSpec, 0));
175     }
176 
177     @Override
onSizeChanged(int w, int h, int oldw, int oldh)178     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
179         super.onSizeChanged(w, h, oldw, oldh);
180         mChanged = true;
181     }
182 
183     @Override
onDraw(Canvas canvas)184     protected void onDraw(Canvas canvas) {
185         super.onDraw(canvas);
186 
187         boolean changed = mChanged;
188         if (changed) {
189             mChanged = false;
190         }
191 
192         int availableWidth = mRight - mLeft;
193         int availableHeight = mBottom - mTop;
194 
195         int x = availableWidth / 2;
196         int y = availableHeight / 2;
197 
198         final Drawable dial = mDial;
199         int w = dial.getIntrinsicWidth();
200         int h = dial.getIntrinsicHeight();
201 
202         boolean scaled = false;
203 
204         if (availableWidth < w || availableHeight < h) {
205             scaled = true;
206             float scale = Math.min((float) availableWidth / (float) w,
207                                    (float) availableHeight / (float) h);
208             canvas.save();
209             canvas.scale(scale, scale, x, y);
210         }
211 
212         if (changed) {
213             dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
214         }
215         dial.draw(canvas);
216 
217         canvas.save();
218         canvas.rotate(mHour / 12.0f * 360.0f, x, y);
219         final Drawable hourHand = mHourHand;
220         if (changed) {
221             w = hourHand.getIntrinsicWidth();
222             h = hourHand.getIntrinsicHeight();
223             hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
224         }
225         hourHand.draw(canvas);
226         canvas.restore();
227 
228         canvas.save();
229         canvas.rotate(mMinutes / 60.0f * 360.0f, x, y);
230 
231         final Drawable minuteHand = mMinuteHand;
232         if (changed) {
233             w = minuteHand.getIntrinsicWidth();
234             h = minuteHand.getIntrinsicHeight();
235             minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
236         }
237         minuteHand.draw(canvas);
238         canvas.restore();
239 
240         if (scaled) {
241             canvas.restore();
242         }
243     }
244 
onTimeChanged()245     private void onTimeChanged() {
246         long nowMillis = mClock.millis();
247         LocalDateTime localDateTime = toLocalDateTime(nowMillis, mClock.getZone());
248 
249         int hour = localDateTime.getHour();
250         int minute = localDateTime.getMinute();
251         int second = localDateTime.getSecond();
252 
253         mMinutes = minute + second / 60.0f;
254         mHour = hour + mMinutes / 60.0f;
255         mChanged = true;
256 
257         updateContentDescription(nowMillis);
258     }
259 
260     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
261         @Override
262         public void onReceive(Context context, Intent intent) {
263             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
264                 String tz = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
265                 mClock = Clock.system(ZoneId.of(tz));
266             }
267 
268             onTimeChanged();
269 
270             invalidate();
271         }
272     };
273 
updateContentDescription(long timeMillis)274     private void updateContentDescription(long timeMillis) {
275         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
276         String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
277         setContentDescription(contentDescription);
278     }
279 
toLocalDateTime(long timeMillis, ZoneId zoneId)280     private static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zoneId) {
281         // java.time types like LocalDateTime / Instant can support the full range of "long millis"
282         // with room to spare so we do not need to worry about overflow / underflow and the
283         // resulting exceptions while the input to this class is a long.
284         Instant instant = Instant.ofEpochMilli(timeMillis);
285         return LocalDateTime.ofInstant(instant, zoneId);
286     }
287 }
288