Add night display pref controllers and change UX

- Convert NightDisplaySettings to a DashboardFragment
- Add preference controllers for all Night Display settings
- Change UX for activation from a toggle to a button

Bug: 73739388
Bug: 69912911
Test: make -j100 and make RunSettingsRoboTests -j100
Change-Id: Ia173f16207ba59bf57eb7546cbb1e2dbca67b063
Merged-In: Ia173f16207ba59bf57eb7546cbb1e2dbca67b063
diff --git a/res/layout/night_display_activation_button.xml b/res/layout/night_display_activation_button.xml
new file mode 100644
index 0000000..b0bfe86
--- /dev/null
+++ b/res/layout/night_display_activation_button.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Button
+        android:id="@+id/night_display_turn_on_button"
+        style="@style/ActionPrimaryButton"
+        android:layout_marginStart="@dimen/screen_margin_sides"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start" />
+
+    <Button
+        android:id="@+id/night_display_turn_off_button"
+        style="@style/ActionSecondaryButton"
+        android:layout_marginStart="@dimen/screen_margin_sides"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/xml/night_display_settings.xml b/res/xml/night_display_settings.xml
index 9442a0d..7b00ea0 100644
--- a/res/xml/night_display_settings.xml
+++ b/res/xml/night_display_settings.xml
@@ -15,31 +15,44 @@
 -->
 
 <PreferenceScreen
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:settings="http://schemas.android.com/apk/res-auto"
-        android:title="@string/night_display_title"
-        android:key="night_display_title"
-        settings:keywords="@string/keywords_display_night_display">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/night_display_title"
+    android:key="night_display_title"
+    settings:keywords="@string/keywords_display_night_display">
 
     <DropDownPreference
-            android:key="night_display_auto_mode"
-            android:title="@string/night_display_auto_mode_title"
-            android:summary="%s" />
+        android:key="night_display_auto_mode"
+        android:title="@string/night_display_auto_mode_title"
+        android:summary="%s"
+        settings:controller="com.android.settings.display.NightDisplayAutoModePreferenceController" />
 
     <Preference
-            android:key="night_display_start_time"
-            android:title="@string/night_display_start_time_title" />
+        android:key="night_display_start_time"
+        android:title="@string/night_display_start_time_title"
+        settings:controller="com.android.settings.display.NightDisplayCustomStartTimePreferenceController" />
 
     <Preference
-            android:key="night_display_end_time"
-            android:title="@string/night_display_end_time_title" />
-
-    <com.android.settings.display.NightDisplayPreference
-            android:key="night_display_activated"
-            android:title="@string/night_display_status_title" />
+        android:key="night_display_end_time"
+        android:title="@string/night_display_end_time_title"
+        settings:controller="com.android.settings.display.NightDisplayCustomEndTimePreferenceController" />
 
     <com.android.settings.widget.SeekBarPreference
-            android:key="night_display_temperature"
-            android:title="@string/night_display_temperature_title"/>
+        android:key="night_display_temperature"
+        android:title="@string/night_display_temperature_title"
+        settings:keywords="@string/keywords_display_night_display"
+        settings:controller="com.android.settings.display.NightDisplayIntensityPreferenceController" />
+
+    <com.android.settings.applications.LayoutPreference
+        android:key="night_display_activated"
+        android:title="@string/night_display_status_title"
+        android:selectable="false"
+        android:layout="@layout/night_display_activation_button"
+        settings:keywords="@string/keywords_display_night_display"
+        settings:controller="com.android.settings.display.NightDisplayActivationPreferenceController" />
+
+    <PreferenceCategory android:key="night_display_footer_category">
+        <com.android.settingslib.widget.FooterPreference />
+    </PreferenceCategory>
 
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/core/SliderPreferenceController.java b/src/com/android/settings/core/SliderPreferenceController.java
index 0baa868..a6e3a5b 100644
--- a/src/com/android/settings/core/SliderPreferenceController.java
+++ b/src/com/android/settings/core/SliderPreferenceController.java
@@ -16,7 +16,6 @@
 
 import android.content.Context;
 import android.support.v7.preference.Preference;
-import android.support.v7.preference.SeekBarPreference;
 
 import com.android.settings.slices.SliceData;
 
