blob: a7c3ff9e01042cdf8c69ace9549ead781e6433a0 [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
Christine Franks03213462017-08-25 13:57:26 -070051import java.time.LocalDateTime;
52import java.time.LocalTime;
53import java.time.ZoneId;
Ruben Brunkde003ae2016-08-30 13:14:33 -070054import java.util.concurrent.atomic.AtomicBoolean;
Christine Franks8ad71492017-10-24 19:04:22 -070055
56import com.android.internal.R;
Justin Klaassen911e8892016-06-21 18:24:24 -070057
Justin Klaassen639214e2016-07-14 21:00:06 -070058import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
59
Justin Klaassen911e8892016-06-21 18:24:24 -070060/**
61 * Tints the display at night.
62 */
63public final class NightDisplayService extends SystemService
64 implements NightDisplayController.Callback {
65
66 private static final String TAG = "NightDisplayService";
Justin Klaassen911e8892016-06-21 18:24:24 -070067
68 /**
Christine Franks7b83b4282017-01-18 14:55:00 -080069 * The transition time, in milliseconds, for Night Display to turn on/off.
70 */
71 private static final long TRANSITION_DURATION = 3000L;
72
73 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070074 * The identity matrix, used if one of the given matrices is {@code null}.
75 */
76 private static final float[] MATRIX_IDENTITY = new float[16];
77 static {
78 Matrix.setIdentityM(MATRIX_IDENTITY, 0);
79 }
80
81 /**
82 * Evaluator used to animate color matrix transitions.
83 */
84 private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
85
Justin Klaassen2696d992016-07-11 21:26:46 -070086 private final Handler mHandler;
Ruben Brunkde003ae2016-08-30 13:14:33 -070087 private final AtomicBoolean mIgnoreAllColorMatrixChanges = new AtomicBoolean();
88 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
89 @Override
90 public void onVrStateChanged(final boolean enabled) {
91 // Turn off all night mode display stuff while device is in VR mode.
92 mIgnoreAllColorMatrixChanges.set(enabled);
93 mHandler.post(new Runnable() {
94 @Override
95 public void run() {
96 // Cancel in-progress animations
97 if (mColorMatrixAnimator != null) {
98 mColorMatrixAnimator.cancel();
99 }
100
101 final DisplayTransformManager dtm =
102 getLocalService(DisplayTransformManager.class);
103 if (enabled) {
104 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
Steven Thomas1e9b4b02017-01-10 16:54:42 -0800105 } else if (mController != null && mController.isActivated()) {
Christine Franks6418d0b2017-02-13 09:48:00 -0800106 setMatrix(mController.getColorTemperature(), mMatrixNight);
107 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, mMatrixNight);
Ruben Brunkde003ae2016-08-30 13:14:33 -0700108 }
109 }
110 });
111 }
112 };
Justin Klaassen2696d992016-07-11 21:26:46 -0700113
Christine Franks6418d0b2017-02-13 09:48:00 -0800114 private float[] mMatrixNight = new float[16];
115
Christine Franks2a125472017-07-26 17:51:16 -0700116 private final float[] mColorTempCoefficients = new float[9];
Christine Franks6418d0b2017-02-13 09:48:00 -0800117
Justin Klaassen911e8892016-06-21 18:24:24 -0700118 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -0700119 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -0700120 private boolean mBootCompleted;
121
122 private NightDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -0700123 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -0700124 private Boolean mIsActivated;
125 private AutoMode mAutoMode;
126
127 public NightDisplayService(Context context) {
128 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -0700129 mHandler = new Handler(Looper.getMainLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -0700130 }
131
132 @Override
133 public void onStart() {
134 // Nothing to publish.
135 }
136
137 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700138 public void onBootPhase(int phase) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800139 if (phase >= PHASE_SYSTEM_SERVICES_READY) {
140 final IVrManager vrManager = IVrManager.Stub.asInterface(
141 getBinderService(Context.VR_SERVICE));
Ruben Brunkde003ae2016-08-30 13:14:33 -0700142 if (vrManager != null) {
143 try {
144 vrManager.registerListener(mVrStateCallbacks);
145 } catch (RemoteException e) {
146 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
147 }
148 }
Christine Frankse5bb03e2017-02-10 17:36:10 -0800149 }
150
151 if (phase >= PHASE_BOOT_COMPLETED) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700152 mBootCompleted = true;
153
154 // Register listeners now that boot is complete.
155 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
156 setUp();
157 }
158 }
159 }
160
161 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700162 public void onStartUser(int userHandle) {
163 super.onStartUser(userHandle);
164
Justin Klaassen911e8892016-06-21 18:24:24 -0700165 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700166 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700167 }
168 }
169
170 @Override
171 public void onSwitchUser(int userHandle) {
172 super.onSwitchUser(userHandle);
173
Justin Klaassen2696d992016-07-11 21:26:46 -0700174 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700175 }
176
177 @Override
178 public void onStopUser(int userHandle) {
179 super.onStopUser(userHandle);
180
Justin Klaassen911e8892016-06-21 18:24:24 -0700181 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700182 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700183 }
184 }
185
Justin Klaassen2696d992016-07-11 21:26:46 -0700186 private void onUserChanged(int userHandle) {
187 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700188
Justin Klaassen2696d992016-07-11 21:26:46 -0700189 if (mCurrentUser != UserHandle.USER_NULL) {
190 if (mUserSetupObserver != null) {
191 cr.unregisterContentObserver(mUserSetupObserver);
192 mUserSetupObserver = null;
193 } else if (mBootCompleted) {
194 tearDown();
195 }
196 }
197
198 mCurrentUser = userHandle;
199
200 if (mCurrentUser != UserHandle.USER_NULL) {
201 if (!isUserSetupCompleted(cr, mCurrentUser)) {
202 mUserSetupObserver = new ContentObserver(mHandler) {
203 @Override
204 public void onChange(boolean selfChange, Uri uri) {
205 if (isUserSetupCompleted(cr, mCurrentUser)) {
206 cr.unregisterContentObserver(this);
207 mUserSetupObserver = null;
208
209 if (mBootCompleted) {
210 setUp();
211 }
212 }
213 }
214 };
215 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
216 false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
217 } else if (mBootCompleted) {
218 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700219 }
220 }
221 }
222
Justin Klaassen2696d992016-07-11 21:26:46 -0700223 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
224 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
225 }
226
227 private void setUp() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700228 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
229
Justin Klaassen911e8892016-06-21 18:24:24 -0700230 // Create a new controller for the current user and start listening for changes.
231 mController = new NightDisplayController(getContext(), mCurrentUser);
232 mController.setListener(this);
233
Christine Franks8ad71492017-10-24 19:04:22 -0700234 setCoefficientMatrix(getContext());
235
Christine Franks6418d0b2017-02-13 09:48:00 -0800236 // Prepare color transformation matrix.
237 setMatrix(mController.getColorTemperature(), mMatrixNight);
238
Justin Klaassen911e8892016-06-21 18:24:24 -0700239 // Initialize the current auto mode.
240 onAutoModeChanged(mController.getAutoMode());
241
242 // Force the initialization current activated state.
243 if (mIsActivated == null) {
244 onActivated(mController.isActivated());
245 }
Christine Franks6418d0b2017-02-13 09:48:00 -0800246
247 // Transition the screen to the current temperature.
248 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700249 }
250
Justin Klaassen2696d992016-07-11 21:26:46 -0700251 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700252 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
253
Justin Klaassen2696d992016-07-11 21:26:46 -0700254 if (mController != null) {
255 mController.setListener(null);
256 mController = null;
257 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700258
259 if (mAutoMode != null) {
260 mAutoMode.onStop();
261 mAutoMode = null;
262 }
263
Justin Klaassen639214e2016-07-14 21:00:06 -0700264 if (mColorMatrixAnimator != null) {
265 mColorMatrixAnimator.end();
266 mColorMatrixAnimator = null;
267 }
268
Justin Klaassen911e8892016-06-21 18:24:24 -0700269 mIsActivated = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700270 }
271
272 @Override
273 public void onActivated(boolean activated) {
274 if (mIsActivated == null || mIsActivated != activated) {
275 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
276
Justin Klaassen908b86c2016-08-08 09:18:42 -0700277 mIsActivated = activated;
278
Christine Frankse5bb03e2017-02-10 17:36:10 -0800279 if (mAutoMode != null) {
280 mAutoMode.onActivated(activated);
281 }
282
Christine Franks6418d0b2017-02-13 09:48:00 -0800283 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700284 }
285 }
286
287 @Override
288 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700289 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
290
Justin Klaassen911e8892016-06-21 18:24:24 -0700291 if (mAutoMode != null) {
292 mAutoMode.onStop();
293 mAutoMode = null;
294 }
295
296 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
297 mAutoMode = new CustomAutoMode();
298 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
299 mAutoMode = new TwilightAutoMode();
300 }
301
302 if (mAutoMode != null) {
303 mAutoMode.onStart();
304 }
305 }
306
307 @Override
Christine Franks03213462017-08-25 13:57:26 -0700308 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700309 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
310
Justin Klaassen911e8892016-06-21 18:24:24 -0700311 if (mAutoMode != null) {
312 mAutoMode.onCustomStartTimeChanged(startTime);
313 }
314 }
315
316 @Override
Christine Franks03213462017-08-25 13:57:26 -0700317 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700318 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
319
Justin Klaassen911e8892016-06-21 18:24:24 -0700320 if (mAutoMode != null) {
321 mAutoMode.onCustomEndTimeChanged(endTime);
322 }
323 }
324
Christine Franks6418d0b2017-02-13 09:48:00 -0800325 @Override
326 public void onColorTemperatureChanged(int colorTemperature) {
327 setMatrix(colorTemperature, mMatrixNight);
328 applyTint(true);
329 }
330
Christine Franks8ad71492017-10-24 19:04:22 -0700331 @Override
332 public void onDisplayColorModeChanged(int colorMode) {
333 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
334 dtm.setColorMode(colorMode);
335
336 setCoefficientMatrix(getContext());
337 setMatrix(mController.getColorTemperature(), mMatrixNight);
338 applyTint(true);
339 }
340
341 private void setCoefficientMatrix(Context context) {
342 final boolean isNative = DisplayTransformManager.isNativeModeEnabled();
343 final String[] coefficients = context.getResources().getStringArray(isNative
344 ? R.array.config_nightDisplayColorTemperatureCoefficientsNative
345 : R.array.config_nightDisplayColorTemperatureCoefficients);
346 for (int i = 0; i < 9 && i < coefficients.length; i++) {
347 mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
348 }
349 }
350
Christine Franks6418d0b2017-02-13 09:48:00 -0800351 /**
352 * Applies current color temperature matrix, or removes it if deactivated.
353 *
354 * @param immediate {@code true} skips transition animation
355 */
356 private void applyTint(boolean immediate) {
357 // Cancel the old animator if still running.
358 if (mColorMatrixAnimator != null) {
359 mColorMatrixAnimator.cancel();
360 }
361
362 // Don't do any color matrix change animations if we are ignoring them anyway.
363 if (mIgnoreAllColorMatrixChanges.get()) {
364 return;
365 }
366
367 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
368 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
369 final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
370
371 if (immediate) {
372 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
373 } else {
374 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
375 from == null ? MATRIX_IDENTITY : from, to);
376 mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
377 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
378 getContext(), android.R.interpolator.fast_out_slow_in));
379 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
380 @Override
381 public void onAnimationUpdate(ValueAnimator animator) {
382 final float[] value = (float[]) animator.getAnimatedValue();
383 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
384 }
385 });
386 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
387
388 private boolean mIsCancelled;
389
390 @Override
391 public void onAnimationCancel(Animator animator) {
392 mIsCancelled = true;
393 }
394
395 @Override
396 public void onAnimationEnd(Animator animator) {
397 if (!mIsCancelled) {
398 // Ensure final color matrix is set at the end of the animation. If the
399 // animation is cancelled then don't set the final color matrix so the new
400 // animator can pick up from where this one left off.
401 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
402 }
403 mColorMatrixAnimator = null;
404 }
405 });
406 mColorMatrixAnimator.start();
407 }
408 }
409
410 /**
411 * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
412 *
413 * @param colorTemperature color temperature in Kelvin
Christine Frankse5bb03e2017-02-10 17:36:10 -0800414 * @param outTemp the 4x4 display transformation matrix for that color temperature
Christine Franks6418d0b2017-02-13 09:48:00 -0800415 */
416 private void setMatrix(int colorTemperature, float[] outTemp) {
417 if (outTemp.length != 16) {
418 Slog.d(TAG, "The display transformation matrix must be 4x4");
419 return;
420 }
421
422 Matrix.setIdentityM(mMatrixNight, 0);
423
424 final float squareTemperature = colorTemperature * colorTemperature;
425 final float red = squareTemperature * mColorTempCoefficients[0]
Christine Franks2a125472017-07-26 17:51:16 -0700426 + colorTemperature * mColorTempCoefficients[1] + mColorTempCoefficients[2];
427 final float green = squareTemperature * mColorTempCoefficients[3]
428 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[5];
429 final float blue = squareTemperature * mColorTempCoefficients[6]
430 + colorTemperature * mColorTempCoefficients[7] + mColorTempCoefficients[8];
Christine Franks6418d0b2017-02-13 09:48:00 -0800431 outTemp[0] = red;
432 outTemp[5] = green;
433 outTemp[10] = blue;
434 }
435
Christine Franks03213462017-08-25 13:57:26 -0700436 /**
437 * Returns the first date time corresponding to the local time that occurs before the
438 * provided date time.
439 *
440 * @param compareTime the LocalDateTime to compare against
441 * @return the prior LocalDateTime corresponding to this local time
442 */
443 public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
444 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
445 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
446
447 // Check if the local time has passed, if so return the same time yesterday.
448 return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
449 }
450
451 /**
452 * Returns the first date time corresponding to this local time that occurs after the
453 * provided date time.
454 *
455 * @param compareTime the LocalDateTime to compare against
456 * @return the next LocalDateTime corresponding to this local time
457 */
458 public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
459 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
460 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
461
462 // Check if the local time has passed, if so return the same time tomorrow.
463 return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
464 }
465
Justin Klaassen911e8892016-06-21 18:24:24 -0700466 private abstract class AutoMode implements NightDisplayController.Callback {
467 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800468
Justin Klaassen911e8892016-06-21 18:24:24 -0700469 public abstract void onStop();
470 }
471
472 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
473
474 private final AlarmManager mAlarmManager;
475 private final BroadcastReceiver mTimeChangedReceiver;
476
Christine Franks03213462017-08-25 13:57:26 -0700477 private LocalTime mStartTime;
478 private LocalTime mEndTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700479
Christine Franks03213462017-08-25 13:57:26 -0700480 private LocalDateTime mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700481
Christine Frankse5bb03e2017-02-10 17:36:10 -0800482 CustomAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700483 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
484 mTimeChangedReceiver = new BroadcastReceiver() {
485 @Override
486 public void onReceive(Context context, Intent intent) {
487 updateActivated();
488 }
489 };
490 }
491
492 private void updateActivated() {
Christine Franks03213462017-08-25 13:57:26 -0700493 final LocalDateTime now = LocalDateTime.now();
494 final LocalDateTime start = getDateTimeBefore(mStartTime, now);
495 final LocalDateTime end = getDateTimeAfter(mEndTime, start);
496 boolean activate = now.isBefore(end);
Justin Klaassen911e8892016-06-21 18:24:24 -0700497
Christine Frankse5bb03e2017-02-10 17:36:10 -0800498 if (mLastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800499 // Maintain the existing activated state if within the current period.
Christine Franks03213462017-08-25 13:57:26 -0700500 if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
501 && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800502 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700503 }
504 }
505
Christine Frankse5bb03e2017-02-10 17:36:10 -0800506 if (mIsActivated == null || mIsActivated != activate) {
507 mController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700508 }
Christine Franks03213462017-08-25 13:57:26 -0700509
Justin Klaassen4346f632016-08-08 15:01:47 -0700510 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700511 }
512
Christine Franks03213462017-08-25 13:57:26 -0700513 private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
Justin Klaassen4346f632016-08-08 15:01:47 -0700514 if (activated != null) {
Christine Franks03213462017-08-25 13:57:26 -0700515 final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
516 : getDateTimeAfter(mStartTime, now);
517 final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
518 mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
Justin Klaassen911e8892016-06-21 18:24:24 -0700519 }
520 }
521
522 @Override
523 public void onStart() {
524 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
525 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
526 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
527
528 mStartTime = mController.getCustomStartTime();
529 mEndTime = mController.getCustomEndTime();
530
Christine Franks1454eae2017-05-31 10:52:22 -0700531 mLastActivatedTime = mController.getLastActivatedTime();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800532
Justin Klaassen911e8892016-06-21 18:24:24 -0700533 // Force an update to initialize state.
534 updateActivated();
535 }
536
537 @Override
538 public void onStop() {
539 getContext().unregisterReceiver(mTimeChangedReceiver);
540
541 mAlarmManager.cancel(this);
542 mLastActivatedTime = null;
543 }
544
545 @Override
546 public void onActivated(boolean activated) {
Christine Franks1454eae2017-05-31 10:52:22 -0700547 mLastActivatedTime = mController.getLastActivatedTime();
Christine Franks03213462017-08-25 13:57:26 -0700548 updateNextAlarm(activated, LocalDateTime.now());
Justin Klaassen911e8892016-06-21 18:24:24 -0700549 }
550
551 @Override
Christine Franks03213462017-08-25 13:57:26 -0700552 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700553 mStartTime = startTime;
554 mLastActivatedTime = null;
555 updateActivated();
556 }
557
558 @Override
Christine Franks03213462017-08-25 13:57:26 -0700559 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700560 mEndTime = endTime;
561 mLastActivatedTime = null;
562 updateActivated();
563 }
564
565 @Override
566 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700567 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700568 updateActivated();
569 }
570 }
571
572 private class TwilightAutoMode extends AutoMode implements TwilightListener {
573
574 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700575
Christine Frankse5bb03e2017-02-10 17:36:10 -0800576 TwilightAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700577 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700578 }
579
Justin Klaassen908b86c2016-08-08 09:18:42 -0700580 private void updateActivated(TwilightState state) {
Justin Klaassen3da4c442017-05-05 15:19:33 -0700581 if (state == null) {
582 // If there isn't a valid TwilightState then just keep the current activated
583 // state.
584 return;
585 }
586
587 boolean activate = state.isNight();
Christine Franks03213462017-08-25 13:57:26 -0700588 final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
Justin Klaassen3da4c442017-05-05 15:19:33 -0700589 if (lastActivatedTime != null) {
Christine Franks03213462017-08-25 13:57:26 -0700590 final LocalDateTime now = LocalDateTime.now();
591 final LocalDateTime sunrise = state.sunrise();
592 final LocalDateTime sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800593 // Maintain the existing activated state if within the current period.
Christine Franks03213462017-08-25 13:57:26 -0700594 if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
595 ^ lastActivatedTime.isBefore(sunset))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800596 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700597 }
598 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700599
Christine Frankse5bb03e2017-02-10 17:36:10 -0800600 if (mIsActivated == null || mIsActivated != activate) {
601 mController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700602 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700603 }
604
605 @Override
606 public void onStart() {
607 mTwilightManager.registerListener(this, mHandler);
608
609 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700610 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700611 }
612
613 @Override
614 public void onStop() {
615 mTwilightManager.unregisterListener(this);
616 }
617
618 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700619 public void onActivated(boolean activated) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700620 }
621
622 @Override
623 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700624 Slog.d(TAG, "onTwilightStateChanged: isNight="
625 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700626 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700627 }
628 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700629
630 /**
631 * Interpolates between two 4x4 color transform matrices (in column-major order).
632 */
633 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
634
635 /**
636 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
637 */
638 private final float[] mResultMatrix = new float[16];
639
640 @Override
641 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
642 for (int i = 0; i < mResultMatrix.length; i++) {
643 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
644 }
645 return mResultMatrix;
646 }
647 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700648}