blob: cba7b11300daec7151ee018c2e8bddbc4d49a37c [file] [log] [blame]
/*
* Copyright (C) 2009 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.wallpaper.polarclock;
import android.service.wallpaper.WallpaperService;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Paint;
import android.graphics.Color;
import android.graphics.RectF;
import android.view.SurfaceHolder;
import android.content.IntentFilter;
import android.content.Intent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.XmlResourceParser;
import android.os.Handler;
import android.os.SystemClock;
import android.text.format.Time;
import android.util.MathUtils;
import android.util.Log;
import java.util.HashMap;
import java.util.TimeZone;
import java.io.IOException;
import org.xmlpull.v1.XmlPullParserException;
import static org.xmlpull.v1.XmlPullParser.*;
import com.android.wallpaper.R;
public class PolarClockWallpaper extends WallpaperService {
private static final String LOG_TAG = "PolarClock";
static final String SHARED_PREFS_NAME = "polar_clock_settings";
static final String PREF_SHOW_SECONDS = "show_seconds";
static final String PREF_VARIABLE_LINE_WIDTH = "variable_line_width";
static final String PREF_PALETTE = "palette";
static final int BACKGROUND_COLOR = 0xffffffff;
static abstract class ClockPalette {
public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
String kind = xrp.getAttributeValue(null, "kind");
if ("cycling".equals(kind)) {
return CyclingClockPalette.parseXmlPaletteTag(xrp);
} else {
return FixedClockPalette.parseXmlPaletteTag(xrp);
}
}
public abstract int getBackgroundColor();
// forAngle should be on [0.0,1.0) but 1.0 must be tolerated
public abstract int getSecondColor(float forAngle);
public abstract int getMinuteColor(float forAngle);
public abstract int getHourColor(float forAngle);
public abstract int getDayColor(float forAngle);
public abstract int getMonthColor(float forAngle);
public abstract String getId();
}
static class FixedClockPalette extends ClockPalette {
protected String mId;
protected int mBackgroundColor;
protected int mSecondColor;
protected int mMinuteColor;
protected int mHourColor;
protected int mDayColor;
protected int mMonthColor;
private static FixedClockPalette sFallbackPalette = null;
public static FixedClockPalette getFallback() {
if (sFallbackPalette == null) {
sFallbackPalette = new FixedClockPalette();
sFallbackPalette.mId = "default";
sFallbackPalette.mBackgroundColor = Color.WHITE;
sFallbackPalette.mSecondColor =
sFallbackPalette.mMinuteColor =
sFallbackPalette.mHourColor =
sFallbackPalette.mDayColor =
sFallbackPalette.mMonthColor =
Color.BLACK;
}
return sFallbackPalette;
}
private FixedClockPalette() { }
public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
final FixedClockPalette pal = new FixedClockPalette();
pal.mId = xrp.getAttributeValue(null, "id");
String val;
if ((val = xrp.getAttributeValue(null, "background")) != null)
pal.mBackgroundColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "second")) != null)
pal.mSecondColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "minute")) != null)
pal.mMinuteColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "hour")) != null)
pal.mHourColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "day")) != null)
pal.mDayColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "month")) != null)
pal.mMonthColor = Color.parseColor(val);
return (pal.mId == null) ? null : pal;
}
@Override
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
public int getSecondColor(float forAngle) {
return mSecondColor;
}
@Override
public int getMinuteColor(float forAngle) {
return mMinuteColor;
}
@Override
public int getHourColor(float forAngle) {
return mHourColor;
}
@Override
public int getDayColor(float forAngle) {
return mDayColor;
}
@Override
public int getMonthColor(float forAngle) {
return mMonthColor;
}
@Override
public String getId() {
return mId;
}
}
static class CyclingClockPalette extends ClockPalette {
protected String mId;
protected int mBackgroundColor;
protected float mSaturation;
protected float mBrightness;
private static final int COLORS_CACHE_COUNT = 720;
private final int[] mColors = new int[COLORS_CACHE_COUNT];
private static CyclingClockPalette sFallbackPalette = null;
public static CyclingClockPalette getFallback() {
if (sFallbackPalette == null) {
sFallbackPalette = new CyclingClockPalette();
sFallbackPalette.mId = "default_c";
sFallbackPalette.mBackgroundColor = Color.WHITE;
sFallbackPalette.mSaturation = 0.8f;
sFallbackPalette.mBrightness = 0.9f;
sFallbackPalette.computeIntermediateColors();
}
return sFallbackPalette;
}
private CyclingClockPalette() { }
private void computeIntermediateColors() {
final int[] colors = mColors;
final int count = colors.length;
float invCount = 1.0f / (float) COLORS_CACHE_COUNT;
for (int i = 0; i < count; i++) {
colors[i] = Color.HSBtoColor(i * invCount, mSaturation, mBrightness);
}
}
public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
final CyclingClockPalette pal = new CyclingClockPalette();
pal.mId = xrp.getAttributeValue(null, "id");
String val;
if ((val = xrp.getAttributeValue(null, "background")) != null)
pal.mBackgroundColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "saturation")) != null)
pal.mSaturation = Float.parseFloat(val);
if ((val = xrp.getAttributeValue(null, "brightness")) != null)
pal.mBrightness = Float.parseFloat(val);
if (pal.mId == null) {
return null;
} else {
pal.computeIntermediateColors();
return pal;
}
}
@Override
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
public int getSecondColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getMinuteColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getHourColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getDayColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getMonthColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public String getId() {
return mId;
}
}
private final Handler mHandler = new Handler();
private IntentFilter mFilter;
@Override
public void onCreate() {
super.onCreate();
mFilter = new IntentFilter();
mFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
}
@Override
public void onDestroy() {
super.onDestroy();
}
public Engine onCreateEngine() {
return new ClockEngine();
}
class ClockEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final float SMALL_RING_THICKNESS = 8.0f;
private static final float MEDIUM_RING_THICKNESS = 16.0f;
private static final float LARGE_RING_THICKNESS = 32.0f;
private static final float DEFAULT_RING_THICKNESS = 24.0f;
private static final float SMALL_GAP = 14.0f;
private static final float LARGE_GAP = 38.0f;
private final HashMap<String, ClockPalette> mPalettes = new HashMap<String, ClockPalette>();
private ClockPalette mPalette;
private SharedPreferences mPrefs;
private boolean mShowSeconds;
private boolean mVariableLineWidth;
private boolean mWatcherRegistered;
private Time mCalendar;
private final Paint mPaint = new Paint();
private final RectF mRect = new RectF();
private float mOffsetX;
private final BroadcastReceiver mWatcher = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
final String timeZone = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(timeZone).getID());
drawFrame();
}
};
private final Runnable mDrawClock = new Runnable() {
public void run() {
drawFrame();
}
};
private boolean mVisible;
ClockEngine() {
XmlResourceParser xrp = getResources().getXml(R.xml.polar_clock_palettes);
try {
int what = xrp.getEventType();
while (what != END_DOCUMENT) {
if (what == START_TAG) {
if ("palette".equals(xrp.getName())) {
ClockPalette pal = ClockPalette.parseXmlPaletteTag(xrp);
if (pal.getId() != null) {
mPalettes.put(pal.getId(), pal);
}
}
}
what = xrp.next();
}
} catch (IOException e) {
Log.e(LOG_TAG, "An error occured during wallpaper configuration:", e);
} catch (XmlPullParserException e) {
Log.e(LOG_TAG, "An error occured during wallpaper configuration:", e);
} finally {
xrp.close();
}
mPalette = CyclingClockPalette.getFallback();
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
mPrefs = PolarClockWallpaper.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
mPrefs.registerOnSharedPreferenceChangeListener(this);
// load up user's settings
onSharedPreferenceChanged(mPrefs, null);
mCalendar = new Time();
mCalendar.setToNow();
final Paint paint = mPaint;
paint.setAntiAlias(true);
paint.setStrokeWidth(DEFAULT_RING_THICKNESS);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
if (isPreview()) {
mOffsetX = 0.5f;
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mWatcherRegistered) {
mWatcherRegistered = false;
unregisterReceiver(mWatcher);
}
mHandler.removeCallbacks(mDrawClock);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
boolean changed = false;
if (key == null || PREF_SHOW_SECONDS.equals(key)) {
mShowSeconds = sharedPreferences.getBoolean(
PREF_SHOW_SECONDS, true);
changed = true;
}
if (key == null || PREF_VARIABLE_LINE_WIDTH.equals(key)) {
mVariableLineWidth = sharedPreferences.getBoolean(
PREF_VARIABLE_LINE_WIDTH, true);
changed = true;
}
if (key == null || PREF_PALETTE.equals(key)) {
String paletteId = sharedPreferences.getString(
PREF_PALETTE, "");
ClockPalette pal = mPalettes.get(paletteId);
if (pal != null) {
mPalette = pal;
changed = true;
}
}
if (mVisible && changed) {
drawFrame();
}
}
@Override
public void onVisibilityChanged(boolean visible) {
mVisible = visible;
if (visible) {
if (!mWatcherRegistered) {
mWatcherRegistered = true;
registerReceiver(mWatcher, mFilter, null, mHandler);
}
mCalendar = new Time();
mCalendar.setToNow();
} else {
if (mWatcherRegistered) {
mWatcherRegistered = false;
unregisterReceiver(mWatcher);
}
mHandler.removeCallbacks(mDrawClock);
}
drawFrame();
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
drawFrame();
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
mVisible = false;
mHandler.removeCallbacks(mDrawClock);
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xStep, float yStep, int xPixels, int yPixels) {
if (isPreview()) return;
mOffsetX = xOffset;
drawFrame();
}
void drawFrame() {
if (mPalette == null) {
Log.w("PolarClockWallpaper", "no palette?!");
return;
}
final SurfaceHolder holder = getSurfaceHolder();
final Rect frame = holder.getSurfaceFrame();
final int width = frame.width();
final int height = frame.height();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
final Time calendar = mCalendar;
final Paint paint = mPaint;
final long millis = System.currentTimeMillis();
calendar.set(millis);
calendar.normalize(false);
int s = width / 2;
int t = height / 2;
c.drawColor(mPalette.getBackgroundColor());
c.translate(s + MathUtils.lerp(s, -s, mOffsetX), t);
c.rotate(-90.0f);
if (height < width) {
c.scale(0.9f, 0.9f);
}
float size = Math.min(width, height) * 0.5f - DEFAULT_RING_THICKNESS;
final RectF rect = mRect;
rect.set(-size, -size, size, size);
float angle;
float lastRingThickness = DEFAULT_RING_THICKNESS;
if (mShowSeconds) {
// Draw seconds
angle = (float) (millis % 60000) / 60000.0f;
//Log.d("PolarClock", "millis=" + millis + ", angle=" + angle);
paint.setColor(mPalette.getSecondColor(angle));
if (mVariableLineWidth) {
lastRingThickness = SMALL_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
}
// Draw minutes
size -= (SMALL_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = ((calendar.minute * 60.0f + calendar.second) % 3600) / 3600.0f;
paint.setColor(mPalette.getMinuteColor(angle));
if (mVariableLineWidth) {
lastRingThickness = MEDIUM_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
// Draw hours
size -= (SMALL_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = ((calendar.hour * 60.0f + calendar.minute) % 1440) / 1440.0f;
paint.setColor(mPalette.getHourColor(angle));
if (mVariableLineWidth) {
lastRingThickness = LARGE_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
// Draw day
size -= (LARGE_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = (calendar.monthDay - 1) /
(float) (calendar.getActualMaximum(Time.MONTH_DAY) - 1);
paint.setColor(mPalette.getDayColor(angle));
if (mVariableLineWidth) {
lastRingThickness = MEDIUM_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
// Draw month
size -= (SMALL_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = (calendar.month) / 11.0f; // NB: month is already on [0..11]
paint.setColor(mPalette.getMonthColor(angle));
if (mVariableLineWidth) {
lastRingThickness = LARGE_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(mDrawClock);
if (mVisible) {
if (mShowSeconds) {
mHandler.postDelayed(mDrawClock, 1000 / 25);
} else {
// If we aren't showing seconds, we don't need to update
// nearly as often.
mHandler.postDelayed(mDrawClock, 2000);
}
}
}
}
}