[2/3] OmniGears: battery light settings

Change-Id: I597b9ac8e40203bb7074b38ead256acc148b35ad
diff --git a/res/xml/battery_light_settings.xml b/res/xml/battery_light_settings.xml
new file mode 100644
index 0000000..776a0dd
--- /dev/null
+++ b/res/xml/battery_light_settings.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The CyanogenMod 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+
+    <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+        android:key="battery_light_enabled"
+        android:title="@string/battery_light_enable"
+        android:persistent="false"/>
+
+    <PreferenceCategory
+        android:key="general_section"
+        android:title="@string/notification_light_general_title">
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="battery_light_pulse"
+            android:title="@string/battery_low_pulse_title"
+            android:dependency="battery_light_enabled"
+            android:persistent="false" />
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="battery_light_only_fully_charged"
+            android:title="@string/battery_light_only_full_charge_title"
+            android:dependency="battery_light_enabled"
+            android:defaultValue="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="colors_list"
+        android:title="@string/battery_light_list_title"
+        android:dependency="battery_light_enabled" >
+
+        <org.omnirom.omnigears.batterylight.BatteryLightPreference
+            android:key="low_color"
+            android:title="@string/battery_light_low_color_title"
+            android:persistent="false" />
+
+        <org.omnirom.omnigears.batterylight.BatteryLightPreference
+            android:key="medium_color"
+            android:title="@string/battery_light_medium_color_title"
+            android:persistent="false" />
+
+        <org.omnirom.omnigears.batterylight.BatteryLightPreference
+            android:key="full_color"
+            android:title="@string/battery_light_full_color_title"
+            android:persistent="false" />
+
+        <org.omnirom.omnigears.batterylight.BatteryLightPreference
+            android:key="really_full_color"
+            android:title="@string/battery_light_really_full_color_title"
+            android:persistent="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="fast_color_cat"
+        android:title="@string/fast_color_cat_title"
+        android:dependency="battery_light_enabled" >
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="fast_charging_led_enabled"
+            android:title="@string/fast_charging_led_enabled_title"
+            android:summary="@string/fast_charging_led_enabled_summary"
+            android:defaultValue="false" />
+
+        <org.omnirom.omnigears.batterylight.BatteryLightPreference
+            android:key="fast_color"
+            android:title="@string/fast_charging_light_color_title"
+            android:summary="@string/fast_charging_light_color_summary"
+            android:persistent="false"
+            android:dependency="fast_charging_led_enabled" />
+
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/org/omnirom/omnigears/batterylight/BatteryLightDialog.java b/src/org/omnirom/omnigears/batterylight/BatteryLightDialog.java
new file mode 100644
index 0000000..cb1e820
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/BatteryLightDialog.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod 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 org.omnirom.omnigears.batterylight;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.text.InputFilter;
+import android.text.InputFilter.LengthFilter;
+import android.util.Pair;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+import com.android.settings.R;
+import org.omnirom.omnigears.ui.ColorPanelView;
+import org.omnirom.omnigears.ui.ColorPickerView;
+import org.omnirom.omnigears.ui.ColorPickerView.OnColorChangedListener;
+
+import java.util.ArrayList;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+public class BatteryLightDialog extends AlertDialog implements
+        ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+    private static final String TAG = "BatteryLightDialog";
+    private final static String STATE_KEY_COLOR = "BatteryLightDialog:color";
+
+    private ColorPickerView mColorPicker;
+
+    private EditText mHexColorInput;
+    private ColorPanelView mNewColor;
+    private LayoutInflater mInflater;
+    private boolean mMultiColor = true;
+    private Spinner mColorList;
+    private LinearLayout mColorListView;
+    private LinearLayout mColorPanelView;
+    private ColorPanelView mNewListColor;
+    private LedColorAdapter mLedColorAdapter;
+    private boolean mWithAlpha;
+
+    protected BatteryLightDialog(Context context, int initialColor) {
+        super(context);
+        mWithAlpha = false;
+        mMultiColor = getContext().getResources().getBoolean(R.bool.config_has_multi_color_led);
+        init(initialColor);
+    }
+
+    private void init(int color) {
+        // To fight color banding.
+        getWindow().setFormat(PixelFormat.RGBA_8888);
+        setUp(color);
+    }
+
+    /**
+     * This function sets up the dialog with the proper values.  If the speedOff parameters
+     * has a -1 value disable both spinners
+     *
+     * @param color - the color to set
+     * @param speedOn - the flash time in ms
+     * @param speedOff - the flash length in ms
+     */
+    private void setUp(int color) {
+        mInflater = (LayoutInflater) getContext()
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View layout = mInflater.inflate(R.layout.dialog_battery_settings, null);
+
+        mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view);
+        mHexColorInput = (EditText) layout.findViewById(R.id.hex_color_input);
+        mNewColor = (ColorPanelView) layout.findViewById(R.id.color_panel);
+        mColorPanelView = (LinearLayout) layout.findViewById(R.id.color_panel_view);
+
+        mColorListView = (LinearLayout) layout.findViewById(R.id.color_list_view);
+        mColorList = (Spinner) layout.findViewById(R.id.color_list_spinner);
+        mNewListColor = (ColorPanelView) layout.findViewById(R.id.color_list_panel);
+
+        mColorPicker.setOnColorChangedListener(this);
+        mHexColorInput.setOnFocusChangeListener(this);
+        setAlphaSliderVisible(mWithAlpha);
+        mColorPicker.setColor(color, true);
+
+        mColorList = (Spinner) layout.findViewById(R.id.color_list_spinner);
+        mLedColorAdapter = new LedColorAdapter(
+                R.array.entries_led_colors,
+                R.array.values_led_colors);
+        mColorList.setAdapter(mLedColorAdapter);
+        mColorList.setSelection(mLedColorAdapter.getColorPosition(color));
+        mColorList.setOnItemSelectedListener(mColorListListener);
+
+        setView(layout);
+
+        // show and hide the correct UI depending if we have multi-color led or not
+        if (mMultiColor){
+            mColorListView.setVisibility(View.GONE);
+            mColorPicker.setVisibility(View.VISIBLE);
+            mColorPanelView.setVisibility(View.VISIBLE);
+        } else {
+            mColorListView.setVisibility(View.VISIBLE);
+            mColorPicker.setVisibility(View.GONE);
+            mColorPanelView.setVisibility(View.GONE);
+        }
+    }
+
+    private AdapterView.OnItemSelectedListener mColorListListener = new AdapterView.OnItemSelectedListener() {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            int color = mLedColorAdapter.getColor(position);
+            mNewListColor.setColor(color);
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+        }
+    };
+
+    @Override
+    public Bundle onSaveInstanceState() {
+        Bundle state = super.onSaveInstanceState();
+        state.putInt(STATE_KEY_COLOR, getColor());
+        return state;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        mColorPicker.setColor(state.getInt(STATE_KEY_COLOR), true);
+    }
+
+    @Override
+    public void onColorChanged(int color) {
+        final boolean hasAlpha = mWithAlpha;
+        final String format = hasAlpha ? "%08x" : "%06x";
+        final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF;
+
+        mNewColor.setColor(color);
+        mHexColorInput.setText(String.format(Locale.US, format, color & mask));
+    }
+
+    public void setAlphaSliderVisible(boolean visible) {
+        mHexColorInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(visible ? 8 : 6) } );
+        mColorPicker.setAlphaSliderVisible(visible);
+    }
+
+    public int getColor() {
+        if (mMultiColor){
+            return mColorPicker.getColor();
+        } else {
+            return mNewListColor.getColor();
+        }
+    }
+
+    class LedColorAdapter extends BaseAdapter implements SpinnerAdapter {
+        private ArrayList<Pair<String, Integer>> mColors;
+
+        public LedColorAdapter(int ledColorResource, int ledValueResource) {
+            mColors = new ArrayList<Pair<String, Integer>>();
+
+            String[] color_names = getContext().getResources().getStringArray(ledColorResource);
+            String[] color_values = getContext().getResources().getStringArray(ledValueResource);
+
+            for(int i = 0; i < color_values.length; ++i) {
+                try {
+                    int color = Color.parseColor(color_values[i]);
+                    mColors.add(new Pair<String, Integer>(color_names[i], color));
+                } catch (IllegalArgumentException ex) {
+                    // Number format is incorrect, ignore entry
+                }
+            }
+        }
+
+        /**
+         * Will return the position of the spinner entry with the specified
+         * color. Returns 0 if there is no such entry.
+         */
+        public int getColorPosition(int color) {
+            for (int position = 0; position < getCount(); ++position) {
+                if (getItem(position).second.equals(color)) {
+                    return position;
+                }
+            }
+
+            return 0;
+        }
+
+        public int getColor(int position) {
+            Pair<String, Integer> item = getItem(position);
+            if (item != null){
+                return item.second;
+            }
+
+            // -1 is white
+            return -1;
+        }
+
+        @Override
+        public int getCount() {
+            return mColors.size();
+        }
+
+        @Override
+        public Pair<String, Integer> getItem(int position) {
+            return mColors.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View view, ViewGroup parent) {
+            if (view == null) {
+                view = mInflater.inflate(R.layout.led_color_item, null);
+            }
+
+            Pair<String, Integer> entry = getItem(position);
+            ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first);
+
+            return view;
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        String hexColor = mHexColorInput.getText().toString();
+        if (!hexColor.isEmpty()) {
+            try {
+                int color = Color.parseColor('#' + hexColor);
+                if (!mWithAlpha) {
+                    color |= 0xFF000000; // set opaque
+                }
+                mColorPicker.setColor(color);
+                mNewColor.setColor(color);
+            } catch (IllegalArgumentException ex) {
+                // Number format is incorrect, ignore
+            }
+        }
+    }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (!hasFocus) {
+            mHexColorInput.removeTextChangedListener(this);
+            InputMethodManager inputMethodManager = (InputMethodManager) getContext()
+                    .getSystemService(Activity.INPUT_METHOD_SERVICE);
+            inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+        } else {
+            mHexColorInput.addTextChangedListener(this);
+        }
+    }
+}
diff --git a/src/org/omnirom/omnigears/batterylight/BatteryLightPreference.java b/src/org/omnirom/omnigears/batterylight/BatteryLightPreference.java
new file mode 100644
index 0000000..5f316c7
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/BatteryLightPreference.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod 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 org.omnirom.omnigears.batterylight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.settings.R;
+
+public class BatteryLightPreference extends Preference implements DialogInterface.OnDismissListener {
+
+    private static String TAG = "BatteryLightPreference";
+    public static final int DEFAULT_COLOR = 0xFFFFFF; //White
+
+    private ImageView mLightColorView;
+    private Resources mResources;
+    private int mColorValue;
+    private Dialog mDialog;
+
+    /**
+     * @param context
+     * @param attrs
+     */
+    public BatteryLightPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mColorValue = DEFAULT_COLOR;
+        init();
+    }
+
+    public BatteryLightPreference(Context context, int color) {
+        super(context, null);
+        mColorValue = color;
+        init();
+    }
+
+    private void init() {
+        setLayoutResource(R.layout.preference_battery_light);
+        mResources = getContext().getResources();
+    }
+
+    public void setColor(int color) {
+        mColorValue = color;
+        updatePreferenceViews();
+    }
+
+    public int getColor() {
+        return mColorValue;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        mLightColorView = (ImageView) holder.findViewById(R.id.light_color);
+
+        updatePreferenceViews();
+    }
+
+    private void updatePreferenceViews() {
+        final int width = (int) mResources.getDimension(R.dimen.color_preference_width);
+        final int height = (int) mResources.getDimension(R.dimen.color_preference_height);
+
+        if (mLightColorView != null) {
+            mLightColorView.setEnabled(true);
+            mLightColorView.setImageDrawable(createRectShape(width, height, 0xFF000000 | mColorValue));
+        }
+    }
+
+    @Override
+    protected void onClick() {
+        if (mDialog != null && mDialog.isShowing()) return;
+        mDialog = getDialog();
+        mDialog.setOnDismissListener(this);
+        mDialog.show();
+    }
+
+    public Dialog getDialog() {
+        final BatteryLightDialog d = new BatteryLightDialog(getContext(),
+                0xFF000000 | mColorValue);
+
+        d.setButton(AlertDialog.BUTTON_POSITIVE, mResources.getString(R.string.ok),
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mColorValue =  d.getColor() & 0x00FFFFFF; // strip alpha, led does not support it
+                updatePreferenceViews();
+                callChangeListener(this);
+            }
+        });
+        d.setButton(AlertDialog.BUTTON_NEGATIVE, mResources.getString(R.string.cancel),
+                (DialogInterface.OnClickListener) null);
+
+        return d;
+    }
+
+    private static ShapeDrawable createRectShape(int width, int height, int color) {
+        ShapeDrawable shape = new ShapeDrawable(new RectShape());
+        shape.setIntrinsicHeight(height);
+        shape.setIntrinsicWidth(width);
+        shape.getPaint().setColor(color);
+        return shape;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        mDialog = null;
+    }
+}
diff --git a/src/org/omnirom/omnigears/batterylight/BatteryLightSettings.java b/src/org/omnirom/omnigears/batterylight/BatteryLightSettings.java
new file mode 100644
index 0000000..0bd47a9
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/BatteryLightSettings.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod 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 org.omnirom.omnigears.batterylight;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v14.preference.PreferenceFragment;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+
+import org.omnirom.omnigears.preference.SystemSettingSwitchPreference;
+import org.omnirom.omnigears.OmniDashboardFragment;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class BatteryLightSettings extends SettingsPreferenceFragment implements
+        Preference.OnPreferenceChangeListener, Indexable {
+    private static final String TAG = "BatteryLightSettings";
+
+    private static final String LOW_COLOR_PREF = "low_color";
+    private static final String MEDIUM_COLOR_PREF = "medium_color";
+    private static final String FULL_COLOR_PREF = "full_color";
+    private static final String REALLY_FULL_COLOR_PREF = "really_full_color";
+    private static final String KEY_CATEGORY_GENERAL = "general_section";
+    private static final String FAST_COLOR_PREF = "fast_color";
+    private static final String FAST_CHARGING_LED_PREF = "fast_charging_led_enabled";
+    private static final String BATTERY_LIGHT_PREF = "battery_light_enabled";
+    private static final String BATTERY_PULSE_PREF = "battery_light_pulse";
+    private static final String BATTERY_LIGHT_ONLY_FULL_PREF = "battery_light_only_fully_charged";
+    private static final String KEY_CATEGORY_FAST_CHARGE = "fast_color_cat";
+    private static final String KEY_CATEGORY_CHARGE_COLORS = "colors_list";
+
+    private boolean mMultiColorLed;
+    private SystemSettingSwitchPreference mEnabledPref;
+    private SystemSettingSwitchPreference mPulsePref;
+    private SystemSettingSwitchPreference mOnlyFullPref;
+    private SystemSettingSwitchPreference mFastBatteryLightEnabledPref;
+    private PreferenceGroup mColorPrefs;
+    private BatteryLightPreference mLowColorPref;
+    private BatteryLightPreference mMediumColorPref;
+    private BatteryLightPreference mFullColorPref;
+    private BatteryLightPreference mReallyFullColorPref;
+    private BatteryLightPreference mFastColorPref;
+    private static final int MENU_RESET = Menu.FIRST;
+    private int mLowBatteryWarningLevel;
+    private boolean mBatteryLightEnabled;
+    private boolean mFastBatteryLightEnabled;
+
+    @Override
+    public int getMetricsCategory() {
+        return OmniDashboardFragment.ACTION_SETTINGS_OMNI;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.battery_light_settings);
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+        ContentResolver resolver = getContentResolver();
+        mLowBatteryWarningLevel = getResources().getInteger(
+                com.android.internal.R.integer.config_lowBatteryWarningLevel);
+        mBatteryLightEnabled = getResources().getBoolean(
+                com.android.internal.R.bool.config_intrusiveBatteryLed);
+
+        mEnabledPref = (SystemSettingSwitchPreference)prefSet.findPreference(BATTERY_LIGHT_PREF);
+        mEnabledPref.setChecked(Settings.System.getInt(resolver,
+                        Settings.System.BATTERY_LIGHT_ENABLED, mBatteryLightEnabled ? 1 : 0) != 0);
+        mEnabledPref.setOnPreferenceChangeListener(this);
+
+        mPulsePref = (SystemSettingSwitchPreference)prefSet.findPreference(BATTERY_PULSE_PREF);
+        mPulsePref.setChecked(Settings.System.getInt(resolver,
+                        Settings.System.BATTERY_LIGHT_PULSE, mBatteryLightEnabled ? 1 : 0) != 0);
+        mPulsePref.setOnPreferenceChangeListener(this);
+
+        mOnlyFullPref = (SystemSettingSwitchPreference)prefSet.findPreference(BATTERY_LIGHT_ONLY_FULL_PREF);
+        mOnlyFullPref.setOnPreferenceChangeListener(this);
+
+        // Does the Device support changing battery LED colors?
+        if (getResources().getBoolean(com.android.internal.R.bool.config_multiColorBatteryLed)) {
+            setHasOptionsMenu(true);
+
+            // Low, Medium and full color preferences
+            mLowColorPref = (BatteryLightPreference) prefSet.findPreference(LOW_COLOR_PREF);
+            mLowColorPref.setOnPreferenceChangeListener(this);
+
+            mMediumColorPref = (BatteryLightPreference) prefSet.findPreference(MEDIUM_COLOR_PREF);
+            mMediumColorPref.setOnPreferenceChangeListener(this);
+
+            mFullColorPref = (BatteryLightPreference) prefSet.findPreference(FULL_COLOR_PREF);
+            mFullColorPref.setOnPreferenceChangeListener(this);
+
+            mReallyFullColorPref = (BatteryLightPreference) prefSet.findPreference(REALLY_FULL_COLOR_PREF);
+            mReallyFullColorPref.setOnPreferenceChangeListener(this);
+
+            mFastBatteryLightEnabledPref = (SystemSettingSwitchPreference)prefSet.findPreference(FAST_CHARGING_LED_PREF);
+
+            mFastColorPref = (BatteryLightPreference) prefSet.findPreference(FAST_COLOR_PREF);
+            mFastColorPref.setOnPreferenceChangeListener(this);
+
+            // Does the Device support fast charge ?
+            if (!getResources().getBoolean(com.android.internal.R.bool.config_FastChargingLedSupported)) {
+                prefSet.removePreference(prefSet.findPreference(KEY_CATEGORY_FAST_CHARGE));
+            }
+        } else {
+            prefSet.removePreference(prefSet.findPreference(KEY_CATEGORY_CHARGE_COLORS));
+            // not multi color cant have fast charge
+            prefSet.removePreference(prefSet.findPreference(KEY_CATEGORY_FAST_CHARGE));
+        }
+        boolean showOnlyWhenFull = Settings.System.getInt(resolver,
+                Settings.System.BATTERY_LIGHT_ONLY_FULLY_CHARGED, 0) != 0;
+        updateEnablement(showOnlyWhenFull);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshDefault();
+    }
+
+    private void refreshDefault() {
+        ContentResolver resolver = getContentResolver();
+        Resources res = getResources();
+
+        if (mLowColorPref != null) {
+            int lowColor = Settings.System.getInt(resolver, Settings.System.BATTERY_LIGHT_LOW_COLOR,
+                    res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB));
+            mLowColorPref.setColor(lowColor);
+        }
+
+        if (mMediumColorPref != null) {
+            int mediumColor = Settings.System.getInt(resolver, Settings.System.BATTERY_LIGHT_MEDIUM_COLOR,
+                    res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB));
+            mMediumColorPref.setColor(mediumColor);
+        }
+
+        if (mFullColorPref != null) {
+            int fullColor = Settings.System.getInt(resolver, Settings.System.BATTERY_LIGHT_FULL_COLOR,
+                    res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB));
+            mFullColorPref.setColor(fullColor);
+        }
+
+        if (mReallyFullColorPref != null) {
+            int reallyFullColor = Settings.System.getInt(resolver, Settings.System.BATTERY_LIGHT_REALLY_FULL_COLOR,
+                    res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB));
+            mReallyFullColorPref.setColor(reallyFullColor);
+        }
+
+        if (mFastColorPref != null) {
+            int fastColor = Settings.System.getInt(resolver, Settings.System.FAST_BATTERY_LIGHT_COLOR,
+                    res.getInteger(com.android.internal.R.integer.config_notificationsFastBatteryARGB));
+            mFastColorPref.setColor(fastColor);
+        }
+    }
+
+    /**
+     * Updates the default or application specific notification settings.
+     *
+     * @param key of the specific setting to update
+     * @param color
+     */
+    protected void updateValues(String key, Integer color) {
+        ContentResolver resolver = getContentResolver();
+
+        if (key.equals(LOW_COLOR_PREF)) {
+            Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_LOW_COLOR, color);
+        } else if (key.equals(MEDIUM_COLOR_PREF)) {
+            Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, color);
+        } else if (key.equals(FULL_COLOR_PREF)) {
+            Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_FULL_COLOR, color);
+        } else if (key.equals(REALLY_FULL_COLOR_PREF)) {
+            Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_REALLY_FULL_COLOR, color);
+        } else if (key.equals(FAST_COLOR_PREF)) {
+            Settings.System.putInt(resolver, Settings.System.FAST_BATTERY_LIGHT_COLOR, color);
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        menu.add(0, MENU_RESET, 0, R.string.reset)
+                .setIcon(R.drawable.ic_settings_backup_restore)
+                .setAlphabeticShortcut('r')
+                .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_RESET:
+                resetToDefaults();
+                return true;
+        }
+        return false;
+    }
+
+    protected void resetColors() {
+        ContentResolver resolver = getActivity().getContentResolver();
+        Resources res = getResources();
+
+        // Reset to the framework default colors
+        Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_LOW_COLOR,
+                res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB));
+        Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_MEDIUM_COLOR,
+                res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB));
+        Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_FULL_COLOR,
+                res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB));
+        Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_REALLY_FULL_COLOR,
+                res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB));
+        Settings.System.putInt(resolver, Settings.System.FAST_BATTERY_LIGHT_COLOR,
+                res.getInteger(com.android.internal.R.integer.config_notificationsFastBatteryARGB));
+        refreshDefault();
+    }
+
+    protected void resetToDefaults() {
+        if (mEnabledPref != null) mEnabledPref.setChecked(true);
+        if (mPulsePref != null) mPulsePref.setChecked(false);
+        if (mOnlyFullPref != null) mOnlyFullPref.setChecked(false);
+        if (mFastBatteryLightEnabledPref != null) mFastBatteryLightEnabledPref.setChecked(false);
+        resetColors();
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (preference == mEnabledPref) {
+            boolean value = (Boolean) objValue;
+            Settings.System.putInt(getActivity().getContentResolver(),
+                    Settings.System.BATTERY_LIGHT_ENABLED, value ? 1:0);
+        } else if (preference == mPulsePref) {
+            boolean value = (Boolean) objValue;
+            Settings.System.putInt(getActivity().getContentResolver(),
+                    Settings.System.BATTERY_LIGHT_PULSE, value ? 1:0);
+        } else if (preference == mOnlyFullPref) {
+            boolean value = (Boolean) objValue;
+            // If enabled, disable all but really full color preference.
+            updateEnablement(value);
+        } else {
+            BatteryLightPreference lightPref = (BatteryLightPreference) preference;
+            updateValues(lightPref.getKey(), lightPref.getColor());
+        }
+        return true;
+    }
+
+    private void updateEnablement(boolean showOnlyWhenFull) {
+        // If enabled, disable all but really full color preference.
+        if (mLowColorPref != null) {
+            mLowColorPref.setEnabled(!showOnlyWhenFull);
+        }
+        if (mMediumColorPref != null) {
+            mMediumColorPref.setEnabled(!showOnlyWhenFull);
+        }
+        if (mFullColorPref != null) {
+            mFullColorPref.setEnabled(!showOnlyWhenFull);
+        }
+        if (mFastColorPref != null) {
+            mFastColorPref.setEnabled(!showOnlyWhenFull);
+        }
+        if (mFastBatteryLightEnabledPref != null) {
+            mFastBatteryLightEnabledPref.setEnabled(!showOnlyWhenFull);
+        }
+    }
+
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+                        boolean enabled) {
+                    ArrayList<SearchIndexableResource> result =
+                            new ArrayList<SearchIndexableResource>();
+
+                    SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.battery_light_settings;
+                    result.add(sir);
+                    return result;
+                }
+
+                @Override
+                public List<String> getNonIndexableKeys(Context context) {
+                    ArrayList<String> result = new ArrayList<String>();
+                    final Resources res = context.getResources();
+                    if (!res.getBoolean(com.android.internal.R.bool.config_intrusiveBatteryLed)) {
+                        result.add(BATTERY_LIGHT_PREF);
+                        result.add(BATTERY_PULSE_PREF);
+                        result.add(BATTERY_LIGHT_ONLY_FULL_PREF);
+                    }
+                    if (!res.getBoolean(com.android.internal.R.bool.config_multiColorBatteryLed)) {
+                        result.add(LOW_COLOR_PREF);
+                        result.add(MEDIUM_COLOR_PREF);
+                        result.add(FULL_COLOR_PREF);
+                        result.add(REALLY_FULL_COLOR_PREF);
+                    }
+                    if (!res.getBoolean(com.android.internal.R.bool.config_FastChargingLedSupported)) {
+                        result.add(FAST_CHARGING_LED_PREF);
+                        result.add(FAST_COLOR_PREF);
+                    }
+                    return result;
+                }
+            };
+}
diff --git a/src/org/omnirom/omnigears/ui/AlphaPatternDrawable.java b/src/org/omnirom/omnigears/ui/AlphaPatternDrawable.java
new file mode 100644
index 0000000..dbe4b77
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/AlphaPatternDrawable.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod 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 org.omnirom.omnigears.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+
+/**
+ * This drawable that draws a simple white and gray chess board pattern. It's
+ * pattern you will often see as a background behind a partly transparent image
+ * in many applications.
+ *
+ * @author Daniel Nilsson
+ */
+public class AlphaPatternDrawable extends Drawable {
+
+    private int mRectangleSize = 10;
+
+    private Paint mPaint = new Paint();
+    private Paint mPaintWhite = new Paint();
+    private Paint mPaintGray = new Paint();
+
+    private int numRectanglesHorizontal;
+    private int numRectanglesVertical;
+
+    /**
+     * Bitmap in which the pattern will be cached.
+     */
+    private Bitmap mBitmap;
+
+    public AlphaPatternDrawable(int rectangleSize) {
+        mRectangleSize = rectangleSize;
+        mPaintWhite.setColor(0xffffffff);
+        mPaintGray.setColor(0xffcbcbcb);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mBitmap != null) {
+            canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
+        }
+    }
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        throw new UnsupportedOperationException("Alpha is not supported by this drawwable.");
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable.");
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+
+        int height = bounds.height();
+        int width = bounds.width();
+
+        numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize));
+        numRectanglesVertical = (int) Math.ceil(height / mRectangleSize);
+
+        generatePatternBitmap();
+    }
+
+    /**
+     * This will generate a bitmap with the pattern as big as the rectangle we
+     * were allow to draw on. We do this to cache the bitmap so we don't need
+     * to recreate it each time draw() is called since it takes a few
+     * milliseconds.
+     */
+    private void generatePatternBitmap() {
+
+        if (getBounds().width() <= 0 || getBounds().height() <= 0) {
+            return;
+        }
+
+        mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(mBitmap);
+
+        Rect r = new Rect();
+        boolean verticalStartWhite = true;
+        for (int i = 0; i <= numRectanglesVertical; i++) {
+            boolean isWhite = verticalStartWhite;
+            for (int j = 0; j <= numRectanglesHorizontal; j++) {
+                r.top = i * mRectangleSize;
+                r.left = j * mRectangleSize;
+                r.bottom = r.top + mRectangleSize;
+                r.right = r.left + mRectangleSize;
+
+                canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray);
+
+                isWhite = !isWhite;
+            }
+
+            verticalStartWhite = !verticalStartWhite;
+        }
+    }
+}
diff --git a/src/org/omnirom/omnigears/ui/ColorPanelView.java b/src/org/omnirom/omnigears/ui/ColorPanelView.java
new file mode 100644
index 0000000..5941bf1
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/ColorPanelView.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod 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 org.omnirom.omnigears.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This class draws a panel which which will be filled with a color which can be
+ * set. It can be used to show the currently selected color which you will get
+ * from the {@link ColorPickerView}.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPanelView extends View {
+
+    /**
+     * The width in pixels of the border surrounding the color panel.
+     */
+    private final static float BORDER_WIDTH_PX = 1;
+
+    private static float mDensity = 1f;
+
+    private int mBorderColor = 0xff6E6E6E;
+    private int mColor = 0xff000000;
+
+    private Paint mBorderPaint;
+    private Paint mColorPaint;
+
+    private RectF mDrawingRect;
+    private RectF mColorRect;
+
+    private AlphaPatternDrawable mAlphaPattern;
+
+    public ColorPanelView(Context context) {
+        this(context, null);
+    }
+
+    public ColorPanelView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ColorPanelView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        init();
+    }
+
+    private void init() {
+        mBorderPaint = new Paint();
+        mColorPaint = new Paint();
+        mDensity = getContext().getResources().getDisplayMetrics().density;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+
+        final RectF rect = mColorRect;
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(mDrawingRect, mBorderPaint);
+        }
+
+        if (mAlphaPattern != null) {
+            mAlphaPattern.draw(canvas);
+        }
+
+        mColorPaint.setColor(mColor);
+
+        canvas.drawRect(rect, mColorPaint);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+
+        setMeasuredDimension(width, height);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        mDrawingRect = new RectF();
+        mDrawingRect.left = getPaddingLeft();
+        mDrawingRect.right = w - getPaddingRight();
+        mDrawingRect.top = getPaddingTop();
+        mDrawingRect.bottom = h - getPaddingBottom();
+
+        setUpColorRect();
+
+    }
+
+    private void setUpColorRect() {
+        final RectF dRect = mDrawingRect;
+
+        float left = dRect.left + BORDER_WIDTH_PX;
+        float top = dRect.top + BORDER_WIDTH_PX;
+        float bottom = dRect.bottom - BORDER_WIDTH_PX;
+        float right = dRect.right - BORDER_WIDTH_PX;
+
+        mColorRect = new RectF(left, top, right, bottom);
+
+        mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+
+        mAlphaPattern.setBounds(Math.round(mColorRect.left),
+                Math.round(mColorRect.top),
+                Math.round(mColorRect.right),
+                Math.round(mColorRect.bottom));
+
+    }
+
+    /**
+     * Set the color that should be shown by this view.
+     *
+     * @param color
+     */
+    public void setColor(int color) {
+        mColor = color;
+        invalidate();
+    }
+
+    /**
+     * Get the color currently show by this view.
+     *
+     * @return
+     */
+    public int getColor() {
+        return mColor;
+    }
+
+    /**
+     * Set the color of the border surrounding the panel.
+     *
+     * @param color
+     */
+    public void setBorderColor(int color) {
+        mBorderColor = color;
+        invalidate();
+    }
+
+    /**
+     * Get the color of the border surrounding the panel.
+     */
+    public int getBorderColor() {
+        return mBorderColor;
+    }
+
+}
diff --git a/src/org/omnirom/omnigears/ui/ColorPickerView.java b/src/org/omnirom/omnigears/ui/ColorPickerView.java
new file mode 100644
index 0000000..72316f3
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/ColorPickerView.java
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod 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 org.omnirom.omnigears.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Displays a color picker to the user and allow them to select a color. A
+ * slider for the alpha channel is also available. Enable it by setting
+ * setAlphaSliderVisible(boolean) to true.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPickerView extends View {
+
+    public interface OnColorChangedListener {
+        public void onColorChanged(int color);
+    }
+
+    private final static int PANEL_SAT_VAL = 0;
+    private final static int PANEL_HUE = 1;
+    private final static int PANEL_ALPHA = 2;
+
+    /**
+     * The width in pixels of the border surrounding all color panels.
+     */
+    private final static float BORDER_WIDTH_PX = 1;
+
+    /**
+     * The width in dp of the hue panel.
+     */
+    private float HUE_PANEL_WIDTH = 30f;
+    /**
+     * The height in dp of the alpha panel
+     */
+    private float ALPHA_PANEL_HEIGHT = 20f;
+    /**
+     * The distance in dp between the different color panels.
+     */
+    private float PANEL_SPACING = 10f;
+    /**
+     * The radius in dp of the color palette tracker circle.
+     */
+    private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
+    /**
+     * The dp which the tracker of the hue or alpha panel will extend outside of
+     * its bounds.
+     */
+    private float RECTANGLE_TRACKER_OFFSET = 2f;
+
+    private static float mDensity = 1f;
+
+    private OnColorChangedListener mListener;
+
+    private Paint mSatValPaint;
+    private Paint mSatValTrackerPaint;
+
+    private Paint mHuePaint;
+    private Paint mHueTrackerPaint;
+
+    private Paint mAlphaPaint;
+    private Paint mAlphaTextPaint;
+
+    private Paint mBorderPaint;
+
+    private Shader mValShader;
+    private Shader mSatShader;
+    private Shader mHueShader;
+    private Shader mAlphaShader;
+
+    private int mAlpha = 0xff;
+    private float mHue = 360f;
+    private float mSat = 0f;
+    private float mVal = 0f;
+
+    private String mAlphaSliderText = "Alpha";
+    private int mSliderTrackerColor = 0xff1c1c1c;
+    private int mBorderColor = 0xff6E6E6E;
+    private boolean mShowAlphaPanel = false;
+
+    /*
+     * To remember which panel that has the "focus" when processing hardware
+     * button data.
+     */
+    private int mLastTouchedPanel = PANEL_SAT_VAL;
+
+    /**
+     * Offset from the edge we must have or else the finger tracker will get
+     * clipped when it is drawn outside of the view.
+     */
+    private float mDrawingOffset;
+
+    /*
+     * Distance form the edges of the view of where we are allowed to draw.
+     */
+    private RectF mDrawingRect;
+
+    private RectF mSatValRect;
+    private RectF mHueRect;
+    private RectF mAlphaRect;
+
+    private AlphaPatternDrawable mAlphaPattern;
+
+    private Point mStartTouchPoint = null;
+
+    public ColorPickerView(Context context) {
+        this(context, null);
+    }
+
+    public ColorPickerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        mDensity = getContext().getResources().getDisplayMetrics().density;
+        PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;
+        RECTANGLE_TRACKER_OFFSET *= mDensity;
+        HUE_PANEL_WIDTH *= mDensity;
+        ALPHA_PANEL_HEIGHT *= mDensity;
+        PANEL_SPACING = PANEL_SPACING * mDensity;
+
+        mDrawingOffset = calculateRequiredOffset();
+        initPaintTools();
+
+        // Needed for receiving track ball motion events.
+        setFocusableInTouchMode(true);
+        setFocusable(true);
+        setClickable(true);
+    }
+
+    private void initPaintTools() {
+        mSatValPaint = new Paint();
+        mSatValTrackerPaint = new Paint();
+        mHuePaint = new Paint();
+        mHueTrackerPaint = new Paint();
+        mAlphaPaint = new Paint();
+        mAlphaTextPaint = new Paint();
+        mBorderPaint = new Paint();
+
+        mSatValTrackerPaint.setStyle(Style.STROKE);
+        mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
+        mSatValTrackerPaint.setAntiAlias(true);
+
+        mHueTrackerPaint.setColor(mSliderTrackerColor);
+        mHueTrackerPaint.setStyle(Style.STROKE);
+        mHueTrackerPaint.setStrokeWidth(2f * mDensity);
+        mHueTrackerPaint.setAntiAlias(true);
+
+        mAlphaTextPaint.setColor(0xff1c1c1c);
+        mAlphaTextPaint.setTextSize(14f * mDensity);
+        mAlphaTextPaint.setAntiAlias(true);
+        mAlphaTextPaint.setTextAlign(Align.CENTER);
+        mAlphaTextPaint.setFakeBoldText(true);
+    }
+
+    private float calculateRequiredOffset() {
+        float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
+        offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
+
+        return offset * 1.5f;
+    }
+
+    private int[] buildHueColorArray() {
+        int[] hue = new int[361];
+
+        int count = 0;
+        for (int i = hue.length - 1; i >= 0; i--, count++) {
+            hue[count] = Color.HSVToColor(new float[] {
+                    i, 1f, 1f
+            });
+        }
+        return hue;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) {
+            return;
+        }
+        drawSatValPanel(canvas);
+        drawHuePanel(canvas);
+        drawAlphaPanel(canvas);
+    }
+
+    private void drawSatValPanel(Canvas canvas) {
+        final RectF rect = mSatValRect;
+        int rgb = Color.HSVToColor(new float[] {
+                mHue, 1f, 1f
+        });
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX,
+                    rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
+        }
+
+        // On Honeycomb+ we need to use software rendering to create the shader properly
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+        }
+
+        // Get the overlaying gradients ready and create the ComposeShader
+        if (mValShader == null) {
+            mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+                    0xffffffff, 0xff000000, TileMode.CLAMP);
+        }
+        mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+                0xffffffff, rgb, TileMode.CLAMP);
+        ComposeShader mShader = new ComposeShader(mValShader, mSatShader, Mode.MULTIPLY);
+        mSatValPaint.setShader(mShader);
+        canvas.drawRect(rect, mSatValPaint);
+
+        Point p = satValToPoint(mSat, mVal);
+        mSatValTrackerPaint.setColor(0xff000000);
+        canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity,
+                mSatValTrackerPaint);
+
+        mSatValTrackerPaint.setColor(0xffdddddd);
+        canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
+    }
+
+    private void drawHuePanel(Canvas canvas) {
+        final RectF rect = mHueRect;
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+                    rect.top - BORDER_WIDTH_PX,
+                    rect.right + BORDER_WIDTH_PX,
+                    rect.bottom + BORDER_WIDTH_PX,
+                    mBorderPaint);
+        }
+
+        if (mHueShader == null) {
+            mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+                    buildHueColorArray(), null, TileMode.CLAMP);
+            mHuePaint.setShader(mHueShader);
+        }
+
+        canvas.drawRect(rect, mHuePaint);
+
+        float rectHeight = 4 * mDensity / 2;
+
+        Point p = hueToPoint(mHue);
+
+        RectF r = new RectF();
+        r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
+        r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
+        r.top = p.y - rectHeight;
+        r.bottom = p.y + rectHeight;
+
+        canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+    }
+
+    private void drawAlphaPanel(Canvas canvas) {
+        if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) {
+            return;
+        }
+
+        final RectF rect = mAlphaRect;
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+                    rect.top - BORDER_WIDTH_PX,
+                    rect.right + BORDER_WIDTH_PX,
+                    rect.bottom + BORDER_WIDTH_PX,
+                    mBorderPaint);
+        }
+
+        mAlphaPattern.draw(canvas);
+
+        float[] hsv = new float[] {
+                mHue, mSat, mVal
+        };
+        int color = Color.HSVToColor(hsv);
+        int acolor = Color.HSVToColor(0, hsv);
+
+        mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+                color, acolor, TileMode.CLAMP);
+
+        mAlphaPaint.setShader(mAlphaShader);
+
+        canvas.drawRect(rect, mAlphaPaint);
+
+        if (mAlphaSliderText != null && mAlphaSliderText != "") {
+            canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity,
+                    mAlphaTextPaint);
+        }
+
+        float rectWidth = 4 * mDensity / 2;
+        Point p = alphaToPoint(mAlpha);
+
+        RectF r = new RectF();
+        r.left = p.x - rectWidth;
+        r.right = p.x + rectWidth;
+        r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
+        r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
+
+        canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+    }
+
+    private Point hueToPoint(float hue) {
+        final RectF rect = mHueRect;
+        final float height = rect.height();
+
+        Point p = new Point();
+        p.y = (int) (height - (hue * height / 360f) + rect.top);
+        p.x = (int) rect.left;
+        return p;
+    }
+
+    private Point satValToPoint(float sat, float val) {
+
+        final RectF rect = mSatValRect;
+        final float height = rect.height();
+        final float width = rect.width();
+
+        Point p = new Point();
+
+        p.x = (int) (sat * width + rect.left);
+        p.y = (int) ((1f - val) * height + rect.top);
+
+        return p;
+    }
+
+    private Point alphaToPoint(int alpha) {
+        final RectF rect = mAlphaRect;
+        final float width = rect.width();
+
+        Point p = new Point();
+        p.x = (int) (width - (alpha * width / 0xff) + rect.left);
+        p.y = (int) rect.top;
+        return p;
+    }
+
+    private float[] pointToSatVal(float x, float y) {
+        final RectF rect = mSatValRect;
+        float[] result = new float[2];
+        float width = rect.width();
+        float height = rect.height();
+
+        if (x < rect.left) {
+            x = 0f;
+        } else if (x > rect.right) {
+            x = width;
+        } else {
+            x = x - rect.left;
+        }
+
+        if (y < rect.top) {
+            y = 0f;
+        } else if (y > rect.bottom) {
+            y = height;
+        } else {
+            y = y - rect.top;
+        }
+
+        result[0] = 1.f / width * x;
+        result[1] = 1.f - (1.f / height * y);
+        return result;
+    }
+
+    private float pointToHue(float y) {
+        final RectF rect = mHueRect;
+        float height = rect.height();
+
+        if (y < rect.top) {
+            y = 0f;
+        } else if (y > rect.bottom) {
+            y = height;
+        } else {
+            y = y - rect.top;
+        }
+        return 360f - (y * 360f / height);
+    }
+
+    private int pointToAlpha(int x) {
+        final RectF rect = mAlphaRect;
+        final int width = (int) rect.width();
+
+        if (x < rect.left) {
+            x = 0;
+        } else if (x > rect.right) {
+            x = width;
+        } else {
+            x = x - (int) rect.left;
+        }
+        return 0xff - (x * 0xff / width);
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        boolean update = false;
+
+        if (event.getAction() == MotionEvent.ACTION_MOVE) {
+            switch (mLastTouchedPanel) {
+                case PANEL_SAT_VAL:
+                    float sat,
+                    val;
+                    sat = mSat + x / 50f;
+                    val = mVal - y / 50f;
+                    if (sat < 0f) {
+                        sat = 0f;
+                    } else if (sat > 1f) {
+                        sat = 1f;
+                    }
+
+                    if (val < 0f) {
+                        val = 0f;
+                    } else if (val > 1f) {
+                        val = 1f;
+                    }
+                    mSat = sat;
+                    mVal = val;
+                    update = true;
+                    break;
+                case PANEL_HUE:
+                    float hue = mHue - y * 10f;
+                    if (hue < 0f) {
+                        hue = 0f;
+                    } else if (hue > 360f) {
+                        hue = 360f;
+                    }
+                    mHue = hue;
+                    update = true;
+                    break;
+                case PANEL_ALPHA:
+                    if (!mShowAlphaPanel || mAlphaRect == null) {
+                        update = false;
+                    } else {
+                        int alpha = (int) (mAlpha - x * 10);
+                        if (alpha < 0) {
+                            alpha = 0;
+                        } else if (alpha > 0xff) {
+                            alpha = 0xff;
+                        }
+                        mAlpha = alpha;
+                        update = true;
+                    }
+                    break;
+            }
+        }
+
+        if (update) {
+            if (mListener != null) {
+                mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+                        mHue, mSat, mVal
+                }));
+            }
+            invalidate();
+            return true;
+        }
+        return super.onTrackballEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean update = false;
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mStartTouchPoint = new Point((int) event.getX(), (int) event.getY());
+                update = moveTrackersIfNeeded(event);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                update = moveTrackersIfNeeded(event);
+                break;
+            case MotionEvent.ACTION_UP:
+                mStartTouchPoint = null;
+                update = moveTrackersIfNeeded(event);
+                break;
+        }
+
+        if (update) {
+            requestFocus();
+            if (mListener != null) {
+                mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+                        mHue, mSat, mVal
+                }));
+            }
+            invalidate();
+            return true;
+        }
+
+        return super.onTouchEvent(event);
+    }
+
+    private boolean moveTrackersIfNeeded(MotionEvent event) {
+
+        if (mStartTouchPoint == null)
+            return false;
+
+        boolean update = false;
+        int startX = mStartTouchPoint.x;
+        int startY = mStartTouchPoint.y;
+
+        if (mHueRect.contains(startX, startY)) {
+            mLastTouchedPanel = PANEL_HUE;
+            mHue = pointToHue(event.getY());
+            update = true;
+        } else if (mSatValRect.contains(startX, startY)) {
+            mLastTouchedPanel = PANEL_SAT_VAL;
+            float[] result = pointToSatVal(event.getX(), event.getY());
+            mSat = result[0];
+            mVal = result[1];
+            update = true;
+        } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) {
+            mLastTouchedPanel = PANEL_ALPHA;
+            mAlpha = pointToAlpha((int) event.getX());
+            update = true;
+        }
+
+        return update;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = 0;
+        int height = 0;
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
+        int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
+
+        widthAllowed = chooseWidth(widthMode, widthAllowed);
+        heightAllowed = chooseHeight(heightMode, heightAllowed);
+
+        if (!mShowAlphaPanel) {
+            height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
+
+            // If calculated height (based on the width) is more than the
+            // allowed height.
+            if (height > heightAllowed && heightMode != MeasureSpec.UNSPECIFIED) {
+                height = heightAllowed;
+                width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH);
+            } else {
+                width = widthAllowed;
+            }
+        } else {
+
+            width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
+
+            if (width > widthAllowed && widthMode != MeasureSpec.UNSPECIFIED) {
+                width = widthAllowed;
+                height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
+            } else {
+                height = heightAllowed;
+            }
+        }
+        setMeasuredDimension(width, height);
+    }
+
+    private int chooseWidth(int mode, int size) {
+        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+            return size;
+        } else { // (mode == MeasureSpec.UNSPECIFIED)
+            return getPrefferedWidth();
+        }
+    }
+
+    private int chooseHeight(int mode, int size) {
+        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+            return size;
+        } else { // (mode == MeasureSpec.UNSPECIFIED)
+            return getPrefferedHeight();
+        }
+    }
+
+    private int getPrefferedWidth() {
+        int width = getPrefferedHeight();
+        if (mShowAlphaPanel) {
+            width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
+        }
+        return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
+    }
+
+    private int getPrefferedHeight() {
+        int height = (int) (200 * mDensity);
+        if (mShowAlphaPanel) {
+            height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+        }
+        return height;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        mDrawingRect = new RectF();
+        mDrawingRect.left = mDrawingOffset + getPaddingLeft();
+        mDrawingRect.right = w - mDrawingOffset - getPaddingRight();
+        mDrawingRect.top = mDrawingOffset + getPaddingTop();
+        mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
+
+        setUpSatValRect();
+        setUpHueRect();
+        setUpAlphaRect();
+    }
+
+    private void setUpSatValRect() {
+        final RectF dRect = mDrawingRect;
+        float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
+
+        if (mShowAlphaPanel) {
+            panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+        }
+
+        float left = dRect.left + BORDER_WIDTH_PX;
+        float top = dRect.top + BORDER_WIDTH_PX;
+        float bottom = top + panelSide;
+        float right = left + panelSide;
+        mSatValRect = new RectF(left, top, right, bottom);
+    }
+
+    private void setUpHueRect() {
+        final RectF dRect = mDrawingRect;
+
+        float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
+        float top = dRect.top + BORDER_WIDTH_PX;
+        float bottom = dRect.bottom - BORDER_WIDTH_PX
+                - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
+        float right = dRect.right - BORDER_WIDTH_PX;
+
+        mHueRect = new RectF(left, top, right, bottom);
+    }
+
+    private void setUpAlphaRect() {
+        if (!mShowAlphaPanel) {
+            return;
+        }
+
+        final RectF dRect = mDrawingRect;
+        float left = dRect.left + BORDER_WIDTH_PX;
+        float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
+        float bottom = dRect.bottom - BORDER_WIDTH_PX;
+        float right = dRect.right - BORDER_WIDTH_PX;
+
+        mAlphaRect = new RectF(left, top, right, bottom);
+        mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+        mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
+                .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
+                .round(mAlphaRect.bottom));
+    }
+
+    /**
+     * Set a OnColorChangedListener to get notified when the color selected by
+     * the user has changed.
+     *
+     * @param listener
+     */
+    public void setOnColorChangedListener(OnColorChangedListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Set the color of the border surrounding all panels.
+     *
+     * @param color
+     */
+    public void setBorderColor(int color) {
+        mBorderColor = color;
+        invalidate();
+    }
+
+    /**
+     * Get the color of the border surrounding all panels.
+     */
+    public int getBorderColor() {
+        return mBorderColor;
+    }
+
+    /**
+     * Get the current color this view is showing.
+     *
+     * @return the current color.
+     */
+    public int getColor() {
+        return Color.HSVToColor(mAlpha, new float[] {
+                mHue, mSat, mVal
+        });
+    }
+
+    /**
+     * Set the color the view should show.
+     *
+     * @param color The color that should be selected.
+     */
+    public void setColor(int color) {
+        setColor(color, false);
+    }
+
+    /**
+     * Set the color this view should show.
+     *
+     * @param color The color that should be selected.
+     * @param callback If you want to get a callback to your
+     *            OnColorChangedListener.
+     */
+    public void setColor(int color, boolean callback) {
+        int alpha = Color.alpha(color);
+        int red = Color.red(color);
+        int blue = Color.blue(color);
+        int green = Color.green(color);
+        float[] hsv = new float[3];
+
+        Color.RGBToHSV(red, green, blue, hsv);
+        mAlpha = alpha;
+        mHue = hsv[0];
+        mSat = hsv[1];
+        mVal = hsv[2];
+
+        if (callback && mListener != null) {
+            mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+                    mHue, mSat, mVal
+            }));
+        }
+        invalidate();
+    }
+
+    /**
+     * Get the drawing offset of the color picker view. The drawing offset is
+     * the distance from the side of a panel to the side of the view minus the
+     * padding. Useful if you want to have your own panel below showing the
+     * currently selected color and want to align it perfectly.
+     *
+     * @return The offset in pixels.
+     */
+    public float getDrawingOffset() {
+        return mDrawingOffset;
+    }
+
+    /**
+     * Set if the user is allowed to adjust the alpha panel. Default is false.
+     * If it is set to false no alpha will be set.
+     *
+     * @param visible
+     */
+    public void setAlphaSliderVisible(boolean visible) {
+        if (mShowAlphaPanel != visible) {
+            mShowAlphaPanel = visible;
+
+            /*
+             * Reset all shader to force a recreation. Otherwise they will not
+             * look right after the size of the view has changed.
+             */
+            mValShader = null;
+            mSatShader = null;
+            mHueShader = null;
+            mAlphaShader = null;
+            requestLayout();
+        }
+
+    }
+
+    public boolean isAlphaSliderVisible() {
+        return mShowAlphaPanel;
+    }
+
+    public void setSliderTrackerColor(int color) {
+        mSliderTrackerColor = color;
+        mHueTrackerPaint.setColor(mSliderTrackerColor);
+        invalidate();
+    }
+
+    public int getSliderTrackerColor() {
+        return mSliderTrackerColor;
+    }
+
+    /**
+     * Set the text that should be shown in the alpha slider. Set to null to
+     * disable text.
+     *
+     * @param res string resource id.
+     */
+    public void setAlphaSliderText(int res) {
+        String text = getContext().getString(res);
+        setAlphaSliderText(text);
+    }
+
+    /**
+     * Set the text that should be shown in the alpha slider. Set to null to
+     * disable text.
+     *
+     * @param text Text that should be shown.
+     */
+    public void setAlphaSliderText(String text) {
+        mAlphaSliderText = text;
+        invalidate();
+    }
+
+    /**
+     * Get the current value of the text that will be shown in the alpha slider.
+     *
+     * @return
+     */
+    public String getAlphaSliderText() {
+        return mAlphaSliderText;
+    }
+}