Set up polar clock color palettes in an XML file.

Instead of a checkbox for "color cycling" vs "palette", we
now have cycling and fixed color palettes. That way you can
easily create a "dark" cycling palette that uses a different
HSB base and background color.

Includes a handful of palettes.

Change-Id: I01042bc9640c0a31c3779de425abbeeb4480c687
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..d9c9df8
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="polar_clock_palette_names">
+    <item>@string/palette_gray</item>
+    <item>@string/palette_white_c</item>
+    <item>@string/palette_black_c</item>
+    <item>@string/palette_matrix</item>
+    <item>@string/palette_halloween</item>
+    <item>@string/palette_violet</item>
+    <item>@string/palette_oceanic</item>
+    <item>@string/palette_zenburn</item>
+  </string-array>
+  <string-array name="polar_clock_palette_ids">
+    <item>palette_gray</item>
+    <item>palette_white_c</item>
+    <item>palette_black_c</item>
+    <item>palette_matrix</item>
+    <item>palette_halloween</item>
+    <item>palette_violet</item>
+    <item>palette_oceanic</item>
+    <item>palette_zenburn</item>
+  </string-array>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 12fbdfa..166d33b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -41,8 +41,26 @@
     <string name="clock_settings">Polar clock settings</string>
     <!-- Polar clock: label for "show seconds" pref -->
     <string name="show_seconds">Show seconds</string>
-    <!-- Polar clock: label for "cycle colors" pref -->
-    <string name="cycle_colors">Cycle ring colors</string>
     <!-- Polar clock: label for "variable widths" pref -->
     <string name="variable_line_width">Vary ring widths</string>
+    <!-- Polar clock: label for "palette" pref -->
+    <string name="palette">Color palette</string>
+
+	<!-- Polar clock: palette name -->
+	<string name="palette_gray">Iron</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_violet">Midnight</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_matrix">Green Screen</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_white_c">Polar Bear (dynamic)</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_black_c">Black Hole (dynamic)</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_halloween">Pumpkin Pie</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_zenburn">Alien Fruit Salad</string>
+	<!-- Polar clock: palette name -->
+	<string name="palette_oceanic">Blue Laser</string>
+
 </resources>
diff --git a/res/xml/polar_clock_palettes.xml b/res/xml/polar_clock_palettes.xml
new file mode 100644
index 0000000..c77d113
--- /dev/null
+++ b/res/xml/polar_clock_palettes.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<palettes>
+  <!-- basic white polar clock with color-cycling rings -->
+  <palette kind="cycling"
+    id="palette_white_c"
+    background="#FFFFFFFF"
+    saturation="0.8" brightness="0.9" />
+  <!-- same as above, but on black -->
+  <palette kind="cycling"
+    id="palette_black_c"
+    background="#FF000000"
+    saturation="1.0" brightness="0.25" />
+  <!-- a nice gray color scheme with an orange seconds ring -->
+  <palette kind="fixed"
+    id="palette_gray"
+    background="#FF555555"
+    second="#FFCC8800" minute="#FF333333" hour="#FF000000"
+    day="#FF999999" month="#FF777777" />
+  <!-- smooth black with a purple cast -->
+  <palette kind="fixed"
+    id="palette_violet"
+    background="#FF181020"
+    second="#FF602070" minute="#603050" hour="#FF040008"
+    day="#FF706080" month="#FF403050" />
+  <!-- green on black -->
+  <palette kind="fixed"
+    id="palette_matrix"
+    background="#FF002200"
+    second="#FF00FF00" minute="#FF00BB00" hour="#FF007700"
+    day="#FF00AA00" month="#FF006600" />
+  <!-- orange -->
+  <palette kind="fixed"
+    id="palette_halloween"
+    background="#FF884400"
+    second="#FFFFFF00" minute="#FFFF8800" hour="#FFFF8800"
+    day="#FF000000" month="#FF000000" />
+  <!-- based on Zenburn by Jani Nurminen -->
+  <palette kind="fixed"
+    id="palette_zenburn"
+    background="#FF3f3f3f"
+    second="#FF8aCCCF" minute="#FFCB9292" hour="#FFCCDC90"
+    day="#FFDCA3A3" month="#FF7f9f7f" />
+  <!-- electric blue -->
+  <palette kind="fixed"
+    id="palette_oceanic"
+    background="#FF000066"
+    second="#FF6666FF" minute="#FF0000FF" hour="#FF0000AA"
+    day="#FF000033" month="#FF000011" />
+</palettes>
diff --git a/res/xml/polar_clock_prefs.xml b/res/xml/polar_clock_prefs.xml
index ae96458..d0a0d20 100644
--- a/res/xml/polar_clock_prefs.xml
+++ b/res/xml/polar_clock_prefs.xml
@@ -25,15 +25,15 @@
             android:persistent="true"
             />
     <CheckBoxPreference
