1 /*
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 
17 package com.android.server.display;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TypeEvaluator;
22 import android.animation.ValueAnimator;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.AlarmManager;
26 import android.content.BroadcastReceiver;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.database.ContentObserver;
32 import android.net.Uri;
33 import android.opengl.Matrix;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.UserHandle;
37 import android.provider.Settings.Secure;
38 import android.util.MathUtils;
39 import android.util.Slog;
40 import android.view.animation.AnimationUtils;
41 
42 import com.android.internal.app.ColorDisplayController;
43 import com.android.server.SystemService;
44 import com.android.server.twilight.TwilightListener;
45 import com.android.server.twilight.TwilightManager;
46 import com.android.server.twilight.TwilightState;
47 
48 import java.time.LocalDateTime;
49 import java.time.LocalTime;
50 import java.time.ZoneId;
51 
52 import com.android.internal.R;
53 
54 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
55 
56 /**
57  * Tints the display at night.
58  */
59 public final class ColorDisplayService extends SystemService
60         implements ColorDisplayController.Callback {
61 
62     private static final String TAG = "ColorDisplayService";
63 
64     /**
65      * The transition time, in milliseconds, for Night Display to turn on/off.
66      */
67     private static final long TRANSITION_DURATION = 3000L;
68 
69     /**
70      * The identity matrix, used if one of the given matrices is {@code null}.
71      */
72     private static final float[] MATRIX_IDENTITY = new float[16];
73     static {
Matrix.setIdentityM(MATRIX_IDENTITY, 0)74         Matrix.setIdentityM(MATRIX_IDENTITY, 0);
75     }
76 
77     /**
78      * Evaluator used to animate color matrix transitions.
79      */
80     private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
81 
82     private final Handler mHandler;
83 
84     private float[] mMatrixNight = new float[16];
85 
86     private final float[] mColorTempCoefficients = new float[9];
87 
88     private int mCurrentUser = UserHandle.USER_NULL;
89     private ContentObserver mUserSetupObserver;
90     private boolean mBootCompleted;
91 
92     private ColorDisplayController mController;
93     private ValueAnimator mColorMatrixAnimator;
94     private Boolean mIsActivated;
95     private AutoMode mAutoMode;
96 
ColorDisplayService(Context context)97     public ColorDisplayService(Context context) {
98         super(context);
99         mHandler = new Handler(Looper.getMainLooper());
100     }
101 
102     @Override
onStart()103     public void onStart() {
104         // Nothing to publish.
105     }
106 
107     @Override
onBootPhase(int phase)108     public void onBootPhase(int phase) {
109         if (phase >= PHASE_BOOT_COMPLETED) {
110             mBootCompleted = true;
111 
112             // Register listeners now that boot is complete.
113             if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
114                 setUp();
115             }
116         }
117     }
118 
119     @Override
onStartUser(int userHandle)120     public void onStartUser(int userHandle) {
121         super.onStartUser(userHandle);
122 
123         if (mCurrentUser == UserHandle.USER_NULL) {
124             onUserChanged(userHandle);
125         }
126     }
127 
128     @Override
onSwitchUser(int userHandle)129     public void onSwitchUser(int userHandle) {
130         super.onSwitchUser(userHandle);
131 
132         onUserChanged(userHandle);
133     }
134 
135     @Override
onStopUser(int userHandle)136     public void onStopUser(int userHandle) {
137         super.onStopUser(userHandle);
138 
139         if (mCurrentUser == userHandle) {
140             onUserChanged(UserHandle.USER_NULL);
141         }
142     }
143 
onUserChanged(int userHandle)144     private void onUserChanged(int userHandle) {
145         final ContentResolver cr = getContext().getContentResolver();
146 
147         if (mCurrentUser != UserHandle.USER_NULL) {
148             if (mUserSetupObserver != null) {
149                 cr.unregisterContentObserver(mUserSetupObserver);
150                 mUserSetupObserver = null;
151             } else if (mBootCompleted) {
152                 tearDown();
153             }
154         }
155 
156         mCurrentUser = userHandle;
157 
158         if (mCurrentUser != UserHandle.USER_NULL) {
159             if (!isUserSetupCompleted(cr, mCurrentUser)) {
160                 mUserSetupObserver = new ContentObserver(mHandler) {
161                     @Override
162                     public void onChange(boolean selfChange, Uri uri) {
163                         if (isUserSetupCompleted(cr, mCurrentUser)) {
164                             cr.unregisterContentObserver(this);
165                             mUserSetupObserver = null;
166 
167                             if (mBootCompleted) {
168                                 setUp();
169                             }
170                         }
171                     }
172                 };
173                 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
174                         false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
175             } else if (mBootCompleted) {
176                 setUp();
177             }
178         }
179     }
180 
isUserSetupCompleted(ContentResolver cr, int userHandle)181     private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
182         return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
183     }
184 
setUp()185     private void setUp() {
186         Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
187 
188         // Create a new controller for the current user and start listening for changes.
189         mController = new ColorDisplayController(getContext(), mCurrentUser);
190         mController.setListener(this);
191 
192         // Set the color mode, if valid, and immediately apply the updated tint matrix based on the
193         // existing activated state. This ensures consistency of tint across the color mode change.
194         onDisplayColorModeChanged(mController.getColorMode());
195 
196         // Reset the activated state.
197         mIsActivated = null;
198 
199         setCoefficientMatrix(getContext(), DisplayTransformManager.needsLinearColorMatrix());
200 
201         // Prepare color transformation matrix.
202         setMatrix(mController.getColorTemperature(), mMatrixNight);
203 
204         // Initialize the current auto mode.
205         onAutoModeChanged(mController.getAutoMode());
206 
207         // Force the initialization current activated state.
208         if (mIsActivated == null) {
209             onActivated(mController.isActivated());
210         }
211     }
212 
tearDown()213     private void tearDown() {
214         Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
215 
216         if (mController != null) {
217             mController.setListener(null);
218             mController = null;
219         }
220 
221         if (mAutoMode != null) {
222             mAutoMode.onStop();
223             mAutoMode = null;
224         }
225 
226         if (mColorMatrixAnimator != null) {
227             mColorMatrixAnimator.end();
228             mColorMatrixAnimator = null;
229         }
230     }
231 
232     @Override
onActivated(boolean activated)233     public void onActivated(boolean activated) {
234         if (mIsActivated == null || mIsActivated != activated) {
235             Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
236 
237             mIsActivated = activated;
238 
239             if (mAutoMode != null) {
240                 mAutoMode.onActivated(activated);
241             }
242 
243             applyTint(false);
244         }
245     }
246 
247     @Override
onAutoModeChanged(int autoMode)248     public void onAutoModeChanged(int autoMode) {
249         Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
250 
251         if (mAutoMode != null) {
252             mAutoMode.onStop();
253             mAutoMode = null;
254         }
255 
256         if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
257             mAutoMode = new CustomAutoMode();
258         } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
259             mAutoMode = new TwilightAutoMode();
260         }
261 
262         if (mAutoMode != null) {
263             mAutoMode.onStart();
264         }
265     }
266 
267     @Override
onCustomStartTimeChanged(LocalTime startTime)268     public void onCustomStartTimeChanged(LocalTime startTime) {
269         Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
270 
271         if (mAutoMode != null) {
272             mAutoMode.onCustomStartTimeChanged(startTime);
273         }
274     }
275 
276     @Override
onCustomEndTimeChanged(LocalTime endTime)277     public void onCustomEndTimeChanged(LocalTime endTime) {
278         Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
279 
280         if (mAutoMode != null) {
281             mAutoMode.onCustomEndTimeChanged(endTime);
282         }
283     }
284 
285     @Override
onColorTemperatureChanged(int colorTemperature)286     public void onColorTemperatureChanged(int colorTemperature) {
287         setMatrix(colorTemperature, mMatrixNight);
288         applyTint(true);
289     }
290 
291     @Override
onDisplayColorModeChanged(int mode)292     public void onDisplayColorModeChanged(int mode) {
293         if (mode == -1) {
294             return;
295         }
296 
297         // Cancel the night display tint animator if it's running.
298         if (mColorMatrixAnimator != null) {
299             mColorMatrixAnimator.cancel();
300         }
301 
302         setCoefficientMatrix(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
303         setMatrix(mController.getColorTemperature(), mMatrixNight);
304 
305         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
306         dtm.setColorMode(mode, (mIsActivated != null && mIsActivated) ? mMatrixNight
307                 : MATRIX_IDENTITY);
308     }
309 
310     @Override
onAccessibilityTransformChanged(boolean state)311     public void onAccessibilityTransformChanged(boolean state) {
312         onDisplayColorModeChanged(mController.getColorMode());
313     }
314 
315     /**
316      * Set coefficients based on whether the color matrix is linear or not.
317      */
setCoefficientMatrix(Context context, boolean needsLinear)318     private void setCoefficientMatrix(Context context, boolean needsLinear) {
319         final String[] coefficients = context.getResources().getStringArray(needsLinear
320                 ? R.array.config_nightDisplayColorTemperatureCoefficients
321                 : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
322         for (int i = 0; i < 9 && i < coefficients.length; i++) {
323             mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
324         }
325     }
326 
327     /**
328      * Applies current color temperature matrix, or removes it if deactivated.
329      *
330      * @param immediate {@code true} skips transition animation
331      */
applyTint(boolean immediate)332     private void applyTint(boolean immediate) {
333         // Cancel the old animator if still running.
334         if (mColorMatrixAnimator != null) {
335             mColorMatrixAnimator.cancel();
336         }
337 
338         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
339         final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
340         final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
341 
342         if (immediate) {
343             dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
344         } else {
345             mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
346                     from == null ? MATRIX_IDENTITY : from, to);
347             mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
348             mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
349                     getContext(), android.R.interpolator.fast_out_slow_in));
350             mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
351                 @Override
352                 public void onAnimationUpdate(ValueAnimator animator) {
353                     final float[] value = (float[]) animator.getAnimatedValue();
354                     dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
355                 }
356             });
357             mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
358 
359                 private boolean mIsCancelled;
360 
361                 @Override
362                 public void onAnimationCancel(Animator animator) {
363                     mIsCancelled = true;
364                 }
365 
366                 @Override
367                 public void onAnimationEnd(Animator animator) {
368                     if (!mIsCancelled) {
369                         // Ensure final color matrix is set at the end of the animation. If the
370                         // animation is cancelled then don't set the final color matrix so the new
371                         // animator can pick up from where this one left off.
372                         dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
373                     }
374                     mColorMatrixAnimator = null;
375                 }
376             });
377             mColorMatrixAnimator.start();
378         }
379     }
380 
381     /**
382      * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
383      *
384      * @param colorTemperature color temperature in Kelvin
385      * @param outTemp          the 4x4 display transformation matrix for that color temperature
386      */
setMatrix(int colorTemperature, float[] outTemp)387     private void setMatrix(int colorTemperature, float[] outTemp) {
388         if (outTemp.length != 16) {
389             Slog.d(TAG, "The display transformation matrix must be 4x4");
390             return;
391         }
392 
393         Matrix.setIdentityM(mMatrixNight, 0);
394 
395         final float squareTemperature = colorTemperature * colorTemperature;
396         final float red = squareTemperature * mColorTempCoefficients[0]
397                 + colorTemperature * mColorTempCoefficients[1] + mColorTempCoefficients[2];
398         final float green = squareTemperature * mColorTempCoefficients[3]
399                 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[5];
400         final float blue = squareTemperature * mColorTempCoefficients[6]
401                 + colorTemperature * mColorTempCoefficients[7] + mColorTempCoefficients[8];
402         outTemp[0] = red;
403         outTemp[5] = green;
404         outTemp[10] = blue;
405     }
406 
407     /**
408      * Returns the first date time corresponding to the local time that occurs before the
409      * provided date time.
410      *
411      * @param compareTime the LocalDateTime to compare against
412      * @return the prior LocalDateTime corresponding to this local time
413      */
getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime)414     public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
415         final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
416                 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
417 
418         // Check if the local time has passed, if so return the same time yesterday.
419         return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
420     }
421 
422     /**
423      * Returns the first date time corresponding to this local time that occurs after the
424      * provided date time.
425      *
426      * @param compareTime the LocalDateTime to compare against
427      * @return the next LocalDateTime corresponding to this local time
428      */
getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime)429     public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
430         final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
431                 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
432 
433         // Check if the local time has passed, if so return the same time tomorrow.
434         return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
435     }
436 
437     private abstract class AutoMode implements ColorDisplayController.Callback {
onStart()438         public abstract void onStart();
439 
onStop()440         public abstract void onStop();
441     }
442 
443     private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
444 
445         private final AlarmManager mAlarmManager;
446         private final BroadcastReceiver mTimeChangedReceiver;
447 
448         private LocalTime mStartTime;
449         private LocalTime mEndTime;
450 
451         private LocalDateTime mLastActivatedTime;
452 
CustomAutoMode()453         CustomAutoMode() {
454             mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
455             mTimeChangedReceiver = new BroadcastReceiver() {
456                 @Override
457                 public void onReceive(Context context, Intent intent) {
458                     updateActivated();
459                 }
460             };
461         }
462 
updateActivated()463         private void updateActivated() {
464             final LocalDateTime now = LocalDateTime.now();
465             final LocalDateTime start = getDateTimeBefore(mStartTime, now);
466             final LocalDateTime end = getDateTimeAfter(mEndTime, start);
467             boolean activate = now.isBefore(end);
468 
469             if (mLastActivatedTime != null) {
470                 // Maintain the existing activated state if within the current period.
471                 if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
472                         && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
473                     activate = mController.isActivated();
474                 }
475             }
476 
477             if (mIsActivated == null || mIsActivated != activate) {
478                 mController.setActivated(activate);
479             }
480 
481             updateNextAlarm(mIsActivated, now);
482         }
483 
updateNextAlarm(@ullable Boolean activated, @NonNull LocalDateTime now)484         private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
485             if (activated != null) {
486                 final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
487                         : getDateTimeAfter(mStartTime, now);
488                 final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
489                 mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
490             }
491         }
492 
493         @Override
onStart()494         public void onStart() {
495             final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
496             intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
497             getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
498 
499             mStartTime = mController.getCustomStartTime();
500             mEndTime = mController.getCustomEndTime();
501 
502             mLastActivatedTime = mController.getLastActivatedTime();
503 
504             // Force an update to initialize state.
505             updateActivated();
506         }
507 
508         @Override
onStop()509         public void onStop() {
510             getContext().unregisterReceiver(mTimeChangedReceiver);
511 
512             mAlarmManager.cancel(this);
513             mLastActivatedTime = null;
514         }
515 
516         @Override
onActivated(boolean activated)517         public void onActivated(boolean activated) {
518             mLastActivatedTime = mController.getLastActivatedTime();
519             updateNextAlarm(activated, LocalDateTime.now());
520         }
521 
522         @Override
onCustomStartTimeChanged(LocalTime startTime)523         public void onCustomStartTimeChanged(LocalTime startTime) {
524             mStartTime = startTime;
525             mLastActivatedTime = null;
526             updateActivated();
527         }
528 
529         @Override
onCustomEndTimeChanged(LocalTime endTime)530         public void onCustomEndTimeChanged(LocalTime endTime) {
531             mEndTime = endTime;
532             mLastActivatedTime = null;
533             updateActivated();
534         }
535 
536         @Override
onAlarm()537         public void onAlarm() {
538             Slog.d(TAG, "onAlarm");
539             updateActivated();
540         }
541     }
542 
543     private class TwilightAutoMode extends AutoMode implements TwilightListener {
544 
545         private final TwilightManager mTwilightManager;
546 
TwilightAutoMode()547         TwilightAutoMode() {
548             mTwilightManager = getLocalService(TwilightManager.class);
549         }
550 
updateActivated(TwilightState state)551         private void updateActivated(TwilightState state) {
552             if (state == null) {
553                 // If there isn't a valid TwilightState then just keep the current activated
554                 // state.
555                 return;
556             }
557 
558             boolean activate = state.isNight();
559             final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
560             if (lastActivatedTime != null) {
561                 final LocalDateTime now = LocalDateTime.now();
562                 final LocalDateTime sunrise = state.sunrise();
563                 final LocalDateTime sunset = state.sunset();
564                 // Maintain the existing activated state if within the current period.
565                 if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
566                         ^ lastActivatedTime.isBefore(sunset))) {
567                     activate = mController.isActivated();
568                 }
569             }
570 
571             if (mIsActivated == null || mIsActivated != activate) {
572                 mController.setActivated(activate);
573             }
574         }
575 
576         @Override
onStart()577         public void onStart() {
578             mTwilightManager.registerListener(this, mHandler);
579 
580             // Force an update to initialize state.
581             updateActivated(mTwilightManager.getLastTwilightState());
582         }
583 
584         @Override
onStop()585         public void onStop() {
586             mTwilightManager.unregisterListener(this);
587         }
588 
589         @Override
onActivated(boolean activated)590         public void onActivated(boolean activated) {
591         }
592 
593         @Override
onTwilightStateChanged(@ullable TwilightState state)594         public void onTwilightStateChanged(@Nullable TwilightState state) {
595             Slog.d(TAG, "onTwilightStateChanged: isNight="
596                     + (state == null ? null : state.isNight()));
597             updateActivated(state);
598         }
599     }
600 
601     /**
602      * Interpolates between two 4x4 color transform matrices (in column-major order).
603      */
604     private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
605 
606         /**
607          * Result matrix returned by {@link #evaluate(float, float[], float[])}.
608          */
609         private final float[] mResultMatrix = new float[16];
610 
611         @Override
evaluate(float fraction, float[] startValue, float[] endValue)612         public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
613             for (int i = 0; i < mResultMatrix.length; i++) {
614                 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
615             }
616             return mResultMatrix;
617         }
618     }
619 }
620