blob: 9c02aab22e9d69534f8bf2685316a1a892e4ccec [file] [log] [blame]
Justin Klaassen911e8892016-06-21 18:24:24 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Christine Franks0ada2772019-02-25 13:54:57 -080017package com.android.server.display.color;
Justin Klaassen911e8892016-06-21 18:24:24 -070018
Christine Franks83cc5412018-07-03 14:46:07 -070019import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME;
20import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED;
21import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT;
Christine Franksd154fe52019-01-04 17:17:45 -080022import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC;
23import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
24import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
25import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
Christine Franksa4ed3762019-01-24 15:37:04 -080026
Christine Franks0ada2772019-02-25 13:54:57 -080027import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
28import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
Christine Franks39b03112018-07-03 14:46:07 -070029
Christine Franks09c229e2018-12-14 10:37:40 -080030import android.Manifest;
Justin Klaassen639214e2016-07-14 21:00:06 -070031import android.animation.Animator;
32import android.animation.AnimatorListenerAdapter;
33import android.animation.TypeEvaluator;
34import android.animation.ValueAnimator;
Justin Klaassen4346f632016-08-08 15:01:47 -070035import android.annotation.NonNull;
Justin Klaassen908b86c2016-08-08 09:18:42 -070036import android.annotation.Nullable;
Christine Franksf3529b22019-01-03 13:20:17 -080037import android.annotation.Size;
Christine Franks55194dc2019-01-15 13:47:06 -080038import android.annotation.UserIdInt;
Justin Klaassen911e8892016-06-21 18:24:24 -070039import android.app.AlarmManager;
40import android.content.BroadcastReceiver;
41import android.content.ContentResolver;
42import android.content.Context;
43import android.content.Intent;
44import android.content.IntentFilter;
Christine Franks09c229e2018-12-14 10:37:40 -080045import android.content.pm.PackageManager;
Daniel Solomon8b72c5b2018-11-25 11:07:13 -080046import android.content.res.Resources;
Justin Klaassen2696d992016-07-11 21:26:46 -070047import android.database.ContentObserver;
Daniel Solomon8b72c5b2018-11-25 11:07:13 -080048import android.graphics.ColorSpace;
Christine Franksf3529b22019-01-03 13:20:17 -080049import android.hardware.display.ColorDisplayManager;
Christine Franks83cc5412018-07-03 14:46:07 -070050import android.hardware.display.ColorDisplayManager.AutoMode;
Christine Franksd154fe52019-01-04 17:17:45 -080051import android.hardware.display.ColorDisplayManager.ColorMode;
Christine Franks39b03112018-07-03 14:46:07 -070052import android.hardware.display.IColorDisplayManager;
Christine Franks83cc5412018-07-03 14:46:07 -070053import android.hardware.display.Time;
Justin Klaassen2696d992016-07-11 21:26:46 -070054import android.net.Uri;
Justin Klaassen639214e2016-07-14 21:00:06 -070055import android.opengl.Matrix;
Christine Franks39b03112018-07-03 14:46:07 -070056import android.os.Binder;
Justin Klaassen911e8892016-06-21 18:24:24 -070057import android.os.Handler;
Daniel Solomona4ab5672019-01-22 19:35:55 -080058import android.os.IBinder;
Justin Klaassen911e8892016-06-21 18:24:24 -070059import android.os.Looper;
Christine Franks09c229e2018-12-14 10:37:40 -080060import android.os.Message;
Christine Franksd154fe52019-01-04 17:17:45 -080061import android.os.SystemProperties;
Justin Klaassen911e8892016-06-21 18:24:24 -070062import android.os.UserHandle;
63import android.provider.Settings.Secure;
Christine Franks57fdde82018-07-03 14:46:07 -070064import android.provider.Settings.System;
Justin Klaassen639214e2016-07-14 21:00:06 -070065import android.util.MathUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070066import android.util.Slog;
Christine Franks55194dc2019-01-15 13:47:06 -080067import android.view.SurfaceControl;
Christine Franksa4ed3762019-01-24 15:37:04 -080068import android.view.SurfaceControl.DisplayPrimaries;
Christine Franks9114f462019-01-04 11:27:30 -080069import android.view.accessibility.AccessibilityManager;
Justin Klaassen639214e2016-07-14 21:00:06 -070070import android.view.animation.AnimationUtils;
Daniel Solomona4ab5672019-01-22 19:35:55 -080071
Christine Franks39b03112018-07-03 14:46:07 -070072import com.android.internal.R;
Christine Franks57fdde82018-07-03 14:46:07 -070073import com.android.internal.annotations.VisibleForTesting;
Christine Franksf3529b22019-01-03 13:20:17 -080074import com.android.internal.util.DumpUtils;
Christine Franks57fdde82018-07-03 14:46:07 -070075import com.android.server.DisplayThread;
Justin Klaassen911e8892016-06-21 18:24:24 -070076import com.android.server.SystemService;
77import com.android.server.twilight.TwilightListener;
78import com.android.server.twilight.TwilightManager;
79import com.android.server.twilight.TwilightState;
Christine Franksa4ed3762019-01-24 15:37:04 -080080
Christine Franksf3529b22019-01-03 13:20:17 -080081import java.io.FileDescriptor;
Daniel Solomon8b72c5b2018-11-25 11:07:13 -080082import java.io.PrintWriter;
Christine Franksf3529b22019-01-03 13:20:17 -080083import java.lang.ref.WeakReference;
Christine Franks57fdde82018-07-03 14:46:07 -070084import java.time.DateTimeException;
85import java.time.Instant;
Christine Franks03213462017-08-25 13:57:26 -070086import java.time.LocalDateTime;
87import java.time.LocalTime;
88import java.time.ZoneId;
Christine Franks57fdde82018-07-03 14:46:07 -070089import java.time.format.DateTimeParseException;
Christine Franks8ad71492017-10-24 19:04:22 -070090
Justin Klaassen911e8892016-06-21 18:24:24 -070091/**
Christine Franks39b03112018-07-03 14:46:07 -070092 * Controls the display's color transforms.
Justin Klaassen911e8892016-06-21 18:24:24 -070093 */
Christine Franks57fdde82018-07-03 14:46:07 -070094public final class ColorDisplayService extends SystemService {
Justin Klaassen911e8892016-06-21 18:24:24 -070095
Christine Franks7119e992019-03-14 17:28:21 -070096 static final String TAG = "ColorDisplayService";
Christine Franks7b83b4282017-01-18 14:55:00 -080097
98 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070099 * The identity matrix, used if one of the given matrices is {@code null}.
100 */
101 private static final float[] MATRIX_IDENTITY = new float[16];
Christine Franks57fdde82018-07-03 14:46:07 -0700102
Justin Klaassen639214e2016-07-14 21:00:06 -0700103 static {
104 Matrix.setIdentityM(MATRIX_IDENTITY, 0);
105 }
106
Christine Franks7119e992019-03-14 17:28:21 -0700107 /**
108 * The transition time, in milliseconds, for Night Display to turn on/off.
109 */
110 private static final long TRANSITION_DURATION = 3000L;
111
Christine Franks09c229e2018-12-14 10:37:40 -0800112 private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 0;
113 private static final int MSG_APPLY_NIGHT_DISPLAY_ANIMATED = 1;
114 private static final int MSG_APPLY_GLOBAL_SATURATION = 2;
Christine Franksc7fb9452019-02-04 08:45:33 -0800115 private static final int MSG_APPLY_DISPLAY_WHITE_BALANCE = 3;
Christine Franks09c229e2018-12-14 10:37:40 -0800116
Justin Klaassen639214e2016-07-14 21:00:06 -0700117 /**
Christine Franks83cc5412018-07-03 14:46:07 -0700118 * Return value if a setting has not been set.
119 */
120 private static final int NOT_SET = -1;
121
122 /**
Justin Klaassen639214e2016-07-14 21:00:06 -0700123 * Evaluator used to animate color matrix transitions.
124 */
125 private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
126
Christine Franks83cc5412018-07-03 14:46:07 -0700127 private final NightDisplayTintController mNightDisplayTintController =
128 new NightDisplayTintController();
Christine Franks245ffd42018-11-16 13:45:14 -0800129
Long Ling1d3f1892019-02-06 12:34:02 -0800130 @VisibleForTesting
131 final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController =
132 new DisplayWhiteBalanceTintController();
Christine Franks245ffd42018-11-16 13:45:14 -0800133
Christine Franks7119e992019-03-14 17:28:21 -0700134 private final TintController mGlobalSaturationTintController =
135 new GlobalSaturationTintController();
Christine Franks09c229e2018-12-14 10:37:40 -0800136
Christine Franks9114f462019-01-04 11:27:30 -0800137 /**
138 * Matrix and offset used for converting color to grayscale.
139 */
140 private static final float[] MATRIX_GRAYSCALE = new float[]{
141 .2126f, .2126f, .2126f, 0f,
142 .7152f, .7152f, .7152f, 0f,
143 .0722f, .0722f, .0722f, 0f,
144 0f, 0f, 0f, 1f
145 };
146
147 /**
148 * Matrix and offset used for luminance inversion. Represents a transform from RGB to YIQ color
149 * space, rotation around the Y axis by 180 degrees, transform back to RGB color space, and
150 * subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's
151 * ProgramCache for full implementation details.
152 */
Christine Franksd154fe52019-01-04 17:17:45 -0800153 private static final float[] MATRIX_INVERT_COLOR = new float[]{
Christine Franks9114f462019-01-04 11:27:30 -0800154 0.402f, -0.598f, -0.599f, 0f,
155 -1.174f, -0.174f, -1.175f, 0f,
156 -0.228f, -0.228f, 0.772f, 0f,
157 1f, 1f, 1f, 1f
158 };
159
Justin Klaassen2696d992016-07-11 21:26:46 -0700160 private final Handler mHandler;
161
Christine Franksf3529b22019-01-03 13:20:17 -0800162 private final AppSaturationController mAppSaturationController = new AppSaturationController();
163
Justin Klaassen911e8892016-06-21 18:24:24 -0700164 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -0700165 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -0700166 private boolean mBootCompleted;
167
Christine Franks57fdde82018-07-03 14:46:07 -0700168 private ContentObserver mContentObserver;
Christine Franks57fdde82018-07-03 14:46:07 -0700169
Christine Franks245ffd42018-11-16 13:45:14 -0800170 private DisplayWhiteBalanceListener mDisplayWhiteBalanceListener;
171
Christine Franks57fdde82018-07-03 14:46:07 -0700172 private NightDisplayAutoMode mNightDisplayAutoMode;
Justin Klaassen911e8892016-06-21 18:24:24 -0700173
Christine Franks5397f032017-11-01 18:35:16 -0700174 public ColorDisplayService(Context context) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700175 super(context);
Christine Franks83cc5412018-07-03 14:46:07 -0700176 mHandler = new TintHandler(DisplayThread.get().getLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -0700177 }
178
179 @Override
180 public void onStart() {
Christine Franks39b03112018-07-03 14:46:07 -0700181 publishBinderService(Context.COLOR_DISPLAY_SERVICE, new BinderService());
Christine Franks245ffd42018-11-16 13:45:14 -0800182 publishLocalService(ColorDisplayServiceInternal.class, new ColorDisplayServiceInternal());
Christine Franks0ada2772019-02-25 13:54:57 -0800183 publishLocalService(DisplayTransformManager.class, new DisplayTransformManager());
Justin Klaassen911e8892016-06-21 18:24:24 -0700184 }
185
186 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700187 public void onBootPhase(int phase) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800188 if (phase >= PHASE_BOOT_COMPLETED) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700189 mBootCompleted = true;
190
191 // Register listeners now that boot is complete.
192 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
193 setUp();
194 }
195 }
196 }
197
198 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700199 public void onStartUser(int userHandle) {
200 super.onStartUser(userHandle);
201
Justin Klaassen911e8892016-06-21 18:24:24 -0700202 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700203 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700204 }
205 }
206
207 @Override
208 public void onSwitchUser(int userHandle) {
209 super.onSwitchUser(userHandle);
210
Justin Klaassen2696d992016-07-11 21:26:46 -0700211 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700212 }
213
214 @Override
215 public void onStopUser(int userHandle) {
216 super.onStopUser(userHandle);
217
Justin Klaassen911e8892016-06-21 18:24:24 -0700218 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700219 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700220 }
221 }
222
Justin Klaassen2696d992016-07-11 21:26:46 -0700223 private void onUserChanged(int userHandle) {
224 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700225
Justin Klaassen2696d992016-07-11 21:26:46 -0700226 if (mCurrentUser != UserHandle.USER_NULL) {
227 if (mUserSetupObserver != null) {
228 cr.unregisterContentObserver(mUserSetupObserver);
229 mUserSetupObserver = null;
230 } else if (mBootCompleted) {
231 tearDown();
232 }
233 }
234
235 mCurrentUser = userHandle;
236
237 if (mCurrentUser != UserHandle.USER_NULL) {
238 if (!isUserSetupCompleted(cr, mCurrentUser)) {
239 mUserSetupObserver = new ContentObserver(mHandler) {
240 @Override
241 public void onChange(boolean selfChange, Uri uri) {
242 if (isUserSetupCompleted(cr, mCurrentUser)) {
243 cr.unregisterContentObserver(this);
244 mUserSetupObserver = null;
245
246 if (mBootCompleted) {
247 setUp();
248 }
249 }
250 }
251 };
252 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
Christine Franks39b03112018-07-03 14:46:07 -0700253 false /* notifyForDescendants */, mUserSetupObserver, mCurrentUser);
Justin Klaassen2696d992016-07-11 21:26:46 -0700254 } else if (mBootCompleted) {
255 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700256 }
257 }
258 }
259
Justin Klaassen2696d992016-07-11 21:26:46 -0700260 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
261 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
262 }
263
264 private void setUp() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700265 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
266
Christine Franks57fdde82018-07-03 14:46:07 -0700267 // Listen for external changes to any of the settings.
268 if (mContentObserver == null) {
269 mContentObserver = new ContentObserver(new Handler(DisplayThread.get().getLooper())) {
270 @Override
271 public void onChange(boolean selfChange, Uri uri) {
272 super.onChange(selfChange, uri);
273
274 final String setting = uri == null ? null : uri.getLastPathSegment();
275 if (setting != null) {
276 switch (setting) {
277 case Secure.NIGHT_DISPLAY_ACTIVATED:
Christine Franks78a4dd42019-02-08 11:09:30 -0800278 final boolean activated = mNightDisplayTintController
279 .isActivatedSetting();
Christine Franks83cc5412018-07-03 14:46:07 -0700280 if (mNightDisplayTintController.isActivatedStateNotSet()
281 || mNightDisplayTintController.isActivated() != activated) {
Christine Franks78a4dd42019-02-08 11:09:30 -0800282 mNightDisplayTintController.setActivated(activated);
Christine Franks83cc5412018-07-03 14:46:07 -0700283 }
Christine Franks57fdde82018-07-03 14:46:07 -0700284 break;
285 case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
Christine Franks78a4dd42019-02-08 11:09:30 -0800286 final int temperature = mNightDisplayTintController
287 .getColorTemperatureSetting();
Christine Franks83cc5412018-07-03 14:46:07 -0700288 if (mNightDisplayTintController.getColorTemperature()
289 != temperature) {
290 mNightDisplayTintController
291 .onColorTemperatureChanged(temperature);
292 }
Christine Franks57fdde82018-07-03 14:46:07 -0700293 break;
294 case Secure.NIGHT_DISPLAY_AUTO_MODE:
Christine Franks83cc5412018-07-03 14:46:07 -0700295 onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
Christine Franks57fdde82018-07-03 14:46:07 -0700296 break;
297 case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME:
298 onNightDisplayCustomStartTimeChanged(
Christine Franks83cc5412018-07-03 14:46:07 -0700299 getNightDisplayCustomStartTimeInternal().getLocalTime());
Christine Franks57fdde82018-07-03 14:46:07 -0700300 break;
301 case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
302 onNightDisplayCustomEndTimeChanged(
Christine Franks83cc5412018-07-03 14:46:07 -0700303 getNightDisplayCustomEndTimeInternal().getLocalTime());
Christine Franks57fdde82018-07-03 14:46:07 -0700304 break;
305 case System.DISPLAY_COLOR_MODE:
Christine Franks71e003e2019-01-24 14:40:20 -0800306 onDisplayColorModeChanged(getColorModeInternal());
Christine Franks57fdde82018-07-03 14:46:07 -0700307 break;
Christine Franks57fdde82018-07-03 14:46:07 -0700308 case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED:
Christine Franks9114f462019-01-04 11:27:30 -0800309 onAccessibilityInversionChanged();
310 onAccessibilityActivated();
311 break;
312 case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
313 onAccessibilityDaltonizerChanged();
314 onAccessibilityActivated();
315 break;
316 case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
317 onAccessibilityDaltonizerChanged();
Christine Franks57fdde82018-07-03 14:46:07 -0700318 break;
Christine Franks245ffd42018-11-16 13:45:14 -0800319 case Secure.DISPLAY_WHITE_BALANCE_ENABLED:
Daniel Solomon8b72c5b2018-11-25 11:07:13 -0800320 updateDisplayWhiteBalanceStatus();
Christine Franks245ffd42018-11-16 13:45:14 -0800321 break;
Christine Franks57fdde82018-07-03 14:46:07 -0700322 }
323 }
324 }
325 };
326 }
327 final ContentResolver cr = getContext().getContentResolver();
328 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_ACTIVATED),
329 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
330 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
331 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
332 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_AUTO_MODE),
333 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
334 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME),
335 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
336 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
337 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
338 cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
339 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
340 cr.registerContentObserver(
341 Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
342 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
343 cr.registerContentObserver(
344 Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
345 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
Christine Franks9114f462019-01-04 11:27:30 -0800346 cr.registerContentObserver(
347 Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER),
348 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
Christine Franks245ffd42018-11-16 13:45:14 -0800349 cr.registerContentObserver(Secure.getUriFor(Secure.DISPLAY_WHITE_BALANCE_ENABLED),
350 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
Justin Klaassen911e8892016-06-21 18:24:24 -0700351
Christine Franks11e63152019-03-14 11:16:06 -0700352 // Apply the accessibility settings first, since they override most other settings.
353 onAccessibilityInversionChanged();
354 onAccessibilityDaltonizerChanged();
355
Christine Frankscf388c22018-05-15 15:48:10 -0700356 // Set the color mode, if valid, and immediately apply the updated tint matrix based on the
357 // existing activated state. This ensures consistency of tint across the color mode change.
Christine Franks71e003e2019-01-24 14:40:20 -0800358 onDisplayColorModeChanged(getColorModeInternal());
Christine Frankscf388c22018-05-15 15:48:10 -0700359
Christine Franksa4ed3762019-01-24 15:37:04 -0800360 if (mNightDisplayTintController.isAvailable(getContext())) {
Christine Franks245ffd42018-11-16 13:45:14 -0800361 // Reset the activated state.
362 mNightDisplayTintController.setActivated(null);
Christine Frankscf388c22018-05-15 15:48:10 -0700363
Christine Franks245ffd42018-11-16 13:45:14 -0800364 // Prepare the night display color transformation matrix.
365 mNightDisplayTintController
366 .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
Christine Franks78a4dd42019-02-08 11:09:30 -0800367 mNightDisplayTintController
368 .setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
Christine Franks8ad71492017-10-24 19:04:22 -0700369
Christine Franks245ffd42018-11-16 13:45:14 -0800370 // Initialize the current auto mode.
Christine Franks83cc5412018-07-03 14:46:07 -0700371 onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
Christine Franks6418d0b2017-02-13 09:48:00 -0800372
Christine Franks83cc5412018-07-03 14:46:07 -0700373 // Force the initialization of the current saved activation state.
Christine Franks245ffd42018-11-16 13:45:14 -0800374 if (mNightDisplayTintController.isActivatedStateNotSet()) {
Christine Franks78a4dd42019-02-08 11:09:30 -0800375 mNightDisplayTintController
376 .setActivated(mNightDisplayTintController.isActivatedSetting());
Christine Franks245ffd42018-11-16 13:45:14 -0800377 }
378 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700379
Christine Franksa4ed3762019-01-24 15:37:04 -0800380 if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
Christine Franks245ffd42018-11-16 13:45:14 -0800381 // Prepare the display white balance transform matrix.
Daniel Solomon8b72c5b2018-11-25 11:07:13 -0800382 mDisplayWhiteBalanceTintController.setUp(getContext(), true /* needsLinear */);
Christine Franks245ffd42018-11-16 13:45:14 -0800383
Daniel Solomon8b72c5b2018-11-25 11:07:13 -0800384 updateDisplayWhiteBalanceStatus();
Justin Klaassen911e8892016-06-21 18:24:24 -0700385 }
386 }
387
Justin Klaassen2696d992016-07-11 21:26:46 -0700388 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700389 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
390
Christine Franks57fdde82018-07-03 14:46:07 -0700391 getContext().getContentResolver().unregisterContentObserver(mContentObserver);
392
Christine Franksa4ed3762019-01-24 15:37:04 -0800393 if (mNightDisplayTintController.isAvailable(getContext())) {
Christine Franks245ffd42018-11-16 13:45:14 -0800394 if (mNightDisplayAutoMode != null) {
395 mNightDisplayAutoMode.onStop();
396 mNightDisplayAutoMode = null;
397 }
398 mNightDisplayTintController.endAnimator();
Justin Klaassen911e8892016-06-21 18:24:24 -0700399 }
400
Christine Franksa4ed3762019-01-24 15:37:04 -0800401 if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
Christine Franks245ffd42018-11-16 13:45:14 -0800402 mDisplayWhiteBalanceTintController.endAnimator();
Justin Klaassen639214e2016-07-14 21:00:06 -0700403 }
Christine Franks6d21d342019-02-07 15:09:03 -0800404
405 if (mGlobalSaturationTintController.isAvailable(getContext())) {
406 mGlobalSaturationTintController.setActivated(null);
407 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700408 }
409
Christine Franks57fdde82018-07-03 14:46:07 -0700410 private void onNightDisplayAutoModeChanged(int autoMode) {
411 Slog.d(TAG, "onNightDisplayAutoModeChanged: autoMode=" + autoMode);
Justin Klaassenec8837a2016-08-23 12:04:42 -0700412
Christine Franks57fdde82018-07-03 14:46:07 -0700413 if (mNightDisplayAutoMode != null) {
414 mNightDisplayAutoMode.onStop();
415 mNightDisplayAutoMode = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700416 }
417
Christine Franks83cc5412018-07-03 14:46:07 -0700418 if (autoMode == AUTO_MODE_CUSTOM_TIME) {
Christine Franks57fdde82018-07-03 14:46:07 -0700419 mNightDisplayAutoMode = new CustomNightDisplayAutoMode();
Christine Franks83cc5412018-07-03 14:46:07 -0700420 } else if (autoMode == AUTO_MODE_TWILIGHT) {
Christine Franks57fdde82018-07-03 14:46:07 -0700421 mNightDisplayAutoMode = new TwilightNightDisplayAutoMode();
Justin Klaassen911e8892016-06-21 18:24:24 -0700422 }
423
Christine Franks57fdde82018-07-03 14:46:07 -0700424 if (mNightDisplayAutoMode != null) {
425 mNightDisplayAutoMode.onStart();
Justin Klaassen911e8892016-06-21 18:24:24 -0700426 }
427 }
428
Christine Franks57fdde82018-07-03 14:46:07 -0700429 private void onNightDisplayCustomStartTimeChanged(LocalTime startTime) {
430 Slog.d(TAG, "onNightDisplayCustomStartTimeChanged: startTime=" + startTime);
Justin Klaassenec8837a2016-08-23 12:04:42 -0700431
Christine Franks57fdde82018-07-03 14:46:07 -0700432 if (mNightDisplayAutoMode != null) {
433 mNightDisplayAutoMode.onCustomStartTimeChanged(startTime);
Justin Klaassen911e8892016-06-21 18:24:24 -0700434 }
435 }
436
Christine Franks57fdde82018-07-03 14:46:07 -0700437 private void onNightDisplayCustomEndTimeChanged(LocalTime endTime) {
438 Slog.d(TAG, "onNightDisplayCustomEndTimeChanged: endTime=" + endTime);
Justin Klaassenec8837a2016-08-23 12:04:42 -0700439
Christine Franks57fdde82018-07-03 14:46:07 -0700440 if (mNightDisplayAutoMode != null) {
441 mNightDisplayAutoMode.onCustomEndTimeChanged(endTime);
Justin Klaassen911e8892016-06-21 18:24:24 -0700442 }
443 }
444
Christine Franks57fdde82018-07-03 14:46:07 -0700445 private void onDisplayColorModeChanged(int mode) {
Christine Franks83cc5412018-07-03 14:46:07 -0700446 if (mode == NOT_SET) {
Christine Frankscf388c22018-05-15 15:48:10 -0700447 return;
448 }
449
Christine Franks245ffd42018-11-16 13:45:14 -0800450 mNightDisplayTintController.cancelAnimator();
451 mDisplayWhiteBalanceTintController.cancelAnimator();
452
Christine Franksa4ed3762019-01-24 15:37:04 -0800453 if (mNightDisplayTintController.isAvailable(getContext())) {
454 mNightDisplayTintController
455 .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
Christine Franks78a4dd42019-02-08 11:09:30 -0800456 mNightDisplayTintController
457 .setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
Christine Franksa4ed3762019-01-24 15:37:04 -0800458 }
Christine Franks245ffd42018-11-16 13:45:14 -0800459
Daniel Solomon8b72c5b2018-11-25 11:07:13 -0800460 updateDisplayWhiteBalanceStatus();
Christine Franks8ad71492017-10-24 19:04:22 -0700461
Christine Franks218e6562017-11-27 10:20:14 -0800462 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
Christine Franks245ffd42018-11-16 13:45:14 -0800463 dtm.setColorMode(mode, mNightDisplayTintController.getMatrix());
Christine Franks8ad71492017-10-24 19:04:22 -0700464 }
465
Christine Franks9114f462019-01-04 11:27:30 -0800466 private void onAccessibilityActivated() {
Christine Franks71e003e2019-01-24 14:40:20 -0800467 onDisplayColorModeChanged(getColorModeInternal());
Daniel Solomon317a3572018-03-30 18:36:37 -0700468 }
469
Christine Franks9114f462019-01-04 11:27:30 -0800470 /**
471 * Apply the accessibility daltonizer transform based on the settings value.
472 */
473 private void onAccessibilityDaltonizerChanged() {
474 final boolean enabled = Secure.getIntForUser(getContext().getContentResolver(),
475 Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, mCurrentUser) != 0;
476 final int daltonizerMode = enabled ? Secure.getIntForUser(getContext().getContentResolver(),
477 Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
478 AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
479 : AccessibilityManager.DALTONIZER_DISABLED;
480
481 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
482 if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
483 // Monochromacy isn't supported by the native Daltonizer implementation; use grayscale.
484 dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE,
485 MATRIX_GRAYSCALE);
486 dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
487 } else {
488 dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null);
489 dtm.setDaltonizerMode(daltonizerMode);
490 }
491 }
492
493 /**
494 * Apply the accessibility inversion transform based on the settings value.
495 */
496 private void onAccessibilityInversionChanged() {
497 final boolean enabled = Secure.getIntForUser(getContext().getContentResolver(),
498 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, mCurrentUser) != 0;
499 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
500 dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_INVERT_COLOR,
501 enabled ? MATRIX_INVERT_COLOR : null);
502 }
Christine Franks8ad71492017-10-24 19:04:22 -0700503
Christine Franks6418d0b2017-02-13 09:48:00 -0800504 /**
505 * Applies current color temperature matrix, or removes it if deactivated.
506 *
507 * @param immediate {@code true} skips transition animation
508 */
Christine Franks245ffd42018-11-16 13:45:14 -0800509 private void applyTint(TintController tintController, boolean immediate) {
510 tintController.cancelAnimator();
Christine Franks6418d0b2017-02-13 09:48:00 -0800511
Christine Franks6418d0b2017-02-13 09:48:00 -0800512 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
Christine Franks245ffd42018-11-16 13:45:14 -0800513 final float[] from = dtm.getColorMatrix(tintController.getLevel());
514 final float[] to = tintController.getMatrix();
Christine Franks6418d0b2017-02-13 09:48:00 -0800515
516 if (immediate) {
Christine Franks245ffd42018-11-16 13:45:14 -0800517 dtm.setColorMatrix(tintController.getLevel(), to);
Christine Franks6418d0b2017-02-13 09:48:00 -0800518 } else {
Christine Franks245ffd42018-11-16 13:45:14 -0800519 tintController.setAnimator(ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
520 from == null ? MATRIX_IDENTITY : from, to));
521 tintController.getAnimator().setDuration(TRANSITION_DURATION);
522 tintController.getAnimator().setInterpolator(AnimationUtils.loadInterpolator(
Christine Franks6418d0b2017-02-13 09:48:00 -0800523 getContext(), android.R.interpolator.fast_out_slow_in));
Christine Franks245ffd42018-11-16 13:45:14 -0800524 tintController.getAnimator().addUpdateListener((ValueAnimator animator) -> {
525 final float[] value = (float[]) animator.getAnimatedValue();
526 dtm.setColorMatrix(tintController.getLevel(), value);
Christine Franks6418d0b2017-02-13 09:48:00 -0800527 });
Christine Franks245ffd42018-11-16 13:45:14 -0800528 tintController.getAnimator().addListener(new AnimatorListenerAdapter() {
Christine Franks6418d0b2017-02-13 09:48:00 -0800529
530 private boolean mIsCancelled;
531
532 @Override
533 public void onAnimationCancel(Animator animator) {
534 mIsCancelled = true;
535 }
536
537 @Override
538 public void onAnimationEnd(Animator animator) {
539 if (!mIsCancelled) {
540 // Ensure final color matrix is set at the end of the animation. If the
541 // animation is cancelled then don't set the final color matrix so the new
542 // animator can pick up from where this one left off.
Christine Franks245ffd42018-11-16 13:45:14 -0800543 dtm.setColorMatrix(tintController.getLevel(), to);
Christine Franks6418d0b2017-02-13 09:48:00 -0800544 }
Christine Franks245ffd42018-11-16 13:45:14 -0800545 tintController.setAnimator(null);
Christine Franks6418d0b2017-02-13 09:48:00 -0800546 }
547 });
Christine Franks245ffd42018-11-16 13:45:14 -0800548 tintController.getAnimator().start();
Christine Franks6418d0b2017-02-13 09:48:00 -0800549 }
550 }
551
552 /**
Christine Franks39b03112018-07-03 14:46:07 -0700553 * Returns the first date time corresponding to the local time that occurs before the provided
554 * date time.
Christine Franks03213462017-08-25 13:57:26 -0700555 *
556 * @param compareTime the LocalDateTime to compare against
557 * @return the prior LocalDateTime corresponding to this local time
558 */
Christine Franks57fdde82018-07-03 14:46:07 -0700559 @VisibleForTesting
560 static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
Christine Franks03213462017-08-25 13:57:26 -0700561 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
562 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
563
564 // Check if the local time has passed, if so return the same time yesterday.
565 return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
566 }
567
568 /**
Christine Franks39b03112018-07-03 14:46:07 -0700569 * Returns the first date time corresponding to this local time that occurs after the provided
570 * date time.
Christine Franks03213462017-08-25 13:57:26 -0700571 *
572 * @param compareTime the LocalDateTime to compare against
573 * @return the next LocalDateTime corresponding to this local time
574 */
Christine Franks57fdde82018-07-03 14:46:07 -0700575 @VisibleForTesting
576 static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
Christine Franks03213462017-08-25 13:57:26 -0700577 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
578 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
579
580 // Check if the local time has passed, if so return the same time tomorrow.
581 return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
582 }
583
Long Ling1d3f1892019-02-06 12:34:02 -0800584 @VisibleForTesting
585 void updateDisplayWhiteBalanceStatus() {
Daniel Solomon8b72c5b2018-11-25 11:07:13 -0800586 boolean oldActivated = mDisplayWhiteBalanceTintController.isActivated();
Christine Franks0ada2772019-02-25 13:54:57 -0800587 mDisplayWhiteBalanceTintController.setActivated(isDisplayWhiteBalanceSettingEnabled()
588 && !mNightDisplayTintController.isActivated()
589 && DisplayTransformManager.needsLinearColorMatrix());
Daniel Solomon8b72c5b2018-11-25 11:07:13 -0800590 boolean activated = mDisplayWhiteBalanceTintController.isActivated();
591
592 if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
593 mDisplayWhiteBalanceListener.onDisplayWhiteBalanceStatusChanged(activated);
Christine Franks245ffd42018-11-16 13:45:14 -0800594 }
Daniel Solomon86508f82019-01-18 19:01:02 -0800595
596 // If disabled, clear the tint. If enabled, do nothing more here and let the next
597 // temperature update set the correct tint.
598 if (!activated) {
Christine Franksc7fb9452019-02-04 08:45:33 -0800599 mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
Daniel Solomon86508f82019-01-18 19:01:02 -0800600 }
Christine Franks245ffd42018-11-16 13:45:14 -0800601 }
602
603 private boolean isDisplayWhiteBalanceSettingEnabled() {
604 return Secure.getIntForUser(getContext().getContentResolver(),
605 Secure.DISPLAY_WHITE_BALANCE_ENABLED, 0, mCurrentUser) == 1;
606 }
607
Christine Franks39b03112018-07-03 14:46:07 -0700608 private boolean isDeviceColorManagedInternal() {
609 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
610 return dtm.isDeviceColorManaged();
611 }
612
Christine Franks55194dc2019-01-15 13:47:06 -0800613 private int getTransformCapabilitiesInternal() {
614 int availabilityFlags = ColorDisplayManager.CAPABILITY_NONE;
615 if (SurfaceControl.getProtectedContentSupport()) {
616 availabilityFlags |= ColorDisplayManager.CAPABILITY_PROTECTED_CONTENT;
617 }
618 final Resources res = getContext().getResources();
619 if (res.getBoolean(R.bool.config_setColorTransformAccelerated)) {
620 availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_GLOBAL;
621 }
622 if (res.getBoolean(R.bool.config_setColorTransformAcceleratedPerLayer)) {
623 availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_PER_APP;
624 }
625 return availabilityFlags;
626 }
627
Christine Franks83cc5412018-07-03 14:46:07 -0700628 private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) {
629 if (getNightDisplayAutoModeInternal() != autoMode) {
630 Secure.putStringForUser(getContext().getContentResolver(),
631 Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
632 null,
633 mCurrentUser);
634 }
635 return Secure.putIntForUser(getContext().getContentResolver(),
636 Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser);
637 }
638
639 private int getNightDisplayAutoModeInternal() {
640 int autoMode = getNightDisplayAutoModeRawInternal();
641 if (autoMode == NOT_SET) {
642 autoMode = getContext().getResources().getInteger(
643 R.integer.config_defaultNightDisplayAutoMode);
644 }
645 if (autoMode != AUTO_MODE_DISABLED
646 && autoMode != AUTO_MODE_CUSTOM_TIME
647 && autoMode != AUTO_MODE_TWILIGHT) {
648 Slog.e(TAG, "Invalid autoMode: " + autoMode);
649 autoMode = AUTO_MODE_DISABLED;
650 }
651 return autoMode;
652 }
653
654 private int getNightDisplayAutoModeRawInternal() {
655 return Secure
656 .getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,
657 NOT_SET, mCurrentUser);
658 }
659
660 private Time getNightDisplayCustomStartTimeInternal() {
661 int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
662 Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser);
663 if (startTimeValue == NOT_SET) {
664 startTimeValue = getContext().getResources().getInteger(
665 R.integer.config_defaultNightDisplayCustomStartTime);
666 }
667 return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000));
668 }
669
670 private boolean setNightDisplayCustomStartTimeInternal(Time startTime) {
671 return Secure.putIntForUser(getContext().getContentResolver(),
672 Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
673 startTime.getLocalTime().toSecondOfDay() * 1000,
674 mCurrentUser);
675 }
676
677 private Time getNightDisplayCustomEndTimeInternal() {
678 int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
679 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser);
680 if (endTimeValue == NOT_SET) {
681 endTimeValue = getContext().getResources().getInteger(
682 R.integer.config_defaultNightDisplayCustomEndTime);
683 }
684 return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000));
685 }
686
687 private boolean setNightDisplayCustomEndTimeInternal(Time endTime) {
688 return Secure.putIntForUser(getContext().getContentResolver(),
689 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000,
690 mCurrentUser);
691 }
692
Christine Franks57fdde82018-07-03 14:46:07 -0700693 /**
694 * Returns the last time the night display transform activation state was changed, or {@link
695 * LocalDateTime#MIN} if night display has never been activated.
696 */
Christine Franks245ffd42018-11-16 13:45:14 -0800697 private LocalDateTime getNightDisplayLastActivatedTimeSetting() {
Christine Franks57fdde82018-07-03 14:46:07 -0700698 final ContentResolver cr = getContext().getContentResolver();
699 final String lastActivatedTime = Secure.getStringForUser(
700 cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, getContext().getUserId());
701 if (lastActivatedTime != null) {
702 try {
703 return LocalDateTime.parse(lastActivatedTime);
704 } catch (DateTimeParseException ignored) {
705 }
706 // Uses the old epoch time.
707 try {
708 return LocalDateTime.ofInstant(
709 Instant.ofEpochMilli(Long.parseLong(lastActivatedTime)),
710 ZoneId.systemDefault());
711 } catch (DateTimeException | NumberFormatException ignored) {
712 }
713 }
714 return LocalDateTime.MIN;
715 }
716
Christine Franksf3529b22019-01-03 13:20:17 -0800717 private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) {
718 return mAppSaturationController
719 .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
720 }
721
Christine Franksd154fe52019-01-04 17:17:45 -0800722 private void setColorModeInternal(@ColorMode int colorMode) {
723 if (!isColorModeAvailable(colorMode)) {
724 throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
725 }
726 System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE,
727 colorMode,
728 mCurrentUser);
729 }
730
Christine Franks71e003e2019-01-24 14:40:20 -0800731 private @ColorMode int getColorModeInternal() {
Christine Franksd154fe52019-01-04 17:17:45 -0800732 final ContentResolver cr = getContext().getContentResolver();
733 if (Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
734 0, mCurrentUser) == 1
735 || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
736 0, mCurrentUser) == 1) {
737 // There are restrictions on the available color modes combined with a11y transforms.
738 if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
739 return COLOR_MODE_SATURATED;
740 } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
741 return COLOR_MODE_AUTOMATIC;
742 }
743 }
744
745 int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser);
746 if (colorMode == -1) {
747 // There might be a system property controlling color mode that we need to respect; if
748 // not, this will set a suitable default.
749 colorMode = getCurrentColorModeFromSystemProperties();
750 }
751
752 // This happens when a color mode is no longer available (e.g., after system update or B&R)
753 // or the device does not support any color mode.
754 if (!isColorModeAvailable(colorMode)) {
755 if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
756 colorMode = COLOR_MODE_NATURAL;
757 } else if (colorMode == COLOR_MODE_SATURATED
758 && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
759 colorMode = COLOR_MODE_AUTOMATIC;
760 } else if (colorMode == COLOR_MODE_AUTOMATIC
761 && isColorModeAvailable(COLOR_MODE_SATURATED)) {
762 colorMode = COLOR_MODE_SATURATED;
763 } else {
764 colorMode = -1;
765 }
766 }
767
768 return colorMode;
769 }
770
771 /**
772 * Get the current color mode from system properties, or return -1 if invalid.
773 *
Christine Franks0ada2772019-02-25 13:54:57 -0800774 * See {@link DisplayTransformManager}
Christine Franksd154fe52019-01-04 17:17:45 -0800775 */
Christine Franks78a4dd42019-02-08 11:09:30 -0800776 private @ColorMode int getCurrentColorModeFromSystemProperties() {
Christine Franksd154fe52019-01-04 17:17:45 -0800777 final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
778 if (displayColorSetting == 0) {
779 return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
780 ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
781 } else if (displayColorSetting == 1) {
782 return COLOR_MODE_SATURATED;
783 } else if (displayColorSetting == 2) {
784 return COLOR_MODE_AUTOMATIC;
785 } else {
786 return -1;
787 }
788 }
789
790 private boolean isColorModeAvailable(@ColorMode int colorMode) {
791 final int[] availableColorModes = getContext().getResources().getIntArray(
792 R.array.config_availableColorModes);
793 if (availableColorModes != null) {
794 for (int mode : availableColorModes) {
795 if (mode == colorMode) {
796 return true;
797 }
798 }
799 }
800 return false;
801 }
802
Christine Franksf3529b22019-01-03 13:20:17 -0800803 private void dumpInternal(PrintWriter pw) {
804 pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)");
Christine Franksa4ed3762019-01-24 15:37:04 -0800805
Christine Franks0ada2772019-02-25 13:54:57 -0800806 pw.println("Night display:");
Christine Franksa4ed3762019-01-24 15:37:04 -0800807 if (mNightDisplayTintController.isAvailable(getContext())) {
Christine Franksf3529b22019-01-03 13:20:17 -0800808 pw.println(" Activated: " + mNightDisplayTintController.isActivated());
Christine Franksa4ed3762019-01-24 15:37:04 -0800809 pw.println(" Color temp: " + mNightDisplayTintController.getColorTemperature());
Christine Franksf3529b22019-01-03 13:20:17 -0800810 } else {
811 pw.println(" Not available");
812 }
Christine Franksa4ed3762019-01-24 15:37:04 -0800813
814 pw.println("Global saturation:");
815 if (mGlobalSaturationTintController.isAvailable(getContext())) {
816 pw.println(" Activated: " + mGlobalSaturationTintController.isActivated());
817 } else {
818 pw.println(" Not available");
819 }
820
Christine Franksf3529b22019-01-03 13:20:17 -0800821 mAppSaturationController.dump(pw);
Christine Franksa4ed3762019-01-24 15:37:04 -0800822
823 pw.println("Display white balance:");
824 if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
825 pw.println(" Activated: " + mDisplayWhiteBalanceTintController.isActivated());
Long Ling1d3f1892019-02-06 12:34:02 -0800826 mDisplayWhiteBalanceTintController.dump(pw);
Christine Franksa4ed3762019-01-24 15:37:04 -0800827 } else {
828 pw.println(" Not available");
829 }
830
831 pw.println("Color mode: " + getColorModeInternal());
Christine Franksf3529b22019-01-03 13:20:17 -0800832 }
833
Christine Franks57fdde82018-07-03 14:46:07 -0700834 private abstract class NightDisplayAutoMode {
835
836 public abstract void onActivated(boolean activated);
837
Justin Klaassen911e8892016-06-21 18:24:24 -0700838 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800839
Justin Klaassen911e8892016-06-21 18:24:24 -0700840 public abstract void onStop();
Christine Franks57fdde82018-07-03 14:46:07 -0700841
842 public void onCustomStartTimeChanged(LocalTime startTime) {
843 }
844
845 public void onCustomEndTimeChanged(LocalTime endTime) {
846 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700847 }
848
Christine Franks57fdde82018-07-03 14:46:07 -0700849 private final class CustomNightDisplayAutoMode extends NightDisplayAutoMode implements
850 AlarmManager.OnAlarmListener {
Justin Klaassen911e8892016-06-21 18:24:24 -0700851
852 private final AlarmManager mAlarmManager;
853 private final BroadcastReceiver mTimeChangedReceiver;
854
Christine Franks03213462017-08-25 13:57:26 -0700855 private LocalTime mStartTime;
856 private LocalTime mEndTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700857
Christine Franks03213462017-08-25 13:57:26 -0700858 private LocalDateTime mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700859
Christine Franks57fdde82018-07-03 14:46:07 -0700860 CustomNightDisplayAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700861 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
862 mTimeChangedReceiver = new BroadcastReceiver() {
863 @Override
864 public void onReceive(Context context, Intent intent) {
865 updateActivated();
866 }
867 };
868 }
869
870 private void updateActivated() {
Christine Franks03213462017-08-25 13:57:26 -0700871 final LocalDateTime now = LocalDateTime.now();
872 final LocalDateTime start = getDateTimeBefore(mStartTime, now);
873 final LocalDateTime end = getDateTimeAfter(mEndTime, start);
874 boolean activate = now.isBefore(end);
Justin Klaassen911e8892016-06-21 18:24:24 -0700875
Christine Frankse5bb03e2017-02-10 17:36:10 -0800876 if (mLastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800877 // Maintain the existing activated state if within the current period.
Christine Franks0ada2772019-02-25 13:54:57 -0800878 if (mLastActivatedTime.isBefore(now)
879 && mLastActivatedTime.isAfter(start)
Christine Franks03213462017-08-25 13:57:26 -0700880 && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
Christine Franks78a4dd42019-02-08 11:09:30 -0800881 activate = mNightDisplayTintController.isActivatedSetting();
Justin Klaassen911e8892016-06-21 18:24:24 -0700882 }
883 }
884
Christine Franks0ada2772019-02-25 13:54:57 -0800885 if (mNightDisplayTintController.isActivatedStateNotSet()
886 || (mNightDisplayTintController.isActivated() != activate)) {
Christine Franks83cc5412018-07-03 14:46:07 -0700887 mNightDisplayTintController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700888 }
Christine Franks03213462017-08-25 13:57:26 -0700889
Christine Franks245ffd42018-11-16 13:45:14 -0800890 updateNextAlarm(mNightDisplayTintController.isActivated(), now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700891 }
892
Christine Franks03213462017-08-25 13:57:26 -0700893 private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
Justin Klaassen4346f632016-08-08 15:01:47 -0700894 if (activated != null) {
Christine Franks03213462017-08-25 13:57:26 -0700895 final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
896 : getDateTimeAfter(mStartTime, now);
897 final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
898 mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
Justin Klaassen911e8892016-06-21 18:24:24 -0700899 }
900 }
901
902 @Override
903 public void onStart() {
904 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
905 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
906 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
907
Christine Franks83cc5412018-07-03 14:46:07 -0700908 mStartTime = getNightDisplayCustomStartTimeInternal().getLocalTime();
909 mEndTime = getNightDisplayCustomEndTimeInternal().getLocalTime();
Justin Klaassen911e8892016-06-21 18:24:24 -0700910
Christine Franks57fdde82018-07-03 14:46:07 -0700911 mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800912
Justin Klaassen911e8892016-06-21 18:24:24 -0700913 // Force an update to initialize state.
914 updateActivated();
915 }
916
917 @Override
918 public void onStop() {
919 getContext().unregisterReceiver(mTimeChangedReceiver);
920
921 mAlarmManager.cancel(this);
922 mLastActivatedTime = null;
923 }
924
925 @Override
926 public void onActivated(boolean activated) {
Christine Franks57fdde82018-07-03 14:46:07 -0700927 mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
Christine Franks03213462017-08-25 13:57:26 -0700928 updateNextAlarm(activated, LocalDateTime.now());
Justin Klaassen911e8892016-06-21 18:24:24 -0700929 }
930
931 @Override
Christine Franks03213462017-08-25 13:57:26 -0700932 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700933 mStartTime = startTime;
934 mLastActivatedTime = null;
935 updateActivated();
936 }
937
938 @Override
Christine Franks03213462017-08-25 13:57:26 -0700939 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700940 mEndTime = endTime;
941 mLastActivatedTime = null;
942 updateActivated();
943 }
944
945 @Override
946 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700947 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700948 updateActivated();
949 }
950 }
951
Christine Franks57fdde82018-07-03 14:46:07 -0700952 private final class TwilightNightDisplayAutoMode extends NightDisplayAutoMode implements
953 TwilightListener {
Justin Klaassen911e8892016-06-21 18:24:24 -0700954
955 private final TwilightManager mTwilightManager;
Christine Franks57fdde82018-07-03 14:46:07 -0700956 private LocalDateTime mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700957
Christine Franks57fdde82018-07-03 14:46:07 -0700958 TwilightNightDisplayAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700959 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700960 }
961
Justin Klaassen908b86c2016-08-08 09:18:42 -0700962 private void updateActivated(TwilightState state) {
Justin Klaassen3da4c442017-05-05 15:19:33 -0700963 if (state == null) {
964 // If there isn't a valid TwilightState then just keep the current activated
965 // state.
966 return;
967 }
968
969 boolean activate = state.isNight();
Christine Franks57fdde82018-07-03 14:46:07 -0700970 if (mLastActivatedTime != null) {
Christine Franks03213462017-08-25 13:57:26 -0700971 final LocalDateTime now = LocalDateTime.now();
972 final LocalDateTime sunrise = state.sunrise();
973 final LocalDateTime sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800974 // Maintain the existing activated state if within the current period.
Christine Franks57fdde82018-07-03 14:46:07 -0700975 if (mLastActivatedTime.isBefore(now) && (mLastActivatedTime.isBefore(sunrise)
976 ^ mLastActivatedTime.isBefore(sunset))) {
Christine Franks78a4dd42019-02-08 11:09:30 -0800977 activate = mNightDisplayTintController.isActivatedSetting();
Justin Klaassen911e8892016-06-21 18:24:24 -0700978 }
979 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700980
Christine Franks245ffd42018-11-16 13:45:14 -0800981 if (mNightDisplayTintController.isActivatedStateNotSet() || (
982 mNightDisplayTintController.isActivated() != activate)) {
Christine Franks83cc5412018-07-03 14:46:07 -0700983 mNightDisplayTintController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700984 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700985 }
986
987 @Override
Christine Franks57fdde82018-07-03 14:46:07 -0700988 public void onActivated(boolean activated) {
989 mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
990 }
991
992 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700993 public void onStart() {
994 mTwilightManager.registerListener(this, mHandler);
Christine Franks57fdde82018-07-03 14:46:07 -0700995 mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
Justin Klaassen911e8892016-06-21 18:24:24 -0700996
997 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700998 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700999 }
1000
1001 @Override
1002 public void onStop() {
1003 mTwilightManager.unregisterListener(this);
Christine Franks57fdde82018-07-03 14:46:07 -07001004 mLastActivatedTime = null;
Justin Klaassen908b86c2016-08-08 09:18:42 -07001005 }
1006
1007 @Override
1008 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -07001009 Slog.d(TAG, "onTwilightStateChanged: isNight="
1010 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -07001011 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -07001012 }
1013 }
Justin Klaassen639214e2016-07-14 21:00:06 -07001014
1015 /**
1016 * Interpolates between two 4x4 color transform matrices (in column-major order).
1017 */
1018 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
1019
1020 /**
1021 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
1022 */
1023 private final float[] mResultMatrix = new float[16];
1024
1025 @Override
1026 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
1027 for (int i = 0; i < mResultMatrix.length; i++) {
1028 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
1029 }
1030 return mResultMatrix;
1031 }
1032 }
Christine Franks39b03112018-07-03 14:46:07 -07001033
Christine Franks83cc5412018-07-03 14:46:07 -07001034 private final class NightDisplayTintController extends TintController {
1035
Christine Franksa4ed3762019-01-24 15:37:04 -08001036 private final float[] mMatrix = new float[16];
Christine Franks83cc5412018-07-03 14:46:07 -07001037 private final float[] mColorTempCoefficients = new float[9];
Christine Franksa4ed3762019-01-24 15:37:04 -08001038
1039 private Boolean mIsAvailable;
Christine Franks83cc5412018-07-03 14:46:07 -07001040 private Integer mColorTemp;
1041
1042 /**
1043 * Set coefficients based on whether the color matrix is linear or not.
1044 */
1045 @Override
1046 public void setUp(Context context, boolean needsLinear) {
1047 final String[] coefficients = context.getResources().getStringArray(needsLinear
1048 ? R.array.config_nightDisplayColorTemperatureCoefficients
1049 : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
1050 for (int i = 0; i < 9 && i < coefficients.length; i++) {
1051 mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
1052 }
1053 }
1054
1055 @Override
1056 public void setMatrix(int cct) {
1057 if (mMatrix.length != 16) {
1058 Slog.d(TAG, "The display transformation matrix must be 4x4");
1059 return;
1060 }
1061
1062 Matrix.setIdentityM(mMatrix, 0);
1063
1064 final float squareTemperature = cct * cct;
1065 final float red = squareTemperature * mColorTempCoefficients[0]
1066 + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
1067 final float green = squareTemperature * mColorTempCoefficients[3]
1068 + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
1069 final float blue = squareTemperature * mColorTempCoefficients[6]
1070 + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
1071 mMatrix[0] = red;
1072 mMatrix[5] = green;
1073 mMatrix[10] = blue;
1074 }
1075
1076 @Override
1077 public float[] getMatrix() {
1078 return isActivated() ? mMatrix : MATRIX_IDENTITY;
1079 }
1080
1081 @Override
1082 public void setActivated(Boolean activated) {
1083 if (activated == null) {
1084 super.setActivated(null);
1085 return;
1086 }
1087
1088 boolean activationStateChanged = activated != isActivated();
1089
1090 if (!isActivatedStateNotSet() && activationStateChanged) {
1091 // This is a true state change, so set this as the last activation time.
1092 Secure.putStringForUser(getContext().getContentResolver(),
1093 Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
1094 LocalDateTime.now().toString(),
1095 mCurrentUser);
1096 }
1097
1098 if (isActivatedStateNotSet() || activationStateChanged) {
1099 super.setActivated(activated);
Christine Franks78a4dd42019-02-08 11:09:30 -08001100 if (isActivatedSetting() != activated) {
1101 Secure.putIntForUser(getContext().getContentResolver(),
1102 Secure.NIGHT_DISPLAY_ACTIVATED,
1103 activated ? 1 : 0, mCurrentUser);
1104 }
Christine Franks83cc5412018-07-03 14:46:07 -07001105 onActivated(activated);
1106 }
1107 }
1108
1109 @Override
1110 public int getLevel() {
1111 return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
1112 }
1113
Christine Franksa4ed3762019-01-24 15:37:04 -08001114 @Override
1115 public boolean isAvailable(Context context) {
1116 if (mIsAvailable == null) {
1117 mIsAvailable = ColorDisplayManager.isNightDisplayAvailable(context);
1118 }
1119 return mIsAvailable;
1120 }
1121
Christine Franks78a4dd42019-02-08 11:09:30 -08001122 private void onActivated(boolean activated) {
Christine Franks83cc5412018-07-03 14:46:07 -07001123 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
1124 if (mNightDisplayAutoMode != null) {
1125 mNightDisplayAutoMode.onActivated(activated);
1126 }
1127
Christine Franksa4ed3762019-01-24 15:37:04 -08001128 if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
Christine Franks83cc5412018-07-03 14:46:07 -07001129 updateDisplayWhiteBalanceStatus();
1130 }
1131
1132 mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED);
1133 }
1134
1135 int getColorTemperature() {
1136 return mColorTemp != null ? clampNightDisplayColorTemperature(mColorTemp)
Christine Franks78a4dd42019-02-08 11:09:30 -08001137 : getColorTemperatureSetting();
Christine Franks83cc5412018-07-03 14:46:07 -07001138 }
1139
1140 boolean setColorTemperature(int temperature) {
1141 mColorTemp = temperature;
1142 final boolean success = Secure.putIntForUser(getContext().getContentResolver(),
1143 Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser);
1144 onColorTemperatureChanged(temperature);
1145 return success;
1146 }
1147
1148 void onColorTemperatureChanged(int temperature) {
1149 setMatrix(temperature);
1150 mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE);
1151 }
Christine Franks78a4dd42019-02-08 11:09:30 -08001152
1153 boolean isActivatedSetting() {
Christine Franks44782612019-03-07 17:25:39 -08001154 if (mCurrentUser == UserHandle.USER_NULL) {
1155 return false;
1156 }
Christine Franks78a4dd42019-02-08 11:09:30 -08001157 return Secure.getIntForUser(getContext().getContentResolver(),
1158 Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1;
1159 }
1160
1161 int getColorTemperatureSetting() {
Christine Franks44782612019-03-07 17:25:39 -08001162 if (mCurrentUser == UserHandle.USER_NULL) {
1163 return NOT_SET;
1164 }
Christine Franks78a4dd42019-02-08 11:09:30 -08001165 return clampNightDisplayColorTemperature(Secure.getIntForUser(
1166 getContext().getContentResolver(), Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
1167 NOT_SET,
1168 mCurrentUser));
1169 }
1170
1171 private int clampNightDisplayColorTemperature(int colorTemperature) {
1172 if (colorTemperature == NOT_SET) {
1173 colorTemperature = getContext().getResources().getInteger(
1174 R.integer.config_nightDisplayColorTemperatureDefault);
1175 }
1176 final int minimumTemperature = ColorDisplayManager
1177 .getMinimumColorTemperature(getContext());
1178 final int maximumTemperature = ColorDisplayManager
1179 .getMaximumColorTemperature(getContext());
1180 if (colorTemperature < minimumTemperature) {
1181 colorTemperature = minimumTemperature;
1182 } else if (colorTemperature > maximumTemperature) {
1183 colorTemperature = maximumTemperature;
1184 }
1185
1186 return colorTemperature;
1187 }
Christine Franks83cc5412018-07-03 14:46:07 -07001188 }
1189
Long Ling1d3f1892019-02-06 12:34:02 -08001190 final class DisplayWhiteBalanceTintController extends TintController {
Christine Franks0ada2772019-02-25 13:54:57 -08001191
Long Ling1d3f1892019-02-06 12:34:02 -08001192 // Three chromaticity coordinates per color: X, Y, and Z
Christine Franks0ada2772019-02-25 13:54:57 -08001193 private static final int NUM_VALUES_PER_PRIMARY = 3;
Long Ling1d3f1892019-02-06 12:34:02 -08001194 // Four colors: red, green, blue, and white
Christine Franks0ada2772019-02-25 13:54:57 -08001195 private static final int NUM_DISPLAY_PRIMARIES_VALS = 4 * NUM_VALUES_PER_PRIMARY;
Long Ling1d3f1892019-02-06 12:34:02 -08001196
1197 private final Object mLock = new Object();
1198 @VisibleForTesting
1199 int mTemperatureMin;
1200 @VisibleForTesting
1201 int mTemperatureMax;
1202 private int mTemperatureDefault;
1203 private float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
1204 @VisibleForTesting
1205 ColorSpace.Rgb mDisplayColorSpaceRGB;
1206 private float[] mChromaticAdaptationMatrix;
1207 @VisibleForTesting
1208 int mCurrentColorTemperature;
1209 private float[] mCurrentColorTemperatureXYZ;
1210 private boolean mSetUp = false;
1211 private float[] mMatrixDisplayWhiteBalance = new float[16];
1212 private Boolean mIsAvailable;
1213
1214 @Override
1215 public void setUp(Context context, boolean needsLinear) {
1216 mSetUp = false;
1217 final Resources res = context.getResources();
1218
1219 ColorSpace.Rgb displayColorSpaceRGB = getDisplayColorSpaceFromSurfaceControl();
1220 if (displayColorSpaceRGB == null) {
1221 Slog.w(TAG, "Failed to get display color space from SurfaceControl, trying res");
1222 displayColorSpaceRGB = getDisplayColorSpaceFromResources(res);
1223 if (displayColorSpaceRGB == null) {
1224 Slog.e(TAG, "Failed to get display color space from resources");
1225 return;
1226 }
1227 }
1228
1229 final String[] nominalWhiteValues = res.getStringArray(
1230 R.array.config_displayWhiteBalanceDisplayNominalWhite);
1231 float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
1232 for (int i = 0; i < nominalWhiteValues.length; i++) {
1233 displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
1234 }
1235
1236 final int colorTemperatureMin = res.getInteger(
1237 R.integer.config_displayWhiteBalanceColorTemperatureMin);
1238 if (colorTemperatureMin <= 0) {
1239 Slog.e(TAG, "Display white balance minimum temperature must be greater than 0");
1240 return;
1241 }
1242
1243 final int colorTemperatureMax = res.getInteger(
1244 R.integer.config_displayWhiteBalanceColorTemperatureMax);
1245 if (colorTemperatureMax < colorTemperatureMin) {
1246 Slog.e(TAG, "Display white balance max temp must be greater or equal to min");
1247 return;
1248 }
1249
1250 final int colorTemperature = res.getInteger(
1251 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
1252
1253 synchronized (mLock) {
1254 mDisplayColorSpaceRGB = displayColorSpaceRGB;
1255 mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
1256 mTemperatureMin = colorTemperatureMin;
1257 mTemperatureMax = colorTemperatureMax;
1258 mTemperatureDefault = colorTemperature;
1259 mSetUp = true;
1260 }
1261
1262 setMatrix(mTemperatureDefault);
1263 }
1264
1265 @Override
1266 public float[] getMatrix() {
1267 return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance : MATRIX_IDENTITY;
1268 }
1269
1270 @Override
1271 public void setMatrix(int cct) {
1272 if (!mSetUp) {
1273 Slog.w(TAG, "Can't set display white balance temperature: uninitialized");
1274 return;
1275 }
1276
1277 if (cct < mTemperatureMin) {
1278 Slog.w(TAG, "Requested display color temperature is below allowed minimum");
1279 cct = mTemperatureMin;
1280 } else if (cct > mTemperatureMax) {
1281 Slog.w(TAG, "Requested display color temperature is above allowed maximum");
1282 cct = mTemperatureMax;
1283 }
1284
1285 Slog.d(TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct);
1286
1287 synchronized (mLock) {
1288 mCurrentColorTemperature = cct;
1289
1290 // Adapt the display's nominal white point to match the requested CCT value
1291 mCurrentColorTemperatureXYZ = ColorSpace.cctToIlluminantdXyz(cct);
1292
1293 mChromaticAdaptationMatrix =
1294 ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
1295 mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
1296
1297 // Convert the adaptation matrix to RGB space
1298 float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix,
1299 mDisplayColorSpaceRGB.getTransform());
1300 result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
1301
1302 // Normalize the transform matrix to peak white value in RGB space
1303 final float adaptedMaxR = result[0] + result[3] + result[6];
1304 final float adaptedMaxG = result[1] + result[4] + result[7];
1305 final float adaptedMaxB = result[2] + result[5] + result[8];
1306 final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
1307 for (int i = 0; i < result.length; i++) {
1308 result[i] /= denum;
1309 }
1310
1311 Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
1312 java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
1313 java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
1314 java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
1315 }
1316 }
1317
1318 @Override
1319 public int getLevel() {
1320 return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
1321 }
1322
1323 @Override
1324 public boolean isAvailable(Context context) {
1325 if (mIsAvailable == null) {
1326 mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context);
1327 }
1328 return mIsAvailable;
1329 }
1330
1331 /**
1332 * Format a given matrix into a string.
1333 *
1334 * @param matrix the matrix to format
1335 * @param cols number of columns in the matrix
1336 */
1337 private String matrixToString(float[] matrix, int cols) {
1338 if (matrix == null || cols <= 0) {
1339 Slog.e(TAG, "Invalid arguments when formatting matrix to string");
1340 return "";
1341 }
1342
1343 StringBuilder sb = new StringBuilder("");
1344 for (int i = 0; i < matrix.length; i++) {
1345 if (i % cols == 0) {
1346 sb.append("\n ");
1347 }
1348 sb.append(String.format("%9.6f ", matrix[i]));
1349 }
1350 return sb.toString();
1351 }
1352
1353 @Override
1354 public void dump(PrintWriter pw) {
1355 synchronized (mLock) {
1356 pw.println(" mSetUp = " + mSetUp);
1357 if (!mSetUp) {
1358 return;
1359 }
1360
1361 pw.println(" mTemperatureMin = " + mTemperatureMin);
1362 pw.println(" mTemperatureMax = " + mTemperatureMax);
1363 pw.println(" mTemperatureDefault = " + mTemperatureDefault);
1364 pw.println(" mCurrentColorTemperature = " + mCurrentColorTemperature);
Christine Franks0ada2772019-02-25 13:54:57 -08001365 pw.println(" mCurrentColorTemperatureXYZ = "
1366 + matrixToString(mCurrentColorTemperatureXYZ, 3));
1367 pw.println(" mDisplayColorSpaceRGB RGB-to-XYZ = "
1368 + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3));
1369 pw.println(" mChromaticAdaptationMatrix = "
1370 + matrixToString(mChromaticAdaptationMatrix, 3));
1371 pw.println(" mDisplayColorSpaceRGB XYZ-to-RGB = "
1372 + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3));
1373 pw.println(" mMatrixDisplayWhiteBalance = "
1374 + matrixToString(mMatrixDisplayWhiteBalance, 4));
Long Ling1d3f1892019-02-06 12:34:02 -08001375 }
1376 }
1377
1378 private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
1379 return new ColorSpace.Rgb(
Christine Franks0ada2772019-02-25 13:54:57 -08001380 "Display Color Space",
1381 redGreenBlueXYZ,
1382 whiteXYZ,
1383 2.2f // gamma, unused for display white balance
Long Ling1d3f1892019-02-06 12:34:02 -08001384 );
1385 }
1386
1387 private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
1388 final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
1389 if (displayToken == null) {
1390 return null;
1391 }
1392
1393 DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken);
Christine Franks0ada2772019-02-25 13:54:57 -08001394 if (primaries == null || primaries.red == null || primaries.green == null
1395 || primaries.blue == null || primaries.white == null) {
Long Ling1d3f1892019-02-06 12:34:02 -08001396 return null;
1397 }
1398
1399 return makeRgbColorSpaceFromXYZ(
Christine Franks0ada2772019-02-25 13:54:57 -08001400 new float[]{
1401 primaries.red.X, primaries.red.Y, primaries.red.Z,
1402 primaries.green.X, primaries.green.Y, primaries.green.Z,
1403 primaries.blue.X, primaries.blue.Y, primaries.blue.Z,
Long Ling1d3f1892019-02-06 12:34:02 -08001404 },
Christine Franks0ada2772019-02-25 13:54:57 -08001405 new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z}
1406 );
Long Ling1d3f1892019-02-06 12:34:02 -08001407 }
1408
1409 private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) {
1410 final String[] displayPrimariesValues = res.getStringArray(
1411 R.array.config_displayWhiteBalanceDisplayPrimaries);
1412 float[] displayRedGreenBlueXYZ =
1413 new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
1414 float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
1415
1416 for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
1417 displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
1418 }
1419
1420 for (int i = 0; i < displayWhiteXYZ.length; i++) {
1421 displayWhiteXYZ[i] = Float.parseFloat(
1422 displayPrimariesValues[displayRedGreenBlueXYZ.length + i]);
1423 }
1424
1425 return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ);
1426 }
Christine Franks0ada2772019-02-25 13:54:57 -08001427 }
Long Ling1d3f1892019-02-06 12:34:02 -08001428
Christine Franks245ffd42018-11-16 13:45:14 -08001429 /**
1430 * Local service that allows color transforms to be enabled from other system services.
1431 */
1432 public final class ColorDisplayServiceInternal {
1433
1434 /**
1435 * Set the current CCT value for the display white balance transform, and if the transform
1436 * is enabled, apply it.
1437 *
1438 * @param cct the color temperature in Kelvin.
1439 */
1440 public boolean setDisplayWhiteBalanceColorTemperature(int cct) {
1441 // Update the transform matrix even if it can't be applied.
Christine Franks245ffd42018-11-16 13:45:14 -08001442 mDisplayWhiteBalanceTintController.setMatrix(cct);
1443
1444 if (mDisplayWhiteBalanceTintController.isActivated()) {
Christine Franksc7fb9452019-02-04 08:45:33 -08001445 mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
Christine Franks245ffd42018-11-16 13:45:14 -08001446 return true;
1447 }
1448 return false;
1449 }
1450
1451 /**
1452 * Sets the listener and returns whether display white balance is currently enabled.
1453 */
1454 public boolean setDisplayWhiteBalanceListener(DisplayWhiteBalanceListener listener) {
1455 mDisplayWhiteBalanceListener = listener;
1456 return mDisplayWhiteBalanceTintController.isActivated();
1457 }
Daniel Solomon8b72c5b2018-11-25 11:07:13 -08001458
Christine Franksf3529b22019-01-03 13:20:17 -08001459 /**
1460 * Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
1461 * invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
1462 */
Christine Franks55194dc2019-01-15 13:47:06 -08001463 public boolean attachColorTransformController(String packageName, @UserIdInt int userId,
Christine Franksf3529b22019-01-03 13:20:17 -08001464 WeakReference<ColorTransformController> controller) {
1465 return mAppSaturationController
Christine Franks55194dc2019-01-15 13:47:06 -08001466 .addColorTransformController(packageName, userId, controller);
Christine Franksf3529b22019-01-03 13:20:17 -08001467 }
Christine Franks245ffd42018-11-16 13:45:14 -08001468 }
1469
1470 /**
1471 * Listener for changes in display white balance status.
1472 */
1473 public interface DisplayWhiteBalanceListener {
1474
1475 /**
1476 * Notify that the display white balance status has changed, either due to preemption by
1477 * another transform or the feature being turned off.
1478 */
1479 void onDisplayWhiteBalanceStatusChanged(boolean enabled);
1480 }
1481
Christine Franks09c229e2018-12-14 10:37:40 -08001482 private final class TintHandler extends Handler {
1483
Christine Franks83cc5412018-07-03 14:46:07 -07001484 private TintHandler(Looper looper) {
Christine Franks09c229e2018-12-14 10:37:40 -08001485 super(looper, null, true /* async */);
1486 }
1487
1488 @Override
1489 public void handleMessage(Message msg) {
1490 switch (msg.what) {
1491 case MSG_APPLY_GLOBAL_SATURATION:
1492 mGlobalSaturationTintController.setMatrix(msg.arg1);
1493 applyTint(mGlobalSaturationTintController, false);
1494 break;
Christine Franks83cc5412018-07-03 14:46:07 -07001495 case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE:
1496 applyTint(mNightDisplayTintController, true);
1497 break;
1498 case MSG_APPLY_NIGHT_DISPLAY_ANIMATED:
1499 applyTint(mNightDisplayTintController, false);
1500 break;
Christine Franksc7fb9452019-02-04 08:45:33 -08001501 case MSG_APPLY_DISPLAY_WHITE_BALANCE:
1502 applyTint(mDisplayWhiteBalanceTintController, false);
1503 break;
Christine Franks09c229e2018-12-14 10:37:40 -08001504 }
1505 }
1506 }
1507
Christine Franksf3529b22019-01-03 13:20:17 -08001508 /**
1509 * Interface for applying transforms to a given AppWindow.
1510 */
1511 public interface ColorTransformController {
1512
Christine Franksd154fe52019-01-04 17:17:45 -08001513 /**
1514 * Apply the given saturation (grayscale) matrix to the associated AppWindow.
1515 */
Christine Franksf3529b22019-01-03 13:20:17 -08001516 void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation);
1517 }
1518
Christine Franks83cc5412018-07-03 14:46:07 -07001519 @VisibleForTesting
1520 final class BinderService extends IColorDisplayManager.Stub {
Christine Franks57fdde82018-07-03 14:46:07 -07001521
Christine Franks39b03112018-07-03 14:46:07 -07001522 @Override
Christine Franksd154fe52019-01-04 17:17:45 -08001523 public void setColorMode(int colorMode) {
1524 getContext().enforceCallingOrSelfPermission(
1525 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1526 "Permission required to set display color mode");
1527 final long token = Binder.clearCallingIdentity();
1528 try {
1529 setColorModeInternal(colorMode);
1530 } finally {
1531 Binder.restoreCallingIdentity(token);
1532 }
1533 }
1534
1535 @Override
1536 public int getColorMode() {
1537 final long token = Binder.clearCallingIdentity();
1538 try {
1539 return getColorModeInternal();
1540 } finally {
1541 Binder.restoreCallingIdentity(token);
1542 }
1543 }
1544
1545 @Override
Christine Franks39b03112018-07-03 14:46:07 -07001546 public boolean isDeviceColorManaged() {
1547 final long token = Binder.clearCallingIdentity();
1548 try {
1549 return isDeviceColorManagedInternal();
1550 } finally {
1551 Binder.restoreCallingIdentity(token);
1552 }
1553 }
Christine Franks09c229e2018-12-14 10:37:40 -08001554
1555 @Override
1556 public boolean setSaturationLevel(int level) {
1557 final boolean hasTransformsPermission = getContext()
1558 .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
1559 == PackageManager.PERMISSION_GRANTED;
1560 final boolean hasLegacyPermission = getContext()
1561 .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
1562 == PackageManager.PERMISSION_GRANTED;
1563 if (!hasTransformsPermission && !hasLegacyPermission) {
1564 throw new SecurityException("Permission required to set display saturation level");
1565 }
1566 final long token = Binder.clearCallingIdentity();
1567 try {
1568 final Message message = mHandler.obtainMessage(MSG_APPLY_GLOBAL_SATURATION);
1569 message.arg1 = level;
1570 mHandler.sendMessage(message);
1571 } finally {
1572 Binder.restoreCallingIdentity(token);
1573 }
1574 return true;
1575 }
Christine Franksf3529b22019-01-03 13:20:17 -08001576
1577 @Override
Christine Franks6d21d342019-02-07 15:09:03 -08001578 public boolean isSaturationActivated() {
1579 getContext().enforceCallingPermission(
1580 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1581 "Permission required to get display saturation level");
1582 final long token = Binder.clearCallingIdentity();
1583 try {
1584 return !mGlobalSaturationTintController.isActivatedStateNotSet()
1585 && mGlobalSaturationTintController.isActivated();
1586 } finally {
1587 Binder.restoreCallingIdentity(token);
1588 }
1589 }
1590
1591 @Override
Christine Franksf3529b22019-01-03 13:20:17 -08001592 public boolean setAppSaturationLevel(String packageName, int level) {
1593 getContext().enforceCallingPermission(
1594 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1595 "Permission required to set display saturation level");
1596 final long token = Binder.clearCallingIdentity();
1597 try {
1598 return setAppSaturationLevelInternal(packageName, level);
1599 } finally {
1600 Binder.restoreCallingIdentity(token);
1601 }
1602 }
1603
Christine Franks55194dc2019-01-15 13:47:06 -08001604 public int getTransformCapabilities() {
1605 getContext().enforceCallingPermission(
1606 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1607 "Permission required to query transform capabilities");
1608 final long token = Binder.clearCallingIdentity();
1609 try {
1610 return getTransformCapabilitiesInternal();
1611 } finally {
1612 Binder.restoreCallingIdentity(token);
1613 }
1614 }
1615
Christine Franksf3529b22019-01-03 13:20:17 -08001616 @Override
Christine Franks83cc5412018-07-03 14:46:07 -07001617 public boolean setNightDisplayActivated(boolean activated) {
1618 getContext().enforceCallingOrSelfPermission(
1619 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1620 "Permission required to set night display activated");
1621 final long token = Binder.clearCallingIdentity();
1622 try {
1623 mNightDisplayTintController.setActivated(activated);
1624 return true;
1625 } finally {
1626 Binder.restoreCallingIdentity(token);
1627 }
1628 }
1629
1630 @Override
1631 public boolean isNightDisplayActivated() {
1632 final long token = Binder.clearCallingIdentity();
1633 try {
1634 return mNightDisplayTintController.isActivated();
1635 } finally {
1636 Binder.restoreCallingIdentity(token);
1637 }
1638 }
1639
1640 @Override
1641 public boolean setNightDisplayColorTemperature(int temperature) {
1642 getContext().enforceCallingOrSelfPermission(
1643 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1644 "Permission required to set night display temperature");
1645 final long token = Binder.clearCallingIdentity();
1646 try {
1647 return mNightDisplayTintController.setColorTemperature(temperature);
1648 } finally {
1649 Binder.restoreCallingIdentity(token);
1650 }
1651 }
1652
1653 @Override
1654 public int getNightDisplayColorTemperature() {
1655 final long token = Binder.clearCallingIdentity();
1656 try {
1657 return mNightDisplayTintController.getColorTemperature();
1658 } finally {
1659 Binder.restoreCallingIdentity(token);
1660 }
1661 }
1662
1663 @Override
1664 public boolean setNightDisplayAutoMode(int autoMode) {
1665 getContext().enforceCallingOrSelfPermission(
1666 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1667 "Permission required to set night display auto mode");
1668 final long token = Binder.clearCallingIdentity();
1669 try {
1670 return setNightDisplayAutoModeInternal(autoMode);
1671 } finally {
1672 Binder.restoreCallingIdentity(token);
1673 }
1674 }
1675
1676 @Override
1677 public int getNightDisplayAutoMode() {
1678 getContext().enforceCallingOrSelfPermission(
1679 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1680 "Permission required to get night display auto mode");
1681 final long token = Binder.clearCallingIdentity();
1682 try {
1683 return getNightDisplayAutoModeInternal();
1684 } finally {
1685 Binder.restoreCallingIdentity(token);
1686 }
1687 }
1688
1689 @Override
1690 public int getNightDisplayAutoModeRaw() {
1691 final long token = Binder.clearCallingIdentity();
1692 try {
1693 return getNightDisplayAutoModeRawInternal();
1694 } finally {
1695 Binder.restoreCallingIdentity(token);
1696 }
1697 }
1698
1699 @Override
1700 public boolean setNightDisplayCustomStartTime(Time startTime) {
1701 getContext().enforceCallingOrSelfPermission(
1702 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1703 "Permission required to set night display custom start time");
1704 final long token = Binder.clearCallingIdentity();
1705 try {
1706 return setNightDisplayCustomStartTimeInternal(startTime);
1707 } finally {
1708 Binder.restoreCallingIdentity(token);
1709 }
1710 }
1711
1712 @Override
1713 public Time getNightDisplayCustomStartTime() {
1714 final long token = Binder.clearCallingIdentity();
1715 try {
1716 return getNightDisplayCustomStartTimeInternal();
1717 } finally {
1718 Binder.restoreCallingIdentity(token);
1719 }
1720 }
1721
1722 @Override
1723 public boolean setNightDisplayCustomEndTime(Time endTime) {
1724 getContext().enforceCallingOrSelfPermission(
1725 Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
1726 "Permission required to set night display custom end time");
1727 final long token = Binder.clearCallingIdentity();
1728 try {
1729 return setNightDisplayCustomEndTimeInternal(endTime);
1730 } finally {
1731 Binder.restoreCallingIdentity(token);
1732 }
1733 }
1734
1735 @Override
1736 public Time getNightDisplayCustomEndTime() {
1737 final long token = Binder.clearCallingIdentity();
1738 try {
1739 return getNightDisplayCustomEndTimeInternal();
1740 } finally {
1741 Binder.restoreCallingIdentity(token);
1742 }
1743 }
1744
1745 @Override
Christine Franksf3529b22019-01-03 13:20:17 -08001746 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Christine Franksd154fe52019-01-04 17:17:45 -08001747 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
1748 return;
1749 }
Christine Franksf3529b22019-01-03 13:20:17 -08001750
1751 final long token = Binder.clearCallingIdentity();
1752 try {
1753 dumpInternal(pw);
1754 } finally {
1755 Binder.restoreCallingIdentity(token);
1756 }
1757 }
Christine Franks39b03112018-07-03 14:46:07 -07001758 }
Justin Klaassen911e8892016-06-21 18:24:24 -07001759}