cmparts: Add LiveDisplay settings

 * Welcome to CMParts, have a nice day!

Change-Id: Ie89830c6315ed583942cb62c3d03e9f3137bf168
diff --git a/res/drawable/color_temperature_preview.xml b/res/drawable/color_temperature_preview.xml
new file mode 100644
index 0000000..0a358f6
--- /dev/null
+++ b/res/drawable/color_temperature_preview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <gradient
+        android:angle="0"
+        android:startColor="#ff3800"
+        android:endColor="#bfd3ff" />
+</shape>
diff --git a/res/drawable/color_tuning_preview.xml b/res/drawable/color_tuning_preview.xml
new file mode 100644
index 0000000..589237a
--- /dev/null
+++ b/res/drawable/color_tuning_preview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <gradient
+        android:angle="0"
+        android:startColor="#000000"
+        android:endColor="#FFFFFF" />
+</shape>
diff --git a/res/layout/display_color_calibration.xml b/res/layout/display_color_calibration.xml
new file mode 100644
index 0000000..bc1b001
--- /dev/null
+++ b/res/layout/display_color_calibration.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013-2015 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+		    xmlns:cm="http://schemas.android.com/apk/res/org.cyanogenmod.cmparts"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center_horizontal"
+            android:paddingStart="20dip"
+            android:paddingEnd="20dip"
+            android:paddingBottom="20dip">
+
+        <TextView android:id="@+id/color_red_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/color_red_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/color_red_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/color_red_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_red_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <TextView android:id="@+id/color_green_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_red_seekbar"
+                android:text="@string/color_green_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/color_green_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_red_seekbar"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/color_green_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_green_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <TextView android:id="@+id/color_blue_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_green_seekbar"
+                android:text="@string/color_blue_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/color_blue_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_green_seekbar"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/color_blue_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/color_blue_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <ImageView android:id="@+id/black_scale_picture_color"
+                android:src="@drawable/color_tuning_preview"
+                android:layout_width="match_parent"
+                android:layout_height="40dip"
+                android:layout_below="@id/color_blue_seekbar"
+                android:paddingTop="20dip" />
+
+    </RelativeLayout>
+</ScrollView>
diff --git a/res/layout/display_picture_adjustment.xml b/res/layout/display_picture_adjustment.xml
new file mode 100644
index 0000000..ebbcb59
--- /dev/null
+++ b/res/layout/display_picture_adjustment.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013-2015 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:cm="http://schemas.android.com/apk/res/org.cyanogenmod.cmparts"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center_horizontal"
+            android:paddingStart="20dip"
+            android:paddingEnd="20dip"
+            android:paddingBottom="20dip">
+
+        <TextView android:id="@+id/adj_hue_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/adj_hue_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/adj_hue_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/adj_hue_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_hue_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <TextView android:id="@+id/adj_saturation_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_hue_seekbar"
+                android:text="@string/adj_saturation_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/adj_saturation_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_hue_seekbar"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/adj_saturation_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_saturation_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <TextView android:id="@+id/adj_intensity_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_saturation_seekbar"
+                android:text="@string/adj_intensity_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/adj_intensity_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_saturation_seekbar"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/adj_intensity_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_intensity_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <TextView android:id="@+id/adj_contrast_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_intensity_seekbar"
+                android:text="@string/adj_contrast_title"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/adj_contrast_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_intensity_seekbar"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <org.cyanogenmod.cmparts.IntervalSeekBar android:id="@+id/adj_contrast_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/adj_contrast_text"
+                android:paddingTop="2dip"
+                cm:min="0.20"
+                cm:max="1.00"
+                cm:defaultValue="1.00"
+                cm:digits="4" />
+
+        <ImageView android:id="@+id/black_scale_picture_adjustment"
+                android:src="@drawable/color_tuning_preview"
+                android:layout_width="match_parent"
+                android:layout_height="40dip"
+                android:layout_below="@id/adj_contrast_seekbar"
+                android:paddingTop="20dip" />
+
+    </RelativeLayout>
+</ScrollView>
diff --git a/res/layout/display_temperature.xml b/res/layout/display_temperature.xml
new file mode 100644
index 0000000..c10f7c4
--- /dev/null
+++ b/res/layout/display_temperature.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center_horizontal"
+            android:paddingStart="20dip"
+            android:paddingEnd="20dip"
+            android:paddingBottom="20dip">
+
+        <TextView android:id="@+id/day_temperature_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/live_display_day"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/day_temperature_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <SeekBar android:id="@+id/day_temperature_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/day_temperature_text"
+                android:paddingTop="2dip" />
+
+        <TextView android:id="@+id/night_temperature_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/day_temperature_seekbar"
+                android:text="@string/live_display_night"
+                android:paddingTop="10dip" />
+        <TextView android:id="@+id/night_temperature_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/day_temperature_seekbar"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip" />
+        <SeekBar android:id="@+id/night_temperature_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/night_temperature_text"
+                android:paddingTop="2dip" />
+
+        <ImageView android:id="@+id/black_scale_picture_color"
+                android:src="@drawable/color_temperature_preview"
+                android:layout_width="match_parent"
+                android:layout_height="40dip"
+                android:layout_below="@id/night_temperature_seekbar"
+                android:paddingTop="20dip" />
+
+    </RelativeLayout>
+</ScrollView>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index eb01190..e871ff3 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -16,4 +16,11 @@
 
 <resources>
     <attr name="preferenceBackgroundColor" format="color" />
+
+    <declare-styleable name="IntervalSeekBar">
+        <attr name="min" format="float" />
+        <attr name="max" format="float" />
+        <attr name="defaultValue" />
+        <attr name="digits" format="integer" />
+    </declare-styleable>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bf1c733..0158414 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -21,8 +21,8 @@
     <string name="dlg_ok">OK</string>
     <string name="cancel">Cancel</string>
     <string name="add">Add</string>
-        <string name="choose_app">Choose app</string>
-        <string name="reset">Reset</string>
+    <string name="choose_app">Choose app</string>
+    <string name="reset">Reset</string>
     <string name="advanced">Advanced</string>
 
     <!-- Privacy Settings Header item -->
@@ -96,5 +96,52 @@
     <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>
 
