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