Color transforms for the tuner

Change-Id: I615be65e2b020acd5520f93c04f426c4006221c5
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e276bc6..bfc56db 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -35,6 +35,7 @@
     public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
     public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
     public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
+    public static final int QS_COLOR_MATRIX = 265;
 
     /**
      * Logged when the user docks a window from recents by longpressing a task and dragging it to
diff --git a/packages/SystemUI/res/drawable/ic_colorize.xml b/packages/SystemUI/res/drawable/ic_colorize.xml
new file mode 100644
index 0000000..79fd6d9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_colorize.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        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/preference_matrix.xml
new file mode 100644
index 0000000..ebf486f
--- /dev/null
+++ b/packages/SystemUI/res/layout/preference_matrix.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+    <GridLayout
+        android:id="@+id/edit_group"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:rowCount="5"
+        android:columnCount="5">
+
+        <Space android:layout_width="40dp" />
+
+        <TextView
+            android:layout_width="40dp"
+            android:text="@string/color_modification_r"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <TextView
+            android:layout_width="40dp"
+            android:text="@string/color_modification_g"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <TextView
+            android:layout_width="40dp"
+            android:text="@string/color_modification_b"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <Space android:layout_width="40dp" />
+
+        <TextView
+            android:layout_width="40dp"
+            android:text="@string/color_modification_r"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+
+        <TextView
+            android:layout_width="40dp"
+            android:text="@string/color_modification_g"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+
+        <TextView
+            android:layout_width="40dp"
+            android:text="@string/color_modification_b"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+
+        <Space android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+        <EditText android:inputType="numberDecimal" android:layout_width="40dp" />
+
+    </GridLayout>
+    <Button
+        android:id="@+id/apply"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="bottom"
+        android:text="@string/color_apply" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/preference_widget_settings.xml b/packages/SystemUI/res/layout/preference_widget_settings.xml
new file mode 100644
index 0000000..082a295
--- /dev/null
+++ b/packages/SystemUI/res/layout/preference_widget_settings.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content">
+
+    <RadioButton
+        android:id="@+id/radio_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="4dp"
+        android:clickable="false"
+        android:focusable="false" />
+
+    <View
+        android:layout_width="1dp"
+        android:layout_height="match_parent"
+        android:background="?android:attr/listDivider" />
+
+    <ImageView
+        android:id="@+id/widget_icon"
+        android:layout_width="50dp"
+        android:layout_height="24dp"
+        android:tint="@android:color/black"
+        android:src="@drawable/ic_settings"
+        android:layout_gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/preference_widget_switch.xml b/packages/SystemUI/res/layout/preference_widget_switch.xml
new file mode 100644
index 0000000..49610de
--- /dev/null
+++ b/packages/SystemUI/res/layout/preference_widget_switch.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content">
+
+    <RadioButton
+        android:id="@+id/radio_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clickable="false"
+        android:focusable="false"
+        android:layout_marginEnd="4dp" />
+
+    <View
+        android:layout_width="1dp"
+        android:layout_height="match_parent"
+        android:background="?android:attr/listDivider" />
+
+    <Switch
+        android:id="@*android:id/switch_widget"
+        android:layout_width="50dp"
+        android:layout_height="wrap_content"
+        android:focusable="false"
+        android:clickable="false"
+        android:background="@null" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6658cfe..45ddd50 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1228,4 +1228,40 @@
     <!-- [CHAR LIMIT=100] Notification Importance slider: max importance level description -->
     <string name="notification_importance_max">Peek onto the screen and make sound</string>
 
+    <!-- Label for no color transform [CHAR LIMIT=30] -->
+    <string name="color_matrix_none">Normal colors</string>
+
+    <!-- Label for night color transform [CHAR LIMIT=30] -->
+    <string name="color_matrix_night">Night colors</string>
+
+    <!-- Label for custom color transform [CHAR LIMIT=30] -->
+    <string name="color_matrix_custom">Custom colors</string>
+
+    <!-- Label for unknown color transform [CHAR LIMIT=30] -->
+    <string name="color_matrix_unknown">Unknown colors</string>
+
+    <!-- Title for color transform [CHAR LIMIT=30] -->
+    <string name="color_transform">Color modification</string>
+
+    <!-- Title for setting to show Quick Settings tile [CHAR LIMIT=60] -->
+    <string name="color_matrix_show_qs">Show Quick Settings tile</string>
+
+    <!-- Title for switch to enable custom color transform [CHAR LIMIT=60] -->
+    <string name="color_enable_custom">Enable custom transform</string>
+
+    <!-- Button to apply settings [CHAR LIMIT=30] -->
+    <string name="color_apply">Apply</string>
+
+    <!-- Title of warning dialog about bad color settings. [CHAR LIMIT=30] -->
+    <string name="color_revert_title">Confirm settings</string>
+
+    <!-- Message warning user about custom color settings [CHAR LIMIT=NONE] -->
+    <string name="color_revert_message">Some color settings can make this
+        device unusable. Click OK to confirm these color settings,
+        otherwise these settings will reset after 10 seconds.</string>
+
+    <string name="color_modification_r" translatable="false">R</string>
+    <string name="color_modification_g" translatable="false">G</string>
+    <string name="color_modification_b" translatable="false">B</string>
+
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7e01866..a9176e0 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -310,7 +310,11 @@
     </style>
 
     <style name="TunerSettings" parent="@android:style/Theme.Material.Settings">
-        <item name="preferenceTheme">@android:style/Theme.Material.Settings</item>
+        <item name="preferenceTheme">@style/TunerPreferenceTheme</item>
+    </style>
+
+    <style name="TunerPreferenceTheme" parent="@android:style/Theme.Material.Settings">
+        <item name="@dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
     </style>
 
 </resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 6103216..f02f763 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -102,6 +102,11 @@
         android:title="@string/demo_mode"
         android:fragment="com.android.systemui.tuner.DemoModeFragment" />
 
+    <Preference
+        android:key="color_transform"
+        android:title="@string/color_transform"
+        android:fragment="com.android.systemui.tuner.ColorMatrixFragment" />
+
     <!-- Warning, this goes last. -->
     <Preference
         android:summary="@string/tuner_persistent_warning"
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 7f27ba7..e6d837a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -64,6 +64,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.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -368,6 +369,8 @@
         else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
         else if (tileSpec.equals("user")) return new UserTile(this);
         else if (tileSpec.equals("battery")) return new BatteryTile(this);
+        else if (tileSpec.equals(ColorMatrixTile.COLOR_MATRIX_SPEC))
+            return new ColorMatrixTile(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/tuner/ColorMatrixFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixFragment.java
new file mode 100644
index 0000000..8ed1b06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixFragment.java
@@ -0,0 +1,355 @@
+/**
+ * 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.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.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import java.util.Objects;
+
+public class ColorMatrixFragment extends PreferenceFragment implements TunerService.Tunable {
+
+    private static final String TAG = "ColorMatrixFragment";
+
+    public static final int CUSTOM_INDEX = 2;
+
+    // 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 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 SwitchPreference mShowQs;
+    private String mTiles;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Context context = getContext();
+        TunerService.get(context).addTunable(this, ColorMatrixTile.COLOR_MATRIX_CUSTOM_ENABLED,
+                ColorMatrixTile.COLOR_MATRIX_CUSTOM_VALUES, QSTileHost.TILES_SETTING,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX);
+    }
+
+    @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) {
+                int index = Integer.parseInt((String) newValue);
+                Settings.Secure.putString(context.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, getValues()[index]);
+                return true;
+            }
+        });
+        getPreferenceScreen().addPreference(mSelectPreference);
+
+        mShowQs = new SwitchPreference(context);
+        mShowQs.setTitle(R.string.color_matrix_show_qs);
+        mShowQs.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object newValue) {
+                boolean showTile = (Boolean) newValue;
+                String newTiles;
+                if (showTile) {
+                    newTiles = mTiles != null ? mTiles + "," + ColorMatrixTile.COLOR_MATRIX_SPEC
+                            : "default," + ColorMatrixTile.COLOR_MATRIX_SPEC;
+                } else {
+                    newTiles =
+                            mTiles.replace(mTiles.contains(ColorMatrixTile.COLOR_MATRIX_SPEC+ ",")
+                            ? ColorMatrixTile.COLOR_MATRIX_SPEC + ","
+                            : "," + ColorMatrixTile.COLOR_MATRIX_SPEC, "");
+                }
+                Settings.Secure.putString(context.getContentResolver(), QSTileHost.TILES_SETTING,
+                        newTiles);
+                return true;
+            }
+        });
+        getPreferenceScreen().addPreference(mShowQs);
+
+        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;
+                Settings.Secure.putInt(context.getContentResolver(),
+                        ColorMatrixTile.COLOR_MATRIX_CUSTOM_ENABLED, enabled ? 1 : 0);
+                return true;
+            }
+        });
+        getPreferenceScreen().addPreference(mEnableCustomPreference);
+
+        mCustomPreference = new MatrixPreference(context);
+        getPreferenceScreen().addPreference(mCustomPreference);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        TunerService.get(getContext()).removeTunable(this);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (ColorMatrixTile.COLOR_MATRIX_CUSTOM_ENABLED.equals(key)) {
+            mCustomEnabled = newValue != null && Integer.parseInt(newValue) != 0;
+            mEnableCustomPreference.setChecked(mCustomEnabled);
+            mCustomPreference.setEnabled(mCustomEnabled);
+            updateSelectOptions();
+        } else if (ColorMatrixTile.COLOR_MATRIX_CUSTOM_VALUES.equals(key)) {
+            mCustomValues = newValue;
+            mCustomPreference.setValues(mCustomValues);
+            updateSelectOptions();
+        } else if (QSTileHost.TILES_SETTING.equals(key)) {
+            mTiles = newValue;
+            boolean hasTile = newValue != null
+                    && newValue.contains(ColorMatrixTile.COLOR_MATRIX_SPEC);
+            mShowQs.setChecked(hasTile);
+        } else {
+            mCurrentValue = newValue;
+            updateSelectOptions();
+        }
+    }
+
+    private void updateSelectOptions() {
+        final int N = CUSTOM_INDEX + (mCustomEnabled ? 1 : 0);
+        String[] values = new String[N];
+        CharSequence[] totalNames = getColorTitles(getContext());
+        CharSequence[] names = new CharSequence[N];
+        for (int i = 0; i < N; i++) {
+            values[i] = String.valueOf(i);
+            names[i] = totalNames[i];
+        }
+        mSelectPreference.setEntries(names);
+        mSelectPreference.setEntryValues(values);
+        String[] entries = getValues();
+        for (int i = 0; i < values.length; i++) {
+            if (Objects.equals(entries[i], mCurrentValue)) {
+                mSelectPreference.setValueIndex(i);
+                return;
+            }
+        }
+        mSelectPreference.setSummary(R.string.color_matrix_unknown);
+        return;
+    }
+
+    private String[] getValues() {
+        String[] ret = getColorTransforms();
+        // Fill in custom based on tuner settings.
+        ret[CUSTOM_INDEX] = mCustomValues;
+        return ret;
+    }
+
+    private void startRevertTimer() {
+        getView().postDelayed(mResetColorMatrix, RESET_DELAY);
+    }
+
+    private void onApply() {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                ColorMatrixTile.COLOR_MATRIX_CUSTOM_VALUES, mCurrentValue);
+        getView().removeCallbacks(mResetColorMatrix);
+    }
+
+    private void onRevert() {
+        getView().removeCallbacks(mResetColorMatrix);
+        mResetColorMatrix.run();
+    }
+
+    public static String[] getColorTransforms() {
+        return new String[] {
+                null,
+                toString(NIGHT_VALUES),
+                null, // Blank spot for custom values
+                null, // Unknown
+        };
+    }
+
+    public static CharSequence[] getColorTitles(Context context) {
+        return new CharSequence[] {
+                context.getString(R.string.color_matrix_none),
+                context.getString(R.string.color_matrix_night),
+                context.getString(R.string.color_matrix_custom),
+                context.getString(R.string.color_matrix_unknown),
+        };
+    }
+
+    private 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();
+    }
+
+    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) {
+            if (customValues == null) {
+                mValues = IDENTITY_MATRIX;
+            } else {
+                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);
+            ViewGroup vg = (ViewGroup) holder.itemView.findViewById(R.id.edit_group);
+            if (mValues == null) {
+                return;
+            }
+            int childIndex = 0;
+            for (int i = 0; i < mValues.length; i++) {
+                final int index = i;
+                while (!(vg.getChildAt(childIndex) instanceof EditText)) {
+                    childIndex++;
+                }
+                final EditText editText = (EditText) vg.getChildAt(childIndex++);
+                editText.setText(String.valueOf(mValues[i]));
+                editText.addTextChangedListener(new TextWatcher() {
+                    @Override
+                    public void afterTextChanged(Editable s) {
+                        if (TextUtils.isEmpty(s.toString())) {
+                            return;
+                        }
+                        try {
+                            mValues[index] = Float.parseFloat(s.toString());
+                        } catch (NumberFormatException e) {
+                            mValues[index] = 0;
+                        }
+                    }
+
+                    @Override
+                    public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    }
+
+                    @Override
+                    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                    }
+                });
+            }
+            ((Button) holder.itemView.findViewById(R.id.apply)).setOnClickListener(this);
+        }
+
+        @Override
+        public void onClick(View v) {
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
+                    ColorMatrixFragment.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
new file mode 100644
index 0000000..1fd2352
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
@@ -0,0 +1,107 @@
+/**
+ * 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.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import libcore.util.Objects;
+
+public class ColorMatrixTile extends QSTile<QSTile.State> 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_MATRIX_SPEC = "colors";
+
+    private int mIndex;
+    private String mCurrentValue;
+
+    private boolean mCustomEnabled;
+    private String[] mValues;
+    private CharSequence[] mValueTitles;
+
+    public ColorMatrixTile(Host host) {
+        super(host);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening) {
+            mValues = ColorMatrixFragment.getColorTransforms();
+            mValueTitles = ColorMatrixFragment.getColorTitles(mContext);
+            TunerService.get(mContext).addTunable(this, COLOR_MATRIX_CUSTOM_ENABLED,
+                    COLOR_MATRIX_CUSTOM_VALUES,
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX);
+        } else {
+            TunerService.get(mContext).removeTunable(this);
+        }
+    }
+
+    @Override
+    protected State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleClick() {
+        mIndex++;
+        if (!mCustomEnabled && (mIndex == ColorMatrixFragment.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
+    public void onTuningChanged(String key, String newValue) {
+        if (COLOR_MATRIX_CUSTOM_ENABLED.equals(key)) {
+            mCustomEnabled = newValue != null && Integer.parseInt(newValue) != 0;
+        } else if (COLOR_MATRIX_CUSTOM_VALUES.equals(key)) {
+            mValues[ColorMatrixFragment.CUSTOM_INDEX] = newValue;
+        } else {
+            mCurrentValue = newValue;
+        }
+        // Last value is unknown, default to that.
+        mIndex = mValues.length - 1;
+        for (int i = 0; i < mValues.length - 1; i++) {
+            if (Objects.equal(mCurrentValue, mValues[i])) {
+                mIndex = i;
+                break;
+            }
+        }
+        refreshState();
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        state.icon = ResourceIcon.get(R.drawable.ic_colorize);
+        state.label = mValueTitles[mIndex];
+        state.contentDescription = mValueTitles[mIndex];
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_COLOR_MATRIX;
+    }
+}