blob: b3cf57b3564ae654df480e7024f2697d4699bca1 [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;
Ruben Brunkde003ae2016-08-30 13:14:33 -070036import android.os.RemoteException;
Justin Klaassen911e8892016-06-21 18:24:24 -070037import android.os.UserHandle;
38import android.provider.Settings.Secure;
Ruben Brunkde003ae2016-08-30 13:14:33 -070039import android.service.vr.IVrManager;
40import android.service.vr.IVrStateCallbacks;
Justin Klaassen639214e2016-07-14 21:00:06 -070041import android.util.MathUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070042import android.util.Slog;
Justin Klaassen639214e2016-07-14 21:00:06 -070043import android.view.animation.AnimationUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070044
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;
50
Ruben Brunkde003ae2016-08-30 13:14:33 -070051import java.util.concurrent.atomic.AtomicBoolean;
Justin Klaassen911e8892016-06-21 18:24:24 -070052import java.util.Calendar;
53import java.util.TimeZone;
54
Justin Klaassen639214e2016-07-14 21:00:06 -070055import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
56
Justin Klaassen911e8892016-06-21 18:24:24 -070057/**
58 * Tints the display at night.
59 */
60public final class NightDisplayService extends SystemService
61 implements NightDisplayController.Callback {
62
63 private static final String TAG = "NightDisplayService";
Justin Klaassen911e8892016-06-21 18:24:24 -070064
65 /**
Christine Franks7b83b4282017-01-18 14:55:00 -080066 * The transition time, in milliseconds, for Night Display to turn on/off.
67 */
68 private static final long TRANSITION_DURATION = 3000L;
69
70 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070071 * The identity matrix, used if one of the given matrices is {@code null}.
72 */
73 private static final float[] MATRIX_IDENTITY = new float[16];
74 static {
75 Matrix.setIdentityM(MATRIX_IDENTITY, 0);
76 }
77
78 /**
79 * Evaluator used to animate color matrix transitions.
80 */
81 private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
82
Justin Klaassen2696d992016-07-11 21:26:46 -070083 private final Handler mHandler;
Ruben Brunkde003ae2016-08-30 13:14:33 -070084 private final AtomicBoolean mIgnoreAllColorMatrixChanges = new AtomicBoolean();
85 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
86 @Override
87 public void onVrStateChanged(final boolean enabled) {
88 // Turn off all night mode display stuff while device is in VR mode.
89 mIgnoreAllColorMatrixChanges.set(enabled);
90 mHandler.post(new Runnable() {
91 @Override
92 public void run() {
93 // Cancel in-progress animations
94 if (mColorMatrixAnimator != null) {
95 mColorMatrixAnimator.cancel();
96 }
97
98 final DisplayTransformManager dtm =
99 getLocalService(DisplayTransformManager.class);
100 if (enabled) {
101 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
Steven Thomas1e9b4b02017-01-10 16:54:42 -0800102 } else if (mController != null && mController.isActivated()) {
Christine Franks6418d0b2017-02-13 09:48:00 -0800103 setMatrix(mController.getColorTemperature(), mMatrixNight);
104 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, mMatrixNight);
Ruben Brunkde003ae2016-08-30 13:14:33 -0700105 }
106 }
107 });
108 }
109 };
Justin Klaassen2696d992016-07-11 21:26:46 -0700110
Christine Franks6418d0b2017-02-13 09:48:00 -0800111 private float[] mMatrixNight = new float[16];
112
113 /**
114 * These coefficients were generated by an LLS quadratic regression fitted to the
115 * overdetermined system based on experimental readings (and subsequent conversion from xy
116 * chromaticity coordinates to gamma-corrected RGB values): { (temperature, R, G, B) } ->
117 * { (7304, 1.0, 1.0, 1.0), (4082, 1.0, 0.857, 0.719), (2850, 1.0, .754, .516),
118 * (2596, 1.0, 0.722, 0.454) }. The 3x3 matrix is formatted like so:
119 * <table>
120 * <tr><td>R: a coefficient</td><td>G: a coefficient</td><td>B: a coefficient</td></tr>
121 * <tr><td>R: b coefficient</td><td>G: b coefficient</td><td>B: b coefficient</td></tr>
122 * <tr><td>R: y-intercept</td><td>G: y-intercept</td><td>B: y-intercept</td></tr>
123 * </table>
124 */
125 private static final float[] mColorTempCoefficients = new float[] {
126 0.0f, -0.00000000962353339f, -0.0000000189359041f,
127 0.0f, 0.000153045476f, 0.000302412211f,
128 1.0f, 0.390782778f, -0.198650895f
129 };
130
Justin Klaassen911e8892016-06-21 18:24:24 -0700131 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -0700132 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -0700133 private boolean mBootCompleted;
134
135 private NightDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -0700136 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -0700137 private Boolean mIsActivated;
138 private AutoMode mAutoMode;
139
140 public NightDisplayService(Context context) {
141 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -0700142 mHandler = new Handler(Looper.getMainLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -0700143 }
144
145 @Override
146 public void onStart() {
147 // Nothing to publish.
148 }
149
150 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700151 public void onBootPhase(int phase) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800152 if (phase >= PHASE_SYSTEM_SERVICES_READY) {
153 final IVrManager vrManager = IVrManager.Stub.asInterface(
154 getBinderService(Context.VR_SERVICE));
Ruben Brunkde003ae2016-08-30 13:14:33 -0700155 if (vrManager != null) {
156 try {
157 vrManager.registerListener(mVrStateCallbacks);
158 } catch (RemoteException e) {
159 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
160 }
161 }
Christine Frankse5bb03e2017-02-10 17:36:10 -0800162 }
163
164 if (phase >= PHASE_BOOT_COMPLETED) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700165 mBootCompleted = true;
166
167 // Register listeners now that boot is complete.
168 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
169 setUp();
170 }
171 }
172 }
173
174 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700175 public void onStartUser(int userHandle) {
176 super.onStartUser(userHandle);
177
Justin Klaassen911e8892016-06-21 18:24:24 -0700178 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700179 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700180 }
181 }
182
183 @Override
184 public void onSwitchUser(int userHandle) {
185 super.onSwitchUser(userHandle);
186
Justin Klaassen2696d992016-07-11 21:26:46 -0700187 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700188 }
189
190 @Override
191 public void onStopUser(int userHandle) {
192 super.onStopUser(userHandle);
193
Justin Klaassen911e8892016-06-21 18:24:24 -0700194 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700195 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700196 }
197 }
198
Justin Klaassen2696d992016-07-11 21:26:46 -0700199 private void onUserChanged(int userHandle) {
200 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700201
Justin Klaassen2696d992016-07-11 21:26:46 -0700202 if (mCurrentUser != UserHandle.USER_NULL) {
203 if (mUserSetupObserver != null) {
204 cr.unregisterContentObserver(mUserSetupObserver);
205 mUserSetupObserver = null;
206 } else if (mBootCompleted) {
207 tearDown();
208 }
209 }
210
211 mCurrentUser = userHandle;
212
213 if (mCurrentUser != UserHandle.USER_NULL) {
214 if (!isUserSetupCompleted(cr, mCurrentUser)) {
215 mUserSetupObserver = new ContentObserver(mHandler) {
216 @Override
217 public void onChange(boolean selfChange, Uri uri) {
218 if (isUserSetupCompleted(cr, mCurrentUser)) {
219 cr.unregisterContentObserver(this);
220 mUserSetupObserver = null;
221
222 if (mBootCompleted) {
223 setUp();
224 }
225 }
226 }
227 };
228 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
229 false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
230 } else if (mBootCompleted) {
231 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700232 }
233 }
234 }
235
Justin Klaassen2696d992016-07-11 21:26:46 -0700236 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
237 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
238 }
239
240 private void setUp() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700241 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
242
Justin Klaassen911e8892016-06-21 18:24:24 -0700243 // Create a new controller for the current user and start listening for changes.
244 mController = new NightDisplayController(getContext(), mCurrentUser);
245 mController.setListener(this);
246
Christine Franks6418d0b2017-02-13 09:48:00 -0800247 // Prepare color transformation matrix.
248 setMatrix(mController.getColorTemperature(), mMatrixNight);
249
Justin Klaassen911e8892016-06-21 18:24:24 -0700250 // Initialize the current auto mode.
251 onAutoModeChanged(mController.getAutoMode());
252
253 // Force the initialization current activated state.
254 if (mIsActivated == null) {
255 onActivated(mController.isActivated());
256 }
Christine Franks6418d0b2017-02-13 09:48:00 -0800257
258 // Transition the screen to the current temperature.
259 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700260 }
261
Justin Klaassen2696d992016-07-11 21:26:46 -0700262 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700263 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
264
Justin Klaassen2696d992016-07-11 21:26:46 -0700265 if (mController != null) {
266 mController.setListener(null);
267 mController = null;
268 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700269
270 if (mAutoMode != null) {
271 mAutoMode.onStop();
272 mAutoMode = null;
273 }
274
Justin Klaassen639214e2016-07-14 21:00:06 -0700275 if (mColorMatrixAnimator != null) {
276 mColorMatrixAnimator.end();
277 mColorMatrixAnimator = null;
278 }
279
Justin Klaassen911e8892016-06-21 18:24:24 -0700280 mIsActivated = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700281 }
282
283 @Override
284 public void onActivated(boolean activated) {
285 if (mIsActivated == null || mIsActivated != activated) {
286 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
287
Justin Klaassen908b86c2016-08-08 09:18:42 -0700288 mIsActivated = activated;
289
Christine Frankse5bb03e2017-02-10 17:36:10 -0800290 if (mAutoMode != null) {
291 mAutoMode.onActivated(activated);
292 }
293
Christine Franks6418d0b2017-02-13 09:48:00 -0800294 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700295 }
296 }
297
298 @Override
299 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700300 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
301
Justin Klaassen911e8892016-06-21 18:24:24 -0700302 if (mAutoMode != null) {
303 mAutoMode.onStop();
304 mAutoMode = null;
305 }
306
307 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
308 mAutoMode = new CustomAutoMode();
309 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
310 mAutoMode = new TwilightAutoMode();
311 }
312
313 if (mAutoMode != null) {
314 mAutoMode.onStart();
315 }
316 }
317
318 @Override
319 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700320 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
321
Justin Klaassen911e8892016-06-21 18:24:24 -0700322 if (mAutoMode != null) {
323 mAutoMode.onCustomStartTimeChanged(startTime);
324 }
325 }
326
327 @Override
328 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700329 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
330
Justin Klaassen911e8892016-06-21 18:24:24 -0700331 if (mAutoMode != null) {
332 mAutoMode.onCustomEndTimeChanged(endTime);
333 }
334 }
335
Christine Franks6418d0b2017-02-13 09:48:00 -0800336 @Override
337 public void onColorTemperatureChanged(int colorTemperature) {
338 setMatrix(colorTemperature, mMatrixNight);
339 applyTint(true);
340 }
341
342 /**
343 * Applies current color temperature matrix, or removes it if deactivated.
344 *
345 * @param immediate {@code true} skips transition animation
346 */
347 private void applyTint(boolean immediate) {
348 // Cancel the old animator if still running.
349 if (mColorMatrixAnimator != null) {
350 mColorMatrixAnimator.cancel();
351 }
352
353 // Don't do any color matrix change animations if we are ignoring them anyway.
354 if (mIgnoreAllColorMatrixChanges.get()) {
355 return;
356 }
357
358 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
359 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
360 final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
361
362 if (immediate) {
363 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
364 } else {
365 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
366 from == null ? MATRIX_IDENTITY : from, to);
367 mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
368 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
369 getContext(), android.R.interpolator.fast_out_slow_in));
370 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
371 @Override
372 public void onAnimationUpdate(ValueAnimator animator) {
373 final float[] value = (float[]) animator.getAnimatedValue();
374 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
375 }
376 });
377 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
378
379 private boolean mIsCancelled;
380
381 @Override
382 public void onAnimationCancel(Animator animator) {
383 mIsCancelled = true;
384 }
385
386 @Override
387 public void onAnimationEnd(Animator animator) {
388 if (!mIsCancelled) {
389 // Ensure final color matrix is set at the end of the animation. If the
390 // animation is cancelled then don't set the final color matrix so the new
391 // animator can pick up from where this one left off.
392 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
393 }
394 mColorMatrixAnimator = null;
395 }
396 });
397 mColorMatrixAnimator.start();
398 }
399 }
400
401 /**
402 * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
403 *
404 * @param colorTemperature color temperature in Kelvin
Christine Frankse5bb03e2017-02-10 17:36:10 -0800405 * @param outTemp the 4x4 display transformation matrix for that color temperature
Christine Franks6418d0b2017-02-13 09:48:00 -0800406 */
407 private void setMatrix(int colorTemperature, float[] outTemp) {
408 if (outTemp.length != 16) {
409 Slog.d(TAG, "The display transformation matrix must be 4x4");
410 return;
411 }
412
413 Matrix.setIdentityM(mMatrixNight, 0);
414
415 final float squareTemperature = colorTemperature * colorTemperature;
416 final float red = squareTemperature * mColorTempCoefficients[0]
417 + colorTemperature * mColorTempCoefficients[3] + mColorTempCoefficients[6];
418 final float green = squareTemperature * mColorTempCoefficients[1]
419 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[7];
420 final float blue = squareTemperature * mColorTempCoefficients[2]
421 + colorTemperature * mColorTempCoefficients[5] + mColorTempCoefficients[8];
422 outTemp[0] = red;
423 outTemp[5] = green;
424 outTemp[10] = blue;
425 }
426
Justin Klaassen911e8892016-06-21 18:24:24 -0700427 private abstract class AutoMode implements NightDisplayController.Callback {
428 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800429
Justin Klaassen911e8892016-06-21 18:24:24 -0700430 public abstract void onStop();
431 }
432
433 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
434
435 private final AlarmManager mAlarmManager;
436 private final BroadcastReceiver mTimeChangedReceiver;
437
438 private NightDisplayController.LocalTime mStartTime;
439 private NightDisplayController.LocalTime mEndTime;
440
441 private Calendar mLastActivatedTime;
442
Christine Frankse5bb03e2017-02-10 17:36:10 -0800443 CustomAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700444 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
445 mTimeChangedReceiver = new BroadcastReceiver() {
446 @Override
447 public void onReceive(Context context, Intent intent) {
448 updateActivated();
449 }
450 };
451 }
452
453 private void updateActivated() {
454 final Calendar now = Calendar.getInstance();
455 final Calendar startTime = mStartTime.getDateTimeBefore(now);
456 final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
Justin Klaassen911e8892016-06-21 18:24:24 -0700457
Christine Frankse5bb03e2017-02-10 17:36:10 -0800458 boolean activate = now.before(endTime);
459 if (mLastActivatedTime != null) {
460 // Convert mLastActivatedTime to the current timezone if needed.
Justin Klaassen911e8892016-06-21 18:24:24 -0700461 final TimeZone currentTimeZone = now.getTimeZone();
462 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
463 final int year = mLastActivatedTime.get(Calendar.YEAR);
464 final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
465 final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
466 final int minute = mLastActivatedTime.get(Calendar.MINUTE);
467
468 mLastActivatedTime.setTimeZone(currentTimeZone);
469 mLastActivatedTime.set(Calendar.YEAR, year);
470 mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
471 mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
472 mLastActivatedTime.set(Calendar.MINUTE, minute);
473 }
474
Christine Frankse5bb03e2017-02-10 17:36:10 -0800475 // Maintain the existing activated state if within the current period.
476 if (mLastActivatedTime.before(now)
477 && mLastActivatedTime.after(startTime)
478 && (mLastActivatedTime.after(endTime) || now.before(endTime))) {
479 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700480 }
481 }
482
Christine Frankse5bb03e2017-02-10 17:36:10 -0800483 if (mIsActivated == null || mIsActivated != activate) {
484 mController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700485 }
Justin Klaassen4346f632016-08-08 15:01:47 -0700486 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700487 }
488
Justin Klaassen4346f632016-08-08 15:01:47 -0700489 private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
490 if (activated != null) {
491 final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
Justin Klaassen911e8892016-06-21 18:24:24 -0700492 : mStartTime.getDateTimeAfter(now);
493 mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
494 }
495 }
496
497 @Override
498 public void onStart() {
499 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
500 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
501 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
502
503 mStartTime = mController.getCustomStartTime();
504 mEndTime = mController.getCustomEndTime();
505
Christine Franks1454eae2017-05-31 10:52:22 -0700506 mLastActivatedTime = mController.getLastActivatedTime();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800507
Justin Klaassen911e8892016-06-21 18:24:24 -0700508 // Force an update to initialize state.
509 updateActivated();
510 }
511
512 @Override
513 public void onStop() {
514 getContext().unregisterReceiver(mTimeChangedReceiver);
515
516 mAlarmManager.cancel(this);
517 mLastActivatedTime = null;
518 }
519
520 @Override
521 public void onActivated(boolean activated) {
Christine Franks1454eae2017-05-31 10:52:22 -0700522 mLastActivatedTime = mController.getLastActivatedTime();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800523 updateNextAlarm(activated, Calendar.getInstance());
Justin Klaassen911e8892016-06-21 18:24:24 -0700524 }
525
526 @Override
527 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
528 mStartTime = startTime;
529 mLastActivatedTime = null;
530 updateActivated();
531 }
532
533 @Override
534 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
535 mEndTime = endTime;
536 mLastActivatedTime = null;
537 updateActivated();
538 }
539
540 @Override
541 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700542 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700543 updateActivated();
544 }
545 }
546
547 private class TwilightAutoMode extends AutoMode implements TwilightListener {
548
549 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700550
Christine Frankse5bb03e2017-02-10 17:36:10 -0800551 TwilightAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700552 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700553 }
554
Justin Klaassen908b86c2016-08-08 09:18:42 -0700555 private void updateActivated(TwilightState state) {
Justin Klaassen3da4c442017-05-05 15:19:33 -0700556 if (state == null) {
557 // If there isn't a valid TwilightState then just keep the current activated
558 // state.
559 return;
560 }
561
562 boolean activate = state.isNight();
Christine Franks1454eae2017-05-31 10:52:22 -0700563 final Calendar lastActivatedTime = mController.getLastActivatedTime();
Justin Klaassen3da4c442017-05-05 15:19:33 -0700564 if (lastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800565 final Calendar now = Calendar.getInstance();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700566 final Calendar sunrise = state.sunrise();
567 final Calendar sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800568
569 // Maintain the existing activated state if within the current period.
Justin Klaassen3da4c442017-05-05 15:19:33 -0700570 if (lastActivatedTime.before(now)
571 && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800572 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700573 }
574 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700575
Christine Frankse5bb03e2017-02-10 17:36:10 -0800576 if (mIsActivated == null || mIsActivated != activate) {
577 mController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700578 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700579 }
580
581 @Override
582 public void onStart() {
583 mTwilightManager.registerListener(this, mHandler);
584
585 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700586 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700587 }
588
589 @Override
590 public void onStop() {
591 mTwilightManager.unregisterListener(this);
592 }
593
594 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700595 public void onActivated(boolean activated) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700596 }
597
598 @Override
599 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700600 Slog.d(TAG, "onTwilightStateChanged: isNight="
601 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700602 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700603 }
604 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700605
606 /**
607 * Interpolates between two 4x4 color transform matrices (in column-major order).
608 */
609 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
610
611 /**
612 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
613 */
614 private final float[] mResultMatrix = new float[16];
615
616 @Override
617 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
618 for (int i = 0; i < mResultMatrix.length; i++) {
619 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
620 }
621 return mResultMatrix;
622 }
623 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700624}