Home | History | Annotate | Download | only in globaltime
      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 
     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      */
     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      */
     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      */
    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      */
    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      */
    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 
    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      */
    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