• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.globaltime;
18 
19 import java.util.Calendar;
20 import java.util.Date;
21 import java.util.TimeZone;
22 
23 import android.graphics.Canvas;
24 import android.graphics.Paint;
25 import android.graphics.Path;
26 import android.graphics.RectF;
27 import android.text.format.DateUtils;
28 import android.view.animation.AccelerateDecelerateInterpolator;
29 import android.view.animation.Interpolator;
30 
31 /**
32  * A class that draws an analog clock face with information about the current
33  * time in a given city.
34  */
35 public class Clock {
36 
37     static final int MILLISECONDS_PER_MINUTE = 60 * 1000;
38     static final int MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
39 
40     private City mCity = null;
41     private long mCitySwitchTime;
42     private long mTime;
43 
44     private float mColorRed = 1.0f;
45     private float mColorGreen = 1.0f;
46     private float mColorBlue = 1.0f;
47 
48     private long mOldOffset;
49 
50     private Interpolator mClockHandInterpolator =
51         new AccelerateDecelerateInterpolator();
52 
Clock()53     public Clock() {
54         // Empty constructor
55     }
56 
57     /**
58      * Adds a line to the given Path.  The line extends from
59      * radius r0 to radius r1 about the center point (cx, cy),
60      * at an angle given by pos.
61      *
62      * @param path the Path to draw to
63      * @param radius the radius of the outer rim of the clock
64      * @param pos the angle, with 0 and 1 at 12:00
65      * @param cx the X coordinate of the clock center
66      * @param cy the Y coordinate of the clock center
67      * @param r0 the starting radius for the line
68      * @param r1 the ending radius for the line
69      */
drawLine(Path path, float radius, float pos, float cx, float cy, float r0, float r1)70     private static void drawLine(Path path,
71         float radius, float pos, float cx, float cy, float r0, float r1) {
72         float theta = pos * Shape.TWO_PI - Shape.PI_OVER_TWO;
73         float dx = (float) Math.cos(theta);
74         float dy = (float) Math.sin(theta);
75         float p0x = cx + dx * r0;
76         float p0y = cy + dy * r0;
77         float p1x = cx + dx * r1;
78         float p1y = cy + dy * r1;
79 
80         float ox =  (p1y - p0y);
81         float oy = -(p1x - p0x);
82 
83         float norm = (radius / 2.0f) / (float) Math.sqrt(ox * ox + oy * oy);
84         ox *= norm;
85         oy *= norm;
86 
87         path.moveTo(p0x - ox, p0y - oy);
88         path.lineTo(p1x - ox, p1y - oy);
89         path.lineTo(p1x + ox, p1y + oy);
90         path.lineTo(p0x + ox, p0y + oy);
91         path.close();
92     }
93 
94     /**
95      * Adds a vertical arrow to the given Path.
96      *
97      * @param path the Path to draw to
98      */
drawVArrow(Path path, float cx, float cy, float width, float height)99     private static void drawVArrow(Path path,
100         float cx, float cy, float width, float height) {
101         path.moveTo(cx - width / 2.0f, cy);
102         path.lineTo(cx, cy + height);
103         path.lineTo(cx + width / 2.0f, cy);
104         path.close();
105     }
106 
107     /**
108      * Adds a horizontal arrow to the given Path.
109      *
110      * @param path the Path to draw to
111      */
drawHArrow(Path path, float cx, float cy, float width, float height)112     private static void drawHArrow(Path path,
113         float cx, float cy, float width, float height) {
114         path.moveTo(cx, cy - height / 2.0f);
115         path.lineTo(cx + width, cy);
116         path.lineTo(cx, cy + height / 2.0f);
117         path.close();
118     }
119 
120     /**
121      * Returns an offset in milliseconds to be subtracted from the current time
122      * in order to obtain an smooth interpolation between the previously
123      * displayed time and the current time.
124      */
getOffset(float lerp)125     private long getOffset(float lerp) {
126         long doffset = (long) (mCity.getOffset() *
127             (float) MILLISECONDS_PER_HOUR - mOldOffset);
128         int sign;
129         if (doffset < 0) {
130             doffset = -doffset;
131             sign = -1;
132         } else {
133             sign = 1;
134         }
135 
136         while (doffset > 12L * MILLISECONDS_PER_HOUR) {
137             doffset -= 12L * MILLISECONDS_PER_HOUR;
138         }
139         if (doffset > 6L * MILLISECONDS_PER_HOUR) {
140             doffset = 12L * MILLISECONDS_PER_HOUR - doffset;
141             sign = -sign;
142         }
143 
144         // Interpolate doffset towards 0
145         doffset = (long)((1.0f - lerp)*doffset);
146 
147         // Keep the same seconds count
148         long dh = doffset / (MILLISECONDS_PER_HOUR);
149         doffset -= dh * MILLISECONDS_PER_HOUR;
150         long dm = doffset / MILLISECONDS_PER_MINUTE;
151         doffset = sign * (60 * dh + dm) * MILLISECONDS_PER_MINUTE;
152 
153         return doffset;
154     }
155 
156     /**
157      * Set the city to be displayed.  setCity(null) resets things so the clock
158      * hand animation won't occur next time.
159      */
setCity(City city)160     public void setCity(City city) {
161         if (mCity != city) {
162             if (mCity != null) {
163                 mOldOffset =
164                     (long) (mCity.getOffset() * (float) MILLISECONDS_PER_HOUR);
165             } else if (city != null) {
166                 mOldOffset =
167                     (long) (city.getOffset() * (float) MILLISECONDS_PER_HOUR);
168             } else {
169                 mOldOffset = 0L; // this will never be used
170             }
171             this.mCitySwitchTime = System.currentTimeMillis();
172             this.mCity = city;
173         }
174     }
175 
setTime(long time)176     public void setTime(long time) {
177         this.mTime = time;
178     }
179 
180     /**
181      * Draws the clock face.
182      *
183      * @param canvas the Canvas to draw to
184      * @param cx the X coordinate of the clock center
185      * @param cy the Y coordinate of the clock center
186      * @param radius the radius of the clock face
187      * @param alpha the translucency of the clock face
188      * @param textAlpha the translucency of the text
189      * @param showCityName if true, display the city name
190      * @param showTime if true, display the time digitally
191      * @param showUpArrow if true, display an up arrow
192      * @param showDownArrow if true, display a down arrow
193      * @param showLeftRightArrows if true, display left and right arrows
194      * @param prefixChars number of characters of the city name to draw in bold
195      */
drawClock(Canvas canvas, float cx, float cy, float radius, float alpha, float textAlpha, boolean showCityName, boolean showTime, boolean showUpArrow, boolean showDownArrow, boolean showLeftRightArrows, int prefixChars)196     public void drawClock(Canvas canvas,
197         float cx, float cy, float radius, float alpha, float textAlpha,
198         boolean showCityName, boolean showTime,
199         boolean showUpArrow,  boolean showDownArrow, boolean showLeftRightArrows,
200         int prefixChars) {
201         Paint paint = new Paint();
202         paint.setAntiAlias(true);
203 
204         int iradius = (int)radius;
205 
206         TimeZone tz = mCity.getTimeZone();
207 
208         // Compute an interpolated time to animate between the previously
209         // displayed time and the current time
210         float lerp = Math.min(1.0f,
211             (System.currentTimeMillis() - mCitySwitchTime) / 500.0f);
212         lerp = mClockHandInterpolator.getInterpolation(lerp);
213         long doffset = lerp < 1.0f ? getOffset(lerp) : 0L;
214 
215         // Determine the interpolated time for the given time zone
216         Calendar cal = Calendar.getInstance(tz);
217         cal.setTimeInMillis(mTime - doffset);
218         int hour = cal.get(Calendar.HOUR_OF_DAY);
219         int minute = cal.get(Calendar.MINUTE);
220         int second = cal.get(Calendar.SECOND);
221         int milli = cal.get(Calendar.MILLISECOND);
222 
223         float offset = tz.getRawOffset() / (float) MILLISECONDS_PER_HOUR;
224         float daylightOffset = tz.inDaylightTime(new Date(mTime)) ?
225             tz.getDSTSavings() / (float) MILLISECONDS_PER_HOUR : 0.0f;
226 
227         float absOffset = offset < 0 ? -offset : offset;
228         int offsetH = (int) absOffset;
229         int offsetM = (int) (60.0f * (absOffset - offsetH));
230         hour %= 12;
231 
232         // Get the city name and digital time strings
233         String cityName = mCity.getName();
234         cal.setTimeInMillis(mTime);
235         String time = DateUtils.timeString(cal.getTimeInMillis()) + " "  +
236             DateUtils.getDayOfWeekString(cal.get(Calendar.DAY_OF_WEEK),
237                     DateUtils.LENGTH_SHORT) + " " +
238             " (UTC" +
239             (offset >= 0 ? "+" : "-") +
240             offsetH +
241             (offsetM == 0 ? "" : ":" + offsetM) +
242             (daylightOffset == 0 ? "" : "+" + daylightOffset) +
243             ")";
244 
245         float th = paint.getTextSize();
246         float tw;
247 
248         // Set the text color
249         paint.setARGB((int) (textAlpha * 255.0f),
250                       (int) (mColorRed * 255.0f),
251                       (int) (mColorGreen * 255.0f),
252                       (int) (mColorBlue * 255.0f));
253 
254         tw = paint.measureText(cityName);
255         if (showCityName) {
256             // Increment prefixChars to include any spaces
257             for (int i = 0; i < prefixChars; i++) {
258                 if (cityName.charAt(i) == ' ') {
259                     ++prefixChars;
260                 }
261             }
262 
263             // Draw the city name
264             canvas.drawText(cityName, cx - tw / 2, cy - radius - th, paint);
265             // Overstrike the first 'prefixChars' characters
266             canvas.drawText(cityName.substring(0, prefixChars),
267                             cx - tw / 2 + 1, cy - radius - th, paint);
268         }
269         tw = paint.measureText(time);
270         if (showTime) {
271             canvas.drawText(time, cx - tw / 2, cy + radius + th + 5, paint);
272         }
273 
274         paint.setARGB((int)(alpha * 255.0f),
275                       (int)(mColorRed * 255.0f),
276                       (int)(mColorGreen * 255.0f),
277                       (int)(mColorBlue * 255.0f));
278 
279         paint.setStyle(Paint.Style.FILL);
280         canvas.drawOval(new RectF(cx - 2, cy - 2, cx + 2, cy + 2), paint);
281 
282         paint.setStyle(Paint.Style.STROKE);
283         paint.setStrokeWidth(radius * 0.12f);
284 
285         canvas.drawOval(new RectF(cx - iradius, cy - iradius,
286                                   cx + iradius, cy + iradius),
287                         paint);
288 
289         float r0 = radius * 0.1f;
290         float r1 = radius * 0.4f;
291         float r2 = radius * 0.6f;
292         float r3 = radius * 0.65f;
293         float r4 = radius * 0.7f;
294         float r5 = radius * 0.9f;
295 
296         Path path = new Path();
297 
298         float ss = second + milli / 1000.0f;
299         float mm = minute + ss / 60.0f;
300         float hh = hour + mm / 60.0f;
301 
302         // Tics for the hours
303         for (int i = 0; i < 12; i++) {
304             drawLine(path, radius * 0.12f, i / 12.0f, cx, cy, r4, r5);
305         }
306 
307         // Hour hand
308         drawLine(path, radius * 0.12f, hh / 12.0f, cx, cy, r0, r1);
309         // Minute hand
310         drawLine(path, radius * 0.12f, mm / 60.0f, cx, cy, r0, r2);
311         // Second hand
312         drawLine(path, radius * 0.036f, ss / 60.0f, cx, cy, r0, r3);
313 
314         if (showUpArrow) {
315             drawVArrow(path, cx + radius * 1.13f, cy - radius,
316                 radius * 0.15f, -radius * 0.1f);
317         }
318         if (showDownArrow) {
319             drawVArrow(path, cx + radius * 1.13f, cy + radius,
320                 radius * 0.15f, radius * 0.1f);
321         }
322         if (showLeftRightArrows) {
323             drawHArrow(path, cx - radius * 1.3f, cy, -radius * 0.1f,
324                 radius * 0.15f);
325             drawHArrow(path, cx + radius * 1.3f, cy,  radius * 0.1f,
326                 radius * 0.15f);
327         }
328 
329         paint.setStyle(Paint.Style.FILL);
330         canvas.drawPath(path, paint);
331     }
332 }
333