Merge "Status bar brightness control (Settings portion)" into android-4.3
diff --git a/res/layout/dialog_light_settings.xml b/res/layout/dialog_light_settings.xml
new file mode 100644
index 0000000..6e7e01f
--- /dev/null
+++ b/res/layout/dialog_light_settings.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <org.omnirom.omnigears.notificationlight.ColorPickerView
+        android:id="@+id/color_picker_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp" />
+
+    <LinearLayout
+        android:id="@+id/color_panel_view"
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:layout_alignStart="@id/color_picker_view"
+        android:layout_alignEnd="@id/color_picker_view"
+        android:layout_below="@id/color_picker_view"
+        android:layout_marginBottom="4dp"
+        android:layout_marginTop="4dp"
+        android:orientation="horizontal" >
+
+        <EditText
+            android:id="@+id/hex_color_input"
+            android:layout_width="0px"
+            android:maxLength="6"
+            android:digits="0123456789ABCDEFabcdef"
+            android:inputType="textNoSuggestions"
+            android:layout_height="match_parent"
+            android:layout_weight="0.5" />
+
+        <org.omnirom.omnigears.notificationlight.ColorPanelView
+            android:id="@+id/color_panel"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="0.5" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/speed_title_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/color_panel_view"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        android:layout_marginTop="4dp"
+        android:orientation="vertical" >
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="2dp"
+            android:background="@android:drawable/divider_horizontal_dark" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            android:text="@string/pulse_speed_title"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingBottom="4dip" >
+
+            <Spinner
+                android:id="@+id/on_spinner"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <View
+                android:layout_width="8dip"
+                android:layout_height="match_parent" />
+
+            <Spinner
+                android:id="@+id/off_spinner"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+        </LinearLayout>
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/res/layout/preference_application_light.xml b/res/layout/preference_application_light.xml
new file mode 100644
index 0000000..f88038c
--- /dev/null
+++ b/res/layout/preference_application_light.xml
@@ -0,0 +1,69 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_light_pref"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground" >
+
+    <ImageView
+        android:id="@+android:id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="4dip"
+        android:maxWidth="36dip"
+        android:maxHeight="36dip"
+        android:adjustViewBounds="true"
+        android:layout_gravity="center" />
+
+    <RelativeLayout
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_margin="4dip"
+        android:layout_weight="1">
+
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="1" />
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+
+        <TextView
+            android:id="@+id/textViewTimeOnValue"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+
+        <TextView
+            android:id="@+id/textViewTimeOffValue"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/light_color"
+        android:layout_width="32dip"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center" />
+
+</LinearLayout>
diff --git a/res/layout/pulse_time_item.xml b/res/layout/pulse_time_item.xml
new file mode 100644
index 0000000..9052caf
--- /dev/null
+++ b/res/layout/pulse_time_item.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/textViewName"
+    android:paddingStart="4dp"
+    android:paddingEnd="4dp"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:textAppearance="?android:attr/textAppearanceMedium" >
+
+</TextView>
diff --git a/res/values/custom_arrays.xml b/res/values/custom_arrays.xml
index 933ad47..c4c7858 100644
--- a/res/values/custom_arrays.xml
+++ b/res/values/custom_arrays.xml
@@ -85,4 +85,39 @@
         <item>@*android:drawable/ic_lockscreen_google_normal</item>
         <item>@*android:drawable/ic_lockscreen_twitter_normal</item>
     </string-array>
+
+    <!-- Values for the notification light pulse spinners -->
+    <string-array name="notification_pulse_length_entries" translatable="false">
+        <item>@string/pulse_length_always_on</item>
+        <item>@string/pulse_length_very_short</item>
+        <item>@string/pulse_length_short</item>
+        <item>@string/pulse_length_normal</item>
+        <item>@string/pulse_length_long</item>
+        <item>@string/pulse_length_very_long</item>
+    </string-array>
+
+    <string-array name="notification_pulse_length_values" translatable="false">
+        <item>1</item>
+        <item>250</item>
+        <item>500</item>
+        <item>1000</item>
+        <item>2500</item>
+        <item>5000</item>
+    </string-array>
+
+    <string-array name="notification_pulse_speed_entries" translatable="false">
+        <item>@string/pulse_speed_very_fast</item>
+        <item>@string/pulse_speed_fast</item>
+        <item>@string/pulse_speed_normal</item>
+        <item>@string/pulse_speed_slow</item>
+        <item>@string/pulse_speed_very_slow</item>
+    </string-array>
+
+    <string-array name="notification_pulse_speed_values" translatable="false">
+        <item>250</item>
+        <item>500</item>
+        <item>1000</item>
+        <item>2500</item>
+        <item>5000</item>
+    </string-array>
 </resources>
diff --git a/res/values/custom_strings.xml b/res/values/custom_strings.xml
index 50262e3..c8293c9 100644
--- a/res/values/custom_strings.xml
+++ b/res/values/custom_strings.xml
@@ -5,7 +5,7 @@
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 2 of the License, or
+  the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
@@ -90,5 +90,55 @@
     <string name="icon_picker_pack_title">Icon pack</string>
 
     <string name="profile_applist_title">Apps</string>