@@ -34,7 +33,13 @@
 
     @Override
     public void updateState(Preference preference) {
-        ((SeekBarPreference) preference).setValue(getSliderPosition());
+        if (preference instanceof com.android.settings.widget.SeekBarPreference) {
+            ((com.android.settings.widget.SeekBarPreference) preference)
+                .setProgress(getSliderPosition());
+        } else if (preference instanceof android.support.v7.preference.SeekBarPreference) {
+            ((android.support.v7.preference.SeekBarPreference) preference)
+                .setValue(getSliderPosition());
+        }
     }
 
     /**
diff --git a/src/com/android/settings/core/TogglePreferenceController.java b/src/com/android/settings/core/TogglePreferenceController.java
index 16cb18b..ffe7fd3 100644
--- a/src/com/android/settings/core/TogglePreferenceController.java
+++ b/src/com/android/settings/core/TogglePreferenceController.java
@@ -42,8 +42,8 @@
     /**
      * Set the Setting to {@param isChecked}
      *
-     * @param isChecked Is {@true} when the setting should be enabled.
-     * @return {@true} if the underlying setting is updated.
+     * @param isChecked Is {@code true} when the setting should be enabled.
+     * @return {@code true} if the underlying setting is updated.
      */
     public abstract boolean setChecked(boolean isChecked);
 
