Omnigears: [2/3] Notification light settings

Change-Id: I1bfd3d1ae2013f41c4ceb4acbbb38f926694c9cb
diff --git a/res/layout/dialog_notification_settings.xml b/res/layout/dialog_notification_settings.xml
new file mode 100644
index 0000000..0abde26
--- /dev/null
+++ b/res/layout/dialog_notification_settings.xml
@@ -0,0 +1,99 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/alert_dialog_padding_material"
+    android:paddingStart="@dimen/alert_dialog_padding_material"
+    android:paddingEnd="@dimen/alert_dialog_padding_material" >
+    <org.omnirom.omnigears.ui.ColorPickerView
+        android:id="@+id/color_picker_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="10dp"
+        android:layout_marginStart="10dp" />
+    <LinearLayout
+        android:id="@+id/color_panel_view"
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:layout_marginEnd="10dp"
+        android:layout_marginStart="10dp"
+        android:layout_marginTop="4dp"
+        android:orientation="horizontal" >
+        <EditText
+            android:id="@+id/hex_color_input"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="0.5"
+            android:digits="0123456789ABCDEFabcdef"
+            android:inputType="textNoSuggestions"
+            android:maxLength="6" />
+        <org.omnirom.omnigears.ui.ColorPanelView
+            android:id="@+id/color_panel"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_marginStart="10dp"
+            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:id="@+id/lights_dialog_divider"
+                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>
+</LinearLayout>
diff --git a/res/layout/preference_icon.xml b/res/layout/preference_icon.xml
new file mode 100644
index 0000000..687de3d
--- /dev/null
+++ b/res/layout/preference_icon.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout for a Preference in a PreferenceActivity. The
+     Preference is able to place a specific widget for its particular
+     type in the "widget_frame" layout. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/widget_frame"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="12dip"
+        android:padding="2dp"
+        android:maxWidth="36dip"
+        android:maxHeight="36dip"
+        android:adjustViewBounds="true"
+        android:layout_gravity="center" />
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="2dip"
+        android:layout_marginEnd="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        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/textAppearanceListItem"
+            android:textColor="?android:attr/textColorPrimary"
+            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/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout/preference_notification_light.xml b/res/layout/preference_notification_light.xml
new file mode 100644
index 0000000..9cc98af
--- /dev/null
+++ b/res/layout/preference_notification_light.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<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/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground">
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingTop="16dip"
+        android:paddingBottom="16dip">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:textColor="?android:attr/textColorPrimary"
+            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:visibility="gone"
+            android:textAlignment="viewStart"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            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:layout_marginEnd="6dp"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary" />
+
+        <TextView
+            android:id="@+id/textViewTimeOffValue"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:layout_marginEnd="6dp"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary" />
+    </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..56d3005
--- /dev/null
+++ b/res/layout/pulse_time_item.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/textViewName"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingStart="4dp"
+    android:paddingEnd="4dp"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:textAppearance="@android:style/TextAppearance.Material.Subhead" >
+
+</TextView>
diff --git a/res/values/custom_arrays.xml b/res/values/custom_arrays.xml
index 44d447b..6514aab 100644
--- a/res/values/custom_arrays.xml
+++ b/res/values/custom_arrays.xml
@@ -260,4 +260,38 @@
         <item>1</item>
         <item>2</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>2000</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>2000</item>
+        <item>5000</item>
+    </string-array>
 </resources>
diff --git a/res/values/custom_strings.xml b/res/values/custom_strings.xml
index 353c8e2..3b429f4 100644
--- a/res/values/custom_strings.xml
+++ b/res/values/custom_strings.xml
@@ -20,6 +20,10 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <string name="ok">OK</string>
+    <string name="advanced">Advanced</string>
+    <string name="profiles_add">Add...</string>
+    <string name="profile_choose_app">Choose app</string>
+    <string name="reset">Reset</string>
     <!-- Hardware keys -->
     <string name="button_keys_title">Keys</string>
     <string name="keys_bindings_title">Key actions</string>
@@ -353,4 +357,41 @@
     <string name="power_menu_animation_enter">Omni (default)</string>
     <string name="power_menu_animation_bottom">Bottom</string>
     <string name="power_menu_animation_top">Top</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_delete_title">Delete</string>
