blob: d9af7cb913ee0d0bfd4e64145a671f8d78eb0e31 [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 Klaassen908b86c2016-08-08 09:18:42 -070023import android.annotation.Nullable;
Justin Klaassen911e8892016-06-21 18:24:24 -070024import android.app.AlarmManager;
25import android.content.BroadcastReceiver;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
Justin Klaassen2696d992016-07-11 21:26:46 -070030import android.database.ContentObserver;
31import android.net.Uri;
Justin Klaassen639214e2016-07-14 21:00:06 -070032import android.opengl.Matrix;
Justin Klaassen911e8892016-06-21 18:24:24 -070033import android.os.Handler;
34import android.os.Looper;
35import android.os.UserHandle;
36import android.provider.Settings.Secure;
Justin Klaassen639214e2016-07-14 21:00:06 -070037import android.util.MathUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070038import android.util.Slog;
Justin Klaassen639214e2016-07-14 21:00:06 -070039import android.view.animation.AnimationUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070040
41import com.android.internal.app.NightDisplayController;
42import com.android.server.SystemService;
43import com.android.server.twilight.TwilightListener;
44import com.android.server.twilight.TwilightManager;
45import com.android.server.twilight.TwilightState;
46
47import java.util.Calendar;
48import java.util.TimeZone;
49
Justin Klaassen639214e2016-07-14 21:00:06 -070050import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
51
Justin Klaassen911e8892016-06-21 18:24:24 -070052/**
53 * Tints the display at night.
54 */
55public final class NightDisplayService extends SystemService
56 implements NightDisplayController.Callback {
57
58 private static final String TAG = "NightDisplayService";
59 private static final boolean DEBUG = false;
60
61 /**
Justin Klaassen22eb1992016-07-11 20:52:23 -070062 * Night display ~= 3400 K.
Justin Klaassen911e8892016-06-21 18:24:24 -070063 */
Justin Klaassen22eb1992016-07-11 20:52:23 -070064 private static final float[] MATRIX_NIGHT = new float[] {
65 1, 0, 0, 0,
66 0, 0.754f, 0, 0,
67 0, 0, 0.516f, 0,
68 0, 0, 0, 1
69 };
Justin Klaassen911e8892016-06-21 18:24:24 -070070
Justin Klaassen639214e2016-07-14 21:00:06 -070071 /**
72 * The identity matrix, used if one of the given matrices is {@code null}.
73 */
74 private static final float[] MATRIX_IDENTITY = new float[16];
75 static {
76 Matrix.setIdentityM(MATRIX_IDENTITY, 0);
77 }
78
79 /**
80 * Evaluator used to animate color matrix transitions.
81 */
82 private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
83
Justin Klaassen2696d992016-07-11 21:26:46 -070084 private final Handler mHandler;
85
Justin Klaassen911e8892016-06-21 18:24:24 -070086 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -070087 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -070088 private boolean mBootCompleted;
89
90 private NightDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -070091 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -070092 private Boolean mIsActivated;
93 private AutoMode mAutoMode;
94
95 public NightDisplayService(Context context) {
96 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -070097 mHandler = new Handler(Looper.getMainLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -070098 }
99
100 @Override
101 public void onStart() {
102 // Nothing to publish.
103 }
104
105 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700106 public void onBootPhase(int phase) {
107 if (phase == PHASE_BOOT_COMPLETED) {
108 mBootCompleted = true;
109
110 // Register listeners now that boot is complete.
111 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
112 setUp();
113 }
114 }
115 }
116
117 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700118 public void onStartUser(int userHandle) {
119 super.onStartUser(userHandle);
120
Justin Klaassen911e8892016-06-21 18:24:24 -0700121 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700122 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700123 }
124 }
125
126 @Override
127 public void onSwitchUser(int userHandle) {
128 super.onSwitchUser(userHandle);
129
Justin Klaassen2696d992016-07-11 21:26:46 -0700130 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700131 }
132
133 @Override
134 public void onStopUser(int userHandle) {
135 super.onStopUser(userHandle);
136
Justin Klaassen911e8892016-06-21 18:24:24 -0700137 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700138 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700139 }
140 }
141
Justin Klaassen2696d992016-07-11 21:26:46 -0700142 private void onUserChanged(int userHandle) {
143 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700144
Justin Klaassen2696d992016-07-11 21:26:46 -0700145 if (mCurrentUser != UserHandle.USER_NULL) {
146 if (mUserSetupObserver != null) {
147 cr.unregisterContentObserver(mUserSetupObserver);
148 mUserSetupObserver = null;
149 } else if (mBootCompleted) {
150 tearDown();
151 }
152 }
153
154 mCurrentUser = userHandle;
155
156 if (mCurrentUser != UserHandle.USER_NULL) {
157 if (!isUserSetupCompleted(cr, mCurrentUser)) {
158 mUserSetupObserver = new ContentObserver(mHandler) {
159 @Override
160 public void onChange(boolean selfChange, Uri uri) {
161 if (isUserSetupCompleted(cr, mCurrentUser)) {
162 cr.unregisterContentObserver(this);
163 mUserSetupObserver = null;
164
165 if (mBootCompleted) {
166 setUp();
167 }
168 }
169 }
170 };
171 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
172 false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
173 } else if (mBootCompleted) {
174 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700175 }
176 }
177 }
178
Justin Klaassen2696d992016-07-11 21:26:46 -0700179 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
180 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
181 }
182
183 private void setUp() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700184 // Create a new controller for the current user and start listening for changes.
185 mController = new NightDisplayController(getContext(), mCurrentUser);
186 mController.setListener(this);
187
188 // Initialize the current auto mode.
189 onAutoModeChanged(mController.getAutoMode());
190
191 // Force the initialization current activated state.
192 if (mIsActivated == null) {
193 onActivated(mController.isActivated());
194 }
195 }
196
Justin Klaassen2696d992016-07-11 21:26:46 -0700197 private void tearDown() {
198 if (mController != null) {
199 mController.setListener(null);
200 mController = null;
201 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700202
203 if (mAutoMode != null) {
204 mAutoMode.onStop();
205 mAutoMode = null;
206 }
207
Justin Klaassen639214e2016-07-14 21:00:06 -0700208 if (mColorMatrixAnimator != null) {
209 mColorMatrixAnimator.end();
210 mColorMatrixAnimator = null;
211 }
212
Justin Klaassen911e8892016-06-21 18:24:24 -0700213 mIsActivated = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700214 }
215
216 @Override
217 public void onActivated(boolean activated) {
218 if (mIsActivated == null || mIsActivated != activated) {
219 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
220
Justin Klaassen911e8892016-06-21 18:24:24 -0700221 if (mAutoMode != null) {
222 mAutoMode.onActivated(activated);
223 }
224
Justin Klaassen908b86c2016-08-08 09:18:42 -0700225 mIsActivated = activated;
226
Justin Klaassen639214e2016-07-14 21:00:06 -0700227 // Cancel the old animator if still running.
228 if (mColorMatrixAnimator != null) {
229 mColorMatrixAnimator.cancel();
230 }
231
Justin Klaassen22eb1992016-07-11 20:52:23 -0700232 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
Justin Klaassen639214e2016-07-14 21:00:06 -0700233 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
234 final float[] to = mIsActivated ? MATRIX_NIGHT : null;
235
236 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
237 from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
238 mColorMatrixAnimator.setDuration(getContext().getResources()
239 .getInteger(android.R.integer.config_longAnimTime));
240 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
241 getContext(), android.R.interpolator.fast_out_slow_in));
242 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
243 @Override
244 public void onAnimationUpdate(ValueAnimator animator) {
245 final float[] value = (float[]) animator.getAnimatedValue();
246 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
247 }
248 });
249 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
250
251 private boolean mIsCancelled;
252
253 @Override
254 public void onAnimationCancel(Animator animator) {
255 mIsCancelled = true;
256 }
257
258 @Override
259 public void onAnimationEnd(Animator animator) {
260 if (!mIsCancelled) {
261 // Ensure final color matrix is set at the end of the animation. If the
262 // animation is cancelled then don't set the final color matrix so the new
263 // animator can pick up from where this one left off.
264 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
265 }
266 mColorMatrixAnimator = null;
267 }
268 });
269 mColorMatrixAnimator.start();
Justin Klaassen911e8892016-06-21 18:24:24 -0700270 }
271 }
272
273 @Override
274 public void onAutoModeChanged(int autoMode) {
275 if (mAutoMode != null) {
276 mAutoMode.onStop();
277 mAutoMode = null;
278 }
279
280 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
281 mAutoMode = new CustomAutoMode();
282 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
283 mAutoMode = new TwilightAutoMode();
284 }
285
286 if (mAutoMode != null) {
287 mAutoMode.onStart();
288 }
289 }
290
291 @Override
292 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
293 if (mAutoMode != null) {
294 mAutoMode.onCustomStartTimeChanged(startTime);
295 }
296 }
297
298 @Override
299 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
300 if (mAutoMode != null) {
301 mAutoMode.onCustomEndTimeChanged(endTime);
302 }
303 }
304
305 private abstract class AutoMode implements NightDisplayController.Callback {
306 public abstract void onStart();
307 public abstract void onStop();
308 }
309
310 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
311
312 private final AlarmManager mAlarmManager;
313 private final BroadcastReceiver mTimeChangedReceiver;
314
315 private NightDisplayController.LocalTime mStartTime;
316 private NightDisplayController.LocalTime mEndTime;
317
318 private Calendar mLastActivatedTime;
319
320 public CustomAutoMode() {
321 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
322 mTimeChangedReceiver = new BroadcastReceiver() {
323 @Override
324 public void onReceive(Context context, Intent intent) {
325 updateActivated();
326 }
327 };
328 }
329
330 private void updateActivated() {
331 final Calendar now = Calendar.getInstance();
332 final Calendar startTime = mStartTime.getDateTimeBefore(now);
333 final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
334 final boolean activated = now.before(endTime);
335
336 boolean setActivated = mIsActivated == null || mLastActivatedTime == null;
337 if (!setActivated && mIsActivated != activated) {
338 final TimeZone currentTimeZone = now.getTimeZone();
339 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
340 final int year = mLastActivatedTime.get(Calendar.YEAR);
341 final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
342 final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
343 final int minute = mLastActivatedTime.get(Calendar.MINUTE);
344
345 mLastActivatedTime.setTimeZone(currentTimeZone);
346 mLastActivatedTime.set(Calendar.YEAR, year);
347 mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
348 mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
349 mLastActivatedTime.set(Calendar.MINUTE, minute);
350 }
351
352 if (mIsActivated) {
353 setActivated = now.before(mStartTime.getDateTimeBefore(mLastActivatedTime))
354 || now.after(mEndTime.getDateTimeAfter(mLastActivatedTime));
355 } else {
356 setActivated = now.before(mEndTime.getDateTimeBefore(mLastActivatedTime))
357 || now.after(mStartTime.getDateTimeAfter(mLastActivatedTime));
358 }
359 }
360
361 if (setActivated) {
362 mController.setActivated(activated);
363 }
364 updateNextAlarm();
365 }
366
367 private void updateNextAlarm() {
368 if (mIsActivated != null) {
369 final Calendar now = Calendar.getInstance();
370 final Calendar next = mIsActivated ? 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) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700399 if (mIsActivated != null) {
400 mLastActivatedTime = Calendar.getInstance();
401 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700402 updateNextAlarm();
403 }
404
405 @Override
406 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
407 mStartTime = startTime;
408 mLastActivatedTime = null;
409 updateActivated();
410 }
411
412 @Override
413 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
414 mEndTime = endTime;
415 mLastActivatedTime = null;
416 updateActivated();
417 }
418
419 @Override
420 public void onAlarm() {
421 if (DEBUG) Slog.d(TAG, "onAlarm");
422 updateActivated();
423 }
424 }
425
426 private class TwilightAutoMode extends AutoMode implements TwilightListener {
427
428 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700429
Justin Klaassen908b86c2016-08-08 09:18:42 -0700430 private Calendar mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700431
432 public TwilightAutoMode() {
433 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700434 }
435
Justin Klaassen908b86c2016-08-08 09:18:42 -0700436 private void updateActivated(TwilightState state) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700437 final boolean isNight = state != null && state.isNight();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700438 boolean setActivated = mIsActivated == null || mIsActivated != isNight;
439 if (setActivated && state != null && mLastActivatedTime != null) {
440 final Calendar sunrise = state.sunrise();
441 final Calendar sunset = state.sunset();
442 if (sunrise.before(sunset)) {
443 setActivated = mLastActivatedTime.before(sunrise)
444 || mLastActivatedTime.after(sunset);
445 } else {
446 setActivated = mLastActivatedTime.before(sunset)
447 || mLastActivatedTime.after(sunrise);
Justin Klaassen911e8892016-06-21 18:24:24 -0700448 }
449 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700450
451 if (setActivated) {
452 mController.setActivated(isNight);
453 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700454 }
455
456 @Override
457 public void onStart() {
458 mTwilightManager.registerListener(this, mHandler);
459
460 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700461 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700462 }
463
464 @Override
465 public void onStop() {
466 mTwilightManager.unregisterListener(this);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700467 mLastActivatedTime = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700468 }
469
470 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700471 public void onActivated(boolean activated) {
472 if (mIsActivated != null) {
473 mLastActivatedTime = Calendar.getInstance();
474 }
475 }
476
477 @Override
478 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700479 if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
Justin Klaassen908b86c2016-08-08 09:18:42 -0700480 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700481 }
482 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700483
484 /**
485 * Interpolates between two 4x4 color transform matrices (in column-major order).
486 */
487 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
488
489 /**
490 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
491 */
492 private final float[] mResultMatrix = new float[16];
493
494 @Override
495 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
496 for (int i = 0; i < mResultMatrix.length; i++) {
497 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
498 }
499 return mResultMatrix;
500 }
501 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700502}