+    <string name="profile_choose_app">Choose app</string>
+    <string name="profiles_add">Add</string>
+
+    <!-- Notification light dialogs -->
+    <string name="edit_light_settings">Edit light settings</string>
+    <string name="pulse_speed_title">Pulse length and speed</string>
+    <string name="default_time">Normal</string>
+    <string name="custom_time">Custom</string>
+    <string name="dialog_test">Test</string>
+    <string name="dialog_delete_title">Delete</string>
+    <string name="dialog_delete_message">Remove selected item?</string>
+    <string name="dialog_test_message">Turn the screen off to see the selected notification in action or dismiss this dialog to stop the test</string>
+    <string name="dialog_test_button">Dismiss</string>
+
+    <!-- Lights settings screen, notification light settings -->
+    <string name="display_lights_settings_title">Display &amp; lights</string>
+    <string name="lights_category_title">Lights</string>
+    <string name="notification_pulse_title">Notification light</string>
+    <string name="notification_light_general_title">General</string>
+    <string name="notification_light_applist_title">Applications</string>
+    <string name="notification_light_phonelist_title">Phone</string>
+    <string name="notification_light_use_custom">Use custom values</string>
+    <string name="notification_light_enabled">Enabled</string>
+    <string name="notification_light_disabled">Disabled</string>
+    <string name="notification_light_default_value">Default</string>
+    <string name="notification_light_missed_call_title">Missed call</string>
+    <string name="notification_light_voicemail_title">Voicemail</string>
+
+    <!-- Values for the notification light pulse spinners -->
+    <string name="pulse_length_always_on">Always on</string>
+    <string name="pulse_length_very_short">Very short</string>
+    <string name="pulse_length_short">Short</string>
+    <string name="pulse_length_normal">Normal</string>
+    <string name="pulse_length_long">Long</string>
+    <string name="pulse_length_very_long">Very long</string>
+    <string name="pulse_speed_very_fast">Very fast</string>
+    <string name="pulse_speed_fast">Fast</string>
+    <string name="pulse_speed_normal">Normal</string>
+    <string name="pulse_speed_slow">Slow</string>
+    <string name="pulse_speed_very_slow">Very slow</string>
+
+    <!-- Battery light settings  -->
+    <string name="battery_light_title">Battery light</string>
+    <string name="battery_low_pulse_title">Pulse if battery low</string>
+    <string name="battery_light_list_title">Colors</string>
+    <string name="battery_light_low_color_title">Battery low</string>
+    <string name="battery_light_medium_color_title">Charging</string>
+    <string name="battery_light_full_color_title">Fully charged</string>
+
 </resources>
 
