SysUI Tuner: Night mode v3

TwilightService:
 - Add support for locking on/off twilight globally
 - Change twilight state to have float amount rather than
   having clients calculate it using the sunrise/set values
 - State controlled through secure setting
 - Override mode which locks to a state for a couple hours then
   resets
 - Add broadcast for SysUI to listen to

Brightness/Power:
 - Updates to handle TwilightService changes
 - Added secure setting to control whether or not to use
   twilight when calculating brightness.

Tuner:
 - UI Overhall
 - NightModeController will set the values of the custom color matrix
   taking into account the current twilight service state and any
   custom calibration set by the user.
 - Probably other stuff.

Change-Id: I213f1f76a143e711c507b2ba7d784f581bfc32b4
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index aa697ea..13ba6cc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -211,6 +211,9 @@
         public int dozeScreenBrightness;
         public int dozeScreenState;
 
+        // If true, use twilight to affect the brightness.
+        public boolean useTwilight;
+
         public DisplayPowerRequest() {
             policy = POLICY_BRIGHT;
             useProximitySensor = false;
@@ -242,6 +245,7 @@
             boostScreenBrightness = other.boostScreenBrightness;
             dozeScreenBrightness = other.dozeScreenBrightness;
             dozeScreenState = other.dozeScreenState;
+            useTwilight = other.useTwilight;
         }
 
         @Override
@@ -262,7 +266,8 @@
                     && lowPowerMode == other.lowPowerMode
                     && boostScreenBrightness == other.boostScreenBrightness
                     && dozeScreenBrightness == other.dozeScreenBrightness
-                    && dozeScreenState == other.dozeScreenState;
+                    && dozeScreenState == other.dozeScreenState
+                    && useTwilight == other.useTwilight;
         }
 
         @Override
@@ -282,7 +287,8 @@
                     + ", lowPowerMode=" + lowPowerMode
                     + ", boostScreenBrightness=" + boostScreenBrightness
                     + ", dozeScreenBrightness=" + dozeScreenBrightness
-                    + ", dozeScreenState=" + Display.stateToString(dozeScreenState);
+                    + ", dozeScreenState=" + Display.stateToString(dozeScreenState)
+                    + ", useTwilight=" + useTwilight;
         }
 
         public static String policyToString(int policy) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ab2b00..46048e4 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5922,6 +5922,52 @@
                 "camera_double_tap_power_gesture_disabled";
 
         /**
+
+        /**
+         * Behavior of twilight on the device.
+         * One of {@link #TWILIGHT_MODE_LOCKED_OFF}, {@link #TWILIGHT_MODE_LOCKED_ON}
+         * or {@link #TWILIGHT_MODE_AUTO}.
+         * @hide
+         */
+        public static final String TWILIGHT_MODE = "twilight_mode";
+
+        /**
+         * Twilight mode always off.
+         * @hide
+         */
+        public static final int TWILIGHT_MODE_LOCKED_OFF = 0;
+
+        /**
+         * Twilight mode always on.
+         * @hide
+         */
+        public static final int TWILIGHT_MODE_LOCKED_ON = 1;
+
+        /**
+         * Twilight mode auto.
+         * @hide
+         */
+        public static final int TWILIGHT_MODE_AUTO = 2;
+
+        /**
+         * Twilight mode auto, temporarily overriden to on.
+         * @hide
+         */
+        public static final int TWILIGHT_MODE_AUTO_OVERRIDE_OFF = 3;
+
+        /**
+         * Twilight mode auto, temporarily overriden to off.
+         * @hide
+         */
+        public static final int TWILIGHT_MODE_AUTO_OVERRIDE_ON = 4;
+
+        /**
+         * Whether brightness should automatically adjust based on twilight state.
+         * @hide
+         */
+        public static final String BRIGHTNESS_USE_TWILIGHT = "brightness_use_twilight";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9b989b9..bb31a020 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -452,6 +452,8 @@
     <protected-broadcast android:name="android.bluetooth.input.profile.action.HANDSHAKE" />
     <protected-broadcast android:name="android.bluetooth.input.profile.action.REPORT" />
 
+    <protected-broadcast android:name="android.intent.action.TWILIGHT_CHANGED" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/packages/SystemUI/res/drawable/ic_colorize.xml b/packages/SystemUI/res/drawable/ic_night_mode.xml
similarity index 95%
rename from packages/SystemUI/res/drawable/ic_colorize.xml
rename to packages/SystemUI/res/drawable/ic_night_mode.xml
index 79fd6d9..caa7a47 100644
--- a/packages/SystemUI/res/drawable/ic_colorize.xml
+++ b/packages/SystemUI/res/drawable/ic_night_mode.xml
@@ -1,5 +1,5 @@
 <!--
-    Copyright (C) 2015 The Android Open Source Project
+    Copyright (C) 2016 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.
diff --git a/packages/SystemUI/res/drawable/ic_colorize.xml b/packages/SystemUI/res/drawable/ic_night_mode_disabled.xml
similarity index 91%
copy from packages/SystemUI/res/drawable/ic_colorize.xml
copy to packages/SystemUI/res/drawable/ic_night_mode_disabled.xml
index 79fd6d9..010815a 100644
--- a/packages/SystemUI/res/drawable/ic_colorize.xml
+++ b/packages/SystemUI/res/drawable/ic_night_mode_disabled.xml
@@ -1,5 +1,5 @@
 <!--
-    Copyright (C) 2015 The Android Open Source Project
+    Copyright (C) 2016 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.
@@ -19,6 +19,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="#4DFFFFFF"
         android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0.0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3.0,16.25L3.0,21.0l4.75,0.0l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,0.0 0.4,-1.0 0.01,-1.42zM6.92,19.0L5.0,17.08l8.06,-8.06 1.92,1.92L6.92,19.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/layout/preference_matrix.xml b/packages/SystemUI/res/layout/calibrate_sliders.xml
similarity index 93%
rename from packages/SystemUI/res/layout/preference_matrix.xml
rename to packages/SystemUI/res/layout/calibrate_sliders.xml
index 1f6066e..0dec8a1 100644
--- a/packages/SystemUI/res/layout/preference_matrix.xml
+++ b/packages/SystemUI/res/layout/calibrate_sliders.xml
@@ -94,11 +94,4 @@
             android:layout_weight="1" />
     </LinearLayout>
 
-    <Button
-        android:id="@+id/apply"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="end"
-        android:text="@string/color_apply" />
-
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/color_matrix_settings.xml b/packages/SystemUI/res/layout/night_mode_settings.xml
similarity index 100%
rename from packages/SystemUI/res/layout/color_matrix_settings.xml
rename to packages/SystemUI/res/layout/night_mode_settings.xml
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fa5b1a9..fe3391c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1240,29 +1240,43 @@
     <!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
     <string name="notification_done">Done</string>
 
-    <!-- Label for no color transform [CHAR LIMIT=30] -->
-    <string name="color_matrix_none">Normal colors</string>
+    <!-- SysUI Tuner: Color and appearance screen title [CHAR LIMIT=50] -->
+    <string name="color_and_appearance">Color and appearance</string>
 
-    <!-- Label for night color transform [CHAR LIMIT=30] -->
-    <string name="color_matrix_night">Night colors</string>
+    <!-- SysUI Tuner: Name of the night mode feature [CHAR LIMIT=30] -->
+    <string name="night_mode">Night mode</string>
 
-    <!-- Label for custom color transform [CHAR LIMIT=30] -->
-    <string name="color_matrix_custom">Custom colors</string>
+    <!-- SysUI Tuner: Name of calibrate display dialog [CHAR LIMIT=30] -->
+    <string name="calibrate_display">Calibrate display</string>
 
-    <!-- Label for auto color transforms [CHAR LIMIT=30] -->
-    <string name="color_matrix_auto">Auto</string>
+    <!-- SysUI Tuner: Summary of night mode when its on [CHAR LIMIT=NONE] -->
+    <string name="night_mode_on">On</string>
 
-    <!-- Label for unknown color transform [CHAR LIMIT=30] -->
-    <string name="color_matrix_unknown">Unknown colors</string>
+    <!-- SysUI Tuner: Summary of night mode when its off [CHAR LIMIT=NONE] -->
+    <string name="night_mode_off">Off</string>
 
-    <!-- Title for color transform [CHAR LIMIT=30] -->
-    <string name="color_transform">Color modification</string>
+    <!-- SysUI Tuner: Label for switch to turn on night mode automatically [CHAR LIMIT=50] -->
+    <string name="turn_on_automatically">Turn on automatically</string>
 
-    <!-- Title for setting to show Quick Settings tile [CHAR LIMIT=60] -->
-    <string name="color_matrix_show_qs">Show Quick Settings tile</string>
+    <!-- SysUI Tuner: Summary for switch to turn on night mode automatically [CHAR LIMIT=NONE] -->
+    <string name="turn_on_auto_summary">Switch into Night Mode as appropriate for location and time of day</string>
 
