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