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