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 
15 package com.android.systemui.statusbar.policy;
16 
17 import android.content.BroadcastReceiver;
18 import android.content.Context;
19 import android.content.Intent;
20 import android.content.IntentFilter;
21 import android.opengl.Matrix;
22 import android.provider.Settings.Secure;
23 import android.util.MathUtils;
24 import com.android.systemui.tuner.TunerService;
25 
26 import 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  */
34 public 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 
NightModeController(Context context)75     public NightModeController(Context context) {
76         this(context, false);
77     }
78 
NightModeController(Context context, boolean updateMatrix)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 
setNightMode(boolean isNight)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 
setAuto(boolean auto)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 
isAuto()113     public boolean isAuto() {
114         return mIsAuto;
115     }
116 
setAdjustTint(Boolean newValue)117     public void setAdjustTint(Boolean newValue) {
118         TunerService.get(mContext).setValue(NIGHT_MODE_ADJUST_TINT, ((Boolean) newValue) ? 1 : 0);
119     }
120 
addListener(Listener listener)121     public void addListener(Listener listener) {
122         mListeners.add(listener);
123         listener.onNightModeChanged();
124         updateListening();
125     }
126 
removeListener(Listener listener)127     public void removeListener(Listener listener) {
128         mListeners.remove(listener);
129         updateListening();
130     }
131 
updateListening()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 
isEnabled()143     public boolean isEnabled() {
144         if (!mListening) {
145             updateNightMode(mContext.registerReceiver(null,
146                     new IntentFilter(ACTION_TWILIGHT_CHANGED)));
147         }
148         return mIsNight;
149     }
150 
getCustomValues()151     public String getCustomValues() {
152         return TunerService.get(mContext).getValue(COLOR_MATRIX_CUSTOM_VALUES);
153     }
154 
setCustomValues(String values)155     public void setCustomValues(String values) {
156         TunerService.get(mContext).setValue(COLOR_MATRIX_CUSTOM_VALUES, values);
157     }
158 
159     @Override
onTuningChanged(String key, String newValue)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 
updateCurrentMatrix()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 
updateNightMode(Intent intent)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 {
onNightModeChanged()206         void onNightModeChanged();
onTwilightAutoChanged()207         void onTwilightAutoChanged();
208     }
209 
multiply(float[] matrix, float[] other)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 
scaleValues(float[] identityMatrix, float[] nightValues, float amount)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 
toString(float[] values)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 
toValues(String customValues)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