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