+
diff --git a/res/xml/battery_light_settings.xml b/res/xml/battery_light_settings.xml
new file mode 100644
index 0000000..d5fda1e
--- /dev/null
+++ b/res/xml/battery_light_settings.xml
@@ -0,0 +1,59 @@
+<?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">
+
+    <PreferenceCategory
+        android:key="general_section"
+        android:title="@string/notification_light_general_title">
+
+        <CheckBoxPreference
+            android:key="battery_light_enabled"
+            android:title="@string/battery_light_title"
+            android:defaultValue="true" />
+
+        <CheckBoxPreference
+            android:key="battery_light_pulse"
+            android:title="@string/battery_low_pulse_title"
+            android:defaultValue="true"
+            android:dependency="battery_light_enabled" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="colors_list"
+        android:title="@string/battery_light_list_title"
+        android:dependency="battery_light_enabled" >
+
+        <org.omnirom.omnigears.notificationlight.ApplicationLightPreference
+            android:key="low_color"
+            android:title="@string/battery_light_low_color_title"
+            android:persistent="false" />
+
+        <org.omnirom.omnigears.notificationlight.ApplicationLightPreference
+            android:key="medium_color"
+            android:title="@string/battery_light_medium_color_title"
+            android:persistent="false" />
+
+        <org.omnirom.omnigears.notificationlight.ApplicationLightPreference
+            android:key="full_color"
+            android:title="@string/battery_light_full_color_title"
+            android:persistent="false" />
+
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/notification_light_settings.xml b/res/xml/notification_light_settings.xml
new file mode 100644
index 0000000..e40c970
--- /dev/null
+++ b/res/xml/notification_light_settings.xml
@@ -0,0 +1,65 @@
+<?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">
+
+    <PreferenceCategory
+        android:key="general_section"
+        android:title="@string/notification_light_general_title">
+
+        <CheckBoxPreference
+            android:key="notification_light_pulse"
+            android:title="@string/notification_pulse_title" />
+
+        <org.omnirom.omnigears.notificationlight.ApplicationLightPreference
+            android:key="default"
+            android:title="@string/notification_light_default_value"
+            android:persistent="false"
+            android:dependency="notification_light_pulse" />
+
+        <CheckBoxPreference
+            android:key="notification_light_pulse_custom_enable"
+            android:title="@string/notification_light_use_custom"
+            android:dependency="notification_light_pulse" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="phone_list"
+        android:title="@string/notification_light_phonelist_title" >
+
+        <org.omnirom.omnigears.notificationlight.ApplicationLightPreference
+            android:key="missed_call"
+            android:title="@string/notification_light_missed_call_title"
+            android:persistent="false"
+            android:dependency="notification_light_pulse" />
+
+        <org.omnirom.omnigears.notificationlight.ApplicationLightPreference
+            android:key="voicemail"
+            android:title="@string/notification_light_voicemail_title"
+            android:persistent="false"
+            android:dependency="notification_light_pulse" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="applications_list"
+        android:title="@string/notification_light_applist_title"
+        android:dependency="notification_light_pulse_custom_enable" >
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/org/omnirom/omnigears/notificationlight/AlphaPatternDrawable.java b/src/org/omnirom/omnigears/notificationlight/AlphaPatternDrawable.java
new file mode 100644
index 0000000..a527151
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/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.notificationlight;
+
+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/notificationlight/ApplicationLightPreference.java b/src/org/omnirom/omnigears/notificationlight/ApplicationLightPreference.java
new file mode 100644
index 0000000..2756be7
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/ApplicationLightPreference.java
@@ -0,0 +1,360 @@
+/*
+ * 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.notificationlight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.os.Bundle;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+public class ApplicationLightPreference extends DialogPreference {
+
+    private static String TAG = "AppLightPreference";
+    public static final int DEFAULT_TIME = 1000;
+    public static final int DEFAULT_COLOR = 0xFFFFFF; //White
+
+    private ImageView mLightColorView;
+    private TextView mOnValueView;
+    private TextView mOffValueView;
+
+    private int mColorValue;
+    private int mOnValue;
+    private int mOffValue;
+    private boolean mOnOffChangeable;
+
+    private Resources mResources;
+    private ScreenReceiver mReceiver = null;
+    private AlertDialog mTestDialog;
+
+    /**
+     * @param context
+     * @param attrs
+     */
+    public ApplicationLightPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mColorValue = DEFAULT_COLOR;
+        mOnValue = DEFAULT_TIME;
+        mOffValue = DEFAULT_TIME;
+        mOnOffChangeable = true;
+        init();
+    }
+
+    /**
+     * @param context
+     * @param color
+     * @param onValue
+     * @param offValue
+     */
+    public ApplicationLightPreference(Context context, int color, int onValue, int offValue) {
+        super(context, null);
+        mColorValue = color;
+        mOnValue = onValue;
+        mOffValue = offValue;
+        mOnOffChangeable = true;
+        init();
+    }
+
+    /**
+     * @param context
+     * @param onLongClickListener
+     * @param color
+     * @param onValue
+     * @param offValue
+     */
+    public ApplicationLightPreference(Context context, int color, int onValue, int offValue, boolean onOffChangeable) {
+        super(context, null);
+        mColorValue = color;
+        mOnValue = onValue;
+        mOffValue = offValue;
+        mOnOffChangeable = onOffChangeable;
+        init();
+    }
+
+    private void init() {
+        setLayoutResource(R.layout.preference_application_light);
+        mResources = getContext().getResources();
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        mLightColorView = (ImageView) view.findViewById(R.id.light_color);
+        mOnValueView = (TextView) view.findViewById(R.id.textViewTimeOnValue);
+        mOffValueView = (TextView) view.findViewById(R.id.textViewTimeOffValue);
+
+        // Hide the summary text - it takes up too much space on a low res device
+        // We use it for storing the package name for the longClickListener
+        TextView tView = (TextView) view.findViewById(android.R.id.summary);
+        tView.setVisibility(View.GONE);
+
+        updatePreferenceViews();
+    }
+
+    private void updatePreferenceViews() {
+        final int width = (int) mResources.getDimension(R.dimen.device_memory_usage_button_width);
+        final int height = (int) mResources.getDimension(R.dimen.device_memory_usage_button_height);
+
+        if (mLightColorView != null) {
+            mLightColorView.setEnabled(true);
+            mLightColorView.setImageDrawable(createRectShape(width, height, 0xFF000000 + mColorValue));
+        }
+        if (mOnValueView != null) {
+            mOnValueView.setText(mapLengthValue(mOnValue));
+        }
+        if (mOffValueView != null) {
+            if (mOnValue == 1) {
+                mOffValueView.setVisibility(View.GONE);
+            } else {
+                mOffValueView.setVisibility(View.VISIBLE);
+            }
+            mOffValueView.setText(mapSpeedValue(mOffValue));
+        }
+    }
+
+    @Override
+    protected void showDialog(Bundle state) {
+        super.showDialog(state);
+
+        if (getDialog() instanceof LightSettingsDialog) {
+        final LightSettingsDialog d = (LightSettingsDialog) getDialog();
+
+        // Intercept the click on the middle button to show the test dialog and prevent the onDismiss
+        d.findViewById(android.R.id.button3).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                int onTime = d.getPulseSpeedOn();
+                int offTime = d.getPulseSpeedOff();
+
+                showTestDialog(d.getColor() - 0xFF000000, onTime, offTime);
+            }
+        });
+        }
+    }
+
+    @Override
+    protected Dialog createDialog() {
+        android.util.Log.e("XPLOD", "XPLOD/ LOLWTF");
+        final LightSettingsDialog d = new LightSettingsDialog(getContext(),
+                0xFF000000 + mColorValue, mOnValue, mOffValue, mOnOffChangeable);
+        d.setAlphaSliderVisible(false);
+
+        d.setButton(AlertDialog.BUTTON_POSITIVE, mResources.getString(R.string.ok),
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mColorValue =  d.getColor() - 0xFF000000; // strip alpha, led does not support it
+                mOnValue = d.getPulseSpeedOn();
+                mOffValue = d.getPulseSpeedOff();
+                updatePreferenceViews();
+                callChangeListener(this);
+            }
+        });
+        d.setButton(AlertDialog.BUTTON_NEUTRAL, mResources.getString(R.string.dialog_test),
+                (DialogInterface.OnClickListener) null);
+        d.setButton(AlertDialog.BUTTON_NEGATIVE, mResources.getString(R.string.cancel),
+                (DialogInterface.OnClickListener) null);
+
+        return d;
+    }
+
+    private void showTestDialog(int color, int speedOn, int speedOff) {
+        final Context context = getContext();
+
+        if (mReceiver != null) {
+            context.unregisterReceiver(mReceiver);
+        }
+        if (mTestDialog != null) {
+            mTestDialog.dismiss();
+        }
+
+        mReceiver = new ScreenReceiver(color, speedOn, speedOff);
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        context.registerReceiver(mReceiver, filter);
+
+        mTestDialog = new AlertDialog.Builder(context)
+                .setTitle(R.string.dialog_test)
+                .setMessage(R.string.dialog_test_message)
+                .setPositiveButton(R.string.dialog_test_button, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (mReceiver != null) {
+                            context.unregisterReceiver(mReceiver);
+                            mReceiver = null;
+                        }
+                    }
+                })
+                .create();
+
+        mTestDialog.show();
+    }
+
+    /**
+     * Getters and Setters
+     */
+
+    public int getColor() {
+        return mColorValue;
+    }
+
+    public void setColor(int color) {
+        mColorValue = color;
+        updatePreferenceViews();
+    }
+
+    public void setOnValue(int value) {
+        mOnValue = value;
+        updatePreferenceViews();
+    }
+
+    public int getOnValue() {
+        return mOnValue;
+    }
+
+    public void setOffValue(int value) {
+        mOffValue = value;
+        updatePreferenceViews();
+    }
+
+    public int getOffValue() {
+        return mOffValue;
+    }
+
+    public void setAllValues(int color, int onValue, int offValue) {
+        mColorValue = color;
+        mOnValue = onValue;
+        mOffValue = offValue;
+        mOnOffChangeable = true;
+        updatePreferenceViews();
+    }
+
+    public void setAllValues(int color, int onValue, int offValue, boolean onOffChangeable) {
+        mColorValue = color;
+        mOnValue = onValue;
+        mOffValue = offValue;
+        mOnOffChangeable = onOffChangeable;
+        updatePreferenceViews();
+    }
+
+    public void setOnOffValue(int onValue, int offValue) {
+        mOnValue = onValue;
+        mOffValue = offValue;
+        updatePreferenceViews();
+    }
+
+    public void setOnOffChangeable(boolean value) {
+        mOnOffChangeable = value;
+    }
+
+    /**
+     * Utility methods
+     */
+    public class ScreenReceiver extends BroadcastReceiver {
+        protected int timeon;
+        protected int timeoff;
+        protected int color;
+
+        public ScreenReceiver(int color, int timeon, int timeoff) {
+            this.timeon = timeon;
+            this.timeoff = timeoff;
+            this.color = color;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final NotificationManager nm =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                Notification.Builder builder = new Notification.Builder(context);
+                builder.setAutoCancel(true);
+                builder.setLights(color, timeon, timeoff);
+                Notification n = builder.getNotification();
+                n.flags |= Notification.FLAG_SHOW_LIGHTS;
+                nm.notify(1, n);
+            } else if(intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+                nm.cancel(1);
+                context.unregisterReceiver(mReceiver);
+                mReceiver = null;
+                mTestDialog.dismiss();
+                mTestDialog = null;
+            }
+        }
+    }
+
+    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;
+    }
+
+    private String mapLengthValue(Integer time) {
+        if (time == DEFAULT_TIME) {
+            return getContext().getString(R.string.default_time);
+        }
+
+        String[] timeNames = mResources.getStringArray(R.array.notification_pulse_length_entries);
+        String[] timeValues = mResources.getStringArray(R.array.notification_pulse_length_values);
+
+        for (int i = 0; i < timeValues.length; i++) {
+            if (Integer.decode(timeValues[i]).equals(time)) {
+                return timeNames[i];
+            }
+        }
+
+        return getContext().getString(R.string.custom_time);
+    }
+
+    private String mapSpeedValue(Integer time) {
+        if (time == DEFAULT_TIME) {
+            return getContext().getString(R.string.default_time);
+        }
+
+        String[] timeNames = mResources.getStringArray(R.array.notification_pulse_speed_entries);
+        String[] timeValues = mResources.getStringArray(R.array.notification_pulse_speed_values);
+
+        for (int i = 0; i < timeValues.length; i++) {
+            if (Integer.decode(timeValues[i]).equals(time)) {
+                return timeNames[i];
+            }
+        }
+
+        return getContext().getString(R.string.custom_time);
+    }
+}
diff --git a/src/org/omnirom/omnigears/notificationlight/BatteryLightSettings.java b/src/org/omnirom/omnigears/notificationlight/BatteryLightSettings.java
new file mode 100644
index 0000000..63b1da1
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/BatteryLightSettings.java
@@ -0,0 +1,161 @@
+/*
+ * 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.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+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;
+
+public class BatteryLightSettings extends SettingsPreferenceFragment implements
+        Preference.OnPreferenceChangeListener {
+    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 boolean mMultiColorLed;
+    private CheckBoxPreference mEnabledPref;
+    private PreferenceGroup mColorPrefs;
+    private ApplicationLightPreference mLowColorPref;
+    private ApplicationLightPreference mMediumColorPref;
+    private ApplicationLightPreference mFullColorPref;
+    private static final int MENU_RESET = Menu.FIRST;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.battery_light_settings);
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+
+        // 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 = (ApplicationLightPreference) prefSet.findPreference(LOW_COLOR_PREF);
+            mLowColorPref.setOnPreferenceChangeListener(this);
+
+            mMediumColorPref = (ApplicationLightPreference) prefSet.findPreference(MEDIUM_COLOR_PREF);
+            mMediumColorPref.setOnPreferenceChangeListener(this);
+
+            mFullColorPref = (ApplicationLightPreference) prefSet.findPreference(FULL_COLOR_PREF);
+            mFullColorPref.setOnPreferenceChangeListener(this);
+        } else {
+            prefSet.removePreference(prefSet.findPreference("colors_list"));
+        }
+    }
+
+    @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.setAllValues(lowColor, 0, 0, false);
+        }
+
+        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.setAllValues(mediumColor, 0, 0, false);
+        }
+
+        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.setAllValues(fullColor, 0, 0, false);
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        menu.add(0, MENU_RESET, 0, R.string.reset)
+                .setIcon(R.drawable.ic_settings_backup) // use the backup icon
+                .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:
+                resetColors();
+                return true;
+        }
+        return false;
+    }
+
+    protected void resetColors() {
+        ContentResolver resolver = 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));
+        refreshDefault();
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        ApplicationLightPreference lightPref = (ApplicationLightPreference) preference;
+        updateValues(lightPref.getKey(), lightPref.getColor());
+
+        return true;
+    }
+}
diff --git a/src/org/omnirom/omnigears/notificationlight/ColorPanelView.java b/src/org/omnirom/omnigears/notificationlight/ColorPanelView.java
new file mode 100644
index 0000000..59eb56d
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/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.notificationlight;
+
+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/notificationlight/ColorPickerView.java b/src/org/omnirom/omnigears/notificationlight/ColorPickerView.java
new file mode 100644
index 0000000..ec2e2c7
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/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.notificationlight;
+
+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;
+    }
+}
diff --git a/src/org/omnirom/omnigears/notificationlight/LightSettingsDialog.java b/src/org/omnirom/omnigears/notificationlight/LightSettingsDialog.java
new file mode 100644
index 0000000..bda612b
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/LightSettingsDialog.java
@@ -0,0 +1,322 @@
+/*
+ * 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.notificationlight;
+
+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.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.notificationlight.ColorPickerView.OnColorChangedListener;
+
+import java.util.ArrayList;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+public class LightSettingsDialog extends AlertDialog implements
+        ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+    private final static String STATE_KEY_COLOR = "LightSettingsDialog:color";
+
+    private ColorPickerView mColorPicker;
+
+    private EditText mHexColorInput;
+    private ColorPanelView mNewColor;
+    private Spinner mPulseSpeedOn;
+    private Spinner mPulseSpeedOff;
+    private LayoutInflater mInflater;
+
+    private OnColorChangedListener mListener;
+
+    /**
+     * @param context
+     * @param initialColor
+     * @param initialSpeedOn
+     * @param initialSpeedOff
+     */
+    protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn,
+            int initialSpeedOff) {
+        super(context);
+
+        init(initialColor, initialSpeedOn, initialSpeedOff, true);
+    }
+
+    /**
+     * @param context
+     * @param initialColor
+     * @param initialSpeedOn
+     * @param initialSpeedOff
+     * @param onOffChangeable
+     */
+    protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn,
+            int initialSpeedOff, boolean onOffChangeable) {
+        super(context);
+
+        init(initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable);
+    }
+
+    private void init(int color, int speedOn, int speedOff, boolean onOffChangeable) {
+        // To fight color banding.
+        getWindow().setFormat(PixelFormat.RGBA_8888);
+        setUp(color, speedOn, speedOff, onOffChangeable);
+    }
+
+    /**
+     * 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, int speedOn, int speedOff, boolean onOffChangeable) {
+        mInflater = (LayoutInflater) getContext()
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View layout = mInflater.inflate(R.layout.dialog_light_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);
+
+        mColorPicker.setOnColorChangedListener(this);
+        mColorPicker.setColor(color, true);
+
+        mHexColorInput.setOnFocusChangeListener(this);
+        mPulseSpeedOn = (Spinner) layout.findViewById(R.id.on_spinner);
+        PulseSpeedAdapter pulseSpeedAdapter = new PulseSpeedAdapter(
+                R.array.notification_pulse_length_entries,
+                R.array.notification_pulse_length_values,
+                speedOn);
+        mPulseSpeedOn.setAdapter(pulseSpeedAdapter);
+        mPulseSpeedOn.setSelection(pulseSpeedAdapter.getTimePosition(speedOn));
+        mPulseSpeedOn.setOnItemSelectedListener(mSelectionListener);
+
+        mPulseSpeedOff = (Spinner) layout.findViewById(R.id.off_spinner);
+        pulseSpeedAdapter = new PulseSpeedAdapter(R.array.notification_pulse_speed_entries,
+                R.array.notification_pulse_speed_values,
+                speedOff);
+        mPulseSpeedOff.setAdapter(pulseSpeedAdapter);
+        mPulseSpeedOff.setSelection(pulseSpeedAdapter.getTimePosition(speedOff));
+
+        mPulseSpeedOn.setEnabled(onOffChangeable);
+        mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable);
+
+        setView(layout);
+        setTitle(R.string.edit_light_settings);
+    }
+
+    private AdapterView.OnItemSelectedListener mSelectionListener = new AdapterView.OnItemSelectedListener() {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            mPulseSpeedOff.setEnabled(getPulseSpeedOn() != 1);
+        }
+
+        @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 = mColorPicker.isAlphaSliderVisible();
+        final String format = hasAlpha ? "%08x" : "%06x";
+        final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF;
+
+        mNewColor.setColor(color);
+        mHexColorInput.setText(String.format(Locale.US, format, color & mask));
+
+        if (mListener != null) {
+            mListener.onColorChanged(color);
+        }
+    }
+
+    public void setAlphaSliderVisible(boolean visible) {
+        mHexColorInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(visible ? 8 : 6) } );
+        mColorPicker.setAlphaSliderVisible(visible);
+    }
+
+    public int getColor() {
+        return mColorPicker.getColor();
+    }
+
+    @SuppressWarnings("unchecked")
+    public int getPulseSpeedOn() {
+        return ((Pair<String, Integer>) mPulseSpeedOn.getSelectedItem()).second;
+    }
+
+    @SuppressWarnings("unchecked")
+    public int getPulseSpeedOff() {
+        // return 0 if 'Always on' is selected
+        return getPulseSpeedOn() == 1 ? 0 : ((Pair<String, Integer>) mPulseSpeedOff.getSelectedItem()).second;
+    }
+
+    class PulseSpeedAdapter extends BaseAdapter implements SpinnerAdapter {
+        private ArrayList<Pair<String, Integer>> times;
+
+        public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource) {
+            times = new ArrayList<Pair<String, Integer>>();
+
+            String[] time_names = getContext().getResources().getStringArray(timeNamesResource);
+            String[] time_values = getContext().getResources().getStringArray(timeValuesResource);
+
+            for(int i = 0; i < time_values.length; ++i) {
+                times.add(new Pair<String, Integer>(time_names[i], Integer.decode(time_values[i])));
+            }
+
+        }
+
+        /**
+         * This constructor apart from taking a usual time entry array takes the
+         * currently configured time value which might cause the addition of a
+         * "Custom" time entry in the spinner in case this time value does not
+         * match any of the predefined ones in the array.
+         *
+         * @param timeNamesResource The time entry names array
+         * @param timeValuesResource The time entry values array
+         * @param customTime Current time value that might be one of the
+         *            predefined values or a totally custom value
+         */
+        public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource, Integer customTime) {
+            this(timeNamesResource, timeValuesResource);
+
+            // Check if we also need to add the custom value entry
+            if (getTimePosition(customTime) == -1) {
+                times.add(new Pair<String, Integer>(getContext().getResources()
+                        .getString(R.string.custom_time), customTime));
+            }
+        }
+
+        /**
+         * Will return the position of the spinner entry with the specified
+         * time. Returns -1 if there is no such entry.
+         *
+         * @param time Time in ms
+         * @return Position of entry with given time or -1 if not found.
+         */
+        public int getTimePosition(Integer time) {
+            for (int position = 0; position < getCount(); ++position) {
+                if (getItem(position).second.equals(time)) {
+                    return position;
+                }
+            }
+
+            return -1;
+        }
+
+        @Override
+        public int getCount() {
+            return times.size();
+        }
+
+        @Override
+        public Pair<String, Integer> getItem(int position) {
+            return times.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.pulse_time_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 (!mColorPicker.isAlphaSliderVisible()) {
+                    color |= 0xFF000000; // set opaque
+                }
+                mColorPicker.setColor(color);
+                mNewColor.setColor(color);
+                if (mListener != null) {
+                    mListener.onColorChanged(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/notificationlight/NotificationLightSettings.java b/src/org/omnirom/omnigears/notificationlight/NotificationLightSettings.java
new file mode 100644
index 0000000..0cbeacd
--- /dev/null
+++ b/src/org/omnirom/omnigears/notificationlight/NotificationLightSettings.java
@@ -0,0 +1,610 @@
+/*
+ * 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.notificationlight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+public class NotificationLightSettings extends SettingsPreferenceFragment implements
+        Preference.OnPreferenceChangeListener, AdapterView.OnItemLongClickListener {
+    private static final String TAG = "NotificationLightSettings";
+    private static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR = "notification_light_pulse_default_color";
+    private static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON = "notification_light_pulse_default_led_on";
+    private static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF = "notification_light_pulse_default_led_off";
+    private static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE = "notification_light_pulse_custom_enable";
+    private static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
+    private static final String NOTIFICATION_LIGHT_PULSE_CALL_COLOR = "notification_light_pulse_call_color";
+    private static final String NOTIFICATION_LIGHT_PULSE_CALL_LED_ON = "notification_light_pulse_call_led_on";
+    private static final String NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF = "notification_light_pulse_call_led_off";
+    private static final String NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR = "notification_light_pulse_vmail_color";
+    private static final String NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON = "notification_light_pulse_vmail_led_on";
+    private static final String NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF = "notification_light_pulse_vmail_led_off";
+    private static final String PULSE_PREF = "pulse_enabled";
+    private static final String DEFAULT_PREF = "default";
+    private static final String CUSTOM_PREF = "custom_enabled";
+    private static final String MISSED_CALL_PREF = "missed_call";
+    private static final String VOICEMAIL_PREF = "voicemail";
+    public static final int ACTION_TEST = 0;
+    public static final int ACTION_DELETE = 1;
+    private static final int MENU_ADD = 0;
+    private static final int DIALOG_APPS = 0;
+    private int mDefaultColor;
+    private int mDefaultLedOn;
+    private int mDefaultLedOff;
+    private PackageManager mPackageManager;
+    private PreferenceGroup mApplicationPrefList;
+    private CheckBoxPreference mEnabledPref;
+    private CheckBoxPreference mCustomEnabledPref;
+    private ApplicationLightPreference mDefaultPref;
+    private ApplicationLightPreference mCallPref;
+    private ApplicationLightPreference mVoicemailPref;
+    private Menu mMenu;
+    private PackageAdapter mPackageAdapter;
+    private String mPackageList;
+    private Map<String, Package> mPackages;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.notification_light_settings);
+
+        Resources resources = getResources();
+        mDefaultColor = resources.getColor(
+                com.android.internal.R.color.config_defaultNotificationColor);
+        mDefaultLedOn = resources.getInteger(
+                com.android.internal.R.integer.config_defaultNotificationLedOn);
+        mDefaultLedOff = resources.getInteger(
+                com.android.internal.R.integer.config_defaultNotificationLedOff);
+
+        mEnabledPref = (CheckBoxPreference)
+                findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE);
+        mEnabledPref.setOnPreferenceChangeListener(this);
+        mCustomEnabledPref = (CheckBoxPreference)
+                findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE);
+        mCustomEnabledPref.setOnPreferenceChangeListener(this);
+
+        mDefaultPref = (ApplicationLightPreference) findPreference(DEFAULT_PREF);
+        mDefaultPref.setOnPreferenceChangeListener(this);
+
+        // Missed call and Voicemail preferences should only show on devices with a voice capabilities
+        TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
+            removePreference("phone_list");
+        } else {
+            mCallPref = (ApplicationLightPreference) findPreference(MISSED_CALL_PREF);
+            mCallPref.setOnPreferenceChangeListener(this);
+
+            mVoicemailPref = (ApplicationLightPreference) findPreference(VOICEMAIL_PREF);
+            mVoicemailPref.setOnPreferenceChangeListener(this);
+        }
+
+        mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+        mApplicationPrefList.setOrderingAsAdded(false);
+
+        // Get launch-able applications
+        mPackageManager = getPackageManager();
+        mPackageAdapter = new PackageAdapter();
+
+        mPackages = new HashMap<String, Package>();
+
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshDefault();
+        refreshCustomApplicationPrefs();
+        getListView().setOnItemLongClickListener(this);
+        getActivity().invalidateOptionsMenu();
+    }
+
+    private void refreshDefault() {
+        ContentResolver resolver = getContentResolver();
+        int color = Settings.System.getInt(resolver,
+                NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+        int timeOn = Settings.System.getInt(resolver,
+                NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn);
+        int timeOff = Settings.System.getInt(resolver,
+                NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff);
+
+        mDefaultPref.setAllValues(color, timeOn, timeOff);
+
+        // Get Missed call and Voicemail values
+        if (mCallPref != null) {
+            int callColor = Settings.System.getInt(resolver,
+                    NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor);
+            int callTimeOn = Settings.System.getInt(resolver,
+                    NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, mDefaultLedOn);
+            int callTimeOff = Settings.System.getInt(resolver,
+                    NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, mDefaultLedOff);
+
+            mCallPref.setAllValues(callColor, callTimeOn, callTimeOff);
+        }
+
+        if (mVoicemailPref != null) {
+            int vmailColor = Settings.System.getInt(resolver,
+                    NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor);
+            int vmailTimeOn = Settings.System.getInt(resolver,
+                    NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, mDefaultLedOn);
+            int vmailTimeOff = Settings.System.getInt(resolver,
+                    NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, mDefaultLedOff);
+
+            mVoicemailPref.setAllValues(vmailColor, vmailTimeOn, vmailTimeOff);
+        }
+
+        mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+        mApplicationPrefList.setOrderingAsAdded(false);
+    }
+
+    private void refreshCustomApplicationPrefs() {
+        Context context = getActivity();
+
+        if (!parsePackageList()) {
+            return;
+        }
+
+        // Add the Application Preferences
+        if (mApplicationPrefList != null) {
+            mApplicationPrefList.removeAll();
+
+            for (Package pkg : mPackages.values()) {
+                try {
+                    PackageInfo info = mPackageManager.getPackageInfo(pkg.name,
+                            PackageManager.GET_META_DATA);
+                    ApplicationLightPreference pref =
+                            new ApplicationLightPreference(context, pkg.color, pkg.timeon, pkg.timeoff);
+
+                    pref.setKey(pkg.name);
+                    pref.setTitle(info.applicationInfo.loadLabel(mPackageManager));
+                    pref.setIcon(info.applicationInfo.loadIcon(mPackageManager));
+                    pref.setPersistent(false);
+                    pref.setOnPreferenceChangeListener(this);
+
+                    mApplicationPrefList.addPreference(pref);
+                } catch (NameNotFoundException e) {
+                    // Do nothing
+                }
+            }
+        }
+    }
+
+    private void addCustomApplicationPref(String packageName) {
+        Package pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            pkg = new Package(packageName, mDefaultColor, mDefaultLedOn, mDefaultLedOff);
+            mPackages.put(packageName, pkg);
+            savePackageList(false);
+            refreshCustomApplicationPrefs();
+        }
+    }
+
+    private void removeCustomApplicationPref(String packageName) {
+        if (mPackages.remove(packageName) != null) {
+            savePackageList(false);
+            refreshCustomApplicationPrefs();
+        }
+    }
+
+    private boolean parsePackageList() {
+        final String baseString = Settings.System.getString(getContentResolver(),
+                Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES);
+
+        if (TextUtils.equals(mPackageList, baseString)) {
+            return false;
+        }
+
+        mPackageList = baseString;
+        mPackages.clear();
+
+        if (baseString != null) {
+            final String[] array = TextUtils.split(baseString, "\\|");
+            for (String item : array) {
+                if (TextUtils.isEmpty(item)) {
+                    continue;
+                }
+                Package pkg = Package.fromString(item);
+                if (pkg != null) {
+                    mPackages.put(pkg.name, pkg);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private void savePackageList(boolean preferencesUpdated) {
+        List<String> settings = new ArrayList<String>();
+        for (Package app : mPackages.values()) {
+            settings.add(app.toString());
+        }
+        final String value = TextUtils.join("|", settings);
+        if (preferencesUpdated) {
+            mPackageList = value;
+        }
+        Settings.System.putString(getContentResolver(),
+                                  Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, value);
+    }
+
+    /**
+     * Updates the default or package specific notification settings.
+     *
+     * @param packageName Package name of application specific settings to update
+     * @param color
+     * @param timeon
+     * @param timeoff
+     */
+    protected void updateValues(String packageName, Integer color, Integer timeon, Integer timeoff) {
+        ContentResolver resolver = getContentResolver();
+
+        if (packageName.equals(DEFAULT_PREF)) {
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color);
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeon);
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, timeoff);
+            refreshDefault();
+            return;
+        } else if (packageName.equals(MISSED_CALL_PREF)) {
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_CALL_COLOR, color);
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, timeon);
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, timeoff);
+            refreshDefault();
+            return;
+        } else if (packageName.equals(VOICEMAIL_PREF)) {
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, color);
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, timeon);
+            Settings.System.putInt(resolver, NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, timeoff);
+            refreshDefault();
+            return;
+        }
+
+        // Find the custom package and sets its new values
+        Package app = mPackages.get(packageName);
+        if (app != null) {
+            app.color = color;
+            app.timeon = timeon;
+            app.timeoff = timeoff;
+            savePackageList(true);
+        }
+    }
+
+    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+        final Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
+
+        if (mApplicationPrefList.findPreference(pref.getKey()) != pref) {
+            return false;
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.dialog_delete_title)
+                .setMessage(R.string.dialog_delete_message)
+                .setIconAttribute(android.R.attr.alertDialogIcon)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        removeCustomApplicationPref(pref.getKey());
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, null);
+
+        builder.show();
+        return true;
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (preference == mEnabledPref || preference == mCustomEnabledPref) {
+            getActivity().invalidateOptionsMenu();
+        } else {
+            ApplicationLightPreference lightPref = (ApplicationLightPreference) preference;
+            updateValues(lightPref.getKey(), lightPref.getColor(),
+                    lightPref.getOnValue(), lightPref.getOffValue());
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        mMenu = menu;
+        mMenu.add(0, MENU_ADD, 0, R.string.profiles_add)
+                .setIcon(R.drawable.ic_menu_add)
+                .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        boolean enableAddButton = mEnabledPref.isChecked() && mCustomEnabledPref.isChecked();
+        menu.findItem(MENU_ADD).setVisible(enableAddButton);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_ADD:
+                showDialog(DIALOG_APPS);
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Utility classes and supporting methods
+     */
+    @Override
+    public Dialog onCreateDialog(int id) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        final Dialog dialog;
+        switch (id) {
+            case DIALOG_APPS:
+                final ListView list = new ListView(getActivity());
+                list.setAdapter(mPackageAdapter);
+
+                builder.setTitle(R.string.profile_choose_app);
+                builder.setView(list);
+                dialog = builder.create();
+
+                list.setOnItemClickListener(new OnItemClickListener() {
+                    @Override
+                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                        // Add empty application definition, the user will be able to edit it later
+                        PackageItem info = (PackageItem) parent.getItemAtPosition(position);
+                        addCustomApplicationPref(info.packageName);
+                        dialog.cancel();
+                    }
+                });
+                break;
+            default:
+                dialog = null;
+        }
+        return dialog;
+    }
+
+    /**
+     * Application class
+     */
+    private static class Package {
+        public String name;
+        public Integer color;
+        public Integer timeon;
+        public Integer timeoff;
+
+        /**
+         * Stores all the application values in one call
+         * @param name
+         * @param color
+         * @param timeon
+         * @param timeoff
+         */
+        public Package(String name, Integer color, Integer timeon, Integer timeoff) {
+            this.name = name;
+            this.color = color;
+            this.timeon = timeon;
+            this.timeoff = timeoff;
+        }
+
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(name);
+            builder.append("=");
+            builder.append(color);
+            builder.append(";");
+            builder.append(timeon);
+            builder.append(";");
+            builder.append(timeoff);
+            return builder.toString();
+        }
+
+        public static Package fromString(String value) {
+            if (TextUtils.isEmpty(value)) {
+                return null;
+            }
+            String[] app = value.split("=", -1);
+            if (app.length != 2)
+                return null;
+
+            String[] values = app[1].split(";", -1);
+            if (values.length != 3)
+                return null;
+
+            try {
+                Package item = new Package(app[0], Integer.parseInt(values[0]), Integer
+                        .parseInt(values[1]), Integer.parseInt(values[2]));
+                return item;
+            } catch (NumberFormatException e) {
+                return null;
+            }
+        }
+
+    };
+
+    /**
+     * AppItem class
+     */
+    private static class PackageItem implements Comparable<PackageItem> {
+        CharSequence title;
+        TreeSet<CharSequence> activityTitles = new TreeSet<CharSequence>();
+        String packageName;
+        Drawable icon;
+
+        @Override
+        public int compareTo(PackageItem another) {
+            int result = title.toString().compareToIgnoreCase(another.title.toString());
+            return result != 0 ? result : packageName.compareTo(another.packageName);
+        }
+    }
+
+    /**
+     * AppAdapter class
+     */
+    private class PackageAdapter extends BaseAdapter {
+        private List<PackageItem> mInstalledPackages = new LinkedList<PackageItem>();
+
+        private void reloadList() {
+            final Handler handler = new Handler();
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (mInstalledPackages) {
+                        mInstalledPackages.clear();
+                    }
+
+                    final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+                    mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+                    List<ResolveInfo> installedAppsInfo =
+                            mPackageManager.queryIntentActivities(mainIntent, 0);
+
+                    for (ResolveInfo info : installedAppsInfo) {
+                        ApplicationInfo appInfo = info.activityInfo.applicationInfo;
+
+                        final PackageItem item = new PackageItem();
+                        item.title = appInfo.loadLabel(mPackageManager);
+                        item.activityTitles.add(info.loadLabel(mPackageManager));
+                        item.icon = appInfo.loadIcon(mPackageManager);
+                        item.packageName = appInfo.packageName;
+
+                        handler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                // NO synchronize here: We know that mInstalledApps.clear()
+                                // was called and will never be called again.
+                                // At this point the only thread modifying mInstalledApp is main
+                                int index = Collections.binarySearch(mInstalledPackages, item);
+                                if (index < 0) {
+                                    mInstalledPackages.add(-index - 1, item);
+                                } else {
+                                    mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles);
+                                }
+                                notifyDataSetChanged();
+                            }
+                        });
+                    }
+                }
+            }).start();
+        }
+
+        public PackageAdapter() {
+            reloadList();
+        }
+
+        @Override
+        public int getCount() {
+            synchronized (mInstalledPackages) {
+                return mInstalledPackages.size();
+            }
+        }
+
+        @Override
+        public PackageItem getItem(int position) {
+            synchronized (mInstalledPackages) {
+                return mInstalledPackages.get(position);
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            synchronized (mInstalledPackages) {
+                // packageName is guaranteed to be unique in mInstalledPackages
+                return mInstalledPackages.get(position).packageName.hashCode();
+            }
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder;
+            if (convertView != null) {
+                holder = (ViewHolder) convertView.getTag();
+            } else {
+                final LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                convertView = layoutInflater.inflate(R.layout.preference_icon, null, false);
+                holder = new ViewHolder();
+                convertView.setTag(holder);
+                holder.title = (TextView) convertView.findViewById(com.android.internal.R.id.title);
+                holder.summary = (TextView) convertView.findViewById(com.android.internal.R.id.summary);
+                holder.icon = (ImageView) convertView.findViewById(R.id.icon);
+            }
+            PackageItem applicationInfo = getItem(position);
+
+            holder.title.setText(applicationInfo.title);
+            holder.icon.setImageDrawable(applicationInfo.icon);
+
+            boolean needSummary = applicationInfo.activityTitles.size() > 0;
+            if (applicationInfo.activityTitles.size() == 1) {
+                if (TextUtils.equals(applicationInfo.title, applicationInfo.activityTitles.first())) {
+                    needSummary = false;
+                }
+            }
+
+            if (needSummary) {
+                holder.summary.setText(TextUtils.join(", ", applicationInfo.activityTitles));
+                holder.summary.setVisibility(View.VISIBLE);
+            } else {
+                holder.summary.setVisibility(View.GONE);
+            }
+
+            return convertView;
+        }
+    }
+
+    static class ViewHolder {
+        TextView title;
+        TextView summary;
+        ImageView icon;
+    }
+}