• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
parseXmlPaletteTag(XmlResourceParser xrp)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 
getBackgroundColor()69         public abstract int getBackgroundColor();
70 
71         // forAngle should be on [0.0,1.0) but 1.0 must be tolerated
getSecondColor(float forAngle)72         public abstract int getSecondColor(float forAngle);
73 
getMinuteColor(float forAngle)74         public abstract int getMinuteColor(float forAngle);
75 
getHourColor(float forAngle)76         public abstract int getHourColor(float forAngle);
77 
getDayColor(float forAngle)78         public abstract int getDayColor(float forAngle);
79 
getMonthColor(float forAngle)80         public abstract int getMonthColor(float forAngle);
81 
getId()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 
getFallback()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 
FixedClockPalette()112         private FixedClockPalette() { }
113 
parseXmlPaletteTag(XmlResourceParser xrp)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
getBackgroundColor()134         public int getBackgroundColor() {
135             return mBackgroundColor;
136         }
137 
138         @Override
getSecondColor(float forAngle)139         public int getSecondColor(float forAngle) {
140             return mSecondColor;
141         }
142 
143         @Override
getMinuteColor(float forAngle)144         public int getMinuteColor(float forAngle) {
145             return mMinuteColor;
146         }
147 
148         @Override
getHourColor(float forAngle)149         public int getHourColor(float forAngle) {
150             return mHourColor;
151         }
152 
153         @Override
getDayColor(float forAngle)154         public int getDayColor(float forAngle) {
155             return mDayColor;
156         }
157 
158         @Override
getMonthColor(float forAngle)159         public int getMonthColor(float forAngle) {
160             return mMonthColor;
161         }
162 
163         @Override
getId()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 
getFallback()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 
CyclingClockPalette()193         private CyclingClockPalette() { }
194 
computeIntermediateColors()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 
parseXmlPaletteTag(XmlResourceParser xrp)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
getBackgroundColor()222         public int getBackgroundColor() {
223             return mBackgroundColor;
224         }
225 
226         @Override
getSecondColor(float forAngle)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
getMinuteColor(float forAngle)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
getHourColor(float forAngle)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
getDayColor(float forAngle)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
getMonthColor(float forAngle)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
getId()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
onCreate()267     public void onCreate() {
268         super.onCreate();
269 
270         mFilter = new IntentFilter();
271         mFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
272     }
273 
274     @Override
onDestroy()275     public void onDestroy() {
276         super.onDestroy();
277     }
278 
onCreateEngine()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 
ClockEngine()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
onCreate(SurfaceHolder surfaceHolder)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
onDestroy()374         public void onDestroy() {
375             super.onDestroy();
376             if (mWatcherRegistered) {
377                 mWatcherRegistered = false;
378                 unregisterReceiver(mWatcher);
379             }
380             mHandler.removeCallbacks(mDrawClock);
381         }
382 
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)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
onVisibilityChanged(boolean visible)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
onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)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
onSurfaceCreated(SurfaceHolder holder)439         public void onSurfaceCreated(SurfaceHolder holder) {
440             super.onSurfaceCreated(holder);
441         }
442 
443         @Override
onSurfaceDestroyed(SurfaceHolder holder)444         public void onSurfaceDestroyed(SurfaceHolder holder) {
445             super.onSurfaceDestroyed(holder);
446             mVisible = false;
447             mHandler.removeCallbacks(mDrawClock);
448         }
449 
450         @Override
onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels)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 
drawFrame()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