-    <!-- Title for switch to enable custom color transform [CHAR LIMIT=60] -->
-    <string name="color_enable_custom">Enable custom transform</string>
+    <!-- SysUI Tuner: Label for section controlling what night mode does [CHAR LIMIT=60] -->
+    <string name="when_night_mode_on">When Night Mode is on</string>
+
+    <!-- SysUI Tuner: Switch controlling whether dark theme is turned on with night mode [CHAR LIMIT=45] -->
+    <string name="use_dark_theme">Use dark theme for Android OS</string>
+
+    <!-- SysUI Tuner: Switch controlling whether tint is changed with night mode [CHAR LIMIT=45] -->
+    <string name="adjust_tint">Adjust tint</string>
+
+    <!-- SysUI Tuner: Switch controlling whether brightness is changed with night mode [CHAR LIMIT=45] -->
+    <string name="adjust_brightness">Adjust brightness</string>
+
+    <!-- SysUI Tuner: Disclaimer about using dark theme with night mode [CHAR LIMIT=NONE] -->
+    <string name="night_mode_disclaimer">The dark theme is applied to
+        core areas of Android OS that are normally displayed in a light theme,
+        such as Settings and notifications.</string>
 
     <!-- Button to apply settings [CHAR LIMIT=30] -->
     <string name="color_apply">Apply</string>
@@ -1344,6 +1358,9 @@
     <!-- Label for feature switch [CHAR LIMIT=30] -->
     <string name="switch_bar_on">On</string>
 
+    <!-- Label for feature switch [CHAR LIMIT=30] -->
+    <string name="switch_bar_off">Off</string>
+
     <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
     <string name="nav_bar">Navigation bar</string>
 
diff --git a/packages/SystemUI/res/xml/color_and_appearance.xml b/packages/SystemUI/res/xml/color_and_appearance.xml
new file mode 100644
index 0000000..21f890e
--- /dev/null
+++ b/packages/SystemUI/res/xml/color_and_appearance.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:title="@string/color_and_appearance">
+
+    <Preference
+        android:key="night_mode"
+        android:title="@string/night_mode"
+        android:fragment="com.android.systemui.tuner.NightModeFragment" />
+
+    <com.android.systemui.tuner.CalibratePreference
+        android:key="calibrate"
+        android:title="@string/calibrate_display" />
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/night_mode.xml b/packages/SystemUI/res/xml/night_mode.xml
new file mode 100644
index 0000000..d5f5333
--- /dev/null
+++ b/packages/SystemUI/res/xml/night_mode.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:title="@string/night_mode">
+
+    <SwitchPreference
+        android:key="auto"
+        android:title="@string/turn_on_automatically"
+        android:summary="@string/turn_on_auto_summary" />
+
+    <PreferenceCategory
+        android:title="@string/when_night_mode_on">
+
+        <SwitchPreference
+            android:key="dark_theme"
+            android:title="@string/use_dark_theme" />
+
+        <SwitchPreference
+            android:key="adjust_tint"
+            android:title="@string/adjust_tint" />
+
+        <SwitchPreference
+            android:key="adjust_brightness"
+            android:title="@string/adjust_brightness" />
+
+    </PreferenceCategory>
+
+    <Preference
+        android:selectable="false"
+        android:summary="@string/night_mode_disclaimer" />
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 39281bc..800762e 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -142,8 +142,8 @@
 
     <Preference
         android:key="color_transform"
-        android:title="@string/color_transform"
-        android:fragment="com.android.systemui.tuner.ColorMatrixFragment" />
+        android:title="@string/color_and_appearance"
+        android:fragment="com.android.systemui.tuner.ColorAndAppearanceFragment" />
 
     <PreferenceScreen
         android:key="volume_and_do_not_disturb"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index e363b76..6b94195 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DisplayController;
+import com.android.systemui.statusbar.policy.NightModeController;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -400,7 +400,7 @@
         UserInfoController getUserInfoController();
         BatteryController getBatteryController();
         TileServices getTileServices();
-        DisplayController getDisplayController();
+        NightModeController getNightModeController();
         void removeTile(String tileSpec);
         ManagedProfileController getManagedProfileController();
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 5c34ceb..45f2d75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -53,9 +53,9 @@
     }
 
     private void addSystemTiles(QSTileHost host) {
-        boolean hasColorMod = host.getDisplayController().isEnabled();
+        boolean hasColorMod = host.getNightModeController().isEnabled();
         String possible = mContext.getString(R.string.quick_settings_tiles_default)
-                + ",hotspot,inversion,saver,work,cast" + (hasColorMod ? ",colors" : "");
+                + ",hotspot,inversion,saver,work,cast" + (hasColorMod ? ",night" : "");
         String[] possibleTiles = possible.split(",");
         final Handler qsHandler = new Handler(host.getLooper());
         final Handler mainHandler = new Handler(Looper.getMainLooper());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 2af2009..80afb9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -57,7 +57,7 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DisplayController;
+import com.android.systemui.statusbar.policy.NightModeController;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -68,7 +68,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.ColorMatrixTile;
+import com.android.systemui.tuner.NightModeTile;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -108,7 +108,7 @@
     private final TileServices mServices;
 
     private final List<Callback> mCallbacks = new ArrayList<>();
-    private final DisplayController mDisplayController;
+    private final NightModeController mNightModeController;
     private final AutoTileManager mAutoTiles;
     private final ManagedProfileController mProfileController;
     private View mHeader;
@@ -137,7 +137,7 @@
         mSecurity = security;
         mBattery = battery;
         mIconController = iconController;
-        mDisplayController = new DisplayController(mContext);
+        mNightModeController = new NightModeController(mContext, true);
         mProfileController = new ManagedProfileController(this);
 
         final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
@@ -292,8 +292,8 @@
         return mIconController;
     }
 