+    <!-- LiveDisplay -->
+    <string name="live_display_summary">Optimize your screen based on time of day and ambient conditions to improve readability and reduce eyestrain</string>
+    <string name="live_display_mode">Display mode</string>
+    <string name="live_display_color_temperature_title">Color temperature</string>
+    <string name="live_display_color_temperature_summary">Day: <xliff:g id="day_temperature">%1$d</xliff:g>K  Night: <xliff:g id="night_temperature">%2$d</xliff:g>K</string>
+    <string name="live_display_color_temperature_label"><xliff:g id="degrees">%1$d</xliff:g>K</string>
+    <string name="live_display_day">Day</string>
+    <string name="live_display_night">Night</string>
+    <string name="live_display_outdoor_mode_title">Automatic outdoor mode</string>
+    <string name="live_display_outdoor_mode_summary">Increase brightness and saturation automatically under bright sunlight</string>
+    <string name="live_display_low_power_title">Reduce power consumption</string>
+    <string name="live_display_low_power_summary">Adjust display for lowest power consumption without degradation</string>
+    <string name="live_display_enhance_color_title">Enhance colors</string>
+    <string name="live_display_enhance_color_summary">Improve color vibrance of flesh tones, scenery, and other images</string>
+    <string name="live_display_color_profile_title">Color profile</string>
+    <string name="live_display_color_profile_standard_title">Standard</string>
+   <string name="live_display_color_profile_standard_summary">Accurate colors and bright whites</string>
+    <string name="live_display_color_profile_natural_title">Natural</string>
+    <string name="live_display_color_profile_natural_summary">Realistic colors and flesh tones</string>
+    <string name="live_display_color_profile_dynamic_title">Dynamic</string>
+    <string name="live_display_color_profile_dynamic_summary">Enhanced colors and bright whites</string>
+    <string name="live_display_color_profile_cinema_title">Cinema</string>
+    <string name="live_display_color_profile_cinema_summary">Perfect color reproduction for video</string>
+    <string name="live_display_color_profile_astronomy_title">Astronomy</string>
+    <string name="live_display_color_profile_astronomy_summary">Deep red for preserving night vision</string>
+    <string name="live_display_color_profile_photography_title">Photography</string>
+    <string name="live_display_color_profile_photography_summary">Perfect color reproduction for photos</string>
+    <string name="live_display_color_profile_basic_title">Basic</string>
+    <string name="live_display_color_profile_basic_summary">Use the display uncalibrated</string>
+    <string name="live_display_color_profile_adaptive_title">Adaptive</string>
+    <string name="live_display_color_profile_adaptive_summary">Colors adapt to ambient conditions</string>
+    <string name="color_calibration_title">Color calibration</string>
+    <string name="color_calibration_summary">Calibrate on-screen colors</string>
+    <string name="color_red_title">Red</string>
+    <string name="color_green_title">Green</string>
+    <string name="color_blue_title">Blue</string>
+    
+
+    <!-- LiveDisplay : Picture Adjustment -->
+    <string name="picture_adjustment_title">Picture adjustment</string>
+    <string name="picture_adjustment_summary">Adjust hue, saturation, intensity, and contrast</string>
+    <string name="adj_hue_title">Hue</string>
+    <string name="adj_saturation_title">Saturation</string>
+    <string name="adj_intensity_title">Intensity</string>
+    <string name="adj_contrast_title">Contrast</string>
+
 </resources>
 
