blob: 9aad71a12a89dfa6dd475559a40e627463fb668f [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.globaltime;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.text.format.DateUtils;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
/**
* A class that draws an analog clock face with information about the current
* time in a given city.
*/
public class Clock {
static final int MILLISECONDS_PER_MINUTE = 60 * 1000;
static final int MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
private City mCity = null;
private long mCitySwitchTime;
private long mTime;
private float mColorRed = 1.0f;
private float mColorGreen = 1.0f;
private float mColorBlue = 1.0f;
private long mOldOffset;
private Interpolator mClockHandInterpolator =
new AccelerateDecelerateInterpolator();
public Clock() {
// Empty constructor
}
/**
* Adds a line to the given Path. The line extends from
* radius r0 to radius r1 about the center point (cx, cy),
* at an angle given by pos.
*
* @param path the Path to draw to
* @param radius the radius of the outer rim of the clock
* @param pos the angle, with 0 and 1 at 12:00
* @param cx the X coordinate of the clock center
* @param cy the Y coordinate of the clock center
* @param r0 the starting radius for the line
* @param r1 the ending radius for the line
*/
private static void drawLine(Path path,
float radius, float pos, float cx, float cy, float r0, float r1) {
float theta = pos * Shape.TWO_PI - Shape.PI_OVER_TWO;
float dx = (float) Math.cos(theta);
float dy = (float) Math.sin(theta);
float p0x = cx + dx * r0;
float p0y = cy + dy * r0;
float p1x = cx + dx * r1;
float p1y = cy + dy * r1;
float ox = (p1y - p0y);
float oy = -(p1x - p0x);
float norm = (radius / 2.0f) / (float) Math.sqrt(ox * ox + oy * oy);
ox *= norm;
oy *= norm;
path.moveTo(p0x - ox, p0y - oy);
path.lineTo(p1x - ox, p1y - oy);
path.lineTo(p1x + ox, p1y + oy);
path.lineTo(p0x + ox, p0y + oy);
path.close();
}
/**
* Adds a vertical arrow to the given Path.
*
* @param path the Path to draw to
*/
private static void drawVArrow(Path path,
float cx, float cy, float width, float height) {
path.moveTo(cx - width / 2.0f, cy);
path.lineTo(cx, cy + height);
path.lineTo(cx + width / 2.0f, cy);
path.close();
}
/**
* Adds a horizontal arrow to the given Path.
*
* @param path the Path to draw to
*/
private static void drawHArrow(Path path,
float cx, float cy, float width, float height) {
path.moveTo(cx, cy - height / 2.0f);
path.lineTo(cx + width, cy);
path.lineTo(cx, cy + height / 2.0f);
path.close();
}
/**
* Returns an offset in milliseconds to be subtracted from the current time
* in order to obtain an smooth interpolation between the previously
* displayed time and the current time.
*/
private long getOffset(float lerp) {
long doffset = (long) (mCity.getOffset() *
(float) MILLISECONDS_PER_HOUR - mOldOffset);
int sign;
if (doffset < 0) {
doffset = -doffset;
sign = -1;
} else {
sign = 1;
}
while (doffset > 12L * MILLISECONDS_PER_HOUR) {
doffset -= 12L * MILLISECONDS_PER_HOUR;
}
if (doffset > 6L * MILLISECONDS_PER_HOUR) {
doffset = 12L * MILLISECONDS_PER_HOUR - doffset;
sign = -sign;
}
// Interpolate doffset towards 0
doffset = (long)((1.0f - lerp)*doffset);
// Keep the same seconds count
long dh = doffset / (MILLISECONDS_PER_HOUR);
doffset -= dh * MILLISECONDS_PER_HOUR;
long dm = doffset / MILLISECONDS_PER_MINUTE;
doffset = sign * (60 * dh + dm) * MILLISECONDS_PER_MINUTE;
return doffset;
}
/**
* Set the city to be displayed. setCity(null) resets things so the clock
* hand animation won't occur next time.
*/
public void setCity(City city) {
if (mCity != city) {
if (mCity != null) {
mOldOffset =
(long) (mCity.getOffset() * (float) MILLISECONDS_PER_HOUR);
} else if (city != null) {
mOldOffset =
(long) (city.getOffset() * (float) MILLISECONDS_PER_HOUR);
} else {
mOldOffset = 0L; // this will never be used
}
this.mCitySwitchTime = System.currentTimeMillis();
this.mCity = city;
}
}
public void setTime(long time) {
this.mTime = time;
}
/**
* Draws the clock face.
*
* @param canvas the Canvas to draw to
* @param cx the X coordinate of the clock center
* @param cy the Y coordinate of the clock center
* @param radius the radius of the clock face
* @param alpha the translucency of the clock face
* @param textAlpha the translucency of the text
* @param showCityName if true, display the city name
* @param showTime if true, display the time digitally
* @param showUpArrow if true, display an up arrow
* @param showDownArrow if true, display a down arrow
* @param showLeftRightArrows if true, display left and right arrows
* @param prefixChars number of characters of the city name to draw in bold
*/
public void 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) {
Paint paint = new Paint();
paint.setAntiAlias(true);
int iradius = (int)radius;
TimeZone tz = mCity.getTimeZone();
// Compute an interpolated time to animate between the previously
// displayed time and the current time
float lerp = Math.min(1.0f,
(System.currentTimeMillis() - mCitySwitchTime) / 500.0f);
lerp = mClockHandInterpolator.getInterpolation(lerp);
long doffset = lerp < 1.0f ? getOffset(lerp) : 0L;
// Determine the interpolated time for the given time zone
Calendar cal = Calendar.getInstance(tz);
cal.setTimeInMillis(mTime - doffset);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
int milli = cal.get(Calendar.MILLISECOND);
float offset = tz.getRawOffset() / (float) MILLISECONDS_PER_HOUR;
float daylightOffset = tz.inDaylightTime(new Date(mTime)) ?
tz.getDSTSavings() / (float) MILLISECONDS_PER_HOUR : 0.0f;
float absOffset = offset < 0 ? -offset : offset;
int offsetH = (int) absOffset;
int offsetM = (int) (60.0f * (absOffset - offsetH));
hour %= 12;
// Get the city name and digital time strings
String cityName = mCity.getName();
cal.setTimeInMillis(mTime);
String time = DateUtils.timeString(cal.getTimeInMillis()) + " " +
DateUtils.getDayOfWeekString(cal.get(Calendar.DAY_OF_WEEK),
DateUtils.LENGTH_SHORT) + " " +
" (UTC" +
(offset >= 0 ? "+" : "-") +
offsetH +
(offsetM == 0 ? "" : ":" + offsetM) +
(daylightOffset == 0 ? "" : "+" + daylightOffset) +
")";
float th = paint.getTextSize();
float tw;
// Set the text color
paint.setARGB((int) (textAlpha * 255.0f),
(int) (mColorRed * 255.0f),
(int) (mColorGreen * 255.0f),
(int) (mColorBlue * 255.0f));
tw = paint.measureText(cityName);
if (showCityName) {
// Increment prefixChars to include any spaces
for (int i = 0; i < prefixChars; i++) {
if (cityName.charAt(i) == ' ') {
++prefixChars;
}
}
// Draw the city name
canvas.drawText(cityName, cx - tw / 2, cy - radius - th, paint);
// Overstrike the first 'prefixChars' characters
canvas.drawText(cityName.substring(0, prefixChars),
cx - tw / 2 + 1, cy - radius - th, paint);
}
tw = paint.measureText(time);
if (showTime) {
canvas.drawText(time, cx - tw / 2, cy + radius + th + 5, paint);
}
paint.setARGB((int)(alpha * 255.0f),
(int)(mColorRed * 255.0f),
(int)(mColorGreen * 255.0f),
(int)(mColorBlue * 255.0f));
paint.setStyle(Paint.Style.FILL);
canvas.drawOval(new RectF(cx - 2, cy - 2, cx + 2, cy + 2), paint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(radius * 0.12f);
canvas.drawOval(new RectF(cx - iradius, cy - iradius,
cx + iradius, cy + iradius),
paint);
float r0 = radius * 0.1f;
float r1 = radius * 0.4f;
float r2 = radius * 0.6f;
float r3 = radius * 0.65f;
float r4 = radius * 0.7f;
float r5 = radius * 0.9f;
Path path = new Path();
float ss = second + milli / 1000.0f;
float mm = minute + ss / 60.0f;
float hh = hour + mm / 60.0f;
// Tics for the hours
for (int i = 0; i < 12; i++) {
drawLine(path, radius * 0.12f, i / 12.0f, cx, cy, r4, r5);
}
// Hour hand
drawLine(path, radius * 0.12f, hh / 12.0f, cx, cy, r0, r1);
// Minute hand
drawLine(path, radius * 0.12f, mm / 60.0f, cx, cy, r0, r2);
// Second hand
drawLine(path, radius * 0.036f, ss / 60.0f, cx, cy, r0, r3);
if (showUpArrow) {
drawVArrow(path, cx + radius * 1.13f, cy - radius,
radius * 0.15f, -radius * 0.1f);
}
if (showDownArrow) {
drawVArrow(path, cx + radius * 1.13f, cy + radius,
radius * 0.15f, radius * 0.1f);
}
if (showLeftRightArrows) {
drawHArrow(path, cx - radius * 1.3f, cy, -radius * 0.1f,
radius * 0.15f);
drawHArrow(path, cx + radius * 1.3f, cy, radius * 0.1f,
radius * 0.15f);
}
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
}
}