-    public DisplayController getDisplayController() {
-        return mDisplayController;
+    public NightModeController getNightModeController() {
+        return mNightModeController;
     }
 
     public ManagedProfileController getManagedProfileController() {
@@ -422,8 +422,8 @@
         else if (tileSpec.equals("user")) return new UserTile(this);
         else if (tileSpec.equals("battery")) return new BatteryTile(this);
         else if (tileSpec.equals("saver")) return new DataSaverTile(this);
-        else if (tileSpec.equals(ColorMatrixTile.COLOR_MATRIX_SPEC))
-            return new ColorMatrixTile(this);
+        else if (tileSpec.equals(NightModeTile.NIGHT_MODE_SPEC))
+            return new NightModeTile(this);
         // Intent tiles.
         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
         else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java
deleted file mode 100644
index d47050c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import libcore.util.Objects;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.provider.Settings;
-
-import com.android.systemui.R;
-import com.android.systemui.tuner.TunerService;
-
-import java.util.ArrayList;
-
-public class DisplayController implements TunerService.Tunable {
-
-    public static final String COLOR_MATRIX_CUSTOM_ENABLED = "tuner_color_custom_enabled";
-    public static final String COLOR_MATRIX_CUSTOM_VALUES = "tuner_color_custom_values";
-
-    public static final String COLOR_STATE = "sysui_color_matrix_state";
-
-    public static final int COLOR_STATE_DISABLED = 0;
-    public static final int COLOR_STATE_ENABLED = 1;
-    public static final int COLOR_STATE_AUTO = 2;
-
-    public static final String AUTO_STRING = "auto_mode";
-    public static final String NONE_STRING = "none";
-
-    public static final int AUTO_INDEX = 2;
-    public static final int CUSTOM_INDEX = 3;
-
-    // Night mode ~= 3400 K
-    private static final float[] NIGHT_VALUES = new float[] {
-        1, 0,     0,     0,
-        0, .754f, 0,     0,
-        0, 0,     .516f, 0,
-        0, 0,     0,     1,
-    };
-    public static final float[] IDENTITY_MATRIX = new float[] {
-        1, 0, 0, 0,
-        0, 1, 0, 0,
-        0, 0, 1, 0,
-        0, 0, 0, 1,
-    };
-
-    private final ArrayList<Listener> mListeners = new ArrayList<>();
-
-    private final Context mContext;
-
-    private String mCurrentValue;
-    private boolean mListening;
-
-    public DisplayController(Context context) {
-        mContext = context;
-        TunerService.get(mContext).addTunable(this, COLOR_STATE,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX);
-    }
-
-    public void addListener(Listener listener) {
-        mListeners.add(listener);
-        listener.onCurrentMatrixChanged();
-    }
-
-    public void removeListener(Listener listener) {
-        mListeners.remove(listener);
-    }
-
-    public boolean isEnabled() {
-        return TunerService.get(mContext).getValue(COLOR_STATE, COLOR_STATE_DISABLED)
-                != COLOR_STATE_DISABLED;
-    }
-
-    public boolean isAuto() {
-        return mListening;
-    }
-
-    public void setAuto(boolean auto) {
-        TunerService.get(mContext).setValue(COLOR_STATE, auto ? COLOR_STATE_AUTO
-                : COLOR_STATE_DISABLED);
-    }
-
-    public boolean isCustomSet() {
-        return isCustomEnabled() && Objects.equal(getCurrentMatrix(), getCustomValues());
-    }
-
-    public String getCurrentMatrix() {
-        return mCurrentValue;
-    }
-
-    public String getCustomValues() {
-        return TunerService.get(mContext).getValue(COLOR_MATRIX_CUSTOM_VALUES);
-    }
-
-    public boolean isCustomEnabled() {
-        return TunerService.get(mContext).getValue(COLOR_MATRIX_CUSTOM_ENABLED, 0) != 0;
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX.equals(key)) {
-            mCurrentValue = newValue;
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onCurrentMatrixChanged();
-            }
-        } else if (COLOR_STATE.equals(key)) {
-            final boolean listening = newValue != null
-                    && Integer.parseInt(newValue) == COLOR_STATE_AUTO;
-            if (listening && !mListening) {
-                mListening = true;
-                mContext.registerReceiver(mReceiver,
-                        new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
-                updateNightMode();
-            } else if (!listening && mListening) {
-                mListening = false;
-                mContext.unregisterReceiver(mReceiver);
-            }
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onCurrentMatrixChanged();
-            }
-        }
-    }
-
-    private void updateNightMode() {
-        final int uiMode = mContext.getResources().getConfiguration().uiMode;
-        final boolean isNightMode = (uiMode & Configuration.UI_MODE_NIGHT_MASK)
-                == Configuration.UI_MODE_NIGHT_YES;
-        String value = null;
-        if (isNightMode) {
-            value = toString(NIGHT_VALUES);
-        }
-        TunerService.get(mContext).setValue(Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
-                value);
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
-                updateNightMode();
-            }
-        }
-    };
-
-    public interface Listener {
-        void onCurrentMatrixChanged();
-    }
-
-    public static String[] getColorTransforms(Context context) {
-        return new String[] {
-                NONE_STRING,
-                toString(NIGHT_VALUES),
-                AUTO_STRING, // Blank spot for auto values
-                null, // Blank spot for custom values
-        };
-    }
-
-    public static CharSequence[] getColorTitles(Context context) {
-        // TODO: Move to string array resource.
-        return new CharSequence[]{
-                context.getString(R.string.color_matrix_none),
-                context.getString(R.string.color_matrix_night),
-                context.getString(R.string.color_matrix_auto),
-                context.getString(R.string.color_matrix_custom),
-        };
-    }
-
-    public static String toString(float[] values) {
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < values.length; i++) {
-            if (builder.length() != 0) {
-                builder.append(',');
-            }
-            builder.append(values[i]);
-        }
-        return builder.toString();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NightModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NightModeController.java
new file mode 100644
index 0000000..0b1911b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NightModeController.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import libcore.util.Objects;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.opengl.Matrix;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.util.MathUtils;
+
+import com.android.systemui.tuner.TunerService;
+
+import java.util.ArrayList;
+
+/**
+ * Listens for changes to twilight from the TwilightService.
+ *
+ * Also pushes the current matrix to accessibility based on the current twilight
+ * and various tuner settings.
+ */
+public class NightModeController implements TunerService.Tunable {
+
+    public static final String NIGHT_MODE_ADJUST_TINT = "tuner_night_mode_adjust_tint";
+    private static final String COLOR_MATRIX_CUSTOM_VALUES = "tuner_color_custom_values";
+
+    private static final String ACTION_TWILIGHT_CHANGED = "android.intent.action.TWILIGHT_CHANGED";
+
+    private static final String EXTRA_IS_NIGHT = "isNight";
+    private static final String EXTRA_AMOUNT = "amount";
+
+    // Night mode ~= 3400 K
+    private static final float[] NIGHT_VALUES = new float[] {
+        1, 0,     0,     0,
+        0, .754f, 0,     0,
+        0, 0,     .516f, 0,
+        0, 0,     0,     1,
+    };
+    public static final float[] IDENTITY_MATRIX = new float[] {
+        1, 0, 0, 0,
+        0, 1, 0, 0,
+        0, 0, 1, 0,
+        0, 0, 0, 1,
+    };
+
+    private final ArrayList<Listener> mListeners = new ArrayList<>();
+
+    private final Context mContext;
+
+    // This is whether or not this is the main NightMode controller in SysUI that should be
+    // updating relevant color matrixes or if its in the tuner process getting current state
+    // for UI.
+    private final boolean mUpdateMatrix;
+
+    private float[] mCustomMatrix;
+    private boolean mListening;
+    private boolean mAdjustTint;
+
+    private boolean mIsNight;
+    private float mAmount;
+    private boolean mIsAuto;
+
+    public NightModeController(Context context) {
+        this(context, false);
+    }
+
+    public NightModeController(Context context, boolean updateMatrix) {
+        mContext = context;
+        mUpdateMatrix = updateMatrix;
+        TunerService.get(mContext).addTunable(this, NIGHT_MODE_ADJUST_TINT,
+                COLOR_MATRIX_CUSTOM_VALUES, Secure.TWILIGHT_MODE);
+    }
+
+    public void setNightMode(boolean isNight) {
+        if (mIsAuto) {
+            if (mIsNight != isNight) {
+                TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, isNight
+                        ? Secure.TWILIGHT_MODE_AUTO_OVERRIDE_ON
+                        : Secure.TWILIGHT_MODE_AUTO_OVERRIDE_OFF);
+            } else {
+                TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE,
+                        Secure.TWILIGHT_MODE_AUTO);
+            }
+        } else {
+            TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, isNight
+                    ? Secure.TWILIGHT_MODE_LOCKED_ON : Secure.TWILIGHT_MODE_LOCKED_OFF);
+        }
+    }
+
+    public void setAuto(boolean auto) {
+        mIsAuto = auto;
+        if (auto) {
+            TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_AUTO);
+        } else {
+            // Lock into the current state
+            TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, mIsNight
+                    ? Secure.TWILIGHT_MODE_LOCKED_ON : Secure.TWILIGHT_MODE_LOCKED_OFF);
+        }
+    }
+
+    public boolean isAuto() {
+        return mIsAuto;
+    }
+
+    public void setAdjustTint(Boolean newValue) {
+        TunerService.get(mContext).setValue(NIGHT_MODE_ADJUST_TINT, ((Boolean) newValue) ? 1 : 0);
+    }
+
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+        listener.onNightModeChanged();
+        updateListening();
+    }
+
+    public void removeListener(Listener listener) {
+        mListeners.remove(listener);
+        updateListening();
+    }
+
+    private void updateListening() {
+        boolean shouldListen = mListeners.size() != 0 || (mUpdateMatrix && mAdjustTint);
+        if (shouldListen == mListening) return;
+        mListening = shouldListen;
+        if (mListening) {
+            mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_TWILIGHT_CHANGED));
+        } else {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    public boolean isEnabled() {
+        if (!mListening) {
+            updateNightMode(mContext.registerReceiver(null,
+                    new IntentFilter(ACTION_TWILIGHT_CHANGED)));
+        }
+        return mIsNight;
+    }
+
+    public String getCustomValues() {
+        return TunerService.get(mContext).getValue(COLOR_MATRIX_CUSTOM_VALUES);
+    }
+
+    public void setCustomValues(String values) {
+        TunerService.get(mContext).setValue(COLOR_MATRIX_CUSTOM_VALUES, values);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (COLOR_MATRIX_CUSTOM_VALUES.equals(key)) {
+            mCustomMatrix = newValue != null ? toValues(newValue) : null;
+            updateCurrentMatrix();
+        } else if (NIGHT_MODE_ADJUST_TINT.equals(key)) {
+            mAdjustTint = newValue == null || Integer.parseInt(newValue) != 0;
+            updateListening();
+            updateCurrentMatrix();
+        } else if (Secure.TWILIGHT_MODE.equals(key)) {
+            mIsAuto = newValue != null && Integer.parseInt(newValue) >= Secure.TWILIGHT_MODE_AUTO;
+        }
+    }
+
+    private void updateCurrentMatrix() {
+        if (!mUpdateMatrix) return;
+        if ((!mAdjustTint || mAmount == 0) && mCustomMatrix == null) {
+            TunerService.get(mContext).setValue(Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
+            return;
+        }
+        float[] values = scaleValues(IDENTITY_MATRIX, NIGHT_VALUES, mAdjustTint ? mAmount : 0);
+        if (mCustomMatrix != null) {
+            values = multiply(values, mCustomMatrix);
+        }
+        TunerService.get(mContext).setValue(Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
+                toString(values));
+    }
+
+    private void updateNightMode(Intent intent) {
+        mIsNight = intent.getBooleanExtra(EXTRA_IS_NIGHT, false);
+        mAmount = intent.getFloatExtra(EXTRA_AMOUNT, 0);
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_TWILIGHT_CHANGED.equals(intent.getAction())) {
+                updateNightMode(intent);
+                updateCurrentMatrix();
+                for (int i = 0; i < mListeners.size(); i++) {
+                    mListeners.get(i).onNightModeChanged();
+                }
+            }
+        }
+    };
+
+    public interface Listener {
+        void onNightModeChanged();
+        void onTwilightAutoChanged();
+    }
+
+    private static float[] multiply(float[] matrix, float[] other) {
+        if (matrix == null) {
+            return other;
+        }
+        float[] result = new float[16];
+        Matrix.multiplyMM(result, 0, matrix, 0, other, 0);
+        return result;
+    }
+
+    private float[] scaleValues(float[] identityMatrix, float[] nightValues, float amount) {
+        float[] values = new float[identityMatrix.length];
+        for (int i = 0; i < values.length; i++) {
+            values[i] = MathUtils.lerp(identityMatrix[i], nightValues[i], amount);
+        }
+        return values;
+    }
+
+    public static String toString(float[] values) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < values.length; i++) {
+            if (builder.length() != 0) {
+                builder.append(',');
+            }
+            builder.append(values[i]);
+        }
+        return builder.toString();
+    }
+
+    public static float[] toValues(String customValues) {
+        String[] strValues = customValues.split(",");
+        float[] values = new float[strValues.length];
+        for (int i = 0; i < values.length; i++) {
+            values[i] = Float.parseFloat(strValues[i]);
+        }
+        return values;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/CalibratePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/CalibratePreference.java
new file mode 100644
index 0000000..ff7be13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/CalibratePreference.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.systemui.tuner;
+
+import android.content.Context;
+import android.support.v7.preference.DialogPreference;
+import android.util.AttributeSet;
+
+public class CalibratePreference extends DialogPreference {
+    public CalibratePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorAndAppearanceFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorAndAppearanceFragment.java
new file mode 100644
index 0000000..9f11325
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorAndAppearanceFragment.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.systemui.tuner;
+
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.SeekBar;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NightModeController;
+
+public class ColorAndAppearanceFragment extends PreferenceFragment {
+
+    private static final String KEY_CALIBRATE = "calibrate";
+
+    private static final long RESET_DELAY = 10000;
+    private static final CharSequence KEY_NIGHT_MODE = "night_mode";
+
+    private NightModeController mNightModeController;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mNightModeController = new NightModeController(getContext());
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        addPreferencesFromResource(R.xml.color_and_appearance);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // TODO: Figure out better title model for Tuner, to avoid any more of this.
+        getActivity().setTitle(R.string.color_and_appearance);
+
+        Preference nightMode = findPreference(KEY_NIGHT_MODE);
+        nightMode.setSummary(mNightModeController.isEnabled()
+                ? R.string.night_mode_on : R.string.night_mode_off);
+    }
+
+    @Override
+    public void onDisplayPreferenceDialog(Preference preference) {
+        if (preference instanceof CalibratePreference) {
+            CalibrateDialog.show(this);
+        } else {
+            super.onDisplayPreferenceDialog(preference);
+        }
+    }
+
+    private void startRevertTimer() {
+        getView().postDelayed(mResetColorMatrix, RESET_DELAY);
+    }
+
+    private void onApply() {
+        mNightModeController.setCustomValues(Settings.Secure.getString(
+                getContext().getContentResolver(), Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX));
+        getView().removeCallbacks(mResetColorMatrix);
+    }
+
+    private void onRevert() {
+        getView().removeCallbacks(mResetColorMatrix);
+        mResetColorMatrix.run();
+    }
+
+    private final Runnable mResetColorMatrix = new Runnable() {
+        @Override
+        public void run() {
+            ((DialogFragment) getFragmentManager().findFragmentByTag("RevertWarning")).dismiss();
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
+        }
+    };
+
+    public static class CalibrateDialog extends DialogFragment implements
+            DialogInterface.OnClickListener {
+        private float[] mValues;
+        private NightModeController mNightModeController;
+
+        public static void show(ColorAndAppearanceFragment fragment) {
+            CalibrateDialog dialog = new CalibrateDialog();
+            dialog.setTargetFragment(fragment, 0);
+            dialog.show(fragment.getFragmentManager(), "Calibrate");
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mNightModeController = new NightModeController(getContext());
+            String customValues = mNightModeController.getCustomValues();
+            if (customValues == null) {
+                // Generate this as a string because its the easiest way to generate a copy of the
+                // identity.
+                customValues = NightModeController.toString(NightModeController.IDENTITY_MATRIX);
+            }
+            mValues = NightModeController.toValues(customValues);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            View v = LayoutInflater.from(getContext()).inflate(R.layout.calibrate_sliders, null);
+            bindView(v.findViewById(R.id.r_group), 0);
+            bindView(v.findViewById(R.id.g_group), 5);
+            bindView(v.findViewById(R.id.b_group), 10);
+            return new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.calibrate_display)
+                    .setView(v)
+                    .setPositiveButton(R.string.color_apply, this)
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .create();
+        }
+
+        private void bindView(View view, final int index) {
+            SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+            seekBar.setMax(1000);
+            seekBar.setProgress((int) (1000 * mValues[index]));
+            seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+                @Override
+                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                    mValues[index] = progress / 1000f;
+                }
+
+                @Override
+                public void onStartTrackingTouch(SeekBar seekBar) {
+                }
+
+                @Override
+                public void onStopTrackingTouch(SeekBar seekBar) {
+                }
+            });
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            if (mValues[0] == 1 && mValues[5] == 1 && mValues[10] == 1) {
+                // Allow removal of matrix by all values set to highest.
+                mNightModeController.setCustomValues(null);
+                return;
+            }
+            ((ColorAndAppearanceFragment) getTargetFragment()).startRevertTimer();
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
+                    NightModeController.toString(mValues));
+            RevertWarning.show((ColorAndAppearanceFragment) getTargetFragment());
+        }
+    }
+
+    public static class RevertWarning extends DialogFragment
+            implements DialogInterface.OnClickListener {
+
+        public static void show(ColorAndAppearanceFragment fragment) {
+            RevertWarning warning = new RevertWarning();
+            warning.setTargetFragment(fragment, 0);
+            warning.show(fragment.getFragmentManager(), "RevertWarning");
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            AlertDialog alertDialog = new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.color_revert_title)
+                    .setMessage(R.string.color_revert_message)
+                    .setPositiveButton(R.string.ok, this)
+                    .create();
+            alertDialog.setCanceledOnTouchOutside(true);
+            return alertDialog;
+        }
+
+        @Override
+        public void onCancel(DialogInterface dialog) {
+            super.onCancel(dialog);
+            ((ColorAndAppearanceFragment) getTargetFragment()).onRevert();
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            ((ColorAndAppearanceFragment) getTargetFragment()).onApply();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixFragment.java
deleted file mode 100644
index dfacd03..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixFragment.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/**
- * Copyright (c) 2015, 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.
- */
-package com.android.systemui.tuner;
-
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.support.v14.preference.PreferenceFragment;
-import android.support.v14.preference.SwitchPreference;
-import android.support.v7.preference.DropDownPreference;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.SeekBar;
-import android.widget.Switch;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.DisplayController;
-
-import java.util.Objects;
-
-public class ColorMatrixFragment extends PreferenceFragment implements TunerService.Tunable {
-
-    private static final String TAG = "ColorMatrixFragment";
-
-    private static final long RESET_DELAY = 10000;
-
-    private boolean mCustomEnabled;
-    private DropDownPreference mSelectPreference;
-    private String mCurrentValue;
-    private String mCustomValues;
-    private SwitchPreference mEnableCustomPreference;
-    private MatrixPreference mCustomPreference;
-    private int mState;
-    private Switch mSwitch;
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Context context = getContext();
-        TunerService.get(context).addTunable(this, DisplayController.COLOR_MATRIX_CUSTOM_ENABLED,
-                DisplayController.COLOR_MATRIX_CUSTOM_VALUES, DisplayController.COLOR_STATE,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        final View view = LayoutInflater.from(getContext()).inflate(
-                R.layout.color_matrix_settings, container, false);
-        ((ViewGroup) view).addView(super.onCreateView(inflater, container, savedInstanceState));
-        return view;
-    }
-
-    @Override
-    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        final Context context = getPreferenceManager().getContext();
-        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(context));
-
-        mSelectPreference = new DropDownPreference(context);
-        mSelectPreference.setTitle(R.string.color_transform);
-        mSelectPreference.setSummary("%s");
-        mSelectPreference.setOnPreferenceChangeListener(
-                new Preference.OnPreferenceChangeListener() {
-            @Override
-            public boolean onPreferenceChange(Preference preference, Object newValue) {
-                if (Objects.equals(newValue, DisplayController.AUTO_STRING)) {
-                    Settings.Secure.putInt(context.getContentResolver(),
-                            DisplayController.COLOR_STATE,
-                            DisplayController.COLOR_STATE_AUTO);
-                    return true;
-                }
-                if (Objects.equals(newValue, DisplayController.NONE_STRING)) {
-                    Settings.Secure.putString(context.getContentResolver(),
-                            Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
-                    return true;
-                }
-                Settings.Secure.putInt(context.getContentResolver(),
-                        DisplayController.COLOR_STATE,
-                        DisplayController.COLOR_STATE_ENABLED);
-                final String value = (String) newValue;
-                Settings.Secure.putString(context.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
-                        value);
-                return true;
-            }
-        });
-        getPreferenceScreen().addPreference(mSelectPreference);
-
-        mEnableCustomPreference = new SwitchPreference(context);
-        mEnableCustomPreference.setTitle(R.string.color_enable_custom);
-        mEnableCustomPreference.setOnPreferenceChangeListener(
-                new Preference.OnPreferenceChangeListener() {
-            @Override
-            public boolean onPreferenceChange(Preference preference, Object newValue) {
-                boolean enabled = (Boolean) newValue;
-                if (!enabled && Objects.equals(mCurrentValue, mCustomValues)) {
-                    Settings.Secure.putString(context.getContentResolver(),
-                            Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
-                }
-                Settings.Secure.putInt(context.getContentResolver(),
-                        DisplayController.COLOR_MATRIX_CUSTOM_ENABLED, enabled ? 1 : 0);
-                return true;
-            }
-        });
-        getPreferenceScreen().addPreference(mEnableCustomPreference);
-
-        mCustomPreference = new MatrixPreference(context);
-        getPreferenceScreen().addPreference(mCustomPreference);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        View switchBar = view.findViewById(R.id.switch_bar);
-        mSwitch = (Switch) switchBar.findViewById(android.R.id.switch_widget);
-        mSwitch.setChecked(mState != DisplayController.COLOR_STATE_DISABLED);
-        switchBar.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                int newState = mState != DisplayController.COLOR_STATE_DISABLED
-                        ? DisplayController.COLOR_STATE_DISABLED
-                        : DisplayController.COLOR_STATE_ENABLED;
-                ContentResolver contentResolver = getContext().getContentResolver();
-                if (newState == DisplayController.COLOR_STATE_DISABLED) {
-                    String tiles = Settings.Secure.getString(contentResolver,
-                            QSTileHost.TILES_SETTING);
-                    if (tiles != null) {
-                        if (tiles.contains(",colors")) {
-                            tiles = tiles.replace(",colors", "");
-                        } else if (tiles.contains("colors,")) {
-                            tiles = tiles.replace("colors,", "");
-                        }
-                        Settings.Secure.putString(contentResolver, QSTileHost.TILES_SETTING,
-                                tiles);
-                    }
-                }
-                Settings.Secure.putInt(contentResolver,
-                        DisplayController.COLOR_STATE, newState);
-            }
-        });
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TunerService.get(getContext()).removeTunable(this);
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (DisplayController.COLOR_MATRIX_CUSTOM_ENABLED.equals(key)) {
-            mCustomEnabled = newValue != null && Integer.parseInt(newValue) != 0;
-            mEnableCustomPreference.setChecked(mCustomEnabled);
-            mCustomPreference.setEnabled(mCustomEnabled
-                    && mState != DisplayController.COLOR_STATE_DISABLED);
-            updateSelectOptions();
-        } else if (DisplayController.COLOR_MATRIX_CUSTOM_VALUES.equals(key)) {
-            mCustomValues = newValue;
-            if (mCustomValues == null) {
-                mCustomValues = DisplayController.toString(DisplayController.IDENTITY_MATRIX);
-            }
-            mCustomPreference.setValues(mCustomValues);
-            updateSelectOptions();
-        } else if (DisplayController.COLOR_STATE.equals(key)) {
-            mState = newValue != null ? Integer.parseInt(newValue) : 0;
-            if (mSwitch != null) {
-                mSwitch.setChecked(mState != DisplayController.COLOR_STATE_DISABLED);
-            }
-            mSelectPreference.setEnabled(mState != DisplayController.COLOR_STATE_DISABLED);
-            mEnableCustomPreference.setEnabled(mState != DisplayController.COLOR_STATE_DISABLED);
-            mCustomPreference.setEnabled(mCustomEnabled
-                    && mState != DisplayController.COLOR_STATE_DISABLED);
-        } else {
-            mCurrentValue = newValue;
-            updateSelectOptions();
-        }
-    }
-
-    private void updateSelectOptions() {
-        final int N = DisplayController.CUSTOM_INDEX + (mCustomEnabled ? 1 : 0);
-        String[] values = new String[N];
-        CharSequence[] names = new CharSequence[N];
-        CharSequence[] totalNames = DisplayController.getColorTitles(getContext());
-        String[] entries = DisplayController.getColorTransforms(getContext());
-        entries[DisplayController.CUSTOM_INDEX] = mCustomValues != null ? mCustomValues : "";
-        for (int i = 0; i < N; i++) {
-            values[i] = entries[i];
-            names[i] = totalNames[i];
-        }
-        mSelectPreference.setEntries(names);
-        mSelectPreference.setEntryValues(values);
-        int index = 0;
-        if (mState == DisplayController.COLOR_STATE_AUTO) {
-            index = DisplayController.AUTO_INDEX;
-        } else if (mCustomValues != null && Objects.equals(mCurrentValue, mCustomValues)) {
-            index = DisplayController.CUSTOM_INDEX;
-        } else if (Objects.equals(mCurrentValue, entries[1])) {
-            index = 1;
-        }
-        mSelectPreference.setValueIndex(index);
-        mSelectPreference.setSummary("%s");
-        return;
-    }
-
-    private void startRevertTimer() {
-        getView().postDelayed(mResetColorMatrix, RESET_DELAY);
-    }
-
-    private void onApply() {
-        Settings.Secure.putString(getContext().getContentResolver(),
-                DisplayController.COLOR_MATRIX_CUSTOM_VALUES, mCurrentValue);
-        getView().removeCallbacks(mResetColorMatrix);
-    }
-
-    private void onRevert() {
-        getView().removeCallbacks(mResetColorMatrix);
-        mResetColorMatrix.run();
-    }
-
-    private final Runnable mResetColorMatrix = new Runnable() {
-        @Override
-        public void run() {
-            ((DialogFragment) getFragmentManager().findFragmentByTag("RevertWarning")).dismiss();
-            Settings.Secure.putString(getContext().getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
-        }
-    };
-
-    private class MatrixPreference extends Preference implements View.OnClickListener {
-        private float[] mValues;
-
-        public MatrixPreference(Context context) {
-            super(context);
-            setLayoutResource(R.layout.preference_matrix);
-        }
-
-        public void setValues(String customValues) {
-            String[] strValues = customValues.split(",");
-            mValues = new float[strValues.length];
-            for (int i = 0; i < mValues.length; i++) {
-                mValues[i] = Float.parseFloat(strValues[i]);
-            }
-            notifyChanged();
-        }
-
-        @Override
-        public void onBindViewHolder(PreferenceViewHolder holder) {
-            super.onBindViewHolder(holder);
-            bindView(holder.findViewById(R.id.r_group), 0);
-            bindView(holder.findViewById(R.id.g_group), 5);
-            bindView(holder.findViewById(R.id.b_group), 10);
-            holder.findViewById(R.id.apply).setOnClickListener(this);
-        }
-
-        private void bindView(View view, final int index) {
-            SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
-            seekBar.setMax(1000);
-            seekBar.setProgress((int) (1000 * mValues[index]));
-            seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-                @Override
-                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                    mValues[index] = progress / 1000f;
-                }
-
-                @Override
-                public void onStartTrackingTouch(SeekBar seekBar) {
-                }
-
-                @Override
-                public void onStopTrackingTouch(SeekBar seekBar) {
-                }
-            });
-        }
-
-        @Override
-        public void onClick(View v) {
-            startRevertTimer();
-            Settings.Secure.putString(getContext().getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
-                    DisplayController.toString(mValues));
-            RevertWarning.show(ColorMatrixFragment.this);
-        }
-
-    }
-
-    public static class RevertWarning extends DialogFragment
-            implements DialogInterface.OnClickListener {
-
-        public static void show(ColorMatrixFragment fragment) {
-            RevertWarning warning = new RevertWarning();
-            warning.setTargetFragment(fragment, 0);
-            warning.show(fragment.getFragmentManager(), "RevertWarning");
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            AlertDialog alertDialog = new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.color_revert_title)
-                    .setMessage(R.string.color_revert_message)
-                    .setPositiveButton(R.string.ok, this)
-                    .create();
-            alertDialog.setCanceledOnTouchOutside(true);
-            return alertDialog;
-        }
-
-        @Override
-        public void onCancel(DialogInterface dialog) {
-            super.onCancel(dialog);
-            ((ColorMatrixFragment) getTargetFragment()).onRevert();
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            ((ColorMatrixFragment) getTargetFragment()).onApply();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
deleted file mode 100644
index d8cf2e2..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * Copyright (c) 2015, 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.
- */
-package com.android.systemui.tuner;
-
-import android.app.ActivityManager;
-import android.provider.Settings;
-
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.policy.DisplayController;
-
-import java.util.Objects;
-
-
-public class ColorMatrixTile extends QSTile<QSTile.State> implements DisplayController.Listener {
-
-    public static final String COLOR_MATRIX_SPEC = "colors";
-
-    private final DisplayController mDisplayController;
-
-    private int mIndex;
-    private String mCurrentValue;
-
-    private boolean mCustomEnabled;
-    private String[] mValues;
-    private CharSequence[] mValueTitles;
-
-    public ColorMatrixTile(Host host) {
-        super(host);
-        mDisplayController = host.getDisplayController();
-    }
-
-    @Override
-    public void setListening(boolean listening) {
-        if (listening) {
-            mValues = DisplayController.getColorTransforms(mContext);
-            mValueTitles = DisplayController.getColorTitles(mContext);
-            mDisplayController.addListener(this);
-        } else {
-            mDisplayController.removeListener(this);
-        }
-    }
-
-    @Override
-    public State newTileState() {
-        return new State();
-    }
-
-    @Override
-    protected void handleClick() {
-        mIndex++;
-        if (mIndex == DisplayController.AUTO_INDEX) {
-            mDisplayController.setAuto(true);
-        } else {
-            mDisplayController.setAuto(false);
-            if (!mDisplayController.isCustomEnabled()
-                    && (mIndex == DisplayController.CUSTOM_INDEX)) {
-                mIndex++;
-            }
-            if (mIndex == mValues.length - 1) {
-                mIndex = 0;
-            }
-            Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, mValues[mIndex],
-                    ActivityManager.getCurrentUser());
-        }
-        refreshState();
-    }
-
-    @Override
-    protected void handleUpdateState(State state, Object arg) {
-        if (mDisplayController.isAuto()) {
-            mIndex = DisplayController.AUTO_INDEX;
-        } else if (mDisplayController.isCustomSet()) {
-            mIndex = DisplayController.CUSTOM_INDEX;
-        } else {
-            mIndex = Objects.equals(mDisplayController.getCurrentMatrix(), mValues[1]) ? 1 : 0;
-        }
-        state.icon = ResourceIcon.get(R.drawable.ic_colorize);
-        state.label = mValueTitles[mIndex];
-        state.contentDescription = mValueTitles[mIndex];
-    }
-
-    @Override
-    public void onCurrentMatrixChanged() {
-        refreshState();
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.QS_COLOR_MATRIX;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NightModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/NightModeFragment.java
new file mode 100644
index 0000000..1377407
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NightModeFragment.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+package com.android.systemui.tuner;
+
+import android.annotation.Nullable;
+import android.app.UiModeManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Switch;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.policy.NightModeController;
+import com.android.systemui.statusbar.policy.NightModeController.Listener;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+public class NightModeFragment extends PreferenceFragment implements Tunable,
+        Listener, OnPreferenceChangeListener {
+
+    private static final String TAG = "NightModeFragment";
+    private static final CharSequence KEY_AUTO = "auto";
+    private static final CharSequence KEY_DARK_THEME = "dark_theme";
+    private static final CharSequence KEY_ADJUST_TINT = "adjust_tint";
+    private static final CharSequence KEY_ADJUST_BRIGHTNESS = "adjust_brightness";
+
+    private Switch mSwitch;
+
+    private NightModeController mNightModeController;
+    private SwitchPreference mAutoSwitch;
+    private SwitchPreference mDarkTheme;
+    private SwitchPreference mAdjustTint;
+    private SwitchPreference mAdjustBrightness;
+    private UiModeManager mUiModeManager;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mNightModeController = new NightModeController(getContext());
+        mUiModeManager = getContext().getSystemService(UiModeManager.class);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View view = LayoutInflater.from(getContext()).inflate(
+                R.layout.night_mode_settings, container, false);
+        ((ViewGroup) view).addView(super.onCreateView(inflater, container, savedInstanceState));
+        return view;
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        final Context context = getPreferenceManager().getContext();
+
+        addPreferencesFromResource(R.xml.night_mode);
+        mAutoSwitch = (SwitchPreference) findPreference(KEY_AUTO);
+        mAutoSwitch.setOnPreferenceChangeListener(this);
+        mDarkTheme = (SwitchPreference) findPreference(KEY_DARK_THEME);
+        mDarkTheme.setOnPreferenceChangeListener(this);
+        mAdjustTint = (SwitchPreference) findPreference(KEY_ADJUST_TINT);
+        mAdjustTint.setOnPreferenceChangeListener(this);
+        mAdjustBrightness = (SwitchPreference) findPreference(KEY_ADJUST_BRIGHTNESS);
+        mAdjustBrightness.setOnPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        View switchBar = view.findViewById(R.id.switch_bar);
+        mSwitch = (Switch) switchBar.findViewById(android.R.id.switch_widget);
+        mSwitch.setChecked(mNightModeController.isEnabled());
+        switchBar.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                boolean newState = !mNightModeController.isEnabled();
+                mNightModeController.setNightMode(newState);
+                mSwitch.setChecked(newState);
+            }
+        });
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mNightModeController.addListener(this);
+        TunerService.get(getContext()).addTunable(this, Secure.BRIGHTNESS_USE_TWILIGHT,
+                NightModeController.NIGHT_MODE_ADJUST_TINT);
+        mDarkTheme.setChecked(mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_AUTO);
+        calculateDisabled();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mNightModeController.removeListener(this);
+        TunerService.get(getContext()).removeTunable(this);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (mAutoSwitch == preference) {
+            mNightModeController.setAuto((Boolean) newValue);
+        } else if (mDarkTheme == preference) {
+            mUiModeManager.setNightMode(((Boolean) newValue) ? UiModeManager.MODE_NIGHT_AUTO
+                    : UiModeManager.MODE_NIGHT_NO);
+            postCalculateDisabled();
+        } else if (mAdjustTint == preference) {
+            mNightModeController.setAdjustTint((Boolean) newValue);
+            postCalculateDisabled();
+        } else if (mAdjustBrightness == preference) {
+            TunerService.get(getContext()).setValue(Secure.BRIGHTNESS_USE_TWILIGHT,
+                    ((Boolean) newValue) ? 1 : 0);
+            postCalculateDisabled();
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    private void postCalculateDisabled() {
+        // Post this because its the easiest way to wait for all state to be calculated.
+        getView().post(new Runnable() {
+            @Override
+            public void run() {
+                calculateDisabled();
+            }
+        });
+    }
+
+    private void calculateDisabled() {
+        int enabledCount = (mDarkTheme.isChecked() ? 1 : 0)
+                + (mAdjustTint.isChecked() ? 1 : 0)
+                + (mAdjustBrightness.isChecked() ? 1 : 0);
+        if (enabledCount == 1) {
+            if (mDarkTheme.isChecked()) {
+                mDarkTheme.setEnabled(false);
+            } else if (mAdjustTint.isChecked()) {
+                mAdjustTint.setEnabled(false);
+            } else {
+                mAdjustBrightness.setEnabled(false);
+            }
+        } else {
+            mDarkTheme.setEnabled(true);
+            mAdjustTint.setEnabled(true);
+            mAdjustBrightness.setEnabled(true);
+        }
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (Secure.BRIGHTNESS_USE_TWILIGHT.equals(key)) {
+            mAdjustBrightness.setChecked(newValue != null && Integer.parseInt(newValue) != 0);
+        } else if (NightModeController.NIGHT_MODE_ADJUST_TINT.equals(key)) {
+            // Default on.
+            mAdjustTint.setChecked(newValue == null || Integer.parseInt(newValue) != 0);
+        }
+    }
+
+    @Override
+    public void onNightModeChanged() {
+        mSwitch.setChecked(mNightModeController.isEnabled());
+    }
+
+    @Override
+    public void onTwilightAutoChanged() {
+        mAutoSwitch.setChecked(mNightModeController.isAuto());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NightModeTile.java b/packages/SystemUI/src/com/android/systemui/tuner/NightModeTile.java
new file mode 100644
index 0000000..1311f30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NightModeTile.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+package com.android.systemui.tuner;
+
+import android.app.ActivityManager;
+import android.provider.Settings;
+
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.NightModeController;
+
+import java.util.Objects;
+
+
+public class NightModeTile extends QSTile<QSTile.State> implements NightModeController.Listener {
+
+    public static final String NIGHT_MODE_SPEC = "night";
+
+    private final NightModeController mNightModeController;
+
+    private int mIndex;
+    private String mCurrentValue;
+
+    private boolean mCustomEnabled;
+    private String[] mValues;
+    private CharSequence[] mValueTitles;
+
+    public NightModeTile(Host host) {
+        super(host);
+        mNightModeController = host.getNightModeController();
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening) {
+            mNightModeController.addListener(this);
+            refreshState();
+        } else {
+            mNightModeController.removeListener(this);
+        }
+    }
+
+    @Override
+    public State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleClick() {
+        mNightModeController.setNightMode(!mNightModeController.isEnabled());
+        refreshState();
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        // TODO: Right now this is just a dropper, needs an actual night icon.
+        boolean enabled = mNightModeController.isEnabled();
+        state.icon = ResourceIcon.get(enabled ? R.drawable.ic_night_mode
+                : R.drawable.ic_night_mode_disabled);
+        state.label = mContext.getString(R.string.night_mode);
+        state.contentDescription = mContext.getString(R.string.night_mode);
+    }
+
+    @Override
+    public void onNightModeChanged() {
+        refreshState();
+    }
+
+    @Override
+    public void onTwilightAutoChanged() {
+        // Don't care.
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.QS_COLOR_MATRIX;
+    }
+}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1908f72..5c80d04 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -60,22 +60,8 @@
     // non-zero, which in turn ensures that the total weight is non-zero.
     private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
 
-    // If true, enables the use of the current time as an auto-brightness adjustment.
-    // The basic idea here is to expand the dynamic range of auto-brightness
-    // when it is especially dark outside.  The light sensor tends to perform
-    // poorly at low light levels so we compensate for it by making an
-    // assumption about the environment.
-    private static final boolean USE_TWILIGHT_ADJUSTMENT =
-            PowerManager.useTwilightAdjustmentFeature();
-
     // Specifies the maximum magnitude of the time of day adjustment.
-    private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f;
-
-    // The amount of time after or before sunrise over which to start adjusting
-    // the gamma.  We want the change to happen gradually so that it is below the
-    // threshold of perceptibility and so that the adjustment has maximum effect
-    // well after dusk.
-    private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+    private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1f;
 
     // Debounce for sampling user-initiated changes in display brightness to ensure
     // the user is satisfied with the result before storing the sample.
@@ -193,6 +179,8 @@
     private int mBrightnessAdjustmentSampleOldBrightness;
     private float mBrightnessAdjustmentSampleOldGamma;
 
+    private boolean mUseTwilight;
+
     public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
             SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
             int brightnessMin, int brightnessMax, float dozeScaleFactor,
@@ -221,10 +209,6 @@
         if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
             mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
         }
