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.NightDisplayController;
     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.util.Calendar;
     49 import java.util.TimeZone;
     50 
     51 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
     52 
     53 /**
     54  * Tints the display at night.
     55  */
     56 public final class NightDisplayService extends SystemService
     57         implements NightDisplayController.Callback {
     58 
     59     private static final String TAG = "NightDisplayService";
     60     private static final boolean DEBUG = false;
     61 
     62     /**
     63      * Night display ~= 3400 K.
     64      */
     65     private static final float[] MATRIX_NIGHT = new float[] {
     66         1,      0,      0, 0,
     67         0, 0.754f,      0, 0,
     68         0,      0, 0.516f, 0,
     69         0,      0,      0, 1
     70     };
     71 
     72     /**
     73      * The identity matrix, used if one of the given matrices is {@code null}.
     74      */
     75     private static final float[] MATRIX_IDENTITY = new float[16];
     76     static {
     77         Matrix.setIdentityM(MATRIX_IDENTITY, 0);
     78     }
     79 
     80     /**
     81      * Evaluator used to animate color matrix transitions.
     82      */
     83     private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
     84 
     85     private final Handler mHandler;
     86 
     87     private int mCurrentUser = UserHandle.USER_NULL;
     88     private ContentObserver mUserSetupObserver;
     89     private boolean mBootCompleted;
     90 
     91     private NightDisplayController mController;
     92     private ValueAnimator mColorMatrixAnimator;
     93     private Boolean mIsActivated;
     94     private AutoMode mAutoMode;
     95 
     96     public NightDisplayService(Context context) {
     97         super(context);
     98         mHandler = new Handler(Looper.getMainLooper());
     99     }
    100 
    101     @Override
    102     public void onStart() {
    103         // Nothing to publish.
    104     }
    105 
    106     @Override
    107     public void onBootPhase(int phase) {
    108         if (phase == PHASE_BOOT_COMPLETED) {
    109             mBootCompleted = true;
    110 
    111             // Register listeners now that boot is complete.
    112             if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
    113                 setUp();
    114             }
    115         }
    116     }
    117 
    118     @Override
    119     public void onStartUser(int userHandle) {
    120         super.onStartUser(userHandle);
    121 
    122         if (mCurrentUser == UserHandle.USER_NULL) {
    123             onUserChanged(userHandle);
    124         }
    125     }
    126 
    127     @Override
    128     public void onSwitchUser(int userHandle) {
    129         super.onSwitchUser(userHandle);
    130 
    131         onUserChanged(userHandle);
    132     }
    133 
    134     @Override
    135     public void onStopUser(int userHandle) {
    136         super.onStopUser(userHandle);
    137 
    138         if (mCurrentUser == userHandle) {
    139             onUserChanged(UserHandle.USER_NULL);
    140         }
    141     }
    142 
    143     private void onUserChanged(int userHandle) {
    144         final ContentResolver cr = getContext().getContentResolver();
    145 
    146         if (mCurrentUser != UserHandle.USER_NULL) {
    147             if (mUserSetupObserver != null) {
    148                 cr.unregisterContentObserver(mUserSetupObserver);
    149                 mUserSetupObserver = null;
    150             } else if (mBootCompleted) {
    151                 tearDown();
    152             }
    153         }
    154 
    155         mCurrentUser = userHandle;
    156 
    157         if (mCurrentUser != UserHandle.USER_NULL) {
    158             if (!isUserSetupCompleted(cr, mCurrentUser)) {
    159                 mUserSetupObserver = new ContentObserver(mHandler) {
    160                     @Override
    161                     public void onChange(boolean selfChange, Uri uri) {
    162                         if (isUserSetupCompleted(cr, mCurrentUser)) {
    163                             cr.unregisterContentObserver(this);
    164                             mUserSetupObserver = null;
    165 
    166                             if (mBootCompleted) {
    167                                 setUp();
    168                             }
    169                         }
    170                     }
    171                 };
    172                 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
    173                         false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
    174             } else if (mBootCompleted) {
    175                 setUp();
    176             }
    177         }
    178     }
    179 
    180     private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
    181         return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
    182     }
    183 
    184     private void setUp() {
    185         // Create a new controller for the current user and start listening for changes.
    186         mController = new NightDisplayController(getContext(), mCurrentUser);
    187         mController.setListener(this);
    188 
    189         // Initialize the current auto mode.
    190         onAutoModeChanged(mController.getAutoMode());
    191 
    192         // Force the initialization current activated state.
    193         if (mIsActivated == null) {
    194             onActivated(mController.isActivated());
    195         }
    196     }
    197 
    198     private void tearDown() {
    199         if (mController != null) {
    200             mController.setListener(null);
    201             mController = null;
    202         }
    203 
    204         if (mAutoMode != null) {
    205             mAutoMode.onStop();
    206             mAutoMode = null;
    207         }
    208 
    209         if (mColorMatrixAnimator != null) {
    210             mColorMatrixAnimator.end();
    211             mColorMatrixAnimator = null;
    212         }
    213 
    214         mIsActivated = null;
    215     }
    216 
    217     @Override
    218     public void onActivated(boolean activated) {
    219         if (mIsActivated == null || mIsActivated != activated) {
    220             Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
    221 
    222             if (mAutoMode != null) {
    223                 mAutoMode.onActivated(activated);
    224             }
    225 
    226             mIsActivated = activated;
    227 
    228             // Cancel the old animator if still running.
    229             if (mColorMatrixAnimator != null) {
    230                 mColorMatrixAnimator.cancel();
    231             }
    232 
    233             final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
    234             final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
    235             final float[] to = mIsActivated ? MATRIX_NIGHT : null;
    236 
    237             mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
    238                     from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
    239             mColorMatrixAnimator.setDuration(getContext().getResources()
    240                     .getInteger(android.R.integer.config_longAnimTime));
    241             mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
    242                     getContext(), android.R.interpolator.fast_out_slow_in));
    243             mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    244                 @Override
    245                 public void onAnimationUpdate(ValueAnimator animator) {
    246                     final float[] value = (float[]) animator.getAnimatedValue();
    247                     dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
    248                 }
    249             });
    250             mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
    251 
    252                 private boolean mIsCancelled;
    253 
    254                 @Override
    255                 public void onAnimationCancel(Animator animator) {
    256                     mIsCancelled = true;
    257                 }
    258 
    259                 @Override
    260                 public void onAnimationEnd(Animator animator) {
    261                     if (!mIsCancelled) {
    262                         // Ensure final color matrix is set at the end of the animation. If the
    263                         // animation is cancelled then don't set the final color matrix so the new
    264                         // animator can pick up from where this one left off.
    265                         dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
    266                     }
    267                     mColorMatrixAnimator = null;
    268                 }
    269             });
    270             mColorMatrixAnimator.start();
    271         }
    272     }
    273 
    274     @Override
    275     public void onAutoModeChanged(int autoMode) {
    276         if (mAutoMode != null) {
    277             mAutoMode.onStop();
    278             mAutoMode = null;
    279         }
    280 
    281         if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
    282             mAutoMode = new CustomAutoMode();
    283         } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
    284             mAutoMode = new TwilightAutoMode();
    285         }
    286 
    287         if (mAutoMode != null) {
    288             mAutoMode.onStart();
    289         }
    290     }
    291 
    292     @Override
    293     public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
    294         if (mAutoMode != null) {
    295             mAutoMode.onCustomStartTimeChanged(startTime);
    296         }
    297     }
    298 
    299     @Override
    300     public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
    301         if (mAutoMode != null) {
    302             mAutoMode.onCustomEndTimeChanged(endTime);
    303         }
    304     }
    305 
    306     private abstract class AutoMode implements NightDisplayController.Callback {
    307         public abstract void onStart();
    308         public abstract void onStop();
    309     }
    310 
    311     private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
    312 
    313         private final AlarmManager mAlarmManager;
    314         private final BroadcastReceiver mTimeChangedReceiver;
    315 
    316         private NightDisplayController.LocalTime mStartTime;
    317         private NightDisplayController.LocalTime mEndTime;
    318 
    319         private Calendar mLastActivatedTime;
    320 
    321         public CustomAutoMode() {
    322             mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
    323             mTimeChangedReceiver = new BroadcastReceiver() {
    324                 @Override
    325                 public void onReceive(Context context, Intent intent) {
    326                     updateActivated();
    327                 }
    328             };
    329         }
    330 
    331         private void updateActivated() {
    332             final Calendar now = Calendar.getInstance();
    333             final Calendar startTime = mStartTime.getDateTimeBefore(now);
    334             final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
    335             final boolean activated = now.before(endTime);
    336 
    337             boolean setActivated = mIsActivated == null || mLastActivatedTime == null;
    338             if (!setActivated && mIsActivated != activated) {
    339                 final TimeZone currentTimeZone = now.getTimeZone();
    340                 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
    341                     final int year = mLastActivatedTime.get(Calendar.YEAR);
    342                     final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
    343                     final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
    344                     final int minute = mLastActivatedTime.get(Calendar.MINUTE);
    345 
    346                     mLastActivatedTime.setTimeZone(currentTimeZone);
    347                     mLastActivatedTime.set(Calendar.YEAR, year);
    348                     mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
    349                     mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
    350                     mLastActivatedTime.set(Calendar.MINUTE, minute);
    351                 }
    352 
    353                 if (mIsActivated) {
    354                     setActivated = now.before(mStartTime.getDateTimeBefore(mLastActivatedTime))
    355                             || now.after(mEndTime.getDateTimeAfter(mLastActivatedTime));
    356                 } else {
    357                     setActivated = now.before(mEndTime.getDateTimeBefore(mLastActivatedTime))
    358                             || now.after(mStartTime.getDateTimeAfter(mLastActivatedTime));
    359                 }
    360             }
    361 
    362             if (setActivated) {
    363                 mController.setActivated(activated);
    364             }
    365             updateNextAlarm(mIsActivated, now);
    366         }
    367 
    368         private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
    369             if (activated != null) {
    370                 final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
    371                         : mStartTime.getDateTimeAfter(now);
    372                 mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
    373             }
    374         }
    375 
    376         @Override
    377         public void onStart() {
    378             final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
    379             intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    380             getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
    381 
    382             mStartTime = mController.getCustomStartTime();
    383             mEndTime = mController.getCustomEndTime();
    384 
    385             // Force an update to initialize state.
    386             updateActivated();
    387         }
    388 
    389         @Override
    390         public void onStop() {
    391             getContext().unregisterReceiver(mTimeChangedReceiver);
    392 
    393             mAlarmManager.cancel(this);
    394             mLastActivatedTime = null;
    395         }
    396 
    397         @Override
    398         public void onActivated(boolean activated) {
    399             final Calendar now = Calendar.getInstance();
    400             if (mIsActivated != null) {
    401                 mLastActivatedTime = now;
    402             }
    403             updateNextAlarm(activated, now);
    404         }
    405 
    406         @Override
    407         public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
    408             mStartTime = startTime;
    409             mLastActivatedTime = null;
    410             updateActivated();
    411         }
    412 
    413         @Override
    414         public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
    415             mEndTime = endTime;
    416             mLastActivatedTime = null;
    417             updateActivated();
    418         }
    419 
    420         @Override
    421         public void onAlarm() {
    422             if (DEBUG) Slog.d(TAG, "onAlarm");
    423             updateActivated();
    424         }
    425     }
    426 
    427     private class TwilightAutoMode extends AutoMode implements TwilightListener {
    428 
    429         private final TwilightManager mTwilightManager;
    430 
    431         private Calendar mLastActivatedTime;
    432 
    433         public TwilightAutoMode() {
    434             mTwilightManager = getLocalService(TwilightManager.class);
    435         }
    436 
    437         private void updateActivated(TwilightState state) {
    438             final boolean isNight = state != null && state.isNight();
    439             boolean setActivated = mIsActivated == null || mIsActivated != isNight;
    440             if (setActivated && state != null && mLastActivatedTime != null) {
    441                 final Calendar sunrise = state.sunrise();
    442                 final Calendar sunset = state.sunset();
    443                 if (sunrise.before(sunset)) {
    444                     setActivated = mLastActivatedTime.before(sunrise)
    445                             || mLastActivatedTime.after(sunset);
    446                 } else {
    447                     setActivated = mLastActivatedTime.before(sunset)
    448                             || mLastActivatedTime.after(sunrise);
    449                 }
    450             }
    451 
    452             if (setActivated) {
    453                 mController.setActivated(isNight);
    454             }
    455         }
    456 
    457         @Override
    458         public void onStart() {
    459             mTwilightManager.registerListener(this, mHandler);
    460 
    461             // Force an update to initialize state.
    462             updateActivated(mTwilightManager.getLastTwilightState());
    463         }
    464 
    465         @Override
    466         public void onStop() {
    467             mTwilightManager.unregisterListener(this);
    468             mLastActivatedTime = null;
    469         }
    470 
    471         @Override
    472         public void onActivated(boolean activated) {
    473             if (mIsActivated != null) {
    474                 mLastActivatedTime = Calendar.getInstance();
    475             }
    476         }
    477 
    478         @Override
    479         public void onTwilightStateChanged(@Nullable TwilightState state) {
    480             if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
    481             updateActivated(state);
    482         }
    483     }
    484 
    485     /**
    486      * Interpolates between two 4x4 color transform matrices (in column-major order).
    487      */
    488     private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
    489 
    490         /**
    491          * Result matrix returned by {@link #evaluate(float, float[], float[])}.
    492          */
    493         private final float[] mResultMatrix = new float[16];
    494 
    495         @Override
    496         public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
    497             for (int i = 0; i < mResultMatrix.length; i++) {
    498                 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
    499             }
    500             return mResultMatrix;
    501         }
    502     }
    503 }
    504