blob: e7fd3d88520e8b0192d36880a4604a80eb07b951 [file] [log] [blame]
Justin Klaassen911e8892016-06-21 18:24:24 -07001/*
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
Justin Klaassen639214e2016-07-14 21:00:06 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.TypeEvaluator;
22import android.animation.ValueAnimator;
Justin Klaassen4346f632016-08-08 15:01:47 -070023import android.annotation.NonNull;
Justin Klaassen908b86c2016-08-08 09:18:42 -070024import android.annotation.Nullable;
Justin Klaassen911e8892016-06-21 18:24:24 -070025import android.app.AlarmManager;
26import android.content.BroadcastReceiver;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
Justin Klaassen2696d992016-07-11 21:26:46 -070031import android.database.ContentObserver;
32import android.net.Uri;
Justin Klaassen639214e2016-07-14 21:00:06 -070033import android.opengl.Matrix;
Justin Klaassen911e8892016-06-21 18:24:24 -070034import android.os.Handler;
35import android.os.Looper;
36import android.os.UserHandle;
37import android.provider.Settings.Secure;
Justin Klaassen639214e2016-07-14 21:00:06 -070038import android.util.MathUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070039import android.util.Slog;
Justin Klaassen639214e2016-07-14 21:00:06 -070040import android.view.animation.AnimationUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070041
42import com.android.internal.app.NightDisplayController;
43import com.android.server.SystemService;
44import com.android.server.twilight.TwilightListener;
45import com.android.server.twilight.TwilightManager;
46import com.android.server.twilight.TwilightState;
47
48import java.util.Calendar;
49import java.util.TimeZone;
50
Justin Klaassen639214e2016-07-14 21:00:06 -070051import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
52
Justin Klaassen911e8892016-06-21 18:24:24 -070053/**
54 * Tints the display at night.
55 */
56public 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 /**
Justin Klaassen22eb1992016-07-11 20:52:23 -070063 * Night display ~= 3400 K.
Justin Klaassen911e8892016-06-21 18:24:24 -070064 */
Justin Klaassen22eb1992016-07-11 20:52:23 -070065 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 };
Justin Klaassen911e8892016-06-21 18:24:24 -070071
Justin Klaassen639214e2016-07-14 21:00:06 -070072 /**
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
Justin Klaassen2696d992016-07-11 21:26:46 -070085 private final Handler mHandler;
86
Justin Klaassen911e8892016-06-21 18:24:24 -070087 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -070088 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -070089 private boolean mBootCompleted;
90
91 private NightDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -070092 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -070093 private Boolean mIsActivated;
94 private AutoMode mAutoMode;
95
96 public NightDisplayService(Context context) {
97 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -070098 mHandler = new Handler(Looper.getMainLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -070099 }
100
101 @Override
102 public void onStart() {
103 // Nothing to publish.
104 }
105
106 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700107 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
Justin Klaassen911e8892016-06-21 18:24:24 -0700119 public void onStartUser(int userHandle) {
120 super.onStartUser(userHandle);
121
Justin Klaassen911e8892016-06-21 18:24:24 -0700122 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700123 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700124 }
125 }
126
127 @Override
128 public void onSwitchUser(int userHandle) {
129 super.onSwitchUser(userHandle);
130
Justin Klaassen2696d992016-07-11 21:26:46 -0700131 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700132 }
133
134 @Override
135 public void onStopUser(int userHandle) {
136 super.onStopUser(userHandle);
137
Justin Klaassen911e8892016-06-21 18:24:24 -0700138 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700139 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700140 }
141 }
142
Justin Klaassen2696d992016-07-11 21:26:46 -0700143 private void onUserChanged(int userHandle) {
144 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700145
Justin Klaassen2696d992016-07-11 21:26:46 -0700146 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();
Justin Klaassen911e8892016-06-21 18:24:24 -0700176 }
177 }
178 }
179
Justin Klaassen2696d992016-07-11 21:26:46 -0700180 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() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700185 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
186
Justin Klaassen911e8892016-06-21 18:24:24 -0700187 // Create a new controller for the current user and start listening for changes.
188 mController = new NightDisplayController(getContext(), mCurrentUser);
189 mController.setListener(this);
190
191 // Initialize the current auto mode.
192 onAutoModeChanged(mController.getAutoMode());
193
194 // Force the initialization current activated state.
195 if (mIsActivated == null) {
196 onActivated(mController.isActivated());
197 }
198 }
199
Justin Klaassen2696d992016-07-11 21:26:46 -0700200 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700201 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
202
Justin Klaassen2696d992016-07-11 21:26:46 -0700203 if (mController != null) {
204 mController.setListener(null);
205 mController = null;
206 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700207
208 if (mAutoMode != null) {
209 mAutoMode.onStop();
210 mAutoMode = null;
211 }
212
Justin Klaassen639214e2016-07-14 21:00:06 -0700213 if (mColorMatrixAnimator != null) {
214 mColorMatrixAnimator.end();
215 mColorMatrixAnimator = null;
216 }
217
Justin Klaassen911e8892016-06-21 18:24:24 -0700218 mIsActivated = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700219 }
220
221 @Override
222 public void onActivated(boolean activated) {
223 if (mIsActivated == null || mIsActivated != activated) {
224 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
225
Justin Klaassen911e8892016-06-21 18:24:24 -0700226 if (mAutoMode != null) {
227 mAutoMode.onActivated(activated);
228 }
229
Justin Klaassen908b86c2016-08-08 09:18:42 -0700230 mIsActivated = activated;
231
Justin Klaassen639214e2016-07-14 21:00:06 -0700232 // Cancel the old animator if still running.
233 if (mColorMatrixAnimator != null) {
234 mColorMatrixAnimator.cancel();
235 }
236
Justin Klaassen22eb1992016-07-11 20:52:23 -0700237 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
Justin Klaassen639214e2016-07-14 21:00:06 -0700238 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
239 final float[] to = mIsActivated ? MATRIX_NIGHT : null;
240
241 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
242 from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
243 mColorMatrixAnimator.setDuration(getContext().getResources()
244 .getInteger(android.R.integer.config_longAnimTime));
245 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
246 getContext(), android.R.interpolator.fast_out_slow_in));
247 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
248 @Override
249 public void onAnimationUpdate(ValueAnimator animator) {
250 final float[] value = (float[]) animator.getAnimatedValue();
251 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
252 }
253 });
254 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
255
256 private boolean mIsCancelled;
257
258 @Override
259 public void onAnimationCancel(Animator animator) {
260 mIsCancelled = true;
261 }
262
263 @Override
264 public void onAnimationEnd(Animator animator) {
265 if (!mIsCancelled) {
266 // Ensure final color matrix is set at the end of the animation. If the
267 // animation is cancelled then don't set the final color matrix so the new
268 // animator can pick up from where this one left off.
269 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
270 }
271 mColorMatrixAnimator = null;
272 }
273 });
274 mColorMatrixAnimator.start();
Justin Klaassen911e8892016-06-21 18:24:24 -0700275 }
276 }
277
278 @Override
279 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700280 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
281
Justin Klaassen911e8892016-06-21 18:24:24 -0700282 if (mAutoMode != null) {
283 mAutoMode.onStop();
284 mAutoMode = null;
285 }
286
287 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
288 mAutoMode = new CustomAutoMode();
289 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
290 mAutoMode = new TwilightAutoMode();
291 }
292
293 if (mAutoMode != null) {
294 mAutoMode.onStart();
295 }
296 }
297
298 @Override
299 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700300 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
301
Justin Klaassen911e8892016-06-21 18:24:24 -0700302 if (mAutoMode != null) {
303 mAutoMode.onCustomStartTimeChanged(startTime);
304 }
305 }
306
307 @Override
308 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700309 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
310
Justin Klaassen911e8892016-06-21 18:24:24 -0700311 if (mAutoMode != null) {
312 mAutoMode.onCustomEndTimeChanged(endTime);
313 }
314 }
315
316 private abstract class AutoMode implements NightDisplayController.Callback {
317 public abstract void onStart();
318 public abstract void onStop();
319 }
320
321 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
322
323 private final AlarmManager mAlarmManager;
324 private final BroadcastReceiver mTimeChangedReceiver;
325
326 private NightDisplayController.LocalTime mStartTime;
327 private NightDisplayController.LocalTime mEndTime;
328
329 private Calendar mLastActivatedTime;
330
331 public CustomAutoMode() {
332 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
333 mTimeChangedReceiver = new BroadcastReceiver() {
334 @Override
335 public void onReceive(Context context, Intent intent) {
336 updateActivated();
337 }
338 };
339 }
340
341 private void updateActivated() {
342 final Calendar now = Calendar.getInstance();
343 final Calendar startTime = mStartTime.getDateTimeBefore(now);
344 final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
345 final boolean activated = now.before(endTime);
346
347 boolean setActivated = mIsActivated == null || mLastActivatedTime == null;
348 if (!setActivated && mIsActivated != activated) {
349 final TimeZone currentTimeZone = now.getTimeZone();
350 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
351 final int year = mLastActivatedTime.get(Calendar.YEAR);
352 final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
353 final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
354 final int minute = mLastActivatedTime.get(Calendar.MINUTE);
355
356 mLastActivatedTime.setTimeZone(currentTimeZone);
357 mLastActivatedTime.set(Calendar.YEAR, year);
358 mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
359 mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
360 mLastActivatedTime.set(Calendar.MINUTE, minute);
361 }
362
363 if (mIsActivated) {
364 setActivated = now.before(mStartTime.getDateTimeBefore(mLastActivatedTime))
365 || now.after(mEndTime.getDateTimeAfter(mLastActivatedTime));
366 } else {
367 setActivated = now.before(mEndTime.getDateTimeBefore(mLastActivatedTime))
368 || now.after(mStartTime.getDateTimeAfter(mLastActivatedTime));
369 }
370 }
371
372 if (setActivated) {
373 mController.setActivated(activated);
374 }
Justin Klaassen4346f632016-08-08 15:01:47 -0700375 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700376 }
377
Justin Klaassen4346f632016-08-08 15:01:47 -0700378 private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
379 if (activated != null) {
380 final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
Justin Klaassen911e8892016-06-21 18:24:24 -0700381 : mStartTime.getDateTimeAfter(now);
382 mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
383 }
384 }
385
386 @Override
387 public void onStart() {
388 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
389 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
390 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
391
392 mStartTime = mController.getCustomStartTime();
393 mEndTime = mController.getCustomEndTime();
394
395 // Force an update to initialize state.
396 updateActivated();
397 }
398
399 @Override
400 public void onStop() {
401 getContext().unregisterReceiver(mTimeChangedReceiver);
402
403 mAlarmManager.cancel(this);
404 mLastActivatedTime = null;
405 }
406
407 @Override
408 public void onActivated(boolean activated) {
Justin Klaassen4346f632016-08-08 15:01:47 -0700409 final Calendar now = Calendar.getInstance();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700410 if (mIsActivated != null) {
Justin Klaassen4346f632016-08-08 15:01:47 -0700411 mLastActivatedTime = now;
Justin Klaassen908b86c2016-08-08 09:18:42 -0700412 }
Justin Klaassen4346f632016-08-08 15:01:47 -0700413 updateNextAlarm(activated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700414 }
415
416 @Override
417 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
418 mStartTime = startTime;
419 mLastActivatedTime = null;
420 updateActivated();
421 }
422
423 @Override
424 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
425 mEndTime = endTime;
426 mLastActivatedTime = null;
427 updateActivated();
428 }
429
430 @Override
431 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700432 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700433 updateActivated();
434 }
435 }
436
437 private class TwilightAutoMode extends AutoMode implements TwilightListener {
438
439 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700440
Justin Klaassen908b86c2016-08-08 09:18:42 -0700441 private Calendar mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700442
443 public TwilightAutoMode() {
444 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700445 }
446
Justin Klaassen908b86c2016-08-08 09:18:42 -0700447 private void updateActivated(TwilightState state) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700448 final boolean isNight = state != null && state.isNight();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700449 boolean setActivated = mIsActivated == null || mIsActivated != isNight;
450 if (setActivated && state != null && mLastActivatedTime != null) {
451 final Calendar sunrise = state.sunrise();
452 final Calendar sunset = state.sunset();
453 if (sunrise.before(sunset)) {
454 setActivated = mLastActivatedTime.before(sunrise)
455 || mLastActivatedTime.after(sunset);
456 } else {
457 setActivated = mLastActivatedTime.before(sunset)
458 || mLastActivatedTime.after(sunrise);
Justin Klaassen911e8892016-06-21 18:24:24 -0700459 }
460 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700461
462 if (setActivated) {
463 mController.setActivated(isNight);
464 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700465 }
466
467 @Override
468 public void onStart() {
469 mTwilightManager.registerListener(this, mHandler);
470
471 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700472 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700473 }
474
475 @Override
476 public void onStop() {
477 mTwilightManager.unregisterListener(this);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700478 mLastActivatedTime = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700479 }
480
481 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700482 public void onActivated(boolean activated) {
483 if (mIsActivated != null) {
484 mLastActivatedTime = Calendar.getInstance();
485 }
486 }
487
488 @Override
489 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700490 Slog.d(TAG, "onTwilightStateChanged: isNight="
491 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700492 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700493 }
494 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700495
496 /**
497 * Interpolates between two 4x4 color transform matrices (in column-major order).
498 */
499 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
500
501 /**
502 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
503 */
504 private final float[] mResultMatrix = new float[16];
505
506 @Override
507 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
508 for (int i = 0; i < mResultMatrix.length; i++) {
509 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
510 }
511 return mResultMatrix;
512 }
513 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700514}