-
-        if (USE_TWILIGHT_ADJUSTMENT) {
-            mTwilight.registerListener(mTwilightListener, mHandler);
-        }
     }
 
     public int getAutomaticScreenBrightness() {
@@ -235,7 +219,7 @@
     }
 
     public void configure(boolean enable, float adjustment, boolean dozing,
-            boolean userInitiatedChange) {
+            boolean userInitiatedChange, boolean useTwilight) {
         // While dozing, the application processor may be suspended which will prevent us from
         // receiving new information from the light sensor. On some devices, we may be able to
         // switch to a wake-up light sensor instead but for now we will simply disable the sensor
@@ -244,6 +228,7 @@
         mDozing = dozing;
         boolean changed = setLightSensorEnabled(enable && !dozing);
         changed |= setScreenAutoBrightnessAdjustment(adjustment);
+        changed |= setUseTwilight(useTwilight);
         if (changed) {
             updateAutoBrightness(false /*sendUpdate*/);
         }
@@ -252,6 +237,17 @@
         }
     }
 
+    private boolean setUseTwilight(boolean useTwilight) {
+        if (mUseTwilight == useTwilight) return false;
+        if (useTwilight) {
+            mTwilight.registerListener(mTwilightListener, mHandler);
+        } else {
+            mTwilight.unregisterListener(mTwilightListener);
+        }
+        mUseTwilight = useTwilight;
+        return true;
+    }
+
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Automatic Brightness Controller Configuration:");
@@ -484,18 +480,13 @@
             }
         }
 