diff --git a/src/com/android/settings/display/NightDisplayActivationPreferenceController.java b/src/com/android/settings/display/NightDisplayActivationPreferenceController.java
new file mode 100644
index 0000000..9634739
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayActivationPreferenceController.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.R;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.core.TogglePreferenceController;
+
+public class NightDisplayActivationPreferenceController extends TogglePreferenceController {
+
+    private ColorDisplayController mController;
+    private NightDisplayTimeFormatter mTimeFormatter;
+    private Button mTurnOffButton;
+    private Button mTurnOnButton;
+
+    private final OnClickListener mListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mController.setActivated(!mController.isActivated());
+            updateStateInternal();
+        }
+    };
+
+    public NightDisplayActivationPreferenceController(Context context, String key) {
+        super(context, key);
+        mController = new ColorDisplayController(context);
+        mTimeFormatter = new NightDisplayTimeFormatter(context);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return ColorDisplayController.isAvailable(mContext) ? AVAILABLE : DISABLED_UNSUPPORTED;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        final LayoutPreference preference = (LayoutPreference) screen.findPreference(
+                getPreferenceKey());
+        mTurnOnButton = preference.findViewById(R.id.night_display_turn_on_button);
+        mTurnOnButton.setOnClickListener(mListener);
+        mTurnOffButton = preference.findViewById(R.id.night_display_turn_off_button);
+        mTurnOffButton.setOnClickListener(mListener);
+    }
+
+    @Override
+    public final void updateState(Preference preference) {
+        updateStateInternal();
+    }
+
+    /** FOR SLICES */
+
+    @Override
+    public boolean isChecked() {
+        return mController.isActivated();
+    }
+
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        return mController.setActivated(isChecked);
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mTimeFormatter.getAutoModeTimeSummary(mContext, mController);
+    }
+
+    private void updateStateInternal() {
+        if (mTurnOnButton == null || mTurnOffButton == null) {
+            return;
+        }
+
+        final boolean isActivated = mController.isActivated();
+        final int autoMode = mController.getAutoMode();
+
+        String buttonText;
+        if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
+            buttonText = mContext.getString(isActivated
+                            ? R.string.night_display_activation_off_custom
+                            : R.string.night_display_activation_on_custom,
+                    mTimeFormatter.getFormattedTimeString(isActivated
+                            ? mController.getCustomStartTime()
+                            : mController.getCustomEndTime()));
+        } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
+            buttonText = mContext.getString(isActivated
+                    ? R.string.night_display_activation_off_twilight
+                    : R.string.night_display_activation_on_twilight);
+        } else {
+            buttonText = mContext.getString(isActivated
+                    ? R.string.night_display_activation_off_manual
+                    : R.string.night_display_activation_on_manual);
+        }
+
+        if (isActivated) {
+            mTurnOnButton.setVisibility(View.GONE);
+            mTurnOffButton.setVisibility(View.VISIBLE);
+            mTurnOffButton.setText(buttonText);
+        } else {
+            mTurnOnButton.setVisibility(View.VISIBLE);
+            mTurnOffButton.setVisibility(View.GONE);
+            mTurnOnButton.setText(buttonText);
+        }
+    }
+}
diff --git a/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java b/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java
new file mode 100644
index 0000000..e7b7de2
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class NightDisplayAutoModePreferenceController extends BasePreferenceController
+        implements Preference.OnPreferenceChangeListener {
+
+    private DropDownPreference mPreference;
+    private ColorDisplayController mController;
+
+    public NightDisplayAutoModePreferenceController(Context context, String key) {
+        super(context, key);
+        mController = new ColorDisplayController(context);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return ColorDisplayController.isAvailable(mContext) ? AVAILABLE : DISABLED_UNSUPPORTED;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        mPreference = (DropDownPreference) screen.findPreference(getPreferenceKey());
+
+        mPreference.setEntries(new CharSequence[]{
+                mContext.getString(R.string.night_display_auto_mode_never),
+                mContext.getString(R.string.night_display_auto_mode_custom),
+                mContext.getString(R.string.night_display_auto_mode_twilight)
+        });
+        mPreference.setEntryValues(new CharSequence[]{
+                String.valueOf(ColorDisplayController.AUTO_MODE_DISABLED),
+                String.valueOf(ColorDisplayController.AUTO_MODE_CUSTOM),
+                String.valueOf(ColorDisplayController.AUTO_MODE_TWILIGHT)
+        });
+    }
+
+    @Override
+    public final void updateState(Preference preference) {
+        mPreference.setValue(String.valueOf(mController.getAutoMode()));
+    }
+
+    @Override
+    public final boolean onPreferenceChange(Preference preference, Object newValue) {
+        return mController.setAutoMode(Integer.parseInt((String) newValue));
+    }
+}
diff --git a/src/com/android/settings/display/NightDisplayCustomEndTimePreferenceController.java b/src/com/android/settings/display/NightDisplayCustomEndTimePreferenceController.java
new file mode 100644
index 0000000..524e7db
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayCustomEndTimePreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.core.BasePreferenceController;
+
+public class NightDisplayCustomEndTimePreferenceController extends BasePreferenceController {
+
+    private ColorDisplayController mController;
+    private NightDisplayTimeFormatter mTimeFormatter;
+
+    public NightDisplayCustomEndTimePreferenceController(Context context, String key) {
+        super(context, key);
+
+        mController = new ColorDisplayController(context);
+        mTimeFormatter = new NightDisplayTimeFormatter(context);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return ColorDisplayController.isAvailable(mContext) ? AVAILABLE : DISABLED_UNSUPPORTED;
+    }
+
+    @Override
+    public final void updateState(Preference preference) {
+        preference.setVisible(mController.getAutoMode() == ColorDisplayController.AUTO_MODE_CUSTOM);
+        preference.setSummary(mTimeFormatter.getFormattedTimeString(
+                mController.getCustomEndTime()));
+    }
+}
diff --git a/src/com/android/settings/display/NightDisplayCustomStartTimePreferenceController.java b/src/com/android/settings/display/NightDisplayCustomStartTimePreferenceController.java
new file mode 100644
index 0000000..4dda8f4
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayCustomStartTimePreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.core.BasePreferenceController;
+
+public class NightDisplayCustomStartTimePreferenceController extends BasePreferenceController {
+
+    private ColorDisplayController mController;
+    private NightDisplayTimeFormatter mTimeFormatter;
+
+    public NightDisplayCustomStartTimePreferenceController(Context context, String key) {
+        super(context, key);
+
+        mController = new ColorDisplayController(context);
+        mTimeFormatter = new NightDisplayTimeFormatter(context);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return ColorDisplayController.isAvailable(mContext) ? AVAILABLE : DISABLED_UNSUPPORTED;
+    }
+
+    @Override
+    public final void updateState(Preference preference) {
+        preference.setVisible(mController.getAutoMode() == ColorDisplayController.AUTO_MODE_CUSTOM);
+        preference.setSummary(mTimeFormatter.getFormattedTimeString(
+                mController.getCustomStartTime()));
+    }
+}
diff --git a/src/com/android/settings/display/NightDisplayFooterPreferenceController.java b/src/com/android/settings/display/NightDisplayFooterPreferenceController.java
new file mode 100644
index 0000000..228d271
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayFooterPreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.FooterPreference;
+
+public class NightDisplayFooterPreferenceController extends BasePreferenceController {
+
+    public NightDisplayFooterPreferenceController(Context context) {
+        super(context, FooterPreference.KEY_FOOTER);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return ColorDisplayController.isAvailable(mContext) ? AVAILABLE : DISABLED_UNSUPPORTED;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        preference.setTitle(R.string.night_display_text);
+    }
+}
diff --git a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
new file mode 100644
index 0000000..b039644
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.core.SliderPreferenceController;
+import com.android.settings.widget.SeekBarPreference;
+
+public class NightDisplayIntensityPreferenceController extends SliderPreferenceController {
+
+    private ColorDisplayController mController;
+
+    public NightDisplayIntensityPreferenceController(Context context, String key) {
+        super(context, key);
+        mController = new ColorDisplayController(context);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (!ColorDisplayController.isAvailable(mContext)) {
+            return DISABLED_UNSUPPORTED;
+        } else if (!mController.isActivated()) {
+            return DISABLED_DEPENDENT_SETTING;
+        }
+        return AVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final SeekBarPreference preference = (SeekBarPreference) screen.findPreference(
+            getPreferenceKey());
+        preference.setContinuousUpdates(true);
+        preference.setMax(getMaxSteps());
+    }
+
+    @Override
+    public final void updateState(Preference preference) {
+        super.updateState(preference);
+        preference.setEnabled(mController.isActivated());
+    }
+
+    @Override
+    public int getSliderPosition() {
+        return convertTemperature(mController.getColorTemperature());
+    }
+
+    @Override
+    public boolean setSliderPosition(int position) {
+        return mController.setColorTemperature(convertTemperature(position));
+    }
+
+    @Override
+    public int getMaxSteps() {
+        return convertTemperature(mController.getMinimumColorTemperature());
+    }
+
+    /**
+     * Inverts and range-adjusts a raw value from the SeekBar (i.e. [0, maxTemp-minTemp]), or
+     * converts an inverted and range-adjusted value to the raw SeekBar value, depending on the
+     * adjustment status of the input.
+     */
+    private int convertTemperature(int temperature) {
+        return mController.getMaximumColorTemperature() - temperature;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/display/NightDisplayPreference.java b/src/com/android/settings/display/NightDisplayPreference.java
index ea39f75..e463292 100644
--- a/src/com/android/settings/display/NightDisplayPreference.java
+++ b/src/com/android/settings/display/NightDisplayPreference.java
@@ -19,25 +19,20 @@
 import android.util.AttributeSet;
 
 import com.android.internal.app.ColorDisplayController;
-import com.android.settings.R;
 
-import java.text.DateFormat;
 import java.time.LocalTime;
-import java.util.Calendar;
-import java.util.TimeZone;
 
 public class NightDisplayPreference extends SwitchPreference
         implements ColorDisplayController.Callback {
 
     private ColorDisplayController mController;
-    private DateFormat mTimeFormatter;
+    private NightDisplayTimeFormatter mTimeFormatter;
 
     public NightDisplayPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         mController = new ColorDisplayController(context);
-        mTimeFormatter = android.text.format.DateFormat.getTimeFormat(context);
-        mTimeFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+        mTimeFormatter = new NightDisplayTimeFormatter(context);
     }
 
     @Override
@@ -59,53 +54,6 @@
         mController.setListener(null);
     }
 
-    private String getFormattedTimeString(LocalTime localTime) {
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(mTimeFormatter.getTimeZone());
-        c.set(Calendar.HOUR_OF_DAY, localTime.getHour());
-        c.set(Calendar.MINUTE, localTime.getMinute());
-        c.set(Calendar.SECOND, 0);
-        c.set(Calendar.MILLISECOND, 0);
-        return mTimeFormatter.format(c.getTime());
-    }
-
-    private void updateSummary() {
-        final Context context = getContext();
-
-        final boolean isActivated = mController.isActivated();
-        final int autoMode = mController.getAutoMode();
-
-        final String autoModeSummary;
-        switch (autoMode) {
-            default:
-            case ColorDisplayController.AUTO_MODE_DISABLED:
-                autoModeSummary = context.getString(isActivated
-                        ? R.string.night_display_summary_on_auto_mode_never
-                        : R.string.night_display_summary_off_auto_mode_never);
-                break;
-            case ColorDisplayController.AUTO_MODE_CUSTOM:
-                if (isActivated) {
-                    autoModeSummary = context.getString(
-                            R.string.night_display_summary_on_auto_mode_custom,
-                            getFormattedTimeString(mController.getCustomEndTime()));
-                } else {
-                    autoModeSummary = context.getString(
-                            R.string.night_display_summary_off_auto_mode_custom,
-                            getFormattedTimeString(mController.getCustomStartTime()));
-                }
-                break;
-            case ColorDisplayController.AUTO_MODE_TWILIGHT:
-                autoModeSummary = context.getString(isActivated
-                        ? R.string.night_display_summary_on_auto_mode_twilight
-                        : R.string.night_display_summary_off_auto_mode_twilight);
-                break;
-        }
-
-        final int summaryFormatResId = isActivated ? R.string.night_display_summary_on
-                : R.string.night_display_summary_off;
-        setSummary(context.getString(summaryFormatResId, autoModeSummary));
-    }
-
     @Override
     public void onActivated(boolean activated) {
         updateSummary();
@@ -125,4 +73,8 @@
     public void onCustomEndTimeChanged(LocalTime endTime) {
         updateSummary();
     }
+
+    private void updateSummary() {
+        setSummary(mTimeFormatter.getAutoModeTimeSummary(getContext(), mController));
+    }
 }
diff --git a/src/com/android/settings/display/NightDisplaySettings.java b/src/com/android/settings/display/NightDisplaySettings.java
index 4e0ebcd..e9bcde5 100644
--- a/src/com/android/settings/display/NightDisplaySettings.java
+++ b/src/com/android/settings/display/NightDisplaySettings.java
@@ -21,51 +21,32 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.provider.SearchIndexableResource;
-import android.support.v7.preference.DropDownPreference;
 import android.support.v7.preference.Preference;
-import android.support.v7.preference.TwoStatePreference;
 
 import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
-import com.android.settings.widget.SeekBarPreference;
-import com.android.settings.SettingsPreferenceFragment;
 import com.android.settingslib.core.AbstractPreferenceController;
 
-import java.text.DateFormat;
 import java.time.LocalTime;
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.List;
-import java.util.TimeZone;
 
 /**
  * Settings screen for Night display.
- * TODO (b/69912911) Upgrade to Dashboard fragment
  */
-public class NightDisplaySettings extends SettingsPreferenceFragment
-        implements ColorDisplayController.Callback, Preference.OnPreferenceChangeListener,
-        Indexable {
+public class NightDisplaySettings extends DashboardFragment
+        implements ColorDisplayController.Callback, Indexable {
 
-    private static final String KEY_NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
-    private static final String KEY_NIGHT_DISPLAY_START_TIME = "night_display_start_time";
-    private static final String KEY_NIGHT_DISPLAY_END_TIME = "night_display_end_time";
-    private static final String KEY_NIGHT_DISPLAY_ACTIVATED = "night_display_activated";
-    private static final String KEY_NIGHT_DISPLAY_TEMPERATURE = "night_display_temperature";
+    private static final String TAG = "NightDisplaySettings";
 
     private static final int DIALOG_START_TIME = 0;
     private static final int DIALOG_END_TIME = 1;
 
     private ColorDisplayController mController;
-    private DateFormat mTimeFormatter;
-
-    private DropDownPreference mAutoModePreference;
-    private Preference mStartTimePreference;
-    private Preference mEndTimePreference;
-    private TwoStatePreference mActivatedPreference;
-    private SeekBarPreference mTemperaturePreference;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -73,45 +54,6 @@
 
         final Context context = getContext();
         mController = new ColorDisplayController(context);
-
-        mTimeFormatter = android.text.format.DateFormat.getTimeFormat(context);
-        mTimeFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
-
-        mTemperaturePreference.setMax(convertTemperature(mController.getMinimumColorTemperature()));
-        mTemperaturePreference.setContinuousUpdates(true);
-    }
-
-    @Override
-    public int getHelpResource() {
-        return R.string.help_url_night_display;
-    }
-
-    @Override
-    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        super.onCreatePreferences(savedInstanceState, rootKey);
-
-        // Load the preferences from xml.
-        addPreferencesFromResource(R.xml.night_display_settings);
-        mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.night_display_text);
-        mAutoModePreference = (DropDownPreference) findPreference(KEY_NIGHT_DISPLAY_AUTO_MODE);
-        mStartTimePreference = findPreference(KEY_NIGHT_DISPLAY_START_TIME);
-        mEndTimePreference = findPreference(KEY_NIGHT_DISPLAY_END_TIME);
-        mActivatedPreference = (TwoStatePreference) findPreference(KEY_NIGHT_DISPLAY_ACTIVATED);
-        mTemperaturePreference = (SeekBarPreference) findPreference(KEY_NIGHT_DISPLAY_TEMPERATURE);
-
-        mAutoModePreference.setEntries(new CharSequence[]{
-                getString(R.string.night_display_auto_mode_never),
-                getString(R.string.night_display_auto_mode_custom),
-                getString(R.string.night_display_auto_mode_twilight)
-        });
-        mAutoModePreference.setEntryValues(new CharSequence[]{
-                String.valueOf(ColorDisplayController.AUTO_MODE_DISABLED),
-                String.valueOf(ColorDisplayController.AUTO_MODE_CUSTOM),
-                String.valueOf(ColorDisplayController.AUTO_MODE_TWILIGHT)
-        });
-        mAutoModePreference.setOnPreferenceChangeListener(this);
-        mActivatedPreference.setOnPreferenceChangeListener(this);
-        mTemperaturePreference.setOnPreferenceChangeListener(this);
     }
 
     @Override
