blob: 0b6786cf8c07a4989fd682cf7bb616654e75f133 [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;
36import android.os.UserHandle;
37import android.provider.Settings.Secure;
Justin Klaassen639214e2016-07-14 21:00:06 -070038import android.util.MathUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070039import android.util.Slog;
Justin Klaassen639214e2016-07-14 21:00:06 -070040import android.view.animation.AnimationUtils;
Justin Klaassen911e8892016-06-21 18:24:24 -070041
Christine Franks5397f032017-11-01 18:35:16 -070042import com.android.internal.app.ColorDisplayController;
Justin Klaassen911e8892016-06-21 18:24:24 -070043import com.android.server.SystemService;
44import com.android.server.twilight.TwilightListener;
45import com.android.server.twilight.TwilightManager;
46import com.android.server.twilight.TwilightState;
47
Christine Franks03213462017-08-25 13:57:26 -070048import java.time.LocalDateTime;
49import java.time.LocalTime;
50import java.time.ZoneId;
Christine Franks8ad71492017-10-24 19:04:22 -070051
52import com.android.internal.R;
Justin Klaassen911e8892016-06-21 18:24:24 -070053
Justin Klaassen639214e2016-07-14 21:00:06 -070054import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
55
Justin Klaassen911e8892016-06-21 18:24:24 -070056/**
57 * Tints the display at night.
58 */
Christine Franks5397f032017-11-01 18:35:16 -070059public final class ColorDisplayService extends SystemService
60 implements ColorDisplayController.Callback {
Justin Klaassen911e8892016-06-21 18:24:24 -070061
Christine Franks5397f032017-11-01 18:35:16 -070062 private static final String TAG = "ColorDisplayService";
Justin Klaassen911e8892016-06-21 18:24:24 -070063
64 /**
Christine Franks7b83b4282017-01-18 14:55:00 -080065 * The transition time, in milliseconds, for Night Display to turn on/off.
66 */
67 private static final long TRANSITION_DURATION = 3000L;
68
69 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070070 * The identity matrix, used if one of the given matrices is {@code null}.
71 */
72 private static final float[] MATRIX_IDENTITY = new float[16];
73 static {
74 Matrix.setIdentityM(MATRIX_IDENTITY, 0);
75 }
76
77 /**
78 * Evaluator used to animate color matrix transitions.
79 */
80 private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
81
Justin Klaassen2696d992016-07-11 21:26:46 -070082 private final Handler mHandler;
83
Christine Franks6418d0b2017-02-13 09:48:00 -080084 private float[] mMatrixNight = new float[16];
85
Christine Franks2a125472017-07-26 17:51:16 -070086 private final float[] mColorTempCoefficients = new float[9];
Christine Franks6418d0b2017-02-13 09:48:00 -080087
Justin Klaassen911e8892016-06-21 18:24:24 -070088 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -070089 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -070090 private boolean mBootCompleted;
91
Christine Franks5397f032017-11-01 18:35:16 -070092 private ColorDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -070093 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -070094 private Boolean mIsActivated;
95 private AutoMode mAutoMode;
96
Christine Franks5397f032017-11-01 18:35:16 -070097 public ColorDisplayService(Context context) {
Justin Klaassen911e8892016-06-21 18:24:24 -070098 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -070099 mHandler = new Handler(Looper.getMainLooper());
Justin Klaassen911e8892016-06-21 18:24:24 -0700100 }
101
102 @Override
103 public void onStart() {
104 // Nothing to publish.
105 }
106
107 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700108 public void onBootPhase(int phase) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800109 if (phase >= PHASE_BOOT_COMPLETED) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700110 mBootCompleted = true;
111
112 // Register listeners now that boot is complete.
113 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
114 setUp();
115 }
116 }
117 }
118
119 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700120 public void onStartUser(int userHandle) {
121 super.onStartUser(userHandle);
122
Justin Klaassen911e8892016-06-21 18:24:24 -0700123 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700124 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700125 }
126 }
127
128 @Override
129 public void onSwitchUser(int userHandle) {
130 super.onSwitchUser(userHandle);
131
Justin Klaassen2696d992016-07-11 21:26:46 -0700132 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700133 }
134
135 @Override
136 public void onStopUser(int userHandle) {
137 super.onStopUser(userHandle);
138
Justin Klaassen911e8892016-06-21 18:24:24 -0700139 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700140 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700141 }
142 }
143
Justin Klaassen2696d992016-07-11 21:26:46 -0700144 private void onUserChanged(int userHandle) {
145 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700146
Justin Klaassen2696d992016-07-11 21:26:46 -0700147 if (mCurrentUser != UserHandle.USER_NULL) {
148 if (mUserSetupObserver != null) {
149 cr.unregisterContentObserver(mUserSetupObserver);
150 mUserSetupObserver = null;
151 } else if (mBootCompleted) {
152 tearDown();
153 }
154 }
155
156 mCurrentUser = userHandle;
157
158 if (mCurrentUser != UserHandle.USER_NULL) {
159 if (!isUserSetupCompleted(cr, mCurrentUser)) {
160 mUserSetupObserver = new ContentObserver(mHandler) {
161 @Override
162 public void onChange(boolean selfChange, Uri uri) {
163 if (isUserSetupCompleted(cr, mCurrentUser)) {
164 cr.unregisterContentObserver(this);
165 mUserSetupObserver = null;
166
167 if (mBootCompleted) {
168 setUp();
169 }
170 }
171 }
172 };
173 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
174 false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
175 } else if (mBootCompleted) {
176 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700177 }
178 }
179 }
180
Justin Klaassen2696d992016-07-11 21:26:46 -0700181 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
182 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
183 }
184
185 private void setUp() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700186 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
187
Justin Klaassen911e8892016-06-21 18:24:24 -0700188 // Create a new controller for the current user and start listening for changes.
Christine Franks5397f032017-11-01 18:35:16 -0700189 mController = new ColorDisplayController(getContext(), mCurrentUser);
Justin Klaassen911e8892016-06-21 18:24:24 -0700190 mController.setListener(this);
191
Christine Frankscf388c22018-05-15 15:48:10 -0700192 // Set the color mode, if valid, and immediately apply the updated tint matrix based on the
193 // existing activated state. This ensures consistency of tint across the color mode change.
194 onDisplayColorModeChanged(mController.getColorMode());
195
196 // Reset the activated state.
197 mIsActivated = null;
198
Chia-I Wu9ec9ea72018-05-07 13:17:04 -0700199 setCoefficientMatrix(getContext(), DisplayTransformManager.needsLinearColorMatrix());
Christine Franks8ad71492017-10-24 19:04:22 -0700200
Christine Franks6418d0b2017-02-13 09:48:00 -0800201 // Prepare color transformation matrix.
202 setMatrix(mController.getColorTemperature(), mMatrixNight);
203
Justin Klaassen911e8892016-06-21 18:24:24 -0700204 // Initialize the current auto mode.
205 onAutoModeChanged(mController.getAutoMode());
206
207 // Force the initialization current activated state.
208 if (mIsActivated == null) {
209 onActivated(mController.isActivated());
210 }
211 }
212
Justin Klaassen2696d992016-07-11 21:26:46 -0700213 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700214 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
215
Justin Klaassen2696d992016-07-11 21:26:46 -0700216 if (mController != null) {
217 mController.setListener(null);
218 mController = null;
219 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700220
221 if (mAutoMode != null) {
222 mAutoMode.onStop();
223 mAutoMode = null;
224 }
225
Justin Klaassen639214e2016-07-14 21:00:06 -0700226 if (mColorMatrixAnimator != null) {
227 mColorMatrixAnimator.end();
228 mColorMatrixAnimator = null;
229 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700230 }
231
232 @Override
233 public void onActivated(boolean activated) {
234 if (mIsActivated == null || mIsActivated != activated) {
235 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
236
Justin Klaassen908b86c2016-08-08 09:18:42 -0700237 mIsActivated = activated;
238
Christine Frankse5bb03e2017-02-10 17:36:10 -0800239 if (mAutoMode != null) {
240 mAutoMode.onActivated(activated);
241 }
242
Christine Franks6418d0b2017-02-13 09:48:00 -0800243 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700244 }
245 }
246
247 @Override
248 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700249 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
250
Justin Klaassen911e8892016-06-21 18:24:24 -0700251 if (mAutoMode != null) {
252 mAutoMode.onStop();
253 mAutoMode = null;
254 }
255
Christine Franks5397f032017-11-01 18:35:16 -0700256 if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700257 mAutoMode = new CustomAutoMode();
Christine Franks5397f032017-11-01 18:35:16 -0700258 } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700259 mAutoMode = new TwilightAutoMode();
260 }
261
262 if (mAutoMode != null) {
263 mAutoMode.onStart();
264 }
265 }
266
267 @Override
Christine Franks03213462017-08-25 13:57:26 -0700268 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700269 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
270
Justin Klaassen911e8892016-06-21 18:24:24 -0700271 if (mAutoMode != null) {
272 mAutoMode.onCustomStartTimeChanged(startTime);
273 }
274 }
275
276 @Override
Christine Franks03213462017-08-25 13:57:26 -0700277 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700278 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
279
Justin Klaassen911e8892016-06-21 18:24:24 -0700280 if (mAutoMode != null) {
281 mAutoMode.onCustomEndTimeChanged(endTime);
282 }
283 }
284
Christine Franks6418d0b2017-02-13 09:48:00 -0800285 @Override
286 public void onColorTemperatureChanged(int colorTemperature) {
287 setMatrix(colorTemperature, mMatrixNight);
288 applyTint(true);
289 }
290
Christine Franks8ad71492017-10-24 19:04:22 -0700291 @Override
Christine Franks218e6562017-11-27 10:20:14 -0800292 public void onDisplayColorModeChanged(int mode) {
Christine Frankscf388c22018-05-15 15:48:10 -0700293 if (mode == -1) {
294 return;
295 }
296
Christine Franks218e6562017-11-27 10:20:14 -0800297 // Cancel the night display tint animator if it's running.
298 if (mColorMatrixAnimator != null) {
299 mColorMatrixAnimator.cancel();
300 }
Christine Franks8ad71492017-10-24 19:04:22 -0700301
Chia-I Wu9ec9ea72018-05-07 13:17:04 -0700302 setCoefficientMatrix(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
Christine Franks8ad71492017-10-24 19:04:22 -0700303 setMatrix(mController.getColorTemperature(), mMatrixNight);
Christine Franks218e6562017-11-27 10:20:14 -0800304
305 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
Christine Frankscf388c22018-05-15 15:48:10 -0700306 dtm.setColorMode(mode, (mIsActivated != null && mIsActivated) ? mMatrixNight
307 : MATRIX_IDENTITY);
Christine Franks8ad71492017-10-24 19:04:22 -0700308 }
309
Daniel Solomon317a3572018-03-30 18:36:37 -0700310 @Override
311 public void onAccessibilityTransformChanged(boolean state) {
312 onDisplayColorModeChanged(mController.getColorMode());
313 }
314
Christine Franks218e6562017-11-27 10:20:14 -0800315 /**
Chia-I Wu9ec9ea72018-05-07 13:17:04 -0700316 * Set coefficients based on whether the color matrix is linear or not.
Christine Franks218e6562017-11-27 10:20:14 -0800317 */
Chia-I Wu9ec9ea72018-05-07 13:17:04 -0700318 private void setCoefficientMatrix(Context context, boolean needsLinear) {
319 final String[] coefficients = context.getResources().getStringArray(needsLinear
320 ? R.array.config_nightDisplayColorTemperatureCoefficients
321 : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
Christine Franks8ad71492017-10-24 19:04:22 -0700322 for (int i = 0; i < 9 && i < coefficients.length; i++) {
323 mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
324 }
325 }
326
Christine Franks6418d0b2017-02-13 09:48:00 -0800327 /**
328 * Applies current color temperature matrix, or removes it if deactivated.
329 *
330 * @param immediate {@code true} skips transition animation
331 */
332 private void applyTint(boolean immediate) {
333 // Cancel the old animator if still running.
334 if (mColorMatrixAnimator != null) {
335 mColorMatrixAnimator.cancel();
336 }
337
Christine Franks6418d0b2017-02-13 09:48:00 -0800338 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
339 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
340 final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
341
342 if (immediate) {
343 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
344 } else {
345 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
346 from == null ? MATRIX_IDENTITY : from, to);
347 mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
348 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
349 getContext(), android.R.interpolator.fast_out_slow_in));
350 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
351 @Override
352 public void onAnimationUpdate(ValueAnimator animator) {
353 final float[] value = (float[]) animator.getAnimatedValue();
354 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
355 }
356 });
357 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
358
359 private boolean mIsCancelled;
360
361 @Override
362 public void onAnimationCancel(Animator animator) {
363 mIsCancelled = true;
364 }
365
366 @Override
367 public void onAnimationEnd(Animator animator) {
368 if (!mIsCancelled) {
369 // Ensure final color matrix is set at the end of the animation. If the
370 // animation is cancelled then don't set the final color matrix so the new
371 // animator can pick up from where this one left off.
372 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
373 }
374 mColorMatrixAnimator = null;
375 }
376 });
377 mColorMatrixAnimator.start();
378 }
379 }
380
381 /**
382 * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
383 *
384 * @param colorTemperature color temperature in Kelvin
Christine Frankse5bb03e2017-02-10 17:36:10 -0800385 * @param outTemp the 4x4 display transformation matrix for that color temperature
Christine Franks6418d0b2017-02-13 09:48:00 -0800386 */
387 private void setMatrix(int colorTemperature, float[] outTemp) {
388 if (outTemp.length != 16) {
389 Slog.d(TAG, "The display transformation matrix must be 4x4");
390 return;
391 }
392
393 Matrix.setIdentityM(mMatrixNight, 0);
394
395 final float squareTemperature = colorTemperature * colorTemperature;
396 final float red = squareTemperature * mColorTempCoefficients[0]
Christine Franks2a125472017-07-26 17:51:16 -0700397 + colorTemperature * mColorTempCoefficients[1] + mColorTempCoefficients[2];
398 final float green = squareTemperature * mColorTempCoefficients[3]
399 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[5];
400 final float blue = squareTemperature * mColorTempCoefficients[6]
401 + colorTemperature * mColorTempCoefficients[7] + mColorTempCoefficients[8];
Christine Franks6418d0b2017-02-13 09:48:00 -0800402 outTemp[0] = red;
403 outTemp[5] = green;
404 outTemp[10] = blue;
405 }
406
Christine Franks03213462017-08-25 13:57:26 -0700407 /**
408 * Returns the first date time corresponding to the local time that occurs before the
409 * provided date time.
410 *
411 * @param compareTime the LocalDateTime to compare against
412 * @return the prior LocalDateTime corresponding to this local time
413 */
414 public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
415 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
416 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
417
418 // Check if the local time has passed, if so return the same time yesterday.
419 return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
420 }
421
422 /**
423 * Returns the first date time corresponding to this local time that occurs after the
424 * provided date time.
425 *
426 * @param compareTime the LocalDateTime to compare against
427 * @return the next LocalDateTime corresponding to this local time
428 */
429 public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
430 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
431 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
432
433 // Check if the local time has passed, if so return the same time tomorrow.
434 return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
435 }
436
Christine Franks5397f032017-11-01 18:35:16 -0700437 private abstract class AutoMode implements ColorDisplayController.Callback {
Justin Klaassen911e8892016-06-21 18:24:24 -0700438 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800439
Justin Klaassen911e8892016-06-21 18:24:24 -0700440 public abstract void onStop();
441 }
442
443 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
444
445 private final AlarmManager mAlarmManager;
446 private final BroadcastReceiver mTimeChangedReceiver;
447
Christine Franks03213462017-08-25 13:57:26 -0700448 private LocalTime mStartTime;
449 private LocalTime mEndTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700450
Christine Franks03213462017-08-25 13:57:26 -0700451 private LocalDateTime mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700452
Christine Frankse5bb03e2017-02-10 17:36:10 -0800453 CustomAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700454 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
455 mTimeChangedReceiver = new BroadcastReceiver() {
456 @Override
457 public void onReceive(Context context, Intent intent) {
458 updateActivated();
459 }
460 };
461 }
462
463 private void updateActivated() {
Christine Franks03213462017-08-25 13:57:26 -0700464 final LocalDateTime now = LocalDateTime.now();
465 final LocalDateTime start = getDateTimeBefore(mStartTime, now);
466 final LocalDateTime end = getDateTimeAfter(mEndTime, start);
467 boolean activate = now.isBefore(end);
Justin Klaassen911e8892016-06-21 18:24:24 -0700468
Christine Frankse5bb03e2017-02-10 17:36:10 -0800469 if (mLastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800470 // Maintain the existing activated state if within the current period.
Christine Franks03213462017-08-25 13:57:26 -0700471 if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
472 && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800473 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700474 }
475 }
476
Christine Frankse5bb03e2017-02-10 17:36:10 -0800477 if (mIsActivated == null || mIsActivated != activate) {
478 mController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700479 }
Christine Franks03213462017-08-25 13:57:26 -0700480
Justin Klaassen4346f632016-08-08 15:01:47 -0700481 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700482 }
483
Christine Franks03213462017-08-25 13:57:26 -0700484 private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
Justin Klaassen4346f632016-08-08 15:01:47 -0700485 if (activated != null) {
Christine Franks03213462017-08-25 13:57:26 -0700486 final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
487 : getDateTimeAfter(mStartTime, now);
488 final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
489 mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
Justin Klaassen911e8892016-06-21 18:24:24 -0700490 }
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 Franks03213462017-08-25 13:57:26 -0700519 updateNextAlarm(activated, LocalDateTime.now());
Justin Klaassen911e8892016-06-21 18:24:24 -0700520 }
521
522 @Override
Christine Franks03213462017-08-25 13:57:26 -0700523 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700524 mStartTime = startTime;
525 mLastActivatedTime = null;
526 updateActivated();
527 }
528
529 @Override
Christine Franks03213462017-08-25 13:57:26 -0700530 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700531 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 Franks03213462017-08-25 13:57:26 -0700559 final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
Justin Klaassen3da4c442017-05-05 15:19:33 -0700560 if (lastActivatedTime != null) {
Christine Franks03213462017-08-25 13:57:26 -0700561 final LocalDateTime now = LocalDateTime.now();
562 final LocalDateTime sunrise = state.sunrise();
563 final LocalDateTime sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800564 // Maintain the existing activated state if within the current period.
Christine Franks03213462017-08-25 13:57:26 -0700565 if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
566 ^ lastActivatedTime.isBefore(sunset))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800567 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700568 }
569 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700570
Christine Frankse5bb03e2017-02-10 17:36:10 -0800571 if (mIsActivated == null || mIsActivated != activate) {
572 mController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700573 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700574 }
575
576 @Override
577 public void onStart() {
578 mTwilightManager.registerListener(this, mHandler);
579
580 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700581 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700582 }
583
584 @Override
585 public void onStop() {
586 mTwilightManager.unregisterListener(this);
587 }
588
589 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700590 public void onActivated(boolean activated) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700591 }
592
593 @Override
594 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700595 Slog.d(TAG, "onTwilightStateChanged: isNight="
596 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700597 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700598 }
599 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700600
601 /**
602 * Interpolates between two 4x4 color transform matrices (in column-major order).
603 */
604 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
605
606 /**
607 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
608 */
609 private final float[] mResultMatrix = new float[16];
610
611 @Override
612 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
613 for (int i = 0; i < mResultMatrix.length; i++) {
614 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
615 }
616 return mResultMatrix;
617 }
618 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700619}