+    <string name="dialog_delete_message">Remove selected item?</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>
+    <!-- Lights settings screen, notification light settings -->
+    <string name="notification_light_title">Notification light</string>
+    <string name="notificationlight_title">Notification LED settings</string>
+    <string name="notification_light_general_title">General</string>
+    <string name="notification_light_applist_title">Apps</string>
+    <string name="notification_light_use_custom">Use custom values</string>
+    <string name="notification_light_default_value">Default</string>
+    <string name="notification_light_brightness" translatable="false">@string/brightness</string>
+    <string name="notification_light_screen_on">Lights with screen on</string>
+    <string name="notification_light_zen_mode">Lights in Do Not Disturb mode</string>
+    <string name="notification_light_use_multiple_leds">Multiple LEDs</string>
+    <string name="keywords_lights_brightness_level">dim leds brightness</string>
+    <string name="notification_light_automagic">Choose colors automatically</string>
+    <!-- Lights settings, LED notification -->
+    <string name="led_notification_title">Light settings</string>
+    <string name="led_notification_text">LED light enabled by settings</string>
+    <string name="notification_light_no_apps_summary">To add per app control, activate \'%1$s\' and press \'\u002b\' on the menu bar</string>
+
 </resources>
diff --git a/res/xml/notification_light_settings.xml b/res/xml/notification_light_settings.xml
new file mode 100644
index 0000000..e93dc73
--- /dev/null
+++ b/res/xml/notification_light_settings.xml
@@ -0,0 +1,64 @@
+<?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">
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="notification_light_pulse"
+            android:title="@string/notification_light_title" />
+
+        <org.omnirom.omnigears.batterylight.NotificationLightPreference
+            android:key="default"
+            android:title="@string/notification_light_default_value"
+            android:persistent="false"
+            android:dependency="notification_light_pulse" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="advanced_section"
+        android:title="@string/advanced">
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="notification_light_screen_on_enable"
+            android:title="@string/notification_light_screen_on"
+            android:dependency="notification_light_pulse" />
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="allow_lights"
+            android:title="@string/notification_light_zen_mode"
+            android:dependency="notification_light_pulse"
+            android:defaultValue="true" />
+
+        <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+            android:key="notification_light_pulse_custom_enable"
+            android:title="@string/notification_light_use_custom"
+            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/batterylight/NotificationLightDialog.java b/src/org/omnirom/omnigears/batterylight/NotificationLightDialog.java
new file mode 100644
index 0000000..6075695
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/NotificationLightDialog.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.batterylight;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+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.ui.ColorPanelView;
+import org.omnirom.omnigears.ui.ColorPickerView;
+import org.omnirom.omnigears.ui.ColorPickerView.OnColorChangedListener;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class NotificationLightDialog extends AlertDialog implements
+        ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+    private static final String TAG = "NotificationLightDialog";
+    private final static String STATE_KEY_COLOR = "NotificationLightDialog:color";
+    private final static long LED_UPDATE_DELAY_MS = 250;
+
+    private ColorPickerView mColorPicker;
+    private View mLightsDialogDivider;
+    private EditText mHexColorInput;
+    private Spinner mColorList;
+    private ColorPanelView mNewColor;
+    private LinearLayout mColorListView;
+    private Spinner mPulseSpeedOn;
+    private Spinner mPulseSpeedOff;
+    private LayoutInflater mInflater;
+    private LinearLayout mColorPanelView;
+    private ColorPanelView mNewListColor;
+    private LedColorAdapter mLedColorAdapter;
+    private boolean mWithAlpha;
+
+    private OnColorChangedListener mListener;
+
+    private NotificationManager mNotificationManager;
+
+    private boolean mReadyForLed;
+    private int mLedLastColor;
+    private int mLedLastSpeedOn;
+    private int mLedLastSpeedOff;
+
+
+    /**
+     * @param context
+     * @param initialColor
+     * @param initialSpeedOn
+     * @param initialSpeedOff
+     */
+    protected NotificationLightDialog(Context context, int initialColor, int initialSpeedOn,
+            int initialSpeedOff) {
+        super(context);
+
+        init(context, initialColor, initialSpeedOn, initialSpeedOff, true);
+    }
+
+    /**
+     * @param context
+     * @param initialColor
+     * @param initialSpeedOn
+     * @param initialSpeedOff
+     * @param onOffChangeable
+     */
+    protected NotificationLightDialog(Context context, int initialColor, int initialSpeedOn,
+            int initialSpeedOff, boolean onOffChangeable) {
+        super(context);
+
+        init(context, initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable);
+    }
+
+    private void init(Context context, int color, int speedOn, int speedOff,
+            boolean onOffChangeable) {
+        mNotificationManager =
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        mReadyForLed = false;
+        mLedLastColor = 0;
+
+        // 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_notification_settings, null);
+
+        mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view);
+        mHexColorInput = (EditText) layout.findViewById(R.id.hex_color_input);
+        mNewColor = (ColorPanelView) layout.findViewById(R.id.color_panel);
+        mColorPanelView = (LinearLayout) layout.findViewById(R.id.color_panel_view);
+
+        mColorPicker.setOnColorChangedListener(this);
+        mHexColorInput.setOnFocusChangeListener(this);
+        setAlphaSliderVisible(mWithAlpha);
+        mColorPicker.setColor(color, true);
+
+        mLightsDialogDivider = (View) layout.findViewById(R.id.lights_dialog_divider);
+        mPulseSpeedOn = (Spinner) layout.findViewById(R.id.on_spinner);
+        mPulseSpeedOff = (Spinner) layout.findViewById(R.id.off_spinner);
+
+        if (onOffChangeable) {
+            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(mPulseSelectionListener);
+
+            pulseSpeedAdapter = new PulseSpeedAdapter(R.array.notification_pulse_speed_entries,
+                    R.array.notification_pulse_speed_values,
+                    speedOff);
+            mPulseSpeedOff.setAdapter(pulseSpeedAdapter);
+            mPulseSpeedOff.setSelection(pulseSpeedAdapter.getTimePosition(speedOff));
+            mPulseSpeedOff.setOnItemSelectedListener(mPulseSelectionListener);
+        } else {
+            View speedSettingsGroup = layout.findViewById(R.id.speed_title_view);
+            speedSettingsGroup.setVisibility(View.GONE);
+        }
+
+        mPulseSpeedOn.setEnabled(onOffChangeable);
+        mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable);
+
+        setView(layout);
+
+        mColorPicker.setVisibility(View.VISIBLE);
+        mColorPanelView.setVisibility(View.VISIBLE);
+
+        if (!getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_multiColorNotificationLed)) {
+            mColorPicker.setVisibility(View.GONE);
+            mLightsDialogDivider.setVisibility(View.GONE);
+        }
+
+        mReadyForLed = true;
+        updateLed();
+
+    }
+
+    private AdapterView.OnItemSelectedListener mPulseSelectionListener =
+            new AdapterView.OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            if (parent == mPulseSpeedOn) {
+                mPulseSpeedOff.setEnabled(mPulseSpeedOn.isEnabled() && getPulseSpeedOn() != 1);
+            }
+            updateLed();
+        }
+
+        @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 onStop() {
+        super.onStop();
+        dismissLed();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        updateLed();
+    }
+
+    @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);
+        }
+
+        updateLed();
+    }
+
+    public void setAlphaSliderVisible(boolean visible) {
+        mHexColorInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(visible ? 8 : 6) } );
+        mColorPicker.setAlphaSliderVisible(visible);
+    }
+
+    public int getColor() {
+        return mColorPicker.getColor();
+    }
+
+    class LedColorAdapter extends BaseAdapter implements SpinnerAdapter {
+        private ArrayList<Pair<String, Integer>> mColors;
+
+        public LedColorAdapter(int ledColorResource, int ledValueResource) {
+            mColors = new ArrayList<Pair<String, Integer>>();
+
+            String[] color_names = getContext().getResources().getStringArray(ledColorResource);
+            String[] color_values = getContext().getResources().getStringArray(ledValueResource);
+
+            for(int i = 0; i < color_values.length; ++i) {
+                try {
+                    int color = Color.parseColor(color_values[i]);
+                    mColors.add(new Pair<String, Integer>(color_names[i], color));
+                } catch (IllegalArgumentException ex) {
+                    // Number format is incorrect, ignore entry
+                }
+            }
+        }
+
+        /**
+         * Will return the position of the spinner entry with the specified
+         * color. Returns 0 if there is no such entry.
+         */
+        public int getColorPosition(int color) {
+            for (int position = 0; position < getCount(); ++position) {
+                if (getItem(position).second.equals(color)) {
+                    return position;
+                }
+            }
+
+            return 0;
+        }
+
+        public int getColor(int position) {
+            Pair<String, Integer> item = getItem(position);
+            if (item != null){
+                return item.second;
+            }
+
+            // -1 is white
+            return -1;
+        }
+
+        @Override
+        public int getCount() {
+            return mColors.size();
+        }
+
+        @Override
+        public Pair<String, Integer> getItem(int position) {
+            return mColors.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View view, ViewGroup parent) {
+            if (view == null) {
+                view = mInflater.inflate(R.layout.led_color_item, null);
+            }
+
+            Pair<String, Integer> entry = getItem(position);
+            ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first);
+
+            return view;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public int getPulseSpeedOn() {
+        if (mPulseSpeedOn.isEnabled()) {
+            return ((Pair<String, Integer>) mPulseSpeedOn.getSelectedItem()).second;
+        } else {
+            return 1;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public int getPulseSpeedOff() {
+        // return 0 if 'Always on' is selected
+        return getPulseSpeedOn() == 1 ? 0 : ((Pair<String, Integer>) mPulseSpeedOff.getSelectedItem()).second;
+    }
+
+    private Handler mLedHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            updateLed();
+        }
+    };
+
+    private void updateLed() {
+        if (!mReadyForLed) {
+            return;
+        }
+
+        final int color = getColor() & 0xFFFFFF;
+        final int speedOn, speedOff;
+        if (mPulseSpeedOn.isEnabled()) {
+            speedOn = getPulseSpeedOn();
+            speedOff = getPulseSpeedOff();
+        } else {
+            speedOn = 1;
+            speedOff = 0;
+        }
+
+        if (mLedLastColor == color && mLedLastSpeedOn == speedOn
+                && mLedLastSpeedOff == speedOff) {
+            return;
+        }
+
+        // Dampen rate of consecutive LED changes
+        if (mLedHandler.hasMessages(0)) {
+            return;
+        }
+        mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS);
+
+        mLedLastColor = color;
+        mLedLastSpeedOn = speedOn;
+        mLedLastSpeedOff = speedOff;
+
+        final Bundle b = new Bundle();
+        b.putBoolean(Notification.EXTRA_FORCE_SHOW_LIGHTS, true);
+
+        final Notification.Builder builder = new Notification.Builder(getContext());
+        builder.setLights(color, speedOn, speedOff);
+        builder.setExtras(b);
+
+        // Set a notification
+        builder.setSmallIcon(R.drawable.ic_settings_leds);
+        builder.setContentTitle(getContext().getString(R.string.led_notification_title));
+        builder.setContentText(getContext().getString(R.string.led_notification_text));
+        builder.setOngoing(true);
+
+        mNotificationManager.notify(1, builder.build());
+    }
+
+    public void dismissLed() {
+        mNotificationManager.cancel(1);
+        // ensure we later reset LED if dialog is
+        // hidden and then made visible
+        mLedLastColor = 0;
+    }
+
+    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, parent, false);
+            }
+
+            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);
+                updateLed();
+                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/batterylight/NotificationLightPreference.java b/src/org/omnirom/omnigears/batterylight/NotificationLightPreference.java
new file mode 100644
index 0000000..9fc752a
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/NotificationLightPreference.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.batterylight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+public class NotificationLightPreference extends Preference implements DialogInterface.OnDismissListener,
+            View.OnLongClickListener {
+
+    private static String TAG = "NotificationLightPreference";
+    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 Resources mResources;
+    private int mColorValue;
+    private Dialog mDialog;
+
+    private int mOnValue;
+    private int mOffValue;
+    private boolean mOnOffChangeable;
+
+    public interface ItemLongClickListener {
+        public boolean onItemLongClick(String key);
+    }
+
+    private ItemLongClickListener mLongClickListener;
+
+    /**
+     * @param context
+     * @param attrs
+     */
+    public NotificationLightPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mColorValue = DEFAULT_COLOR;
+        mOnValue = DEFAULT_TIME;
+        mOffValue = DEFAULT_TIME;
+        mOnOffChangeable = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_ledCanPulse);
+        init();
+    }
+
+    /**
+     * @param context
+     * @param color
+     * @param onValue
+     * @param offValue
+     */
+    public NotificationLightPreference(Context context, int color, int onValue, int offValue) {
+        super(context, null);
+        mColorValue = color;
+        mOnValue = onValue;
+        mOffValue = offValue;
+        mOnOffChangeable = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_ledCanPulse);
+        init();
+    }
+
+    /**
+     * @param context
+     * @param color
+     * @param onValue
+     * @param offValue
+     */
+    public NotificationLightPreference(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_notification_light);
+        mResources = getContext().getResources();
+    }
+
+    public void setColor(int color) {
+        mColorValue = color;
+        updatePreferenceViews();
+    }
+
+    public int getColor() {
+        return mColorValue;
+    }
+
+    public void onStart() {
+        NotificationLightDialog d = (NotificationLightDialog) getDialog();
+        if (d != null) {
+            d.onStart();
+        }
+    }
+
+    public void onStop() {
+        NotificationLightDialog d = (NotificationLightDialog) getDialog();
+        if (d != null) {
+            d.onStop();
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        if (mLongClickListener != null) {
+            return mLongClickListener.onItemLongClick(getKey());
+        }
+        return false;
+    }
+
+    public void setOnLongClickListener(ItemLongClickListener l) {
+        mLongClickListener = l;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        mLightColorView = (ImageView) holder.findViewById(R.id.light_color);
+        mOnValueView = (TextView) holder.findViewById(R.id.textViewTimeOnValue);
+        mOffValueView = (TextView) holder.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) holder.findViewById(android.R.id.summary);
+        tView.setVisibility(View.GONE);
+
+        if (!getContext().getResources().getBoolean(com.android.internal.R.bool.config_multiColorNotificationLed)) {
+            mLightColorView.setVisibility(View.GONE);
+        }
+
+        updatePreferenceViews();
+        holder.itemView.setOnLongClickListener(this);
+    }
+
+    private void updatePreferenceViews() {
+        final int width = (int) mResources.getDimension(R.dimen.color_preference_width);
+        final int height = (int) mResources.getDimension(R.dimen.color_preference_height);
+
+        if (mLightColorView != null) {
+            mLightColorView.setEnabled(true);
+            mLightColorView.setImageDrawable(createRectShape(width, height, 0xFF000000 | mColorValue));
+        }
+        if (mOnValueView != null) {
+            mOnValueView.setText(mapLengthValue(mOnValue));
+        }
+        if (mOffValueView != null) {
+            if (mOnValue == 1 || !mOnOffChangeable) {
+                mOffValueView.setEnabled(false);
+            } else {
+                mOffValueView.setEnabled(true);
+            }
+            mOffValueView.setText(mapSpeedValue(mOffValue));
+        }
+    }
+
+    @Override
+    protected void onClick() {
+        if (mDialog != null && mDialog.isShowing()) return;
+        mDialog = getDialog();
+        mDialog.setOnDismissListener(this);
+        mDialog.show();
+    }
+
+    public Dialog getDialog() {
+        final NotificationLightDialog d = new NotificationLightDialog(getContext(),
+                0xFF000000 + mColorValue, mOnValue, mOffValue, mOnOffChangeable); 
+
+        d.setButton(AlertDialog.BUTTON_POSITIVE, mResources.getString(R.string.ok),
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mColorValue =  d.getColor() & 0x00FFFFFF; // strip alpha, led does not support it
+                mOnValue = d.getPulseSpeedOn();
+                mOffValue = d.getPulseSpeedOff();
+                updatePreferenceViews();
+                callChangeListener(this);
+            }
+        });
+        d.setButton(AlertDialog.BUTTON_NEGATIVE, mResources.getString(R.string.cancel),
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+            }
+        });
+
+        return d;
+    }
+
+    private static ShapeDrawable createRectShape(int width, int height, int color) {
+        ShapeDrawable shape = new ShapeDrawable(new RectShape());
+        shape.setIntrinsicHeight(height);
+        shape.setIntrinsicWidth(width);
+        shape.getPaint().setColor(color);
+        return shape;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    public void setAllValues(int color, int onValue, int offValue) {
+        mColorValue = color;
+        mOnValue = onValue;
+        mOffValue = offValue;
+        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;
+    }
+
+    private String mapLengthValue(Integer time) {
+        if (!mOnOffChangeable) {
+            return getContext().getResources().getString(R.string.pulse_length_always_on);
+        }
+        if (time == DEFAULT_TIME) {
+            return getContext().getResources().getString(R.string.default_time);
+        }
+
+        String[] timeNames = getContext().getResources().getStringArray(R.array.notification_pulse_length_entries);
+        String[] timeValues = getContext().getResources().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().getResources().getString(R.string.custom_time);
+    }
+
+    private String mapSpeedValue(Integer time) {
+        if (time == DEFAULT_TIME) {
+            return getContext().getResources().getString(R.string.default_time);
+        }
+
+        String[] timeNames = getContext().getResources().getStringArray(R.array.notification_pulse_speed_entries);
+        String[] timeValues = getContext().getResources().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().getResources().getString(R.string.custom_time);
+    }
+
+}
diff --git a/src/org/omnirom/omnigears/batterylight/NotificationLightSettings.java b/src/org/omnirom/omnigears/batterylight/NotificationLightSettings.java
new file mode 100644
index 0000000..708feb6
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/NotificationLightSettings.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.batterylight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.internal.util.omni.ColorUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+
+import org.omnirom.omnigears.preference.SystemSettingSwitchPreference;
+import org.omnirom.omnigears.preference.AppSelectListPreference;
+import org.omnirom.omnigears.preference.AppSelectListPreference.PackageItem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NotificationLightSettings extends SettingsPreferenceFragment implements
+        Preference.OnPreferenceChangeListener, NotificationLightPreference.ItemLongClickListener, Indexable {
+    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 DEFAULT_PREF = "default";
+    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 SystemSettingSwitchPreference mEnabledPref;
+    private SystemSettingSwitchPreference mScreenOnLightsPref;
+    private SystemSettingSwitchPreference mCustomEnabledPref;
+    private NotificationLightPreference mDefaultPref;
+    private Menu mMenu;
+    private AppSelectListPreference mPackageAdapter;
+    private String mPackageList;
+    private Map<String, Package> mPackages;
+    private boolean mMultiColorLed;
+
+    @Override
+    protected int getMetricsCategory() {
+        return MetricsEvent.OMNI_SETTINGS;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.notification_light_settings);
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+        Resources resources = getResources();
+
+        PreferenceGroup mAdvancedPrefs = (PreferenceGroup) prefSet.findPreference("advanced_section");
+
+        // Get the system defined default notification color
+        mDefaultColor =
+                resources.getColor(com.android.internal.R.color.config_defaultNotificationColor, null);
+
+        mDefaultLedOn = resources.getInteger(
+                com.android.internal.R.integer.config_defaultNotificationLedOn);
+        mDefaultLedOff = resources.getInteger(
+                com.android.internal.R.integer.config_defaultNotificationLedOff);
+
+        mEnabledPref = (SystemSettingSwitchPreference)
+                findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE);
+        mEnabledPref.setOnPreferenceChangeListener(this);
+
+        mDefaultPref = (NotificationLightPreference) findPreference(DEFAULT_PREF);
+        mDefaultPref.setOnPreferenceChangeListener(this);
+
+        mScreenOnLightsPref = (SystemSettingSwitchPreference)
+                findPreference(Settings.System.NOTIFICATION_LIGHT_SCREEN_ON);
+        mScreenOnLightsPref.setOnPreferenceChangeListener(this);
+
+        // Advanced light settings
+        mCustomEnabledPref = (SystemSettingSwitchPreference)
+                findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE);
+        mCustomEnabledPref.setOnPreferenceChangeListener(this);
+
+        mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+        mApplicationPrefList.setOrderingAsAdded(false);
+
+        // Get launch-able applications
+        mPackageManager = getPackageManager();
+        mPackageAdapter = new AppSelectListPreference(getActivity());
+
+        mPackages = new HashMap<String, Package>();
+        setHasOptionsMenu(true);
+
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshDefault();
+        refreshCustomApplicationPrefs();
+        getActivity().invalidateOptionsMenu();
+    }
+
+    private void refreshDefault() {
+        ContentResolver resolver = getContentResolver();
+        int color = Settings.System.getInt(resolver,
+                Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+        int timeOn = Settings.System.getInt(resolver,
+                Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn);
+        int timeOff = Settings.System.getInt(resolver,
+                Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff);
+
+        mDefaultPref.setAllValues(color, timeOn, timeOff);
+
+        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);
+                    NotificationLightPreference pref =
+                            new NotificationLightPreference(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);
+                    pref.setOnLongClickListener(this);
+                    mApplicationPrefList.addPreference(pref);
+                } catch (NameNotFoundException e) {
+                    // Do nothing
+                }
+            }
+
+            /* Display a pref explaining how to add apps */
+            if (mApplicationPrefList.getPreferenceCount() == 0) {
+                String summary = getResources().getString(
+                        R.string.notification_light_no_apps_summary);
+                String useCustom = getResources().getString(
+                        R.string.notification_light_use_custom);
+                Preference pref = new Preference(context);
+                pref.setSummary(String.format(summary, useCustom));
+                pref.setEnabled(false);
+                mApplicationPrefList.addPreference(pref);
+            }
+        }
+    }
+
+    private int getInitialColorForPackage(String packageName) {
+        boolean autoColor = true;
+        int color = mDefaultColor;
+        if (autoColor) {
+            try {
+                Drawable icon = mPackageManager.getApplicationIcon(packageName);
+                color = ColorUtils.getIconColorFromDrawable(icon);
+            } catch (NameNotFoundException e) {
+                // shouldn't happen, but just return default
+            }
+        }
+        return color;
+    }
+
+    private void addCustomApplicationPref(String packageName) {
+        Package pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            int color = getInitialColorForPackage(packageName);
+            pkg = new Package(packageName, color, 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, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color);
+            Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeon);
+            Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_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);
+        }
+    }
+
+    protected void resetColors() {
+        ContentResolver resolver = getContentResolver();
+
+        // Reset to the framework default colors
+        Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+
+        refreshDefault();
+    }
+
+    public boolean onItemLongClick(final String key) {
+        final NotificationLightPreference pref =
+                (NotificationLightPreference) getPreferenceScreen().findPreference(key);
+
+        if (mApplicationPrefList.findPreference(key) == null) {
+            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(key);
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, null);
+
+        builder.show();
+        return true;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (preference == mEnabledPref || preference == mCustomEnabledPref ||
+            preference == mScreenOnLightsPref) {
+            getActivity().invalidateOptionsMenu();
+        } else {
+            NotificationLightPreference lightPref = (NotificationLightPreference) 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_white)
+                .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;
+            }
+        }
+
+    }
+
+/*    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+                        boolean enabled) {
+                    ArrayList<SearchIndexableResource> result =
+                            new ArrayList<SearchIndexableResource>();
+
+                    SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.battery_light_settings;
+                    result.add(sir);
+                    return result;
+                }
+
+                @Override
+                public List<String> getNonIndexableKeys(Context context) {
+                    ArrayList<String> result = new ArrayList<String>();
+                    final Resources res = context.getResources();
+                    if (!res.getBoolean(com.android.internal.R.bool.config_intrusiveBatteryLed)) {
+                        result.add(BATTERY_LIGHT_PREF);
+                        result.add(BATTERY_PULSE_PREF);
+                    }
+                    if (!res.getBoolean(com.android.internal.R.bool.config_multiColorBatteryLed)) {
+                        result.add(LOW_COLOR_PREF);
+                        result.add(MEDIUM_COLOR_PREF);
+                        result.add(FULL_COLOR_PREF);
+                        result.add(REALLY_FULL_COLOR_PREF);
+                    }
+                    return result;
+                }
+            }; */
+}
diff --git a/src/org/omnirom/omnigears/preference/AppSelectListPreference.java b/src/org/omnirom/omnigears/preference/AppSelectListPreference.java
index ff2ce2f..59f9a34 100644
--- a/src/org/omnirom/omnigears/preference/AppSelectListPreference.java
+++ b/src/org/omnirom/omnigears/preference/AppSelectListPreference.java
@@ -1,213 +1,186 @@
 /*
- *  Copyright (C) 2016 The OmniROM Project
+ * Copyright (C) 2012-2014 The CyanogenMod Project
  *
- * 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
- * (at your option) any later version.
+ * 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
  *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
+ * 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.preference;
 
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.preference.DialogPreference;
-import android.util.AttributeSet;
-import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.CheckBox;
+import android.widget.BaseAdapter;
 import android.widget.ImageView;
-import android.widget.ListView;
 import android.widget.TextView;
 
 import com.android.settings.R;
 
-import java.text.Collator;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
+import java.util.TreeSet;
 
-public class AppSelectListPreference extends DialogPreference {
-    private final List<MyApplicationInfo> mPackageInfoList = new ArrayList<MyApplicationInfo>();
-    private AppListAdapter mAdapter;
-    private String mReturnValue;
+public class AppSelectListPreference extends BaseAdapter implements Runnable {
+    private PackageManager mPm;
+    private LayoutInflater mInflater;
+    private List<PackageItem> mInstalledPackages = new LinkedList<PackageItem>();
 
-    public AppSelectListPreference(Context context) {
-        this(context, null);
-    }
+    // Packages which don't have launcher icons, but which we want to show nevertheless
+    private static final String[] PACKAGE_WHITELIST = new String[] {
+        "android",                          /* system server */
+        "com.android.systemui",             /* system UI */
+        "com.android.providers.downloads"   /* download provider */
+    };
 
