Home | History | Annotate | Download | only in display
      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 {
     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 
     97     public ColorDisplayService(Context context) {
     98         super(context);
     99         mHandler = new Handler(Looper.getMainLooper());
    100     }
    101 
    102     @Override
    103     public void onStart() {
    104         // Nothing to publish.
    105     }
    106 
    107     @Override
    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
    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
    129     public void onSwitchUser(int userHandle) {
    130         super.onSwitchUser(userHandle);
    131 
    132         onUserChanged(userHandle);
    133     }
    134 
    135     @Override
    136     public void onStopUser(int userHandle) {
    137         super.onStopUser(userHandle);
    138 
    139         if (mCurrentUser == userHandle) {
    140             onUserChanged(UserHandle.USER_NULL);
    141         }
    142     }
    143 
    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 
    181     private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
    182         return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
    183     }
    184 
    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 
    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
    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
    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
    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
    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
    286     public void onColorTemperatureChanged(int colorTemperature) {
    287         setMatrix(colorTemperature, mMatrixNight);
    288         applyTint(true);
    289     }
    290 
    291     @Override
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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 {
    438         public abstract void onStart();
    439 
    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 
    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 
    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 
    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
    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
    509         public void onStop() {
    510             getContext().unregisterReceiver(mTimeChangedReceiver);
    511 
    512             mAlarmManager.cancel(this);
    513             mLastActivatedTime = null;
    514         }
    515 
    516         @Override
    517         public void onActivated(boolean activated) {
    518             mLastActivatedTime = mController.getLastActivatedTime();
    519             updateNextAlarm(activated, LocalDateTime.now());
    520         }
    521 
    522         @Override
    523         public void onCustomStartTimeChanged(LocalTime startTime) {
    524             mStartTime = startTime;
    525             mLastActivatedTime = null;
    526             updateActivated();
    527         }
    528 
    529         @Override
    530         public void onCustomEndTimeChanged(LocalTime endTime) {
    531             mEndTime = endTime;
    532             mLastActivatedTime = null;
    533             updateActivated();
    534         }
    535 
    536         @Override
    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 
    547         TwilightAutoMode() {
    548             mTwilightManager = getLocalService(TwilightManager.class);
    549         }
    550 
    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
    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
    585         public void onStop() {
    586             mTwilightManager.unregisterListener(this);
    587         }
    588 
    589         @Override
    590         public void onActivated(boolean activated) {
    591         }
    592 
    593         @Override
    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
    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