-            android:key="cycle_colors"
-            android:title="@string/cycle_colors"
-            android:defaultValue="false"
-            android:persistent="true"
-            />
-    <CheckBoxPreference
             android:key="variable_line_width"
             android:title="@string/variable_line_width"
             android:defaultValue="true"
             android:persistent="true"
             />
+    <ListPreference
+            android:key="palette"
+            android:title="@string/palette"
+            android:entries="@array/polar_clock_palette_names"
+            android:entryValues="@array/polar_clock_palette_ids"
+            />
 </PreferenceScreen>
diff --git a/src/com/android/wallpaper/polarclock/PolarClockWallpaper.java b/src/com/android/wallpaper/polarclock/PolarClockWallpaper.java
index 724f224..5b4a060 100644
--- a/src/com/android/wallpaper/polarclock/PolarClockWallpaper.java
+++ b/src/com/android/wallpaper/polarclock/PolarClockWallpaper.java
@@ -28,22 +28,231 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.res.XmlResourceParser;
+import android.preference.ListPreference;
+
 import android.os.Handler;
 import android.os.SystemClock;
 import android.text.format.Time;
 import android.util.MathUtils;
 
+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 {
     public static final String SHARED_PREFS_NAME = "polar_clock_settings";
-    
+
     public static final String PREF_SHOW_SECONDS = "show_seconds";
     public static final String PREF_VARIABLE_LINE_WIDTH = "variable_line_width";
-    public static final String PREF_CYCLE_COLORS = "cycle_colors";
+    public static final String PREF_PALETTE = "palette";
 
     public 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();
+
+        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() { }
+
+        @Override
+        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);
+            }
+        }
+
+        @Override
+        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) {
+            return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
+        }
+
+        @Override
+        public int getMinuteColor(float forAngle) {
+            return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
+        }
+
+        @Override
+        public int getHourColor(float forAngle) {
+            return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
+        }
+
+        @Override
+        public int getDayColor(float forAngle) {
+            return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
+        }
+
+        @Override
+        public int getMonthColor(float forAngle) {
+            return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
+        }
+
+        @Override
+        public String getId() {
+            return mId;
+        }
+    }
+
     private final Handler mHandler = new Handler();
 
     private IntentFilter mFilter;
@@ -67,9 +276,6 @@
 
     class ClockEngine extends Engine
             implements SharedPreferences.OnSharedPreferenceChangeListener {
-        private static final float SATURATION = 0.8f;
-        private static final float BRIGHTNESS = 0.9f;
-
         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;
@@ -79,44 +285,20 @@
         private static final float SMALL_GAP = 14.0f;
         private static final float LARGE_GAP = 38.0f;
 
-        private static final int COLORS_CACHE_COUNT = 720;
-
-        class ColorPalette {
-            ColorPalette(String name, int bg, int s, int m, int h, int d, int o) {
-                this.name = name; this.bg = bg;
-                second = s; minute = m; hour = h; day = d; month = o;
-            }
-            public final String name;
-            public final int bg;
-            public final int second;
-            public final int minute;
-            public final int hour;
-            public final int day;
-            public final int month;
-        }
-        
-        // XXX: make this an array of named palettes, selectable in prefs 
-        //      via a spinner (bonus points: move to XML)
-        private final ColorPalette mPalette = new ColorPalette(
-            "MutedAndroid",
-            0xFF555555, 
-            0xFF00FF00, 0xFF333333, 0xFF000000,
-            0xFF888888, 0xFFAAAAAA
-        );
+        private final HashMap<String, ClockPalette> mPalettes = new HashMap<String, ClockPalette>();
+        private ClockPalette mPalette;
 
         private SharedPreferences mPrefs;
         private boolean mShowSeconds;
         private boolean mVariableLineWidth;
-        private boolean mCycleColors;
-        
+
         private boolean mWatcherRegistered;
         private float mStartTime;
         private Time mCalendar;
 
         private final Paint mPaint = new Paint();
         private final RectF mRect = new RectF();
-        private final int[] mColors = new int[COLORS_CACHE_COUNT];
-        
+
         private float mOffsetX;
 
         private final BroadcastReceiver mWatcher = new BroadcastReceiver() {
@@ -126,7 +308,7 @@
                 drawFrame();
             }
         };
-        
+
         private final Runnable mDrawClock = new Runnable() {
             public void run() {
                 drawFrame();
@@ -135,13 +317,29 @@
         private boolean mVisible;
 
         ClockEngine() {
-            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, SATURATION, BRIGHTNESS);
+            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) {
+                // XXX: Log?
+            } catch (XmlPullParserException e) {
+                // XXX: Log?
+            } finally {
+                xrp.close();
             }
+
+            mPalette = CyclingClockPalette.getFallback();
         }
 
         @Override
@@ -150,6 +348,8 @@
 
             mPrefs = PolarClockWallpaper.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
             mPrefs.registerOnSharedPreferenceChangeListener(this);
