Home | History | Annotate | Download | only in polarclock
      1 /*
      2  * Copyright (C) 2009 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.wallpaper.polarclock;
     18 
     19 import android.service.wallpaper.WallpaperService;
     20 import android.graphics.Canvas;
     21 import android.graphics.Rect;
     22 import android.graphics.Paint;
     23 import android.graphics.Color;
     24 import android.graphics.RectF;
     25 import android.view.SurfaceHolder;
     26 import android.content.IntentFilter;
     27 import android.content.Intent;
     28 import android.content.BroadcastReceiver;
     29 import android.content.Context;
     30 import android.content.SharedPreferences;
     31 import android.content.res.XmlResourceParser;
     32 
     33 import android.os.Handler;
     34 import android.os.SystemClock;
     35 import android.text.format.Time;
     36 import android.util.MathUtils;
     37 import android.util.Log;
     38 
     39 import java.util.HashMap;
     40 import java.util.TimeZone;
     41 import java.io.IOException;
     42 
     43 import org.xmlpull.v1.XmlPullParserException;
     44 import static org.xmlpull.v1.XmlPullParser.*;
     45 
     46 import com.android.wallpaper.R;
     47 
     48 public class PolarClockWallpaper extends WallpaperService {
     49     private static final String LOG_TAG = "PolarClock";
     50 
     51     static final String SHARED_PREFS_NAME = "polar_clock_settings";
     52 
     53     static final String PREF_SHOW_SECONDS = "show_seconds";
     54     static final String PREF_VARIABLE_LINE_WIDTH = "variable_line_width";
     55     static final String PREF_PALETTE = "palette";
     56 
     57     static final int BACKGROUND_COLOR = 0xffffffff;
     58 
     59     static abstract class ClockPalette {
     60         public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
     61             String kind = xrp.getAttributeValue(null, "kind");
     62             if ("cycling".equals(kind)) {
     63                 return CyclingClockPalette.parseXmlPaletteTag(xrp);
     64             } else {
     65                 return FixedClockPalette.parseXmlPaletteTag(xrp);
     66             }
     67         }
     68 
     69         public abstract int getBackgroundColor();
     70 
     71         // forAngle should be on [0.0,1.0) but 1.0 must be tolerated
     72         public abstract int getSecondColor(float forAngle);
     73 
     74         public abstract int getMinuteColor(float forAngle);
     75 
     76         public abstract int getHourColor(float forAngle);
     77 
     78         public abstract int getDayColor(float forAngle);
     79 
     80         public abstract int getMonthColor(float forAngle);
     81 
     82         public abstract String getId();
     83 
     84     }
     85 
     86     static class FixedClockPalette extends ClockPalette {
     87         protected String mId;
     88         protected int mBackgroundColor;
     89         protected int mSecondColor;
     90         protected int mMinuteColor;
     91         protected int mHourColor;
     92         protected int mDayColor;
     93         protected int mMonthColor;
     94 
     95         private static FixedClockPalette sFallbackPalette = null;
     96 
     97         public static FixedClockPalette getFallback() {
     98             if (sFallbackPalette == null) {
     99                 sFallbackPalette = new FixedClockPalette();
    100                 sFallbackPalette.mId = "default";
    101                 sFallbackPalette.mBackgroundColor = Color.WHITE;
    102                 sFallbackPalette.mSecondColor =
    103                     sFallbackPalette.mMinuteColor =
    104                     sFallbackPalette.mHourColor =
    105                     sFallbackPalette.mDayColor =
    106                     sFallbackPalette.mMonthColor =
    107                     Color.BLACK;
    108             }
    109             return sFallbackPalette;
    110         }
    111 
    112         private FixedClockPalette() { }
    113 
    114         public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
    115             final FixedClockPalette pal = new FixedClockPalette();
    116             pal.mId = xrp.getAttributeValue(null, "id");
    117             String val;
    118             if ((val = xrp.getAttributeValue(null, "background")) != null)
    119                 pal.mBackgroundColor = Color.parseColor(val);
    120             if ((val = xrp.getAttributeValue(null, "second")) != null)
    121                 pal.mSecondColor = Color.parseColor(val);
    122             if ((val = xrp.getAttributeValue(null, "minute")) != null)
    123                 pal.mMinuteColor = Color.parseColor(val);
    124             if ((val = xrp.getAttributeValue(null, "hour")) != null)
    125                 pal.mHourColor = Color.parseColor(val);
    126             if ((val = xrp.getAttributeValue(null, "day")) != null)
    127                 pal.mDayColor = Color.parseColor(val);
    128             if ((val = xrp.getAttributeValue(null, "month")) != null)
    129                 pal.mMonthColor = Color.parseColor(val);
    130             return (pal.mId == null) ? null : pal;
    131         }
    132 
    133         @Override
    134         public int getBackgroundColor() {
    135             return mBackgroundColor;
    136         }
    137 
    138         @Override
    139         public int getSecondColor(float forAngle) {
    140             return mSecondColor;
    141         }
    142 
    143         @Override
    144         public int getMinuteColor(float forAngle) {
    145             return mMinuteColor;
    146         }
    147 
    148         @Override
    149         public int getHourColor(float forAngle) {
    150             return mHourColor;
    151         }
    152 
    153         @Override
    154         public int getDayColor(float forAngle) {
    155             return mDayColor;
    156         }
    157 
    158         @Override
    159         public int getMonthColor(float forAngle) {
    160             return mMonthColor;
    161         }
    162 
    163         @Override
    164         public String getId() {
    165             return mId;
    166         }
    167 
    168     }
    169 
    170     static class CyclingClockPalette extends ClockPalette {
    171         protected String mId;
    172         protected int mBackgroundColor;
    173         protected float mSaturation;
    174         protected float mBrightness;
    175 
    176         private static final int COLORS_CACHE_COUNT = 720;
    177         private final int[] mColors = new int[COLORS_CACHE_COUNT];
    178 
    179         private static CyclingClockPalette sFallbackPalette = null;
    180 
    181         public static CyclingClockPalette getFallback() {
    182             if (sFallbackPalette == null) {
    183                 sFallbackPalette = new CyclingClockPalette();
    184                 sFallbackPalette.mId = "default_c";
    185                 sFallbackPalette.mBackgroundColor = Color.WHITE;
    186                 sFallbackPalette.mSaturation = 0.8f;
    187                 sFallbackPalette.mBrightness = 0.9f;
    188                 sFallbackPalette.computeIntermediateColors();
    189             }
    190             return sFallbackPalette;
    191         }
    192 
    193         private CyclingClockPalette() { }
    194 
    195         private void computeIntermediateColors() {
    196             final int[] colors = mColors;
    197             final int count = colors.length;
    198             float invCount = 1.0f / (float) COLORS_CACHE_COUNT;
    199             for (int i = 0; i < count; i++) {
    200                 colors[i] = Color.HSBtoColor(i * invCount, mSaturation, mBrightness);
    201             }
    202         }
    203 
    204         public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
    205             final CyclingClockPalette pal = new CyclingClockPalette();
    206             pal.mId = xrp.getAttributeValue(null, "id");
    207             String val;
    208             if ((val = xrp.getAttributeValue(null, "background")) != null)
    209                 pal.mBackgroundColor = Color.parseColor(val);
    210             if ((val = xrp.getAttributeValue(null, "saturation")) != null)
    211                 pal.mSaturation = Float.parseFloat(val);
    212             if ((val = xrp.getAttributeValue(null, "brightness")) != null)
    213                 pal.mBrightness = Float.parseFloat(val);
    214             if (pal.mId == null) {
    215                 return null;
    216             } else {
    217                 pal.computeIntermediateColors();
    218                 return pal;
    219             }
    220         }
    221         @Override
    222         public int getBackgroundColor() {
    223             return mBackgroundColor;
    224         }
    225 
    226         @Override
    227         public int getSecondColor(float forAngle) {
    228             if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
    229             return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
    230         }
    231 
    232         @Override
    233         public int getMinuteColor(float forAngle) {
    234             if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
    235             return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
    236         }
    237 
    238         @Override
    239         public int getHourColor(float forAngle) {
    240             if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
    241             return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
    242         }
    243 
    244         @Override
    245         public int getDayColor(float forAngle) {
    246             if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
    247             return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
    248         }
    249 
    250         @Override
    251         public int getMonthColor(float forAngle) {
    252             if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
    253             return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
    254         }
    255 
    256         @Override
    257         public String getId() {
    258             return mId;
    259         }
    260     }
    261 
    262     private final Handler mHandler = new Handler();
    263 
    264     private IntentFilter mFilter;
    265 
    266     @Override
    267     public void onCreate() {
    268         super.onCreate();
    269 
    270         mFilter = new IntentFilter();
    271         mFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    272     }
    273 
    274     @Override
    275     public void onDestroy() {
    276         super.onDestroy();
    277     }
    278 
    279     public Engine onCreateEngine() {
    280         return new ClockEngine();
    281     }
    282 
    283     class ClockEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener {
    284         private static final float SMALL_RING_THICKNESS = 8.0f;
    285         private static final float MEDIUM_RING_THICKNESS = 16.0f;
    286         private static final float LARGE_RING_THICKNESS = 32.0f;
    287 
    288         private static final float DEFAULT_RING_THICKNESS = 24.0f;
    289 
    290         private static final float SMALL_GAP = 14.0f;
    291         private static final float LARGE_GAP = 38.0f;
    292 
    293         private final HashMap<String, ClockPalette> mPalettes = new HashMap<String, ClockPalette>();
    294         private ClockPalette mPalette;
    295 
    296         private SharedPreferences mPrefs;
    297         private boolean mShowSeconds;
    298         private boolean mVariableLineWidth;
    299 
    300         private boolean mWatcherRegistered;
    301         private Time mCalendar;
    302 
    303         private final Paint mPaint = new Paint();
    304         private final RectF mRect = new RectF();
    305 
    306         private float mOffsetX;
    307 
    308         private final BroadcastReceiver mWatcher = new BroadcastReceiver() {
    309             public void onReceive(Context context, Intent intent) {
    310                 final String timeZone = intent.getStringExtra("time-zone");
    311                 mCalendar = new Time(TimeZone.getTimeZone(timeZone).getID());
    312                 drawFrame();
    313             }
    314         };
    315 
    316         private final Runnable mDrawClock = new Runnable() {
    317             public void run() {
    318                 drawFrame();
    319             }
    320         };
    321         private boolean mVisible;
    322 
    323         ClockEngine() {
    324             XmlResourceParser xrp = getResources().getXml(R.xml.polar_clock_palettes);
    325             try {
    326                 int what = xrp.getEventType();
    327                 while (what != END_DOCUMENT) {
    328                     if (what == START_TAG) {
    329                         if ("palette".equals(xrp.getName())) {
    330                             ClockPalette pal = ClockPalette.parseXmlPaletteTag(xrp);
    331                             if (pal.getId() != null) {
    332                                 mPalettes.put(pal.getId(), pal);
    333                             }
    334                         }
    335                     }
    336                     what = xrp.next();
    337                 }
    338             } catch (IOException e) {
    339                 Log.e(LOG_TAG, "An error occured during wallpaper configuration:", e);
    340             } catch (XmlPullParserException e) {
    341                 Log.e(LOG_TAG, "An error occured during wallpaper configuration:", e);
    342             } finally {
    343                 xrp.close();
    344             }
    345 
    346             mPalette = CyclingClockPalette.getFallback();
    347         }
    348 
    349         @Override
    350         public void onCreate(SurfaceHolder surfaceHolder) {
    351             super.onCreate(surfaceHolder);
    352 
    353             mPrefs = PolarClockWallpaper.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
    354             mPrefs.registerOnSharedPreferenceChangeListener(this);
    355 
    356             // load up user's settings
    357             onSharedPreferenceChanged(mPrefs, null);
    358 
    359             mCalendar = new Time();
    360             mCalendar.setToNow();
    361 
    362             final Paint paint = mPaint;
    363             paint.setAntiAlias(true);
    364             paint.setStrokeWidth(DEFAULT_RING_THICKNESS);
    365             paint.setStrokeCap(Paint.Cap.ROUND);
    366             paint.setStyle(Paint.Style.STROKE);
    367 
    368             if (isPreview()) {
    369                 mOffsetX = 0.5f;
    370             }
    371         }
    372 
    373         @Override
    374         public void onDestroy() {
    375             super.onDestroy();
    376             if (mWatcherRegistered) {
    377                 mWatcherRegistered = false;
    378                 unregisterReceiver(mWatcher);
    379             }
    380             mHandler.removeCallbacks(mDrawClock);
    381         }
    382 
    383         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
    384                 String key) {
    385 
    386             boolean changed = false;
    387             if (key == null || PREF_SHOW_SECONDS.equals(key)) {
    388                 mShowSeconds = sharedPreferences.getBoolean(
    389                     PREF_SHOW_SECONDS, true);
    390                 changed = true;
    391             }
    392             if (key == null || PREF_VARIABLE_LINE_WIDTH.equals(key)) {
    393                 mVariableLineWidth = sharedPreferences.getBoolean(
    394                     PREF_VARIABLE_LINE_WIDTH, true);
    395                 changed = true;
    396             }
    397             if (key == null || PREF_PALETTE.equals(key)) {
    398                 String paletteId = sharedPreferences.getString(
    399                     PREF_PALETTE, "");
    400                 ClockPalette pal = mPalettes.get(paletteId);
    401                 if (pal != null) {
    402                     mPalette = pal;
    403                     changed = true;
    404                 }
    405             }
    406 
    407             if (mVisible && changed) {
    408                 drawFrame();
    409             }
    410         }
    411 
    412         @Override
    413         public void onVisibilityChanged(boolean visible) {
    414             mVisible = visible;
    415             if (visible) {
    416                 if (!mWatcherRegistered) {
    417                     mWatcherRegistered = true;
    418                     registerReceiver(mWatcher, mFilter, null, mHandler);
    419                 }
    420                 mCalendar = new Time();
    421                 mCalendar.setToNow();
    422             } else {
    423                 if (mWatcherRegistered) {
    424                     mWatcherRegistered = false;
    425                     unregisterReceiver(mWatcher);
    426                 }
    427                 mHandler.removeCallbacks(mDrawClock);
    428             }
    429             drawFrame();
    430         }
    431 
    432         @Override
    433         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    434             super.onSurfaceChanged(holder, format, width, height);
    435             drawFrame();
    436         }
    437 
    438         @Override
    439         public void onSurfaceCreated(SurfaceHolder holder) {
    440             super.onSurfaceCreated(holder);
    441         }
    442 
    443         @Override
    444         public void onSurfaceDestroyed(SurfaceHolder holder) {
    445             super.onSurfaceDestroyed(holder);
    446             mVisible = false;
    447             mHandler.removeCallbacks(mDrawClock);
    448         }
    449 
    450         @Override
    451         public void onOffsetsChanged(float xOffset, float yOffset,
    452                 float xStep, float yStep, int xPixels, int yPixels) {
    453             if (isPreview()) return;
    454 
    455             mOffsetX = xOffset;
    456             drawFrame();
    457         }
    458 
    459         void drawFrame() {
    460             if (mPalette == null) {
    461                 Log.w("PolarClockWallpaper", "no palette?!");
    462                 return;
    463             }
    464 
    465             final SurfaceHolder holder = getSurfaceHolder();
    466             final Rect frame = holder.getSurfaceFrame();
    467             final int width = frame.width();
    468             final int height = frame.height();
    469 
    470             Canvas c = null;
    471             try {
    472                 c = holder.lockCanvas();
    473                 if (c != null) {
    474                     final Time calendar = mCalendar;
    475                     final Paint paint = mPaint;
    476 
    477                     final long millis = System.currentTimeMillis();
    478                     calendar.set(millis);
    479                     calendar.normalize(false);
    480 
    481                     int s = width / 2;
    482                     int t = height / 2;
    483 
    484                     c.drawColor(mPalette.getBackgroundColor());
    485 
    486                     c.translate(s + MathUtils.lerp(s, -s, mOffsetX), t);
    487                     c.rotate(-90.0f);
    488                     if (height < width) {
    489                         c.scale(0.9f, 0.9f);
    490                     }
    491 
    492                     float size = Math.min(width, height) * 0.5f - DEFAULT_RING_THICKNESS;
    493                     final RectF rect = mRect;
    494                     rect.set(-size, -size, size, size);
    495                     float angle;
    496 
    497                     float lastRingThickness = DEFAULT_RING_THICKNESS;
    498 
    499                     if (mShowSeconds) {
    500                         // Draw seconds
    501                         angle = (float) (millis % 60000) / 60000.0f;
    502                         //Log.d("PolarClock", "millis=" + millis + ", angle=" + angle);
    503                         paint.setColor(mPalette.getSecondColor(angle));
    504 
    505                         if (mVariableLineWidth) {
    506                             lastRingThickness = SMALL_RING_THICKNESS;
    507                             paint.setStrokeWidth(lastRingThickness);
    508                         }
    509                         c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
    510                     }
    511 
    512                     // Draw minutes
    513                     size -= (SMALL_GAP + lastRingThickness);
    514                     rect.set(-size, -size, size, size);
    515 
    516                     angle = ((calendar.minute * 60.0f + calendar.second) % 3600) / 3600.0f;
    517                     paint.setColor(mPalette.getMinuteColor(angle));
    518 
    519                     if (mVariableLineWidth) {
    520                         lastRingThickness = MEDIUM_RING_THICKNESS;
    521                         paint.setStrokeWidth(lastRingThickness);
    522                     }
    523                     c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
    524 
    525                     // Draw hours
    526                     size -= (SMALL_GAP + lastRingThickness);
    527                     rect.set(-size, -size, size, size);
    528 
    529                     angle = ((calendar.hour * 60.0f + calendar.minute) % 1440) / 1440.0f;
    530                     paint.setColor(mPalette.getHourColor(angle));
    531 
    532                     if (mVariableLineWidth) {
    533                         lastRingThickness = LARGE_RING_THICKNESS;
    534                         paint.setStrokeWidth(lastRingThickness);
    535                     }
    536                     c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
    537 
    538                     // Draw day
    539                     size -= (LARGE_GAP + lastRingThickness);
    540                     rect.set(-size, -size, size, size);
    541 
    542                     angle = (calendar.monthDay - 1) /
    543                             (float) (calendar.getActualMaximum(Time.MONTH_DAY) - 1);
    544                     paint.setColor(mPalette.getDayColor(angle));
    545 
    546                     if (mVariableLineWidth) {
    547                         lastRingThickness = MEDIUM_RING_THICKNESS;
    548                         paint.setStrokeWidth(lastRingThickness);
    549                     }
    550                     c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
    551 
    552                     // Draw month
    553                     size -= (SMALL_GAP + lastRingThickness);
    554                     rect.set(-size, -size, size, size);
    555 
    556                     angle = (calendar.month) / 11.0f; // NB: month is already on [0..11]
    557 
    558                     paint.setColor(mPalette.getMonthColor(angle));
    559 
    560                     if (mVariableLineWidth) {
    561                         lastRingThickness = LARGE_RING_THICKNESS;
    562                         paint.setStrokeWidth(lastRingThickness);
    563                     }
    564                     c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
    565                 }
    566             } finally {
    567                 if (c != null) holder.unlockCanvasAndPost(c);
    568             }
    569 
    570             mHandler.removeCallbacks(mDrawClock);
    571             if (mVisible) {
    572                 if (mShowSeconds) {
    573                     mHandler.postDelayed(mDrawClock, 1000 / 25);
    574                 } else {
    575                     // If we aren't showing seconds, we don't need to update
    576                     // nearly as often.
    577                     mHandler.postDelayed(mDrawClock, 2000);
    578                 }
    579             }
    580         }
    581     }
    582 }
    583