-        if (USE_TWILIGHT_ADJUSTMENT) {
+        if (mUseTwilight) {
             TwilightState state = mTwilight.getCurrentState();
             if (state != null && state.isNight()) {
                 final long now = System.currentTimeMillis();
-                final float earlyGamma =
-                        getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise());
-                final float lateGamma =
-                        getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise());
-                gamma *= earlyGamma * lateGamma;
+                gamma *= 1 + state.getAmount() * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
                 if (DEBUG) {
-                    Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma
-                            + ", lateGamma=" + lateGamma);
+                    Slog.d(TAG, "updateAutoBrightness: twilight amount=" + state.getAmount());
                 }
             }
         }
@@ -579,25 +570,6 @@
         }
     }
 
-    private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) {
-        if (lastSunset < 0 || nextSunrise < 0
-                || now < lastSunset || now > nextSunrise) {
-            return 1.0f;
-        }
-
-        if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) {
-            return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
-                    (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME);
-        }
-
-        if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) {
-            return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
-                    (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME);
-        }
-
-        return TWILIGHT_ADJUSTMENT_MAX_GAMMA;
-    }
-
     private final class AutomaticBrightnessHandler extends Handler {
         public AutomaticBrightnessHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1038d97..1ed7070 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -615,7 +615,7 @@
                     && mPowerRequest.brightnessSetByUser;
             mAutomaticBrightnessController.configure(autoBrightnessEnabled,
                     mPowerRequest.screenAutoBrightnessAdjustment, state != Display.STATE_ON,
-                    userInitiatedChange);
+                    userInitiatedChange, mPowerRequest.useTwilight);
         }
 
         // Apply brightness boost.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a1f24f7..dbaa598 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -49,6 +49,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.service.dreams.DreamManagerInternal;
 import android.util.EventLog;
 import android.util.Slog;
