1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.statusbar.policy;
16
17import android.content.BroadcastReceiver;
18import android.content.Context;
19import android.content.Intent;
20import android.content.IntentFilter;
21import android.opengl.Matrix;
22import android.provider.Settings.Secure;
23import android.util.MathUtils;
24import com.android.systemui.tuner.TunerService;
25
26import java.util.ArrayList;
27
28/**
29 * Listens for changes to twilight from the TwilightService.
30 *
31 * Also pushes the current matrix to accessibility based on the current twilight
32 * and various tuner settings.
33 */
34public class NightModeController implements TunerService.Tunable {
35
36    public static final String NIGHT_MODE_ADJUST_TINT = "tuner_night_mode_adjust_tint";
37    private static final String COLOR_MATRIX_CUSTOM_VALUES = "tuner_color_custom_values";
38
39    private static final String ACTION_TWILIGHT_CHANGED = "android.intent.action.TWILIGHT_CHANGED";
40
41    private static final String EXTRA_IS_NIGHT = "isNight";
42    private static final String EXTRA_AMOUNT = "amount";
43
44    // Night mode ~= 3400 K
45    private static final float[] NIGHT_VALUES = new float[] {
46        1, 0,     0,     0,
47        0, .754f, 0,     0,
48        0, 0,     .516f, 0,
49        0, 0,     0,     1,
50    };
51    public static final float[] IDENTITY_MATRIX = new float[] {
52        1, 0, 0, 0,
53        0, 1, 0, 0,
54        0, 0, 1, 0,
55        0, 0, 0, 1,
56    };
57
58    private final ArrayList<Listener> mListeners = new ArrayList<>();
59
60    private final Context mContext;
61
62    // This is whether or not this is the main NightMode controller in SysUI that should be
63    // updating relevant color matrixes or if its in the tuner process getting current state
64    // for UI.
65    private final boolean mUpdateMatrix;
66
67    private float[] mCustomMatrix;
68    private boolean mListening;
69    private boolean mAdjustTint;
70
71    private boolean mIsNight;
72    private float mAmount;
73    private boolean mIsAuto;
74
75    public NightModeController(Context context) {
76        this(context, false);
77    }
78
79    public NightModeController(Context context, boolean updateMatrix) {
80        mContext = context;
81        mUpdateMatrix = updateMatrix;
82        TunerService.get(mContext).addTunable(this, NIGHT_MODE_ADJUST_TINT,
83                COLOR_MATRIX_CUSTOM_VALUES, Secure.TWILIGHT_MODE);
84    }
85
86    public void setNightMode(boolean isNight) {
87        if (mIsAuto) {
88            if (mIsNight != isNight) {
89                TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, isNight
90                        ? Secure.TWILIGHT_MODE_AUTO_OVERRIDE_ON
91                        : Secure.TWILIGHT_MODE_AUTO_OVERRIDE_OFF);
92            } else {
93                TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE,
94                        Secure.TWILIGHT_MODE_AUTO);
95            }
96        } else {
97            TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, isNight
98                    ? Secure.TWILIGHT_MODE_LOCKED_ON : Secure.TWILIGHT_MODE_LOCKED_OFF);
99        }
100    }
101
102    public void setAuto(boolean auto) {
103        mIsAuto = auto;
104        if (auto) {
105            TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_AUTO);
106        } else {
107            // Lock into the current state
108            TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, mIsNight
109                    ? Secure.TWILIGHT_MODE_LOCKED_ON : Secure.TWILIGHT_MODE_LOCKED_OFF);
110        }
111    }
112
113    public boolean isAuto() {
114        return mIsAuto;
115    }
116
117    public void setAdjustTint(Boolean newValue) {
118        TunerService.get(mContext).setValue(NIGHT_MODE_ADJUST_TINT, ((Boolean) newValue) ? 1 : 0);
119    }
120
121    public void addListener(Listener listener) {
122        mListeners.add(listener);
123        listener.onNightModeChanged();
124        updateListening();
125    }
126
127    public void removeListener(Listener listener) {
128        mListeners.remove(listener);
129        updateListening();
130    }
131
132    private void updateListening() {
133        boolean shouldListen = mListeners.size() != 0 || (mUpdateMatrix && mAdjustTint);
134        if (shouldListen == mListening) return;
135        mListening = shouldListen;
136        if (mListening) {
137            mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_TWILIGHT_CHANGED));
138        } else {
139            mContext.unregisterReceiver(mReceiver);
140        }
141    }
142
143    public boolean isEnabled() {
144        if (!mListening) {
145            updateNightMode(mContext.registerReceiver(null,
146                    new IntentFilter(ACTION_TWILIGHT_CHANGED)));
147        }
148        return mIsNight;
149    }
150
151    public String getCustomValues() {
152        return TunerService.get(mContext).getValue(COLOR_MATRIX_CUSTOM_VALUES);
153    }
154
155    public void setCustomValues(String values) {
156        TunerService.get(mContext).setValue(COLOR_MATRIX_CUSTOM_VALUES, values);
157    }
158
159    @Override
160    public void onTuningChanged(String key, String newValue) {
161        if (COLOR_MATRIX_CUSTOM_VALUES.equals(key)) {
162            mCustomMatrix = newValue != null ? toValues(newValue) : null;
163            updateCurrentMatrix();
164        } else if (NIGHT_MODE_ADJUST_TINT.equals(key)) {
165            mAdjustTint = newValue == null || Integer.parseInt(newValue) != 0;
166            updateListening();
167            updateCurrentMatrix();
168        } else if (Secure.TWILIGHT_MODE.equals(key)) {
169            mIsAuto = newValue != null && Integer.parseInt(newValue) >= Secure.TWILIGHT_MODE_AUTO;
170        }
171    }
172
173    private void updateCurrentMatrix() {
174        if (!mUpdateMatrix) return;
175        if ((!mAdjustTint || mAmount == 0) && mCustomMatrix == null) {
176            TunerService.get(mContext).setValue(Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
177            return;
178        }
179        float[] values = scaleValues(IDENTITY_MATRIX, NIGHT_VALUES, mAdjustTint ? mAmount : 0);
180        if (mCustomMatrix != null) {
181            values = multiply(values, mCustomMatrix);
182        }
183        TunerService.get(mContext).setValue(Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
184                toString(values));
185    }
186
187    private void updateNightMode(Intent intent) {
188        mIsNight = intent != null && intent.getBooleanExtra(EXTRA_IS_NIGHT, false);
189        mAmount = intent != null ? intent.getFloatExtra(EXTRA_AMOUNT, 0) : 0;
190    }
191
192    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
193        @Override
194        public void onReceive(Context context, Intent intent) {
195            if (ACTION_TWILIGHT_CHANGED.equals(intent.getAction())) {
196                updateNightMode(intent);
197                updateCurrentMatrix();
198                for (int i = 0; i < mListeners.size(); i++) {
199                    mListeners.get(i).onNightModeChanged();
200                }
201            }
202        }
203    };
204
205    public interface Listener {
206        void onNightModeChanged();
207        void onTwilightAutoChanged();
208    }
209
210    private static float[] multiply(float[] matrix, float[] other) {
211        if (matrix == null) {
212            return other;
213        }
214        float[] result = new float[16];
215        Matrix.multiplyMM(result, 0, matrix, 0, other, 0);
216        return result;
217    }
218
219    private float[] scaleValues(float[] identityMatrix, float[] nightValues, float amount) {
220        float[] values = new float[identityMatrix.length];
221        for (int i = 0; i < values.length; i++) {
222            values[i] = MathUtils.lerp(identityMatrix[i], nightValues[i], amount);
223        }
224        return values;
225    }
226
227    public static String toString(float[] values) {
228        StringBuilder builder = new StringBuilder();
229        for (int i = 0; i < values.length; i++) {
230            if (builder.length() != 0) {
231                builder.append(',');
232            }
233            builder.append(values[i]);
234        }
235        return builder.toString();
236    }
237
238    public static float[] toValues(String customValues) {
239        String[] strValues = customValues.split(",");
240        float[] values = new float[strValues.length];
241        for (int i = 0; i < values.length; i++) {
242            values[i] = Float.parseFloat(strValues[i]);
243        }
244        return values;
245    }
246}
247