+
+            // load up user's settings
             onSharedPreferenceChanged(mPrefs, null);
 
             mCalendar = new Time();
@@ -175,23 +375,27 @@
 
         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
                 String key) {
-            
+
             boolean changed = false;
-            if (null == key || PREF_SHOW_SECONDS.equals(key)) {
+            if (key == null || PREF_SHOW_SECONDS.equals(key)) {
                 mShowSeconds = sharedPreferences.getBoolean(
                     PREF_SHOW_SECONDS, true);
                 changed = true;
             }
-            if (null == key || PREF_CYCLE_COLORS.equals(key)) {
-                mCycleColors = sharedPreferences.getBoolean(
-                    PREF_CYCLE_COLORS, false);
-                changed = true;
-            }
-            if (null == key || PREF_VARIABLE_LINE_WIDTH.equals(key)) {
+            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();
@@ -242,8 +446,13 @@
             mOffsetX = xOffset;
             drawFrame();
         }
-        
+
         void drawFrame() {
+            if (mPalette == null) {
+                android.util.Log.w("PolarClockWallpaper", "no palette?!");
+                return;
+            }
+
             final SurfaceHolder holder = getSurfaceHolder();
             final Rect frame = holder.getSurfaceFrame();
             final int width = frame.width();
@@ -255,7 +464,6 @@
                 if (c != null) {
                     final Time calendar = mCalendar;
                     final Paint paint = mPaint;
-                    final int[] colors = mColors;
 
                     calendar.setToNow();
                     calendar.normalize(false);
@@ -263,11 +471,8 @@
                     int s = width / 2;
                     int t = height / 2;
 
-                    if (mCycleColors) {
-                        c.drawColor(BACKGROUND_COLOR);
-                    } else {
-                        c.drawColor(mPalette.bg);
-                    }
+                    c.drawColor(mPalette.getBackgroundColor());
+
                     c.translate(s + MathUtils.lerp(s, -s, mOffsetX), t);
                     c.rotate(-90.0f);
                     if (height < width) {
@@ -280,16 +485,13 @@
                     float angle;
 
                     float lastRingThickness = DEFAULT_RING_THICKNESS;
-                    
+
                     if (mShowSeconds) {
                         // Draw seconds  
                         angle = ((mStartTime + SystemClock.elapsedRealtime()) % 60000) / 60000.0f;
                         if (angle < 0) angle = -angle;
-                        if (mCycleColors) {
-                            paint.setColor(colors[((int) (angle * COLORS_CACHE_COUNT))]);
-                        } else {
-                            paint.setColor(mPalette.second);
-                        }
+
+                        paint.setColor(mPalette.getSecondColor(angle));
 
                         if (mVariableLineWidth) {
                             lastRingThickness = SMALL_RING_THICKNESS;
@@ -303,28 +505,21 @@
                     rect.set(-size, -size, size, size);
 
                     angle = ((calendar.minute * 60.0f + calendar.second) % 3600) / 3600.0f;
-                    if (mCycleColors) {
-                        paint.setColor(colors[((int) (angle * COLORS_CACHE_COUNT))]);                    
-                    } else {
-                        paint.setColor(mPalette.minute);
-                    }
+                    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;
-                    if (mCycleColors) {
-                        paint.setColor(colors[((int) (angle * COLORS_CACHE_COUNT))]);                    
-                    } else {
-                        paint.setColor(mPalette.hour);
-                    }
+                    paint.setColor(mPalette.getHourColor(angle));
+
                     if (mVariableLineWidth) {
                         lastRingThickness = LARGE_RING_THICKNESS;
                         paint.setStrokeWidth(lastRingThickness);
@@ -337,11 +532,8 @@
 
                     angle = (calendar.monthDay - 1) /
                             (float) (calendar.getActualMaximum(Time.MONTH_DAY) - 1);
-                    if (mCycleColors) {
-                        paint.setColor(colors[((int) (angle * COLORS_CACHE_COUNT))]);                    
-                    } else {
-                        paint.setColor(mPalette.day);
-                    }
+                    paint.setColor(mPalette.getDayColor(angle));
+
                     if (mVariableLineWidth) {
                         lastRingThickness = MEDIUM_RING_THICKNESS;
                         paint.setStrokeWidth(lastRingThickness);
@@ -353,11 +545,9 @@
                     rect.set(-size, -size, size, size);
 
                     angle = (calendar.month - 1) / 11.0f;
-                    if (mCycleColors) {
-                        paint.setColor(colors[((int) (angle * COLORS_CACHE_COUNT))]);                    
-                    } else {
-                        paint.setColor(mPalette.month);
-                    }
+
+                    paint.setColor(mPalette.getMonthColor(angle));
+
                     if (mVariableLineWidth) {
                         lastRingThickness = LARGE_RING_THICKNESS;
                         paint.setStrokeWidth(lastRingThickness);