@@ -468,6 +469,9 @@
     private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
             = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
 
+    // True if brightness should be affected by twilight.
+    private boolean mBrightnessUseTwilight;
+
     private native void nativeInit();
 
     private static native void nativeAcquireSuspendBlocker(String name);
@@ -620,6 +624,9 @@
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.DOUBLE_TAP_TO_WAKE),
                     false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Secure.BRIGHTNESS_USE_TWILIGHT),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
             // Go.
             readConfigurationLocked();
             updateSettingsLocked();
@@ -726,6 +733,9 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
 
+        mBrightnessUseTwilight = Settings.Secure.getIntForUser(resolver,
+                Secure.BRIGHTNESS_USE_TWILIGHT, 0, UserHandle.USER_CURRENT) != 0;
+
         final boolean lowPowerModeEnabled = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE, 0) != 0;
         final boolean autoLowPowerModeConfigured = Settings.Global.getInt(resolver,
@@ -1997,6 +2007,7 @@
             mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
             mDisplayPowerRequest.lowPowerMode = mLowPowerModeEnabled;
             mDisplayPowerRequest.boostScreenBrightness = mScreenBrightnessBoostInProgress;
+            mDisplayPowerRequest.useTwilight = mBrightnessUseTwilight;
 
             if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
                 mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
diff --git a/services/core/java/com/android/server/twilight/TwilightManager.java b/services/core/java/com/android/server/twilight/TwilightManager.java
index b3de58b..56137a4 100644
--- a/services/core/java/com/android/server/twilight/TwilightManager.java
+++ b/services/core/java/com/android/server/twilight/TwilightManager.java
@@ -20,5 +20,6 @@
 
 public interface TwilightManager {
     void registerListener(TwilightListener listener, Handler handler);
+    void unregisterListener(TwilightListener listener);
     TwilightState getCurrentState();
 }
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index a71961c..ac1ab64 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -19,12 +19,15 @@
 import com.android.server.SystemService;
 import com.android.server.TwilightCalculator;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.location.Criteria;
 import android.location.Location;
 import android.location.LocationListener;
@@ -33,6 +36,9 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.text.format.DateUtils;
 import android.text.format.Time;
 import android.util.Slog;
@@ -54,6 +60,26 @@
     static final String ACTION_UPDATE_TWILIGHT_STATE =
             "com.android.server.action.UPDATE_TWILIGHT_STATE";
 
+    // The amount of time after or before sunrise over which to start adjusting
+    // twilight affected things.  We want the change to happen gradually so that
+    // it is below the threshold of perceptibility and so that the adjustment has
+    // maximum effect well after dusk.
+    private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+
+    // Broadcast when twilight changes.
+    public static final String ACTION_TWILIGHT_CHANGED = "android.intent.action.TWILIGHT_CHANGED";
+
+    public static final String EXTRA_IS_NIGHT = "isNight";
+    public static final String EXTRA_AMOUNT = "amount";
+
+    // Amount of time the TwilightService will stay locked in an override state before switching
+    // back to auto.
+    private static final long RESET_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+    private static final String EXTRA_RESET_USER = "user";
+
+    private static final String ACTION_RESET_TWILIGHT_AUTO =
+            "com.android.server.action.RESET_TWILIGHT_AUTO";
+
     final Object mLock = new Object();
 
     AlarmManager mAlarmManager;
@@ -65,6 +91,10 @@
 
     TwilightState mTwilightState;
 
+    private int mCurrentUser;
+    private boolean mLocked;
+    private boolean mBootCompleted;
+
     public TwilightService(Context context) {
         super(context);
     }
@@ -75,14 +105,93 @@
         mLocationManager = (LocationManager) getContext().getSystemService(
                 Context.LOCATION_SERVICE);
         mLocationHandler = new LocationHandler();
+        mCurrentUser = ActivityManager.getCurrentUser();
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(ACTION_UPDATE_TWILIGHT_STATE);
-        getContext().registerReceiver(mUpdateLocationReceiver, filter);
+        getContext().registerReceiver(mReceiver, filter);
 
         publishLocalService(TwilightManager.class, mService);
+        getContext().getContentResolver().registerContentObserver(
+                Secure.getUriFor(Secure.TWILIGHT_MODE), false, mContentObserver, mCurrentUser);
+        mContentObserver.onChange(true);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_BOOT_COMPLETED) {
+            mBootCompleted = true;
+            sendBroadcast();
+        }
+    }
+
+    private void reregisterSettingObserver() {
+        final ContentResolver contentResolver = getContext().getContentResolver();
+        contentResolver.unregisterContentObserver(mContentObserver);
+        contentResolver.registerContentObserver(Secure.getUriFor(Secure.TWILIGHT_MODE), false,
+                mContentObserver, mCurrentUser);
+        mContentObserver.onChange(true);
+    }
+
+    private void setLockedState(TwilightState state) {
+        synchronized (mLock) {
+            // Make sure we aren't locked so we can set the state.
+            mLocked = false;
+            setTwilightState(state);
+            // Make sure we leave the state locked, so it cant be changed.
+            mLocked = true;
+            // TODO: Don't bother updating state when locked.
+        }
+    }
+
+    private void setTwilightState(TwilightState state) {
+        synchronized (mLock) {
+            if (mLocked) {
+                // State has been locked by secure setting, shouldn't be changed.
+                return;
+            }
+            if (!Objects.equal(mTwilightState, state)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Twilight state changed: " + state);
+                }
+
+                mTwilightState = state;
+
+                final int listenerLen = mListeners.size();
+                for (int i = 0; i < listenerLen; i++) {
+                    mListeners.get(i).postUpdate();
+                }
+            }
+        }
+        sendBroadcast();
+    }
+
+    private void sendBroadcast() {
+        synchronized (mLock) {
+            if (mTwilightState == null) {
+                return;
+            }
+            if (mBootCompleted) {
+                Intent intent = new Intent(ACTION_TWILIGHT_CHANGED);
+                intent.putExtra(EXTRA_IS_NIGHT, mTwilightState.isNight());
+                intent.putExtra(EXTRA_AMOUNT, mTwilightState.getAmount());
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            }
+        }
+    }
+
+    private void scheduleReset() {
+        long resetTime = System.currentTimeMillis() + RESET_TIME;
+        Intent resetIntent = new Intent(ACTION_RESET_TWILIGHT_AUTO);
+        resetIntent.putExtra(EXTRA_RESET_USER, mCurrentUser);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                getContext(), 0, resetIntent, 0);
+        mAlarmManager.cancel(pendingIntent);
+        mAlarmManager.setExact(AlarmManager.RTC, resetTime, pendingIntent);
     }
 
     private static class TwilightListenerRecord implements Runnable {
@@ -133,24 +242,22 @@
                 }
             }
         }