-    public AppSelectListPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        setDialogLayoutResource(R.layout.preference_app_list);
-
-        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        List<ResolveInfo> installedAppsInfo = getContext().getPackageManager().queryIntentActivities(
-                mainIntent, 0);
-
-        for (ResolveInfo info : installedAppsInfo) {
-            MyApplicationInfo myInfo = new MyApplicationInfo();
-            myInfo.resolveInfo = info;
-            myInfo.label = getResolveInfoTitle(info);
-            mPackageInfoList.add(myInfo);
-        }
-        Collections.sort(mPackageInfoList, sDisplayNameComparator);
-    }
-
-    public String getValue() {
-        return mReturnValue;
-    }
-
-    @Override
-    protected void onBindDialogView(View view) {
-        super.onBindDialogView(view);
-
-        mAdapter = new AppListAdapter(getContext());
-        final ListView listView = (ListView) view.findViewById(R.id.app_list);
-        listView.setAdapter(mAdapter);
-        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                final AppViewHolder holder = (AppViewHolder) view.getTag();
-
-                MyApplicationInfo myInfo = mAdapter.getItem(position);
-                ResolveInfo info = myInfo.resolveInfo;
-                Intent intent = getIntentForResolveInfo(info, Intent.ACTION_MAIN);
-                intent.addCategory(Intent.CATEGORY_LAUNCHER);
-
-                mReturnValue = intent.toUri(0).toString();
-                AppSelectListPreference.this.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
-                getDialog().dismiss();
-            }
-        });
-    }
-
-    @Override
-    protected void onPrepareDialogBuilder(Builder builder) {
-        super.onPrepareDialogBuilder(builder);
-        builder.setPositiveButton(null, null);
-    }
-
-    @Override
-    protected void onDialogClosed(boolean positiveResult) {
-        super.onDialogClosed(positiveResult);
-        callChangeListener(mReturnValue);
-    }
-
-    private String getResolveInfoTitle(ResolveInfo info) {
-        CharSequence label = info.loadLabel(getContext().getPackageManager());
-        if (label == null) label = info.activityInfo.name;
-        return label != null ? label.toString() : null;
-    }
-
-    private Intent getIntentForResolveInfo(ResolveInfo info, String action) {
-        Intent intent = new Intent(action);
-        ActivityInfo ai = info.activityInfo;
-        intent.setClassName(ai.packageName, ai.name);
-        return intent;
-    }
-
-    class MyApplicationInfo {
-        ApplicationInfo info;
-        CharSequence label;
-        ResolveInfo resolveInfo;
-    }
-
-    public class AppListAdapter extends ArrayAdapter<MyApplicationInfo> {
-        private final LayoutInflater mInflater;
-
-        public AppListAdapter(Context context) {
-            super(context, 0);
-            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            addAll(mPackageInfoList);
-        }
-
+    private final Handler mHandler = new Handler() {
         @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            // A ViewHolder keeps references to children views to avoid unnecessary calls
-            // to findViewById() on each row.
-            AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
-            convertView = holder.rootView;
-            MyApplicationInfo info = getItem(position);
-            holder.appName.setText(info.label);
-            Drawable icon = info.resolveInfo.loadIcon(getContext().getPackageManager());
-            if (icon != null) {
-                holder.appIcon.setImageDrawable(icon);
+        public void handleMessage(Message msg) {
+            PackageItem item = (PackageItem) msg.obj;
+            int index = Collections.binarySearch(mInstalledPackages, item);
+            if (index < 0) {
+                mInstalledPackages.add(-index - 1, item);
             } else {
-                holder.appIcon.setImageDrawable(null);
+                mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles);
             }
-            return convertView;
-        }
-
-        @Override
-        public MyApplicationInfo getItem(int position) {
-            return mPackageInfoList.get(position);
-        }
-    }
-
-    public static class AppViewHolder {
-        public View rootView;
-        public TextView appName;
-        public ImageView appIcon;
-        public CheckBox checkBox;
-
-        public static AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
-            if (convertView == null) {
-                convertView = inflater.inflate(R.layout.app_select_item, null);
-
-                // Creates a ViewHolder and store references to the two children views
-                // we want to bind data to.
-                AppViewHolder holder = new AppViewHolder();
-                holder.rootView = convertView;
-                holder.appName = (TextView) convertView.findViewById(R.id.app_name);
-                holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
-                holder.checkBox = (CheckBox) convertView.findViewById(android.R.id.checkbox);
-                holder.checkBox.setVisibility(View.GONE);
-                convertView.setTag(holder);
-                return holder;
-            } else {
-                // Get the ViewHolder back to get fast access to the TextView
-                // and the ImageView.
-                return (AppViewHolder)convertView.getTag();
-            }
-        }
-    }
-
-    private final static Comparator<MyApplicationInfo> sDisplayNameComparator
-            = new Comparator<MyApplicationInfo>() {
-
-        private final Collator collator = Collator.getInstance();
-
-        public final int compare(MyApplicationInfo a, MyApplicationInfo b) {
-            return collator.compare(a.label, b.label);
+            notifyDataSetChanged();
         }
     };