@@ -120,14 +62,6 @@
 
         // Listen for changes only while visible.
         mController.setListener(this);
-
-        // Update the current state since it have changed while not visible.
-        onActivated(mController.isActivated());
-        onAutoModeChanged(mController.getAutoMode());
-        onCustomStartTimeChanged(mController.getCustomStartTime());
-        onCustomEndTimeChanged(mController.getCustomEndTime());
-        onColorTemperatureChanged(mController.getColorTemperature());
-        onDisplayColorModeChanged(mController.getColorMode());
     }
 
     @Override
@@ -140,12 +74,12 @@
 
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
-        if (preference == mStartTimePreference) {
-            showDialog(DIALOG_START_TIME);
-            return true;
-        } else if (preference == mEndTimePreference) {
+        if ("night_display_end_time".equals(preference.getKey())) {
             showDialog(DIALOG_END_TIME);
             return true;
+        } else if ("night_display_start_time".equals(preference.getKey())) {
+            showDialog(DIALOG_START_TIME);
+            return true;
         }
         return super.onPreferenceTreeClick(preference);
     }
@@ -188,63 +122,37 @@
 
     @Override
     public void onActivated(boolean activated) {
-        mActivatedPreference.setChecked(activated);
-        mTemperaturePreference.setEnabled(activated);
+        // Update activated and temperature preferences.
+        updatePreferenceStates();
     }
 
     @Override
     public void onAutoModeChanged(int autoMode) {
-        mAutoModePreference.setValue(String.valueOf(autoMode));
-
-        final boolean showCustomSchedule = autoMode == ColorDisplayController.AUTO_MODE_CUSTOM;
-        mStartTimePreference.setVisible(showCustomSchedule);
-        mEndTimePreference.setVisible(showCustomSchedule);
+        // Update auto mode, start time, and end time preferences.
+        updatePreferenceStates();
     }
 
     @Override
     public void onColorTemperatureChanged(int colorTemperature) {
-        mTemperaturePreference.setProgress(convertTemperature(colorTemperature));
-    }
-
-    private String getFormattedTimeString(LocalTime localTime) {
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(mTimeFormatter.getTimeZone());
-        c.set(Calendar.HOUR_OF_DAY, localTime.getHour());
-        c.set(Calendar.MINUTE, localTime.getMinute());
-        c.set(Calendar.SECOND, 0);
-        c.set(Calendar.MILLISECOND, 0);
-        return mTimeFormatter.format(c.getTime());
-    }
-
-    /**
-     * Inverts and range-adjusts a raw value from the SeekBar (i.e. [0, maxTemp-minTemp]), or
-     * converts an inverted and range-adjusted value to the raw SeekBar value, depending on the
-     * adjustment status of the input.
-     */
-    private int convertTemperature(int temperature) {
-        return mController.getMaximumColorTemperature() - temperature;
+        // Update temperature preference.
+        updatePreferenceStates();
     }
 
     @Override
     public void onCustomStartTimeChanged(LocalTime startTime) {
-        mStartTimePreference.setSummary(getFormattedTimeString(startTime));
+        // Update start time preference.
+        updatePreferenceStates();
     }
 
     @Override
     public void onCustomEndTimeChanged(LocalTime endTime) {
-        mEndTimePreference.setSummary(getFormattedTimeString(endTime));
+        // Update end time preference.
+        updatePreferenceStates();
     }
 
     @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        if (preference == mAutoModePreference) {
-            return mController.setAutoMode(Integer.parseInt((String) newValue));
-        } else if (preference == mActivatedPreference) {
-            return mController.setActivated((Boolean) newValue);
-        } else if (preference == mTemperaturePreference) {
-            return mController.setColorTemperature(convertTemperature((Integer) newValue));
-        }
-        return false;
+    protected int getPreferenceScreenResId() {
+        return R.xml.night_display_settings;
     }
 
     @Override
