blob: 026921deea558f4a7e9ce8d8b35c262412729f14 [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 /**
Christine Franks82a4d842017-07-24 18:14:05 -0700114 * The 3x3 color transformation matrix is formatted like so:
Christine Franks6418d0b2017-02-13 09:48:00 -0800115 * <table>
116 * <tr><td>R: a coefficient</td><td>G: a coefficient</td><td>B: a coefficient</td></tr>
117 * <tr><td>R: b coefficient</td><td>G: b coefficient</td><td>B: b coefficient</td></tr>
118 * <tr><td>R: y-intercept</td><td>G: y-intercept</td><td>B: y-intercept</td></tr>
119 * </table>
120 */
121 private static final float[] mColorTempCoefficients = new float[] {
Christine Franks6e843d12017-07-26 17:51:16 -0700122 0.0f, -0.000000014365268757f, -0.000000000910931179f,
123 0.0f, 0.000255092801250106f, 0.000207598323269139f,
124 1.0f, -0.064156942434907716f, -0.349361641294833436f
Christine Franks6418d0b2017-02-13 09:48:00 -0800125 };
126
Justin Klaassen911e8892016-06-21 18:24:24 -0700127 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -0700128 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -0700129 private boolean mBootCompleted;
130
131 private NightDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -0700132 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -0700133 private Boolean mIsActivated;
134 private AutoMode mAutoMode;
135
136 public NightDisplayService(Context context) {
137 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -0700138 mHandler = new Handler(Looper.getMainLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -0700139 }
140
141 @Override
142 public void onStart() {
143 // Nothing to publish.
144 }
145
146 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700147 public void onBootPhase(int phase) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800148 if (phase >= PHASE_SYSTEM_SERVICES_READY) {
149 final IVrManager vrManager = IVrManager.Stub.asInterface(
150 getBinderService(Context.VR_SERVICE));
Ruben Brunkde003ae2016-08-30 13:14:33 -0700151 if (vrManager != null) {
152 try {
153 vrManager.registerListener(mVrStateCallbacks);
154 } catch (RemoteException e) {
155 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
156 }
157 }
Christine Frankse5bb03e2017-02-10 17:36:10 -0800158 }
159
160 if (phase >= PHASE_BOOT_COMPLETED) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700161 mBootCompleted = true;
162
163 // Register listeners now that boot is complete.
164 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
165 setUp();
166 }
167 }
168 }
169
170 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700171 public void onStartUser(int userHandle) {
172 super.onStartUser(userHandle);
173
Justin Klaassen911e8892016-06-21 18:24:24 -0700174 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700175 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700176 }
177 }
178
179 @Override
180 public void onSwitchUser(int userHandle) {
181 super.onSwitchUser(userHandle);
182
Justin Klaassen2696d992016-07-11 21:26:46 -0700183 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700184 }
185
186 @Override
187 public void onStopUser(int userHandle) {
188 super.onStopUser(userHandle);
189
Justin Klaassen911e8892016-06-21 18:24:24 -0700190 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700191 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700192 }
193 }
194
Justin Klaassen2696d992016-07-11 21:26:46 -0700195 private void onUserChanged(int userHandle) {
196 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700197
Justin Klaassen2696d992016-07-11 21:26:46 -0700198 if (mCurrentUser != UserHandle.USER_NULL) {
199 if (mUserSetupObserver != null) {
200 cr.unregisterContentObserver(mUserSetupObserver);
201 mUserSetupObserver = null;
202 } else if (mBootCompleted) {
203 tearDown();
204 }
205 }
206
207 mCurrentUser = userHandle;
208
209 if (mCurrentUser != UserHandle.USER_NULL) {
210 if (!isUserSetupCompleted(cr, mCurrentUser)) {
211 mUserSetupObserver = new ContentObserver(mHandler) {
212 @Override
213 public void onChange(boolean selfChange, Uri uri) {
214 if (isUserSetupCompleted(cr, mCurrentUser)) {
215 cr.unregisterContentObserver(this);
216 mUserSetupObserver = null;
217
218 if (mBootCompleted) {
219 setUp();
220 }
221 }
222 }
223 };
224 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
225 false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
226 } else if (mBootCompleted) {
227 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700228 }
229 }
230 }
231
Justin Klaassen2696d992016-07-11 21:26:46 -0700232 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
233 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
234 }
235
236 private void setUp() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700237 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
238
Justin Klaassen911e8892016-06-21 18:24:24 -0700239 // Create a new controller for the current user and start listening for changes.
240 mController = new NightDisplayController(getContext(), mCurrentUser);
241 mController.setListener(this);
242
Christine Franks6418d0b2017-02-13 09:48:00 -0800243 // Prepare color transformation matrix.
244 setMatrix(mController.getColorTemperature(), mMatrixNight);
245
Justin Klaassen911e8892016-06-21 18:24:24 -0700246 // Initialize the current auto mode.
247 onAutoModeChanged(mController.getAutoMode());
248
249 // Force the initialization current activated state.
250 if (mIsActivated == null) {
251 onActivated(mController.isActivated());
252 }
Christine Franks6418d0b2017-02-13 09:48:00 -0800253
254 // Transition the screen to the current temperature.
255 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700256 }
257
Justin Klaassen2696d992016-07-11 21:26:46 -0700258 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700259 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
260
Justin Klaassen2696d992016-07-11 21:26:46 -0700261 if (mController != null) {
262 mController.setListener(null);
263 mController = null;
264 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700265
266 if (mAutoMode != null) {
267 mAutoMode.onStop();
268 mAutoMode = null;
269 }
270
Justin Klaassen639214e2016-07-14 21:00:06 -0700271 if (mColorMatrixAnimator != null) {
272 mColorMatrixAnimator.end();
273 mColorMatrixAnimator = null;
274 }
275
Justin Klaassen911e8892016-06-21 18:24:24 -0700276 mIsActivated = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700277 }
278
279 @Override
280 public void onActivated(boolean activated) {
281 if (mIsActivated == null || mIsActivated != activated) {
282 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
283
Justin Klaassen908b86c2016-08-08 09:18:42 -0700284 mIsActivated = activated;
285
Christine Frankse5bb03e2017-02-10 17:36:10 -0800286 if (mAutoMode != null) {
287 mAutoMode.onActivated(activated);
288 }
289
Christine Franks6418d0b2017-02-13 09:48:00 -0800290 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700291 }
292 }
293
294 @Override
295 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700296 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
297
Justin Klaassen911e8892016-06-21 18:24:24 -0700298 if (mAutoMode != null) {
299 mAutoMode.onStop();
300 mAutoMode = null;
301 }
302
303 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
304 mAutoMode = new CustomAutoMode();
305 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
306 mAutoMode = new TwilightAutoMode();
307 }
308
309 if (mAutoMode != null) {
310 mAutoMode.onStart();
311 }
312 }
313
314 @Override
315 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700316 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
317
Justin Klaassen911e8892016-06-21 18:24:24 -0700318 if (mAutoMode != null) {
319 mAutoMode.onCustomStartTimeChanged(startTime);
320 }
321 }
322
323 @Override
324 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700325 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
326
Justin Klaassen911e8892016-06-21 18:24:24 -0700327 if (mAutoMode != null) {
328 mAutoMode.onCustomEndTimeChanged(endTime);
329 }
330 }
331
Christine Franks6418d0b2017-02-13 09:48:00 -0800332 @Override
333 public void onColorTemperatureChanged(int colorTemperature) {
334 setMatrix(colorTemperature, mMatrixNight);
335 applyTint(true);
336 }
337
338 /**
339 * Applies current color temperature matrix, or removes it if deactivated.
340 *
341 * @param immediate {@code true} skips transition animation
342 */
343 private void applyTint(boolean immediate) {
344 // Cancel the old animator if still running.
345 if (mColorMatrixAnimator != null) {
346 mColorMatrixAnimator.cancel();
347 }
348
349 // Don't do any color matrix change animations if we are ignoring them anyway.
350 if (mIgnoreAllColorMatrixChanges.get()) {
351 return;
352 }
353
354 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
355 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
356 final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
357
358 if (immediate) {
359 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
360 } else {
361 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
362 from == null ? MATRIX_IDENTITY : from, to);
363 mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
364 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
365 getContext(), android.R.interpolator.fast_out_slow_in));
366 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
367 @Override
368 public void onAnimationUpdate(ValueAnimator animator) {
369 final float[] value = (float[]) animator.getAnimatedValue();
370 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
371 }
372 });
373 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
374
375 private boolean mIsCancelled;
376
377 @Override
378 public void onAnimationCancel(Animator animator) {
379 mIsCancelled = true;
380 }
381
382 @Override
383 public void onAnimationEnd(Animator animator) {
384 if (!mIsCancelled) {
385 // Ensure final color matrix is set at the end of the animation. If the
386 // animation is cancelled then don't set the final color matrix so the new
387 // animator can pick up from where this one left off.
388 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
389 }
390 mColorMatrixAnimator = null;
391 }
392 });
393 mColorMatrixAnimator.start();
394 }
395 }
396
397 /**
398 * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
399 *
400 * @param colorTemperature color temperature in Kelvin
Christine Frankse5bb03e2017-02-10 17:36:10 -0800401 * @param outTemp the 4x4 display transformation matrix for that color temperature
Christine Franks6418d0b2017-02-13 09:48:00 -0800402 */
403 private void setMatrix(int colorTemperature, float[] outTemp) {
404 if (outTemp.length != 16) {
405 Slog.d(TAG, "The display transformation matrix must be 4x4");
406 return;
407 }
408
Christine Franks82a4d842017-07-24 18:14:05 -0700409 Matrix.setIdentityM(outTemp, 0);
Christine Franks6418d0b2017-02-13 09:48:00 -0800410
411 final float squareTemperature = colorTemperature * colorTemperature;
412 final float red = squareTemperature * mColorTempCoefficients[0]
413 + colorTemperature * mColorTempCoefficients[3] + mColorTempCoefficients[6];
414 final float green = squareTemperature * mColorTempCoefficients[1]
415 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[7];
416 final float blue = squareTemperature * mColorTempCoefficients[2]
417 + colorTemperature * mColorTempCoefficients[5] + mColorTempCoefficients[8];
418 outTemp[0] = red;
419 outTemp[5] = green;
420 outTemp[10] = blue;
421 }
422
Justin Klaassen911e8892016-06-21 18:24:24 -0700423 private abstract class AutoMode implements NightDisplayController.Callback {
424 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800425
Justin Klaassen911e8892016-06-21 18:24:24 -0700426 public abstract void onStop();
427 }
428
429 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
430
431 private final AlarmManager mAlarmManager;
432 private final BroadcastReceiver mTimeChangedReceiver;
433
434 private NightDisplayController.LocalTime mStartTime;
435 private NightDisplayController.LocalTime mEndTime;
436
437 private Calendar mLastActivatedTime;
438
Christine Frankse5bb03e2017-02-10 17:36:10 -0800439 CustomAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700440 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
441 mTimeChangedReceiver = new BroadcastReceiver() {
442 @Override
443 public void onReceive(Context context, Intent intent) {
444 updateActivated();
445 }
446 };
447 }
448
449 private void updateActivated() {
450 final Calendar now = Calendar.getInstance();
451 final Calendar startTime = mStartTime.getDateTimeBefore(now);
452 final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
Justin Klaassen911e8892016-06-21 18:24:24 -0700453
Christine Frankse5bb03e2017-02-10 17:36:10 -0800454 boolean activate = now.before(endTime);
455 if (mLastActivatedTime != null) {
456 // Convert mLastActivatedTime to the current timezone if needed.
Justin Klaassen911e8892016-06-21 18:24:24 -0700457 final TimeZone currentTimeZone = now.getTimeZone();
458 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
459 final int year = mLastActivatedTime.get(Calendar.YEAR);
460 final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
461 final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
462 final int minute = mLastActivatedTime.get(Calendar.MINUTE);
463
464 mLastActivatedTime.setTimeZone(currentTimeZone);
465 mLastActivatedTime.set(Calendar.YEAR, year);
466 mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
467 mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
468 mLastActivatedTime.set(Calendar.MINUTE, minute);
469 }
470
Christine Frankse5bb03e2017-02-10 17:36:10 -0800471 // Maintain the existing activated state if within the current period.
472 if (mLastActivatedTime.before(now)
473 && mLastActivatedTime.after(startTime)
474 && (mLastActivatedTime.after(endTime) || now.before(endTime))) {
475 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700476 }
477 }
478
Christine Frankse5bb03e2017-02-10 17:36:10 -0800479 if (mIsActivated == null || mIsActivated != activate) {
480 mController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700481 }
Justin Klaassen4346f632016-08-08 15:01:47 -0700482 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700483 }
484
Justin Klaassen4346f632016-08-08 15:01:47 -0700485 private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
486 if (activated != null) {
487 final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
Justin Klaassen911e8892016-06-21 18:24:24 -0700488 : mStartTime.getDateTimeAfter(now);
489 mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
490 }
491 }
492
493 @Override
494 public void onStart() {
495 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
496 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
497 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
498
499 mStartTime = mController.getCustomStartTime();
500 mEndTime = mController.getCustomEndTime();
501
Christine Franks1454eae2017-05-31 10:52:22 -0700502 mLastActivatedTime = mController.getLastActivatedTime();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800503
Justin Klaassen911e8892016-06-21 18:24:24 -0700504 // Force an update to initialize state.
505 updateActivated();
506 }
507
508 @Override
509 public void onStop() {
510 getContext().unregisterReceiver(mTimeChangedReceiver);
511
512 mAlarmManager.cancel(this);
513 mLastActivatedTime = null;
514 }
515
516 @Override
517 public void onActivated(boolean activated) {
Christine Franks1454eae2017-05-31 10:52:22 -0700518 mLastActivatedTime = mController.getLastActivatedTime();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800519 updateNextAlarm(activated, Calendar.getInstance());
Justin Klaassen911e8892016-06-21 18:24:24 -0700520 }
521
522 @Override
523 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
524 mStartTime = startTime;
525 mLastActivatedTime = null;
526 updateActivated();
527 }
528
529 @Override
530 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
531 mEndTime = endTime;
532 mLastActivatedTime = null;
533 updateActivated();
534 }
535
536 @Override
537 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700538 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700539 updateActivated();
540 }
541 }
542
543 private class TwilightAutoMode extends AutoMode implements TwilightListener {
544
545 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700546
Christine Frankse5bb03e2017-02-10 17:36:10 -0800547 TwilightAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700548 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700549 }
550
Justin Klaassen908b86c2016-08-08 09:18:42 -0700551 private void updateActivated(TwilightState state) {
Justin Klaassen3da4c442017-05-05 15:19:33 -0700552 if (state == null) {
553 // If there isn't a valid TwilightState then just keep the current activated
554 // state.
555 return;
556 }
557
558 boolean activate = state.isNight();
Christine Franks1454eae2017-05-31 10:52:22 -0700559 final Calendar lastActivatedTime = mController.getLastActivatedTime();
Justin Klaassen3da4c442017-05-05 15:19:33 -0700560 if (lastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800561 final Calendar now = Calendar.getInstance();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700562 final Calendar sunrise = state.sunrise();
563 final Calendar sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800564
565 // Maintain the existing activated state if within the current period.
Justin Klaassen3da4c442017-05-05 15:19:33 -0700566 if (lastActivatedTime.before(now)
567 && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800568 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700569 }
570 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700571
Christine Frankse5bb03e2017-02-10 17:36:10 -0800572 if (mIsActivated == null || mIsActivated != activate) {
573 mController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700574 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700575 }
576
577 @Override
578 public void onStart() {
579 mTwilightManager.registerListener(this, mHandler);
580
581 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700582 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700583 }
584
585 @Override
586 public void onStop() {
587 mTwilightManager.unregisterListener(this);
588 }
589
590 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700591 public void onActivated(boolean activated) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700592 }
593
594 @Override
595 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700596 Slog.d(TAG, "onTwilightStateChanged: isNight="
597 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700598 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700599 }
600 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700601
602 /**
603 * Interpolates between two 4x4 color transform matrices (in column-major order).
604 */
605 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
606
607 /**
608 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
609 */
610 private final float[] mResultMatrix = new float[16];
611
612 @Override
613 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
614 for (int i = 0; i < mResultMatrix.length; i++) {
615 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
616 }
617 return mResultMatrix;
618 }
619 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700620}