+
diff --git a/res/xml/livedisplay.xml b/res/xml/livedisplay.xml
new file mode 100644
index 0000000..ef1d9bd
--- /dev/null
+++ b/res/xml/livedisplay.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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">
+
+    <PreferenceCategory
+        android:key="live_display_options"
+        android:title="@*cyanogenmod.platform:string/live_display_title">
+
+        <!-- Color profile -->
+        <ListPreference
+                android:key="live_display_color_profile"
+                android:title="@string/live_display_color_profile_title"
+                android:persistent="false" />
+
+        <ListPreference
+                android:key="live_display"
+                android:title="@string/live_display_mode"
+                android:persistent="false" />
+
+        <!-- Manual temperature selection -->
+        <org.cyanogenmod.cmparts.livedisplay.DisplayTemperature
+                android:key="live_display_color_temperature"
+                android:title="@string/live_display_color_temperature_title"
+                android:dialogTitle="@*cyanogenmod.platform:string/live_display_title"
+                android:persistent="false" />
+
+        <!-- Outdoor mode / SRE -->
+        <cyanogenmod.preference.CMSystemSettingSwitchPreference
+                android:key="display_auto_outdoor_mode"
+                android:title="@string/live_display_outdoor_mode_title"
+                android:summary="@string/live_display_outdoor_mode_summary"
+                android:defaultValue="@*cyanogenmod.platform:bool/config_defaultAutoOutdoorMode" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+            android:key="advanced"
+            android:title="@string/advanced">
+
+        <!-- RGB display color adjustments -->
+        <org.cyanogenmod.cmparts.livedisplay.DisplayColor
+                android:key="color_calibration"
+                android:title="@string/color_calibration_title"
+                android:dialogTitle="@string/color_calibration_title"
+                android:summary="@string/color_calibration_summary"
+                android:persistent="false"/>
+
+        <!-- HSIC display color adjustments -->
+        <org.cyanogenmod.cmparts.livedisplay.PictureAdjustment
+                android:key="picture_adjustment"
+                android:title="@string/picture_adjustment_title"
+                android:dialogTitle="@string/picture_adjustment_title"
+                android:summary="@string/picture_adjustment_summary"
+                android:persistent="false"/>
+
+        <!-- Adaptive backlight -->
+        <cyanogenmod.preference.CMSystemSettingSwitchPreference
+                android:key="display_low_power"
+                android:title="@string/live_display_low_power_title"
+                android:summary="@string/live_display_low_power_summary"
+                android:defaultValue="@*cyanogenmod.platform:bool/config_defaultCABC" />
+
+        <!-- Color enhancement -->
+        <cyanogenmod.preference.CMSystemSettingSwitchPreference
+                android:key="display_color_enhance"
+                android:title="@string/live_display_enhance_color_title"
+                android:summary="@string/live_display_enhance_color_summary"
+                android:defaultValue="@*cyanogenmod.platform:bool/config_defaultColorEnhancement" />
+
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/org/cyanogenmod/cmparts/CustomDialogPreference.java b/src/org/cyanogenmod/cmparts/CustomDialogPreference.java
index 8e8ab92..5fa9175 100644
--- a/src/org/cyanogenmod/cmparts/CustomDialogPreference.java
+++ b/src/org/cyanogenmod/cmparts/CustomDialogPreference.java
@@ -26,7 +26,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 
-public class CustomDialogPreference<T extends Dialog> extends DialogPreference {
+public class CustomDialogPreference<T extends DialogInterface> extends DialogPreference {
 
     private CustomPreferenceDialogFragment mFragment;
 
@@ -48,7 +48,7 @@
     }
 
     public boolean isDialogOpen() {
-        return getDialog() != null && getDialog().isShowing();
+        return getDialog() != null && getDialog() instanceof Dialog && ((Dialog)getDialog()).isShowing();
     }
 
     public T getDialog() {
@@ -62,7 +62,7 @@
     protected void onDialogClosed(boolean positiveResult) {
     }
 
-    protected void onClick(DialogInterface dialog, int which) {
+    protected void onClick(T dialog, int which) {
     }
 
     protected void onBindDialogView(View view) {
@@ -80,6 +80,10 @@
         mFragment = fragment;
     }
 
+    protected boolean onDismissDialog(T dialog, int which) {
+        return true;
+    }
+
     public static class CustomPreferenceDialogFragment extends PreferenceDialogFragment {
 
         public static CustomPreferenceDialogFragment newInstance(String key) {
@@ -94,6 +98,43 @@
             return (CustomDialogPreference) getPreference();
         }
 
+        private class OnDismissListener implements View.OnClickListener {
+            private final int mWhich;
+            private final DialogInterface mDialog;
+
+            public OnDismissListener(DialogInterface dialog, int which) {
+                mWhich = which;
+                mDialog = dialog;
+            }
+
+            @Override
+            public void onClick(View view) {
+                if (getCustomizablePreference().onDismissDialog(mDialog, mWhich)) {
+                    mDialog.dismiss();
+                }
+            }
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            if (getDialog() instanceof AlertDialog) {
+                AlertDialog a = (AlertDialog)getDialog();
+                if (a.getButton(Dialog.BUTTON_NEUTRAL) != null) {
+                    a.getButton(Dialog.BUTTON_NEUTRAL).setOnClickListener(
+                            new OnDismissListener(a, Dialog.BUTTON_NEUTRAL));
+                }
+                if (a.getButton(Dialog.BUTTON_POSITIVE) != null) {
+                    a.getButton(Dialog.BUTTON_POSITIVE).setOnClickListener(
+                            new OnDismissListener(a, Dialog.BUTTON_POSITIVE));
+                }
+                if (a.getButton(Dialog.BUTTON_NEGATIVE) != null) {
+                    a.getButton(Dialog.BUTTON_NEGATIVE).setOnClickListener(
+                            new OnDismissListener(a, Dialog.BUTTON_NEGATIVE));
+                }
+            }
+        }
+
         @Override
         protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
             super.onPrepareDialogBuilder(builder);
diff --git a/src/org/cyanogenmod/cmparts/IntervalSeekBar.java b/src/org/cyanogenmod/cmparts/IntervalSeekBar.java
new file mode 100644
index 0000000..995d34d
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/IntervalSeekBar.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 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.cyanogenmod.cmparts;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.SeekBar;
+
+/**
+ * Custom SeekBar that allows setting both a minimum and maximum value.
+ * This also handles floating point values (to 2 decimal places) through
+ * integer conversions.
+ */
+public class IntervalSeekBar extends SeekBar {
+    private float mMin;
+    private float mMax;
+    private float mDefault;
+    private float mMultiplier;
+
+    public IntervalSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray seekBarType = context.obtainStyledAttributes(attrs,
+                R.styleable.IntervalSeekBar, 0, 0);
+
+        mMax = seekBarType.getFloat(R.styleable.IntervalSeekBar_max, 1.5f);
+        mMin = seekBarType.getFloat(R.styleable.IntervalSeekBar_min, 0.5f);
+        mDefault = seekBarType.getFloat(R.styleable.IntervalSeekBar_defaultValue, 1.0f);
+
+        int digits = seekBarType.getInt(R.styleable.IntervalSeekBar_digits, 0);
+        mMultiplier = (float) Math.pow(10, digits);
+
+        if (mMin > mMax) {
+            float temp = mMax;
+            mMax = mMin;
+            mMin = temp;
+        }
+
+        setMax(convertFloatToProgress(mMax));
+        setProgressFloat(mDefault);
+
+        seekBarType.recycle();
+    }
+
+    /*
+     * Converts from SeekBar units (which the SeekBar uses), to scale units
+     *  (which are saved).
+     *  This operation is the inverse of setFontScaling.
+     */
+    public float getProgressFloat() {
+        return (getProgress() / mMultiplier) + mMin;
+    }
+
+    /*
+     * Converts from scale units (which are saved), to SeekBar units
+     * (which the SeekBar uses). This also sets the SeekBar progress.
+     * This operation is the inverse of getProgressFloat.
+     */
+    public void setProgressFloat(float progress) {
+        setProgress(convertFloatToProgress(progress));
+    }
+
+    private int convertFloatToProgress(float value) {
+        return Math.round((value - mMin) * mMultiplier);
+    }
+
+    public float getMinimum() {
+        return mMin;
+    }
+
+    public float getMaximum() {
+        return mMax;
+    }
+
+    public float getDefault() {
+        return mDefault;
+    }
+
+    public void setMaximum(float max) {
+        mMax = max;
+        setMax(convertFloatToProgress(mMax));
+    }
+
+    public void setMinimum(float min) {
+        mMin = min;
+    }
+}
diff --git a/src/org/cyanogenmod/cmparts/PartsActivity.java b/src/org/cyanogenmod/cmparts/PartsActivity.java
index b463b9a..65dbd01 100644
--- a/src/org/cyanogenmod/cmparts/PartsActivity.java
+++ b/src/org/cyanogenmod/cmparts/PartsActivity.java
@@ -20,19 +20,24 @@
 import android.app.FragmentTransaction;
 import android.os.Bundle;
 import android.preference.PreferenceActivity;
+import android.util.Log;
 
+import org.cyanogenmod.cmparts.livedisplay.LiveDisplay;
 import org.cyanogenmod.cmparts.notificationlight.BatteryLightSettings;
 import org.cyanogenmod.cmparts.notificationlight.NotificationLightSettings;
 
 public class PartsActivity extends PreferenceActivity {
 
+    public static final String TAG = "PartsActivity";
+
     public static final String EXTRA_PART = "part";
     public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
 
     public static final String FRAGMENT_PREFIX = "cmparts:";
 
-    public static final String FRAGMENT_NOTIFICATION_LIGHTS = "notification_lights";
     public static final String FRAGMENT_BATTERY_LIGHTS = "battery_lights";
+    public static final String FRAGMENT_NOTIFICATION_LIGHTS = "notification_lights";
+    public static final String FRAGMENT_LIVEDISPLAY = "livedisplay";
 
     private ActionBar mActionBar;
 
@@ -47,11 +52,17 @@
                 return;
             }
             String part = keys[1];
+            Log.d(TAG, "Launching fragment: " + partExtra);
+
             SettingsPreferenceFragment fragment = null;
             if (part.equals(FRAGMENT_NOTIFICATION_LIGHTS)) {
                 fragment = new NotificationLightSettings();
             } else if (part.equals(FRAGMENT_BATTERY_LIGHTS)) {
                 fragment = new BatteryLightSettings();
+            } else if (part.equals(FRAGMENT_LIVEDISPLAY)) {
+                fragment = new LiveDisplay();
+            } else {
+                Log.d(TAG, "Unknown fragment: " + part);
             }
 
             mActionBar = getActionBar();
diff --git a/src/org/cyanogenmod/cmparts/livedisplay/DisplayColor.java b/src/org/cyanogenmod/cmparts/livedisplay/DisplayColor.java
new file mode 100644
index 0000000..d112131
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/livedisplay/DisplayColor.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013-2015 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.cyanogenmod.cmparts.livedisplay;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.CustomDialogPreference;
+import org.cyanogenmod.cmparts.IntervalSeekBar;
+import org.cyanogenmod.cmparts.R;
+
+import cyanogenmod.hardware.LiveDisplayManager;
+
+/**
+ * Special preference type that allows configuration of Color settings
+ */
+public class DisplayColor extends CustomDialogPreference<AlertDialog> {
+    private static final String TAG = "ColorCalibration";
+
+    private final Context mContext;
+    private final LiveDisplayManager mLiveDisplay;
+
+    // These arrays must all match in length and order
+    private static final int[] SEEKBAR_ID = new int[] {
+        R.id.color_red_seekbar,
+        R.id.color_green_seekbar,
+        R.id.color_blue_seekbar
+    };
+
+    private static final int[] SEEKBAR_VALUE_ID = new int[] {
+        R.id.color_red_value,
+        R.id.color_green_value,
+        R.id.color_blue_value
+    };
+
+    private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length];
+
+    private final float[] mCurrentColors = new float[3];
+    private final float[] mOriginalColors = new float[3];
+
+    public DisplayColor(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mContext = context;
+        mLiveDisplay = LiveDisplayManager.getInstance(mContext);
+
+        setDialogLayoutResource(R.layout.display_color_calibration);
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener) {
+        super.onPrepareDialogBuilder(builder, listener);
+
+        builder.setNeutralButton(R.string.reset,
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+            }
+        });
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        System.arraycopy(mLiveDisplay.getColorAdjustment(), 0, mOriginalColors, 0, 3);
+        System.arraycopy(mOriginalColors, 0, mCurrentColors, 0, 3);
+
+        for (int i = 0; i < SEEKBAR_ID.length; i++) {
+            IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]);
+            TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]);
+            mSeekBars[i] = new ColorSeekBar(seekBar, value, i);
+            mSeekBars[i].mSeekBar.setMinimum(0.1f);
+            mSeekBars[i].mSeekBar.setMaximum(1.0f);
+
+            mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]);
+            int percent = Math.round(100F * mCurrentColors[i]);
+            value.setText(String.format("%d%%", percent));
+        }
+    }
+
+    @Override
+    protected boolean onDismissDialog(AlertDialog dialog, int which) {
+        // Can't use onPrepareDialogBuilder for this as we want the dialog
+        // to be kept open on click
+        if (which == DialogInterface.BUTTON_NEUTRAL) {
+            for (int i = 0; i < mSeekBars.length; i++) {
+                mSeekBars[i].mSeekBar.setProgressFloat(1.0f);
+                mCurrentColors[i] = 1.0f;
+            }
+            updateColors(mCurrentColors);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        updateColors(positiveResult ? mCurrentColors : mOriginalColors);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (getDialog() == null || !getDialog().isShowing()) {
+            return superState;
+        }
+
+        // Save the dialog state
+        final SavedState myState = new SavedState(superState);
+        myState.currentColors = mCurrentColors;
+        myState.originalColors = mOriginalColors;
+
+        // Restore the old state when the activity or dialog is being paused
+        updateColors(mOriginalColors);
+
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+
+        System.arraycopy(myState.originalColors, 0, mOriginalColors, 0, 3);
+        System.arraycopy(myState.currentColors, 0, mCurrentColors, 0, 3);
+        for (int i = 0; i < mSeekBars.length; i++) {
+            mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]);
+        }
+        updateColors(mCurrentColors);
+    }
+
+    private static class SavedState extends BaseSavedState {
+        float[] originalColors;
+        float[] currentColors;
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public SavedState(Parcel source) {
+            super(source);
+            originalColors = source.createFloatArray();
+            currentColors = source.createFloatArray();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeFloatArray(originalColors);
+            dest.writeFloatArray(currentColors);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    private void updateColors(float[] colors) {
+        mLiveDisplay.setColorAdjustment(colors);
+    }
+
+    private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener {
+        private int mIndex;
+        private final IntervalSeekBar mSeekBar;
+        private TextView mValue;
+
+        public ColorSeekBar(IntervalSeekBar seekBar, TextView value, int index) {
+            mSeekBar = seekBar;
+            mValue = value;
+            mIndex = index;
+
+            mSeekBar.setOnSeekBarChangeListener(this);
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            IntervalSeekBar isb = (IntervalSeekBar)seekBar;
+            float fp = isb.getProgressFloat();
+            if (fromUser) {
+                mCurrentColors[mIndex] = fp > 1.0f ? 1.0f : fp;
+                updateColors(mCurrentColors);
+            }
+
+            int percent = Math.round(100F * fp);
+            mValue.setText(String.format("%d%%", percent));
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            // Do nothing here
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Do nothing here
+        }
+    }
+}
diff --git a/src/org/cyanogenmod/cmparts/livedisplay/DisplayTemperature.java b/src/org/cyanogenmod/cmparts/livedisplay/DisplayTemperature.java
new file mode 100644
index 0000000..e272bb0
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/livedisplay/DisplayTemperature.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2015 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.cyanogenmod.cmparts.livedisplay;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.CustomDialogPreference;
+import org.cyanogenmod.cmparts.R;
+import org.cyanogenmod.internal.util.MathUtils;
+
+import cyanogenmod.hardware.LiveDisplayConfig;
+import cyanogenmod.hardware.LiveDisplayManager;
+
+/**
+ * Preference for selection of color temperature range for LiveDisplay
+ */
+public class DisplayTemperature extends CustomDialogPreference<AlertDialog> {
+    private static final String TAG = "DisplayTemperature";
+
+    private final Context mContext;
+
+    private ColorTemperatureSeekBar mDayTemperature;
+    private ColorTemperatureSeekBar mNightTemperature;
+
+    private int mOriginalDayTemperature;
+    private int mOriginalNightTemperature;
+
+    private final LiveDisplayManager mLiveDisplay;
+    private final LiveDisplayConfig mConfig;
+
+    private static final int STEP = 100;
+
+    public DisplayTemperature(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        mLiveDisplay = LiveDisplayManager.getInstance(mContext);
+        mConfig = mLiveDisplay.getConfig();
+
+        setDialogLayoutResource(R.layout.display_temperature);
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener) {
+        super.onPrepareDialogBuilder(builder, listener);
+
+        builder.setNeutralButton(R.string.reset,
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+            }
+        });
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mOriginalDayTemperature = mLiveDisplay.getDayColorTemperature();
+        mOriginalNightTemperature = mLiveDisplay.getNightColorTemperature();
+
+        SeekBar day = (SeekBar) view.findViewById(R.id.day_temperature_seekbar);
+        TextView dayText = (TextView) view.findViewById(R.id.day_temperature_value);
+        mDayTemperature = new ColorTemperatureSeekBar(day, dayText);
+
+        SeekBar night = (SeekBar) view.findViewById(R.id.night_temperature_seekbar);
+        TextView nightText = (TextView) view.findViewById(R.id.night_temperature_value);
+        mNightTemperature = new ColorTemperatureSeekBar(night, nightText);
+
+        mDayTemperature.setTemperature(mOriginalDayTemperature);
+        mNightTemperature.setTemperature(mOriginalNightTemperature);
+    }
+
+
+    @Override
+    protected boolean onDismissDialog(AlertDialog dialog, int which) {
+        // Can't use onPrepareDialogBuilder for this as we want the dialog
+        // to be kept open on click
+        if (which == DialogInterface.BUTTON_NEUTRAL) {
+            mDayTemperature.setTemperature(mConfig.getDefaultDayTemperature());
+            mNightTemperature.setTemperature(mConfig.getDefaultNightTemperature());
+            updateTemperature(true);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        updateTemperature(positiveResult);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (getDialog() == null || !getDialog().isShowing()) {
+            return superState;
+        }
+
+        // Save the dialog state
+        final SavedState myState = new SavedState(superState);
+        myState.originalDayTemperature = mOriginalDayTemperature;
+        myState.originalNightTemperature = mOriginalNightTemperature;
+        myState.currentDayTemperature = mDayTemperature.getTemperature();
+        myState.currentNightTemperature = mNightTemperature.getTemperature();
+
+        // Restore the old state when the activity or dialog is being paused
+        updateTemperature(false);
+
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+
+        mOriginalDayTemperature = myState.originalDayTemperature;
+        mOriginalNightTemperature = myState.originalNightTemperature;
+        mDayTemperature.setTemperature(myState.currentDayTemperature);
+        mNightTemperature.setTemperature(myState.currentNightTemperature);;
+
+        updateTemperature(true);
+    }
+
+    private static class SavedState extends BaseSavedState {
+        int originalDayTemperature;
+        int originalNightTemperature;
+        int currentDayTemperature;
+        int currentNightTemperature;
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public SavedState(Parcel source) {
+            super(source);
+            originalDayTemperature = source.readInt();
+            originalNightTemperature = source.readInt();
+            currentDayTemperature = source.readInt();
+            currentNightTemperature = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(originalDayTemperature);
+            dest.writeInt(originalNightTemperature);
+            dest.writeInt(currentDayTemperature);
+            dest.writeInt(currentNightTemperature);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    private void updateTemperature(boolean accept) {
+        int day = accept ? mDayTemperature.getTemperature() : mOriginalDayTemperature;
+        int night = accept ? mNightTemperature.getTemperature() : mOriginalNightTemperature;
+        callChangeListener(new Integer[] { day, night });
+
+        mLiveDisplay.setDayColorTemperature(day);
+        mLiveDisplay.setNightColorTemperature(night);
+    }
+
+    int roundUp(int value) {
+        return ((value + STEP / 2) / STEP) * STEP;
+    }
+
+    private class ColorTemperatureSeekBar implements SeekBar.OnSeekBarChangeListener {
+        private final SeekBar mSeekBar;
+        private final TextView mValue;
+
+        private final int mMin;
+        private final int mMax;
+
+        private final int mBalanceMin;
+        private final int mBalanceMax;
+
+        private final int mBarMax;
+
+        private final boolean mUseBalance;
+        private final double[] mBalanceCurve;
+
+        public ColorTemperatureSeekBar(SeekBar seekBar, TextView value) {
+            mSeekBar = seekBar;
+            mValue = value;
+            mMin = mConfig.getColorTemperatureRange().getLower();
+            mMax = mConfig.getColorTemperatureRange().getUpper();
+            mBalanceMin = mConfig.getColorBalanceRange().getLower();
+            mBalanceMax = mConfig.getColorBalanceRange().getUpper();
+            mUseBalance = mConfig.hasFeature(LiveDisplayManager.FEATURE_COLOR_BALANCE) &&
+                    ((mBalanceMin != 0) || (mBalanceMax != 0));
+
+            if (mUseBalance) {
+                mBalanceCurve = MathUtils.powerCurve(mMin, mConfig.getDefaultDayTemperature(), mMax);
+                mBarMax = mBalanceMax - mBalanceMin;
+            } else {
+                mBalanceCurve = null;
+                mBarMax = (mMax - mMin) / STEP;
+            }
+            mSeekBar.setMax(mBarMax);
+            mSeekBar.setOnSeekBarChangeListener(this);
+
+            // init text value
+            int p = mSeekBar.getProgress();
+            onProgressChanged(mSeekBar, p, false);
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            if (fromUser) {
+                updateTemperature(true);
+            }
+
+            int displayValue;
+            if (mUseBalance) {
+                displayValue = roundUp(Math.round((float)MathUtils.linearToPowerCurve(
+                        mBalanceCurve, (double)progress / (double)mBarMax)));
+            } else {
+                displayValue = progress * STEP + mMin;
+            }
+            Log.d(TAG, "onProgressChanged: progress=" + progress + " displayValue=" + displayValue);
+
+            mValue.setText(mContext.getResources().getString(
+                    R.string.live_display_color_temperature_label, displayValue));
+        }
+
+        public void setTemperature(int temperature) {
+            if (mUseBalance) {
+                double z = MathUtils.powerCurveToLinear(mBalanceCurve, (double)temperature);
+                mSeekBar.setProgress(Math.round((float)(z * (double)mBarMax)));
+                return;
+            }
+            int p = Math.max(temperature, mMin) - mMin;
+            mSeekBar.setProgress(Math.round((float) p / STEP));
+        }
+
+        public int getTemperature() {
+            if (mUseBalance) {
+                return Math.round((float)MathUtils.linearToPowerCurve(
+                        mBalanceCurve, (double)mSeekBar.getProgress() / (double)mBarMax));
+            }
+            return mSeekBar.getProgress() * STEP + mMin;
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            // Do nothing here
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Do nothing here
+        }
+    }
+}
diff --git a/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java b/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java
new file mode 100644
index 0000000..8e0d536
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2015 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.cyanogenmod.cmparts.livedisplay;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v14.preference.SwitchPreference;
+import android.provider.SearchIndexableResource;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.cyanogenmod.cmparts.R;
+import org.cyanogenmod.cmparts.SettingsPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cyanogenmod.hardware.CMHardwareManager;
+import cyanogenmod.hardware.DisplayMode;
+import cyanogenmod.hardware.LiveDisplayConfig;
+import cyanogenmod.hardware.LiveDisplayManager;
+import cyanogenmod.providers.CMSettings;
+
+import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_CABC;
+import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT;
+import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_COLOR_ENHANCEMENT;
+import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_DISPLAY_MODES;
+import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT;
+import static cyanogenmod.hardware.LiveDisplayManager.MODE_OFF;
+import static cyanogenmod.hardware.LiveDisplayManager.MODE_OUTDOOR;
+
+public class LiveDisplay extends SettingsPreferenceFragment implements
+        Preference.OnPreferenceChangeListener {
+
+    private static final String TAG = "LiveDisplay";
+
+    private static final String KEY_CATEGORY_LIVE_DISPLAY = "live_display_options";
+    private static final String KEY_CATEGORY_ADVANCED = "advanced";
+
+    private static final String KEY_LIVE_DISPLAY = "live_display";
+    private static final String KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE =
+            "display_auto_outdoor_mode";
+    private static final String KEY_LIVE_DISPLAY_LOW_POWER = "display_low_power";
+    private static final String KEY_LIVE_DISPLAY_COLOR_ENHANCE = "display_color_enhance";
+    private static final String KEY_LIVE_DISPLAY_TEMPERATURE = "live_display_color_temperature";
+
+    private static final String KEY_DISPLAY_COLOR = "color_calibration";
+    private static final String KEY_PICTURE_ADJUSTMENT = "picture_adjustment";
+
+    private static final String KEY_LIVE_DISPLAY_COLOR_PROFILE = "live_display_color_profile";
+
+    private final Handler mHandler = new Handler();
+    private final SettingsObserver mObserver = new SettingsObserver();
+
+    private ListPreference mLiveDisplay;
+
+    private SwitchPreference mColorEnhancement;
+    private SwitchPreference mLowPower;
+    private SwitchPreference mOutdoorMode;
+
+    private PictureAdjustment mPictureAdjustment;
+    private DisplayTemperature mDisplayTemperature;
+    private DisplayColor mDisplayColor;
+
+    private ListPreference mColorProfile;
+    private String[] mColorProfileSummaries;
+
+    private String[] mModeEntries;
+    private String[] mModeValues;
+    private String[] mModeSummaries;
+
+    private boolean mHasDisplayModes = false;
+
+    private LiveDisplayManager mLiveDisplayManager;
+    private LiveDisplayConfig mConfig;
+
+    private CMHardwareManager mHardware;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Resources res = getResources();
+
+        mHardware = CMHardwareManager.getInstance(getActivity());
+        mLiveDisplayManager = LiveDisplayManager.getInstance(getActivity());
+        mConfig = mLiveDisplayManager.getConfig();
+
+        addPreferencesFromResource(R.xml.livedisplay);
+
+        PreferenceCategory liveDisplayPrefs = (PreferenceCategory)
+                findPreference(KEY_CATEGORY_LIVE_DISPLAY);
+        PreferenceCategory advancedPrefs = (PreferenceCategory)
+                findPreference(KEY_CATEGORY_ADVANCED);
+
+        int adaptiveMode = mLiveDisplayManager.getMode();
+
+        mLiveDisplay = (ListPreference) findPreference(KEY_LIVE_DISPLAY);
+        mLiveDisplay.setValue(String.valueOf(adaptiveMode));
+
+        mModeEntries = res.getStringArray(
+                org.cyanogenmod.platform.internal.R.array.live_display_entries);
+        mModeValues = res.getStringArray(
+                org.cyanogenmod.platform.internal.R.array.live_display_values);
+        mModeSummaries = res.getStringArray(
+                org.cyanogenmod.platform.internal.R.array.live_display_summaries);
+
+        // Remove outdoor mode from lists if there is no support
+        if (!mConfig.hasFeature(LiveDisplayManager.MODE_OUTDOOR)) {
+            int idx = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OUTDOOR));
+            String[] entriesTemp = new String[mModeEntries.length - 1];
+            String[] valuesTemp = new String[mModeValues.length - 1];
+            String[] summariesTemp = new String[mModeSummaries.length - 1];
+            int j = 0;
+            for (int i = 0; i < mModeEntries.length; i++) {
+                if (i == idx) {
+                    continue;
+                }
+                entriesTemp[j] = mModeEntries[i];
+                valuesTemp[j] = mModeValues[i];
+                summariesTemp[j] = mModeSummaries[i];
+                j++;
+            }
+            mModeEntries = entriesTemp;
+            mModeValues = valuesTemp;
+            mModeSummaries = summariesTemp;
+        }
+
+        mLiveDisplay.setEntries(mModeEntries);
+        mLiveDisplay.setEntryValues(mModeValues);
+        mLiveDisplay.setOnPreferenceChangeListener(this);
+
+        mDisplayTemperature = (DisplayTemperature) findPreference(KEY_LIVE_DISPLAY_TEMPERATURE);
+
+        mColorProfile = (ListPreference) findPreference(KEY_LIVE_DISPLAY_COLOR_PROFILE);
+        if (liveDisplayPrefs != null && mColorProfile != null
+                && (!mConfig.hasFeature(FEATURE_DISPLAY_MODES) || !updateDisplayModes())) {
+            liveDisplayPrefs.removePreference(mColorProfile);
+        } else {
+            mHasDisplayModes = true;
+            mColorProfile.setOnPreferenceChangeListener(this);
+        }
+
+        mOutdoorMode = (SwitchPreference) findPreference(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE);
+        if (liveDisplayPrefs != null && mOutdoorMode != null
+                && !mConfig.hasFeature(MODE_OUTDOOR)) {
+            liveDisplayPrefs.removePreference(mOutdoorMode);
+            mOutdoorMode = null;
+        }
+
+        mLowPower = (SwitchPreference) findPreference(KEY_LIVE_DISPLAY_LOW_POWER);
+        if (advancedPrefs != null && mLowPower != null
+                && !mConfig.hasFeature(FEATURE_CABC)) {
+            advancedPrefs.removePreference(mLowPower);
+            mLowPower = null;
+        }
+
+        mColorEnhancement = (SwitchPreference) findPreference(KEY_LIVE_DISPLAY_COLOR_ENHANCE);
+        if (advancedPrefs != null && mColorEnhancement != null
+                && !mConfig.hasFeature(FEATURE_COLOR_ENHANCEMENT)) {
+            advancedPrefs.removePreference(mColorEnhancement);
+            mColorEnhancement = null;
+        }
+
+        mPictureAdjustment = (PictureAdjustment) findPreference(KEY_PICTURE_ADJUSTMENT);
+        if (advancedPrefs != null && mPictureAdjustment != null &&
+                    !mConfig.hasFeature(LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT)) {
+            advancedPrefs.removePreference(mPictureAdjustment);
+            mPictureAdjustment = null;
+        }
+
+        mDisplayColor = (DisplayColor) findPreference(KEY_DISPLAY_COLOR);
+        if (advancedPrefs != null && mDisplayColor != null &&
+                !mConfig.hasFeature(LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT)) {
+            advancedPrefs.removePreference(mDisplayColor);
+            mDisplayColor = null;
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateModeSummary();
+        updateTemperatureSummary();
+        updateColorProfileSummary(null);
+        mObserver.register(true);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mObserver.register(false);
+    }
+
+    private String getStringForResourceName(String resourceName, String defaultValue) {
+        Resources res = getResources();
+        int resId = res.getIdentifier(resourceName, "string", "org.cyanogenmod.livedisplay");
+        if (resId <= 0) {
+            Log.e(TAG, "No resource found for " + resourceName);
+            return defaultValue;
+        } else {
+            return res.getString(resId);
+        }
+    }
+
+    private boolean updateDisplayModes() {
+        final DisplayMode[] modes = mHardware.getDisplayModes();
+        if (modes == null || modes.length == 0) {
+            return false;
+        }
+
+        final DisplayMode cur = mHardware.getCurrentDisplayMode() != null
+                ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode();
+        int curId = -1;
+        String[] entries = new String[modes.length];
+        String[] values = new String[modes.length];
+        mColorProfileSummaries = new String[modes.length];
+        for (int i = 0; i < modes.length; i++) {
+            values[i] = String.valueOf(modes[i].id);
+            String name = modes[i].name.toLowerCase().replace(" ", "_");
+            String nameRes = String.format("live_display_color_profile_%s_title", name);
+            entries[i] = getStringForResourceName(nameRes, modes[i].name);
+
+            // Populate summary
+            String summaryRes = String.format("live_display_color_profile_%s_summary", name);
+            String summary = getStringForResourceName(summaryRes, null);
+            if (summary != null) {
+                summary = String.format("%s - %s", entries[i], summary);
+            }
+            mColorProfileSummaries[i] = summary;
+
+            if (cur != null && modes[i].id == cur.id) {
+                curId = cur.id;
+            }
+        }
+        mColorProfile.setEntries(entries);
+        mColorProfile.setEntryValues(values);
+        if (curId >= 0) {
+            mColorProfile.setValue(String.valueOf(curId));
+        }
+
+        return true;
+    }
+
+    private void updateColorProfileSummary(String value) {
+        if (!mHasDisplayModes) {
+            return;
+        }
+
+        if (value == null) {
+            DisplayMode cur = mHardware.getCurrentDisplayMode() != null
+                    ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode();
+            if (cur != null && cur.id >= 0) {
+                value = String.valueOf(cur.id);
+            }
+        }
+
+        int idx = mColorProfile.findIndexOfValue(value);
+        if (idx < 0) {
+            Log.e(TAG, "No summary resource found for profile " + value);
+            mColorProfile.setSummary(null);
+            return;
+        }
+
+        mColorProfile.setValue(value);
+        mColorProfile.setSummary(mColorProfileSummaries[idx]);
+    }
+
+    private void updateModeSummary() {
+        int mode = mLiveDisplayManager.getMode();
+
+        int index = ArrayUtils.indexOf(mModeValues, String.valueOf(mode));
+        if (index < 0) {
+            index = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OFF));
+        }
+
+        mLiveDisplay.setSummary(mModeSummaries[index]);
+        mLiveDisplay.setValue(String.valueOf(mode));
+
+        if (mDisplayTemperature != null) {
+            mDisplayTemperature.setEnabled(mode != MODE_OFF);
+        }
+        if (mOutdoorMode != null) {
+            mOutdoorMode.setEnabled(mode != MODE_OFF);
+        }
+    }
+
+    private void updateTemperatureSummary() {
+        int day = mLiveDisplayManager.getDayColorTemperature();
+        int night = mLiveDisplayManager.getNightColorTemperature();
+
+        mDisplayTemperature.setSummary(getResources().getString(
+                R.string.live_display_color_temperature_summary,
+                mDisplayTemperature.roundUp(day),
+                mDisplayTemperature.roundUp(night)));
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (preference == mLiveDisplay) {
+            mLiveDisplayManager.setMode(Integer.valueOf((String)objValue));
+        } else if (preference == mColorProfile) {
+            int id = Integer.valueOf((String)objValue);
+            Log.i("LiveDisplay", "Setting mode: " + id);
+            for (DisplayMode mode : mHardware.getDisplayModes()) {
+                if (mode.id == id) {
+                    mHardware.setDisplayMode(mode, true);
+                    updateColorProfileSummary((String)objValue);
+                    break;
+                }
+            }
+        }
+        return true;
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri DISPLAY_TEMPERATURE_DAY_URI =
+                CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_DAY);
+        private final Uri DISPLAY_TEMPERATURE_NIGHT_URI =
+                CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_NIGHT);
+        private final Uri DISPLAY_TEMPERATURE_MODE_URI =
+                CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_MODE);
+
+        public SettingsObserver() {
+            super(mHandler);
+        }
+
+        public void register(boolean register) {
+            final ContentResolver cr = getContentResolver();
+            if (register) {
+                cr.registerContentObserver(DISPLAY_TEMPERATURE_DAY_URI, false, this, UserHandle.USER_ALL);
+                cr.registerContentObserver(DISPLAY_TEMPERATURE_NIGHT_URI, false, this, UserHandle.USER_ALL);
+                cr.registerContentObserver(DISPLAY_TEMPERATURE_MODE_URI, false, this, UserHandle.USER_ALL);
+            } else {
+                cr.unregisterContentObserver(this);
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange,  uri);
+            updateModeSummary();
+            updateTemperatureSummary();
+        }
+    }
+
+    /*
+     * Disabled until search query is implemented
+     *
+    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.livedisplay;
+            result.add(sir);
+
+            return result;
+        }
+
+        @Override
+        public List<String> getNonIndexableKeys(Context context) {
+            final CMHardwareManager hardware = CMHardwareManager.getInstance(context);
+            final LiveDisplayConfig config = LiveDisplayManager.getInstance(context).getConfig();
+
+            ArrayList<String> result = new ArrayList<String>();
+            if (!hardware.isSupported(FEATURE_DISPLAY_MODES)) {
+                result.add(KEY_LIVE_DISPLAY_COLOR_PROFILE);
+            }
+            if (!config.hasFeature(MODE_OUTDOOR)) {
+                result.add(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE);
+            }
+            if (!config.hasFeature(FEATURE_COLOR_ENHANCEMENT)) {
+                result.add(KEY_LIVE_DISPLAY_COLOR_ENHANCE);
+            }
+            if (!config.hasFeature(FEATURE_CABC)) {
+                result.add(KEY_LIVE_DISPLAY_LOW_POWER);
+            }
+            if (!config.hasFeature(FEATURE_COLOR_ADJUSTMENT)) {
+                result.add(KEY_DISPLAY_COLOR);
+            }
+            if (!config.hasFeature(FEATURE_PICTURE_ADJUSTMENT)) {
+                result.add(KEY_PICTURE_ADJUSTMENT);
+            }
+            return result;
+        }
+    };
+    */
+}
diff --git a/src/org/cyanogenmod/cmparts/livedisplay/PictureAdjustment.java b/src/org/cyanogenmod/cmparts/livedisplay/PictureAdjustment.java
new file mode 100644
index 0000000..cb79840
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/livedisplay/PictureAdjustment.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2016 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.cyanogenmod.cmparts.livedisplay;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Range;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.CustomDialogPreference;
+import org.cyanogenmod.cmparts.IntervalSeekBar;
+import org.cyanogenmod.cmparts.R;
+
+import java.util.List;
+
+import cyanogenmod.hardware.HSIC;
+import cyanogenmod.hardware.LiveDisplayManager;
+
+/**
+ * Special preference type that allows configuration of Color settings
+ */
+public class PictureAdjustment extends CustomDialogPreference<AlertDialog> {
+    private static final String TAG = "PictureAdjustment";
+
+    private final Context mContext;
+    private final LiveDisplayManager mLiveDisplay;
+    private final List<Range<Float>> mRanges;
+
+    // These arrays must all match in length and order
+    private static final int[] SEEKBAR_ID = new int[] {
+        R.id.adj_hue_seekbar,
+        R.id.adj_saturation_seekbar,
+        R.id.adj_intensity_seekbar,
+        R.id.adj_contrast_seekbar
+    };
+
+    private static final int[] SEEKBAR_VALUE_ID = new int[] {
+        R.id.adj_hue_value,
+        R.id.adj_saturation_value,
+        R.id.adj_intensity_value,
+        R.id.adj_contrast_value
+    };
+
+    private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length];
+
+    private final float[] mCurrentAdj = new float[5];
+    private final float[] mOriginalAdj = new float[5];
+
+    public PictureAdjustment(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mContext = context;
+        mLiveDisplay = LiveDisplayManager.getInstance(mContext);
+        mRanges = mLiveDisplay.getConfig().getPictureAdjustmentRanges();
+
+        setDialogLayoutResource(R.layout.display_picture_adjustment);
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener) {
+        super.onPrepareDialogBuilder(builder, listener);
+
+        builder.setNeutralButton(R.string.reset,
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+            }
+        });
+    }
+
+    private void updateBars() {
+        for (int i = 0; i < SEEKBAR_ID.length; i++) {
+            mSeekBars[i].setValue(mCurrentAdj[i]);
+        }
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        System.arraycopy(mLiveDisplay.getPictureAdjustment().toFloatArray(), 0, mOriginalAdj, 0, 5);
+        System.arraycopy(mOriginalAdj, 0, mCurrentAdj, 0, 5);
+
+        for (int i = 0; i < SEEKBAR_ID.length; i++) {
+            IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]);
+            TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]);
+            final Range<Float> range = mRanges.get(i);
+            mSeekBars[i] = new ColorSeekBar(seekBar, range, value, i);
+        }
+        updateBars();
+    }
+
+    @Override
+    protected boolean onDismissDialog(AlertDialog dialog, int which) {
+        // Can't use onPrepareDialogBuilder for this as we want the dialog
+        // to be kept open on click
+        if (which == DialogInterface.BUTTON_NEUTRAL) {
+            System.arraycopy(mLiveDisplay.getDefaultPictureAdjustment().toFloatArray(),
+                    0, mCurrentAdj, 0, 5);
+            updateBars();
+            updateAdjustment(mCurrentAdj);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        updateAdjustment(positiveResult ? mCurrentAdj : mOriginalAdj);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (getDialog() == null || !getDialog().isShowing()) {
+            return superState;
+        }
+
+        // Save the dialog state
+        final SavedState myState = new SavedState(superState);
+        myState.currentAdj = mCurrentAdj;
+        myState.originalAdj = mOriginalAdj;
+
+        // Restore the old state when the activity or dialog is being paused
+        updateAdjustment(mOriginalAdj);
+
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+
+        System.arraycopy(myState.originalAdj, 0, mOriginalAdj, 0, 5);
+        System.arraycopy(myState.currentAdj, 0, mCurrentAdj, 0, 5);
+
+        updateBars();
+        updateAdjustment(mCurrentAdj);
+    }
+
+    private static class SavedState extends BaseSavedState {
+        float[] originalAdj;
+        float[] currentAdj;
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public SavedState(Parcel source) {
+            super(source);
+            originalAdj = source.createFloatArray();
+            currentAdj = source.createFloatArray();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeFloatArray(originalAdj);
+            dest.writeFloatArray(currentAdj);
+        }
+
+        public static final Creator<SavedState> CREATOR =
+                new Creator<PictureAdjustment.SavedState>() {
+
+            public PictureAdjustment.SavedState createFromParcel(Parcel in) {
+                return new PictureAdjustment.SavedState(in);
+            }
+
+            public PictureAdjustment.SavedState[] newArray(int size) {
+                return new PictureAdjustment.SavedState[size];
+            }
+        };
+    }
+
+    private void updateAdjustment(final float[] adjustment) {
+        mLiveDisplay.setPictureAdjustment(HSIC.fromFloatArray(adjustment));
+    }
+
+    private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener {
+        private int mIndex;
+        private final IntervalSeekBar mSeekBar;
+        private TextView mValue;
+        private Range<Float> mRange;
+
+        public ColorSeekBar(IntervalSeekBar seekBar, Range<Float> range, TextView value, int index) {
+            mSeekBar = seekBar;
+            mValue = value;
+            mIndex = index;
+            mRange = range;
+            mSeekBar.setMinimum(range.getLower());
+            mSeekBar.setMaximum(range.getUpper());
+
+            mSeekBar.setOnSeekBarChangeListener(this);
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            IntervalSeekBar isb = (IntervalSeekBar)seekBar;
+            float fp = isb.getProgressFloat();
+            if (fromUser) {
+                mCurrentAdj[mIndex] = mRanges.get(mIndex).clamp(fp);
+                updateAdjustment(mCurrentAdj);
+            }
+            mValue.setText(getLabel(mCurrentAdj[mIndex]));
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            // Do nothing here
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Do nothing here
+        }
+
+        private String getLabel(float value) {
+            if (mRange.getUpper() == 1.0f) {
+                return String.format("%d%%", Math.round(100F * value));
+            }
+            return String.format("%d", Math.round(value));
+        }
+
+        public void setValue(float value) {
+            mSeekBar.setProgressFloat(value);
+            mValue.setText(getLabel(value));
+        }
+    }
+}