-    };
 
-    private void setTwilightState(TwilightState state) {
-        synchronized (mLock) {
-            if (!Objects.equal(mTwilightState, state)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Twilight state changed: " + state);
+        @Override
+        public void unregisterListener(TwilightListener listener) {
+            synchronized (mLock) {
+                for (int i = 0; i < mListeners.size(); i++) {
+                    if (mListeners.get(i).mListener == listener) {
+                        mListeners.remove(i);
+                    }
                 }
 
-                mTwilightState = state;
-
-                final int listenerLen = mListeners.size();
-                for (int i = 0; i < listenerLen; i++) {
-                    mListeners.get(i).postUpdate();
+                if (mListeners.size() == 0) {
+                    mLocationHandler.disableLocationUpdates();
                 }
             }
         }
-    }
+    };
 
     // The user has moved if the accuracy circles of the two locations don't overlap.
     private static boolean hasMoved(Location from, Location to) {
@@ -183,6 +290,7 @@
         private static final int MSG_GET_NEW_LOCATION_UPDATE = 2;
         private static final int MSG_PROCESS_NEW_LOCATION = 3;
         private static final int MSG_DO_TWILIGHT_UPDATE = 4;
+        private static final int MSG_DISABLE_LOCATION_UPDATES = 5;
 
         private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS;
         private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
@@ -210,6 +318,10 @@
             sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
         }
 
+        public void disableLocationUpdates() {
+            sendEmptyMessage(MSG_DISABLE_LOCATION_UPDATES);
+        }
+
         public void requestLocationUpdate() {
             sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE);
         }
@@ -311,6 +423,11 @@
                     }
                     break;
 