-}
 
+    public static class PackageItem implements Comparable<PackageItem> {
+        public final String packageName;
+        public final CharSequence title;
+        private final TreeSet<CharSequence> activityTitles = new TreeSet<CharSequence>();
+        public final Drawable icon;
+
+        PackageItem(String packageName, CharSequence title, Drawable icon) {
+            this.packageName = packageName;
+            this.title = title;
+            this.icon = icon;
+        }
+
+        @Override
+        public int compareTo(PackageItem another) {
+            int result = title.toString().compareToIgnoreCase(another.title.toString());
+            return result != 0 ? result : packageName.compareTo(another.packageName);
+        }
+    }
+
+    public AppSelectListPreference(Context context) {
+        mPm = context.getPackageManager();
+        mInflater = LayoutInflater.from(context);
+        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 {
+            convertView = mInflater.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;
+    }
+
+    private void reloadList() {
+        mInstalledPackages.clear();
+        new Thread(this).start();
+    }
+
+    @Override
+    public void run() {
+        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        List<ResolveInfo> installedAppsInfo = mPm.queryIntentActivities(mainIntent, 0);
+
+        for (ResolveInfo info : installedAppsInfo) {
+            ApplicationInfo appInfo = info.activityInfo.applicationInfo;
+            final PackageItem item = new PackageItem(appInfo.packageName,
+                    appInfo.loadLabel(mPm), appInfo.loadIcon(mPm));
+            item.activityTitles.add(info.loadLabel(mPm));
+            mHandler.obtainMessage(0, item).sendToTarget();
+        }
+
+        for (String packageName : PACKAGE_WHITELIST) {
+            try {
+                ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
+                final PackageItem item = new PackageItem(appInfo.packageName,
+                        appInfo.loadLabel(mPm), appInfo.loadIcon(mPm));
+                mHandler.obtainMessage(0, item).sendToTarget();
+            } catch (PackageManager.NameNotFoundException ignored) {
+                // package not present, so nothing to add -> ignore it
+            }
+        }
+    }
+
+    private static class ViewHolder {
+        TextView title;
+        TextView summary;
+        ImageView icon;
+    }
+}