@@ -252,13 +160,33 @@
         return MetricsEvent.NIGHT_DISPLAY_SETTINGS;
     }
 
+    @Override
+    public int getHelpResource() {
+        return R.string.help_url_night_display;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        return buildPreferenceControllers(context);
+    }
+
+    private static List <AbstractPreferenceController> buildPreferenceControllers(Context context) {
+        final List<AbstractPreferenceController> controllers = new ArrayList<>(1);
+        controllers.add(new NightDisplayFooterPreferenceController(context));
+        return controllers;
+    }
+
     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
                 @Override
                 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
                         boolean enabled) {
                     final ArrayList<SearchIndexableResource> result = new ArrayList<>();
-
                     final SearchIndexableResource sir = new SearchIndexableResource(context);
                     sir.xmlResId = R.xml.night_display_settings;
                     result.add(sir);
@@ -269,5 +197,11 @@
                 protected boolean isPageSearchEnabled(Context context) {
                     return ColorDisplayController.isAvailable(context);
                 }
+
+                @Override
+                public List<AbstractPreferenceController> createPreferenceControllers(
+                    Context context) {
+                    return buildPreferenceControllers(context);
+                }
             };
 }
diff --git a/src/com/android/settings/display/NightDisplayTimeFormatter.java b/src/com/android/settings/display/NightDisplayTimeFormatter.java
new file mode 100644
index 0000000..48a1994
--- /dev/null
+++ b/src/com/android/settings/display/NightDisplayTimeFormatter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import android.content.Context;
+
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.R;
+
+import java.text.DateFormat;
+import java.time.LocalTime;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+public class NightDisplayTimeFormatter {
+
+    private DateFormat mTimeFormatter;
+
+    NightDisplayTimeFormatter(Context context) {
+        mTimeFormatter = android.text.format.DateFormat.getTimeFormat(context);
+        mTimeFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    public String getFormattedTimeString(LocalTime localTime) {
+        final Calendar c = Calendar.getInstance();
+        c.setTimeZone(mTimeFormatter.getTimeZone());
+        c.set(Calendar.HOUR_OF_DAY, localTime.getHour());
+        c.set(Calendar.MINUTE, localTime.getMinute());
+        c.set(Calendar.SECOND, 0);
+        c.set(Calendar.MILLISECOND, 0);
+        return mTimeFormatter.format(c.getTime());
+    }
+
+    public String getAutoModeTimeSummary(Context context, ColorDisplayController controller) {
+        final int summaryFormatResId = controller.isActivated() ? R.string.night_display_summary_on
+                : R.string.night_display_summary_off;
+        return context.getString(summaryFormatResId, getAutoModeSummary(context, controller));
+    }
+
+    private String getAutoModeSummary(Context context, ColorDisplayController controller) {
+        final boolean isActivated = controller.isActivated();
+        final int autoMode = controller.getAutoMode();
+        if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
+            if (isActivated) {
+                return context.getString(R.string.night_display_summary_on_auto_mode_custom,
+                        getFormattedTimeString(controller.getCustomEndTime()));
+            } else {
+                return context.getString(R.string.night_display_summary_off_auto_mode_custom,
+                        getFormattedTimeString(controller.getCustomStartTime()));
+            }
+        } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
+            return context.getString(isActivated
+                    ? R.string.night_display_summary_on_auto_mode_twilight
+                    : R.string.night_display_summary_off_auto_mode_twilight);
+        } else {
+            return context.getString(isActivated
+                    ? R.string.night_display_summary_on_auto_mode_never
+                    : R.string.night_display_summary_off_auto_mode_never);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/core/SettingsSliderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/core/SettingsSliderPreferenceControllerTest.java
new file mode 100644
index 0000000..8553f11
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/core/SettingsSliderPreferenceControllerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 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.settings.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import com.android.settings.widget.SeekBarPreference;
+
+import com.android.settings.slices.SliceData;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class SettingsSliderPreferenceControllerTest {
+
+    private FakeSliderPreferenceController mSliderController;
+
+    private SeekBarPreference mPreference;
+
+    @Before
+    public void setUp() {
+        mPreference = new SeekBarPreference(RuntimeEnvironment.application);
+        mSliderController = new FakeSliderPreferenceController(RuntimeEnvironment.application,
+                "key");
+
+        mPreference.setContinuousUpdates(true);
+        mPreference.setMax(mSliderController.getMaxSteps());
+    }
+
+    @Test
+    public void onPreferenceChange_updatesPosition() {
+        final int newValue = 28;
+
+        mSliderController.onPreferenceChange(mPreference, newValue);
+
+        assertThat(mSliderController.getSliderPosition()).isEqualTo(newValue);
+    }
+
+    @Test
+    public void updateState_setsPreferenceToCurrentValue() {
+        final int newValue = 28;
+        mSliderController.setSliderPosition(newValue);
+
+        mSliderController.updateState(mPreference);
+
+        assertThat(mPreference.getProgress()).isEqualTo(newValue);
+    }
+
+    @Test
+    public void testSliceType_returnsSliceType() {
+        assertThat(mSliderController.getSliceType()).isEqualTo(
+                SliceData.SliceType.SLIDER);
+    }
+
+    private class FakeSliderPreferenceController extends SliderPreferenceController {
+
+        private final int MAX_STEPS = 2112;
+        private int mPosition;
+
+        public FakeSliderPreferenceController(Context context, String key) {
+            super(context, key);
+        }
+
+        @Override
+        public int getSliderPosition() {
+            return mPosition;
+        }
+
+        @Override
+        public boolean setSliderPosition(int position) {
+            mPosition = position;
+            return true;
+        }
+
+        @Override
+        public int getMaxSteps() {
+            return MAX_STEPS;
+        }
+
+        @Override
+        public int getAvailabilityStatus() {
+            return AVAILABLE;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/core/SliderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/core/SliderPreferenceControllerTest.java
index f9f656e..2d598c5 100644
--- a/tests/robotests/src/com/android/settings/core/SliderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/core/SliderPreferenceControllerTest.java
@@ -30,10 +30,10 @@
 @RunWith(SettingsRobolectricTestRunner.class)
 public class SliderPreferenceControllerTest {
 
-    FakeSlider mSliderController;
+    private FakeSlider mSliderController;
 
-    Context mContext;
-    SeekBarPreference mPreference;
+    private Context mContext;
+    private SeekBarPreference mPreference;
 
     @Before
     public void setUp() {
diff --git a/tests/robotests/src/com/android/settings/display/NightDisplayActivationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/NightDisplayActivationPreferenceControllerTest.java
new file mode 100644
index 0000000..88adc51
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/NightDisplayActivationPreferenceControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings.Secure;
+import android.support.v7.preference.PreferenceScreen;
+
+import android.view.View;
+import com.android.settings.R;
+import com.android.settings.applications.LayoutPreference;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
+public class NightDisplayActivationPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    private LayoutPreference mPreference;
+    private Context mContext;
+    private NightDisplayActivationPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new LayoutPreference(mContext, R.layout.night_display_activation_button);
+        when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+        mController = new NightDisplayActivationPreferenceController(mContext,
+            "night_display_activation");
+        mController.displayPreference(mScreen);
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredUnavailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void onClick_activates() {
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_ACTIVATED, 0);
+
+        final View view = mPreference.findViewById(R.id.night_display_turn_on_button);
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+        view.performClick();
+
+        assertThat(Secure.getInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_ACTIVATED, -1))
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void onClick_deactivates() {
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+
+        final View view = mPreference.findViewById(R.id.night_display_turn_on_button);
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+        view.performClick();
+
+        assertThat(Secure.getInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_ACTIVATED, -1))
+                .isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/NightDisplayAutoModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/NightDisplayAutoModePreferenceControllerTest.java
new file mode 100644
index 0000000..20b0380
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/NightDisplayAutoModePreferenceControllerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings.Secure;
+import com.android.internal.app.ColorDisplayController;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
+public class NightDisplayAutoModePreferenceControllerTest {
+
+    private Context mContext;
+    private NightDisplayAutoModePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mController = new NightDisplayAutoModePreferenceController(mContext,
+            "night_display_auto_mode");
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredUnavailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChange_changesAutoMode() {
+        mController.onPreferenceChange(null,
+                String.valueOf(ColorDisplayController.AUTO_MODE_TWILIGHT));
+        assertThat(Secure.getInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, -1))
+                .isEqualTo(ColorDisplayController.AUTO_MODE_TWILIGHT);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/NightDisplayCustomEndTimePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/NightDisplayCustomEndTimePreferenceControllerTest.java
new file mode 100644
index 0000000..ed6618b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/NightDisplayCustomEndTimePreferenceControllerTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
+public class NightDisplayCustomEndTimePreferenceControllerTest {
+
+    private NightDisplayCustomEndTimePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mController = new NightDisplayCustomEndTimePreferenceController(
+                RuntimeEnvironment.application, "night_display_end_time");
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredUnavailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/NightDisplayCustomStartTimePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/NightDisplayCustomStartTimePreferenceControllerTest.java
new file mode 100644
index 0000000..c413000
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/NightDisplayCustomStartTimePreferenceControllerTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
+public class NightDisplayCustomStartTimePreferenceControllerTest {
+
+    private NightDisplayCustomStartTimePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mController = new NightDisplayCustomStartTimePreferenceController(
+                RuntimeEnvironment.application, "night_display_start_time");
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredUnavailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/NightDisplayFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/NightDisplayFooterPreferenceControllerTest.java
new file mode 100644
index 0000000..8168c20
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/NightDisplayFooterPreferenceControllerTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
+public class NightDisplayFooterPreferenceControllerTest {
+
+    private NightDisplayFooterPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mController = new NightDisplayFooterPreferenceController(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredUnavailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/display/NightDisplayIntensityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/NightDisplayIntensityPreferenceControllerTest.java
new file mode 100644
index 0000000..1a69b6b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/NightDisplayIntensityPreferenceControllerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 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.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings.Secure;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
+public class NightDisplayIntensityPreferenceControllerTest {
+
+    private Context mContext;
+    private NightDisplayIntensityPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mController = new NightDisplayIntensityPreferenceController(mContext,
+            "night_display_temperature");
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable_isActivated_available() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredAvailable_isNotActivated_available() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, true);
+        Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_ACTIVATED, 0);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_configuredUnavailable_unavailable() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_nightDisplayAvailable, false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChange_changesTemperature() {
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.integer.config_nightDisplayColorTemperatureMin, 2950);
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.integer.config_nightDisplayColorTemperatureMax, 3050);
+        // A slider-adjusted "20" here would be 1/5 from the left / least-intense, i.e. 3030.
+        mController.onPreferenceChange(null, 20);
+
+        assertThat(Secure.getInt(mContext.getContentResolver(),
+                Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1))
+                .isEqualTo(3030);
+    }
+}