+                case MSG_DISABLE_LOCATION_UPDATES:
+                    mLocationManager.removeUpdates(mLocationListener);
+                    removeMessages(MSG_ENABLE_LOCATION_UPDATES);
+                    break;
+
                 case MSG_DO_TWILIGHT_UPDATE:
                     updateTwilightState();
                     break;
@@ -368,11 +485,6 @@
 
             final long now = System.currentTimeMillis();
 
-            // calculate yesterday's twilight
-            mTwilightCalculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS,
-                    mLocation.getLatitude(), mLocation.getLongitude());
-            final long yesterdaySunset = mTwilightCalculator.mSunset;
-
             // calculate today's twilight
             mTwilightCalculator.calculateTwilight(now,
                     mLocation.getLatitude(), mLocation.getLongitude());
@@ -385,9 +497,19 @@
                     mLocation.getLatitude(), mLocation.getLongitude());
             final long tomorrowSunrise = mTwilightCalculator.mSunrise;
 
+            float amount = 0;
+            if (isNight) {
+                if (todaySunrise == -1 || todaySunset == -1) {
+                    amount = 1;
+                } else if (now > todaySunset) {
+                    amount = Math.min(1, (now - todaySunset) / (float) TWILIGHT_ADJUSTMENT_TIME);
+                } else {
+                    amount = Math.max(0, 1
+                            - (todaySunrise - now) / (float) TWILIGHT_ADJUSTMENT_TIME);
+                }
+            }
             // set twilight state
-            TwilightState state = new TwilightState(isNight, yesterdaySunset,
-                    todaySunrise, todaySunset, tomorrowSunrise);
+            TwilightState state = new TwilightState(isNight, amount);
             if (DEBUG) {
                 Slog.d(TAG, "Updating twilight state: " + state);
             }
@@ -402,12 +524,18 @@
                 // add some extra time to be on the safe side.
                 nextUpdate += DateUtils.MINUTE_IN_MILLIS;
 
-                if (now > todaySunset) {
-                    nextUpdate += tomorrowSunrise;
-                } else if (now > todaySunrise) {
-                    nextUpdate += todaySunset;
+                if (amount == 1 || amount == 0) {
+                    if (now > todaySunset) {
+                        nextUpdate += tomorrowSunrise;
+                    } else if (now > todaySunrise) {
+                        nextUpdate += todaySunset;
+                    } else {
+                        nextUpdate += todaySunrise;
+                    }
                 } else {
-                    nextUpdate += todaySunrise;
+                    // This is the update rate while transitioning.
+                    // Leave at 10 min for now (one from above).
+                    nextUpdate += 9 * DateUtils.MINUTE_IN_MILLIS;
                 }
             }
 
@@ -423,9 +551,37 @@
         }
     }
 
-    private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() {
+    private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            int value = Secure.getIntForUser(getContext().getContentResolver(),
+                    Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_LOCKED_OFF, mCurrentUser);
+            if (value == Secure.TWILIGHT_MODE_LOCKED_OFF) {
+                setLockedState(new TwilightState(false, 0));
+            } else if (value == Secure.TWILIGHT_MODE_LOCKED_ON) {
+                setLockedState(new TwilightState(true, 1));
+            } else if (value == Secure.TWILIGHT_MODE_AUTO_OVERRIDE_OFF) {
+                setLockedState(new TwilightState(false, 0));
+                scheduleReset();
+            } else if (value == Secure.TWILIGHT_MODE_AUTO_OVERRIDE_ON) {
+                setLockedState(new TwilightState(true, 1));
+                scheduleReset();
+            } else {
+                mLocked = false;
+                mLocationHandler.requestTwilightUpdate();
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                mCurrentUser = ActivityManager.getCurrentUser();
+                reregisterSettingObserver();
+                return;
+            }
             if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())
                     && !intent.getBooleanExtra("state", false)) {
                 // Airplane mode is now off!
@@ -433,6 +589,12 @@
                 return;
             }
 
+            if (ACTION_RESET_TWILIGHT_AUTO.equals(intent.getAction())) {
+                int user = intent.getIntExtra(EXTRA_RESET_USER, 0);
+                Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                        Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_AUTO, user);
+                return;
+            }
             // Time zone has changed or alarm expired.
             mLocationHandler.requestTwilightUpdate();
         }
diff --git a/services/core/java/com/android/server/twilight/TwilightState.java b/services/core/java/com/android/server/twilight/TwilightState.java
index 91e24d7..81abc13 100644
--- a/services/core/java/com/android/server/twilight/TwilightState.java
+++ b/services/core/java/com/android/server/twilight/TwilightState.java
@@ -25,20 +25,11 @@
  */
 public class TwilightState {
     private final boolean mIsNight;
-    private final long mYesterdaySunset;
-    private final long mTodaySunrise;
-    private final long mTodaySunset;
-    private final long mTomorrowSunrise;
+    private final float mAmount;
 
-    TwilightState(boolean isNight,
-            long yesterdaySunset,
-            long todaySunrise, long todaySunset,
-            long tomorrowSunrise) {
+    TwilightState(boolean isNight, float amount) {
         mIsNight = isNight;
-        mYesterdaySunset = yesterdaySunset;
-        mTodaySunrise = todaySunrise;
-        mTodaySunset = todaySunset;
-        mTomorrowSunrise = tomorrowSunrise;
+        mAmount = amount;
     }
 
     /**
@@ -49,35 +40,11 @@
     }
 
     /**
-     * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase,
-     * or -1 if the sun never sets.
+     * For twilight affects that change gradually over time, this is the amount they
+     * should currently be in effect.
      */
-    public long getYesterdaySunset() {
-        return mYesterdaySunset;
-    }
-
-    /**
-     * Returns the time of today's sunrise in the System.currentTimeMillis() timebase,
-     * or -1 if the sun never rises.
-     */
-    public long getTodaySunrise() {
-        return mTodaySunrise;
-    }
-
-    /**
-     * Returns the time of today's sunset in the System.currentTimeMillis() timebase,
-     * or -1 if the sun never sets.
-     */
-    public long getTodaySunset() {
-        return mTodaySunset;
-    }
-
-    /**
-     * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase,
-     * or -1 if the sun never rises.
-     */
-    public long getTomorrowSunrise() {
-        return mTomorrowSunrise;
+    public float getAmount() {
+        return mAmount;
     }
 
     @Override
@@ -88,10 +55,7 @@
     public boolean equals(TwilightState other) {
         return other != null
                 && mIsNight == other.mIsNight
-                && mYesterdaySunset == other.mYesterdaySunset
-                && mTodaySunrise == other.mTodaySunrise
-                && mTodaySunset == other.mTodaySunset
-                && mTomorrowSunrise == other.mTomorrowSunrise;
+                && mAmount == other.mAmount;
     }
 
     @Override
@@ -103,10 +67,7 @@
     public String toString() {
         DateFormat f = DateFormat.getDateTimeInstance();
         return "{TwilightState: isNight=" + mIsNight
-                + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset))
-                + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise))
-                + ", mTodaySunset=" + f.format(new Date(mTodaySunset))
-                + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise))
+                + ", mAmount=" + mAmount
                 + "}";
     }
 }