blob: d5742657c5e2d924b2a40a3f3b0cf9bce80fbdf0 [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
Christine Frankse5bb03e2017-02-10 17:36:10 -0800288 if (mIsActivated != null) {
289 Secure.putLongForUser(getContext().getContentResolver(),
290 Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, System.currentTimeMillis(),
291 mCurrentUser);
Justin Klaassen911e8892016-06-21 18:24:24 -0700292 }
293
Justin Klaassen908b86c2016-08-08 09:18:42 -0700294 mIsActivated = activated;
295
Christine Frankse5bb03e2017-02-10 17:36:10 -0800296 if (mAutoMode != null) {
297 mAutoMode.onActivated(activated);
298 }
299
Christine Franks6418d0b2017-02-13 09:48:00 -0800300 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700301 }
302 }
303
304 @Override
305 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700306 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
307
Justin Klaassen911e8892016-06-21 18:24:24 -0700308 if (mAutoMode != null) {
309 mAutoMode.onStop();
310 mAutoMode = null;
311 }
312
313 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
314 mAutoMode = new CustomAutoMode();
315 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
316 mAutoMode = new TwilightAutoMode();
317 }
318
319 if (mAutoMode != null) {
320 mAutoMode.onStart();
321 }
322 }
323
324 @Override
325 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700326 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
327
Justin Klaassen911e8892016-06-21 18:24:24 -0700328 if (mAutoMode != null) {
329 mAutoMode.onCustomStartTimeChanged(startTime);
330 }
331 }
332
333 @Override
334 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700335 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
336
Justin Klaassen911e8892016-06-21 18:24:24 -0700337 if (mAutoMode != null) {
338 mAutoMode.onCustomEndTimeChanged(endTime);
339 }
340 }
341
Christine Franks6418d0b2017-02-13 09:48:00 -0800342 @Override
343 public void onColorTemperatureChanged(int colorTemperature) {
344 setMatrix(colorTemperature, mMatrixNight);
345 applyTint(true);
346 }
347
348 /**
349 * Applies current color temperature matrix, or removes it if deactivated.
350 *
351 * @param immediate {@code true} skips transition animation
352 */
353 private void applyTint(boolean immediate) {
354 // Cancel the old animator if still running.
355 if (mColorMatrixAnimator != null) {
356 mColorMatrixAnimator.cancel();
357 }
358
359 // Don't do any color matrix change animations if we are ignoring them anyway.
360 if (mIgnoreAllColorMatrixChanges.get()) {
361 return;
362 }
363
364 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
365 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
366 final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
367
368 if (immediate) {
369 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
370 } else {
371 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
372 from == null ? MATRIX_IDENTITY : from, to);
373 mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
374 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
375 getContext(), android.R.interpolator.fast_out_slow_in));
376 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
377 @Override
378 public void onAnimationUpdate(ValueAnimator animator) {
379 final float[] value = (float[]) animator.getAnimatedValue();
380 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
381 }
382 });
383 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
384
385 private boolean mIsCancelled;
386
387 @Override
388 public void onAnimationCancel(Animator animator) {
389 mIsCancelled = true;
390 }
391
392 @Override
393 public void onAnimationEnd(Animator animator) {
394 if (!mIsCancelled) {
395 // Ensure final color matrix is set at the end of the animation. If the
396 // animation is cancelled then don't set the final color matrix so the new
397 // animator can pick up from where this one left off.
398 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
399 }
400 mColorMatrixAnimator = null;
401 }
402 });
403 mColorMatrixAnimator.start();
404 }
405 }
406
407 /**
408 * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
409 *
410 * @param colorTemperature color temperature in Kelvin
Christine Frankse5bb03e2017-02-10 17:36:10 -0800411 * @param outTemp the 4x4 display transformation matrix for that color temperature
Christine Franks6418d0b2017-02-13 09:48:00 -0800412 */
413 private void setMatrix(int colorTemperature, float[] outTemp) {
414 if (outTemp.length != 16) {
415 Slog.d(TAG, "The display transformation matrix must be 4x4");
416 return;
417 }
418
419 Matrix.setIdentityM(mMatrixNight, 0);
420
421 final float squareTemperature = colorTemperature * colorTemperature;
422 final float red = squareTemperature * mColorTempCoefficients[0]
423 + colorTemperature * mColorTempCoefficients[3] + mColorTempCoefficients[6];
424 final float green = squareTemperature * mColorTempCoefficients[1]
425 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[7];
426 final float blue = squareTemperature * mColorTempCoefficients[2]
427 + colorTemperature * mColorTempCoefficients[5] + mColorTempCoefficients[8];
428 outTemp[0] = red;
429 outTemp[5] = green;
430 outTemp[10] = blue;
431 }
432
Christine Frankse5bb03e2017-02-10 17:36:10 -0800433 private Calendar getLastActivatedTime() {
434 final ContentResolver cr = getContext().getContentResolver();
435 final long lastActivatedTimeMillis = Secure.getLongForUser(
436 cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1, mCurrentUser);
437 if (lastActivatedTimeMillis < 0) {
438 return null;
439 }
440
441 final Calendar lastActivatedTime = Calendar.getInstance();
442 lastActivatedTime.setTimeInMillis(lastActivatedTimeMillis);
443 return lastActivatedTime;
444 }
445
Justin Klaassen911e8892016-06-21 18:24:24 -0700446 private abstract class AutoMode implements NightDisplayController.Callback {
447 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800448
Justin Klaassen911e8892016-06-21 18:24:24 -0700449 public abstract void onStop();
450 }
451
452 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
453
454 private final AlarmManager mAlarmManager;
455 private final BroadcastReceiver mTimeChangedReceiver;
456
457 private NightDisplayController.LocalTime mStartTime;
458 private NightDisplayController.LocalTime mEndTime;
459
460 private Calendar mLastActivatedTime;
461
Christine Frankse5bb03e2017-02-10 17:36:10 -0800462 CustomAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700463 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
464 mTimeChangedReceiver = new BroadcastReceiver() {
465 @Override
466 public void onReceive(Context context, Intent intent) {
467 updateActivated();
468 }
469 };
470 }
471
472 private void updateActivated() {
473 final Calendar now = Calendar.getInstance();
474 final Calendar startTime = mStartTime.getDateTimeBefore(now);
475 final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
Justin Klaassen911e8892016-06-21 18:24:24 -0700476
Christine Frankse5bb03e2017-02-10 17:36:10 -0800477 boolean activate = now.before(endTime);
478 if (mLastActivatedTime != null) {
479 // Convert mLastActivatedTime to the current timezone if needed.
Justin Klaassen911e8892016-06-21 18:24:24 -0700480 final TimeZone currentTimeZone = now.getTimeZone();
481 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
482 final int year = mLastActivatedTime.get(Calendar.YEAR);
483 final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
484 final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
485 final int minute = mLastActivatedTime.get(Calendar.MINUTE);
486
487 mLastActivatedTime.setTimeZone(currentTimeZone);
488 mLastActivatedTime.set(Calendar.YEAR, year);
489 mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
490 mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
491 mLastActivatedTime.set(Calendar.MINUTE, minute);
492 }
493
Christine Frankse5bb03e2017-02-10 17:36:10 -0800494 // Maintain the existing activated state if within the current period.
495 if (mLastActivatedTime.before(now)
496 && mLastActivatedTime.after(startTime)
497 && (mLastActivatedTime.after(endTime) || now.before(endTime))) {
498 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700499 }
500 }
501
Christine Frankse5bb03e2017-02-10 17:36:10 -0800502 if (mIsActivated == null || mIsActivated != activate) {
503 mController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700504 }
Justin Klaassen4346f632016-08-08 15:01:47 -0700505 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700506 }
507
Justin Klaassen4346f632016-08-08 15:01:47 -0700508 private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
509 if (activated != null) {
510 final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
Justin Klaassen911e8892016-06-21 18:24:24 -0700511 : mStartTime.getDateTimeAfter(now);
512 mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
513 }
514 }
515
516 @Override
517 public void onStart() {
518 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
519 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
520 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
521
522 mStartTime = mController.getCustomStartTime();
523 mEndTime = mController.getCustomEndTime();
524
Christine Frankse5bb03e2017-02-10 17:36:10 -0800525 mLastActivatedTime = getLastActivatedTime();
526
Justin Klaassen911e8892016-06-21 18:24:24 -0700527 // Force an update to initialize state.
528 updateActivated();
529 }
530
531 @Override
532 public void onStop() {
533 getContext().unregisterReceiver(mTimeChangedReceiver);
534
535 mAlarmManager.cancel(this);
536 mLastActivatedTime = null;
537 }
538
539 @Override
540 public void onActivated(boolean activated) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800541 mLastActivatedTime = getLastActivatedTime();
542 updateNextAlarm(activated, Calendar.getInstance());
Justin Klaassen911e8892016-06-21 18:24:24 -0700543 }
544
545 @Override
546 public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
547 mStartTime = startTime;
548 mLastActivatedTime = null;
549 updateActivated();
550 }
551
552 @Override
553 public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
554 mEndTime = endTime;
555 mLastActivatedTime = null;
556 updateActivated();
557 }
558
559 @Override
560 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700561 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700562 updateActivated();
563 }
564 }
565
566 private class TwilightAutoMode extends AutoMode implements TwilightListener {
567
568 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700569
Christine Frankse5bb03e2017-02-10 17:36:10 -0800570 TwilightAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700571 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700572 }
573
Justin Klaassen908b86c2016-08-08 09:18:42 -0700574 private void updateActivated(TwilightState state) {
Justin Klaassen3da4c442017-05-05 15:19:33 -0700575 if (state == null) {
576 // If there isn't a valid TwilightState then just keep the current activated
577 // state.
578 return;
579 }
580
581 boolean activate = state.isNight();
582 final Calendar lastActivatedTime = getLastActivatedTime();
583 if (lastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800584 final Calendar now = Calendar.getInstance();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700585 final Calendar sunrise = state.sunrise();
586 final Calendar sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800587
588 // Maintain the existing activated state if within the current period.
Justin Klaassen3da4c442017-05-05 15:19:33 -0700589 if (lastActivatedTime.before(now)
590 && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800591 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700592 }
593 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700594
Christine Frankse5bb03e2017-02-10 17:36:10 -0800595 if (mIsActivated == null || mIsActivated != activate) {
596 mController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700597 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700598 }
599
600 @Override
601 public void onStart() {
602 mTwilightManager.registerListener(this, mHandler);
603
604 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700605 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700606 }
607
608 @Override
609 public void onStop() {
610 mTwilightManager.unregisterListener(this);
611 }
612
613 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700614 public void onActivated(boolean activated) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700615 }
616
617 @Override
618 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700619 Slog.d(TAG, "onTwilightStateChanged: isNight="
620 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700621 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700622 }
623 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700624
625 /**
626 * Interpolates between two 4x4 color transform matrices (in column-major order).
627 */
628 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
629
630 /**
631 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
632 */
633 private final float[] mResultMatrix = new float[16];
634
635 @Override
636 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
637 for (int i = 0; i < mResultMatrix.length; i++) {
638 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
639 }
640 return mResultMatrix;
641 }
642 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700643}