blob: 9cf136720881f209f413edecd2c0ebcad2b3c6dc [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;
Justin Klaassen911e8892016-06-21 18:24:24 -070055import java.util.TimeZone;
56
Justin Klaassen639214e2016-07-14 21:00:06 -070057import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
58
Justin Klaassen911e8892016-06-21 18:24:24 -070059/**
60 * Tints the display at night.
61 */
62public final class NightDisplayService extends SystemService
63 implements NightDisplayController.Callback {
64
65 private static final String TAG = "NightDisplayService";
Justin Klaassen911e8892016-06-21 18:24:24 -070066
67 /**
Christine Franks7b83b4282017-01-18 14:55:00 -080068 * The transition time, in milliseconds, for Night Display to turn on/off.
69 */
70 private static final long TRANSITION_DURATION = 3000L;
71
72 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070073 * The identity matrix, used if one of the given matrices is {@code null}.
74 */
75 private static final float[] MATRIX_IDENTITY = new float[16];
76 static {
77 Matrix.setIdentityM(MATRIX_IDENTITY, 0);
78 }
79
80 /**
81 * Evaluator used to animate color matrix transitions.
82 */
83 private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
84
Justin Klaassen2696d992016-07-11 21:26:46 -070085 private final Handler mHandler;
Ruben Brunkde003ae2016-08-30 13:14:33 -070086 private final AtomicBoolean mIgnoreAllColorMatrixChanges = new AtomicBoolean();
87 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
88 @Override
89 public void onVrStateChanged(final boolean enabled) {
90 // Turn off all night mode display stuff while device is in VR mode.
91 mIgnoreAllColorMatrixChanges.set(enabled);
92 mHandler.post(new Runnable() {
93 @Override
94 public void run() {
95 // Cancel in-progress animations
96 if (mColorMatrixAnimator != null) {
97 mColorMatrixAnimator.cancel();
98 }
99
100 final DisplayTransformManager dtm =
101 getLocalService(DisplayTransformManager.class);
102 if (enabled) {
103 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
Steven Thomas1e9b4b02017-01-10 16:54:42 -0800104 } else if (mController != null && mController.isActivated()) {
Christine Franks6418d0b2017-02-13 09:48:00 -0800105 setMatrix(mController.getColorTemperature(), mMatrixNight);
106 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, mMatrixNight);
Ruben Brunkde003ae2016-08-30 13:14:33 -0700107 }
108 }
109 });
110 }
111 };
Justin Klaassen2696d992016-07-11 21:26:46 -0700112
Christine Franks6418d0b2017-02-13 09:48:00 -0800113 private float[] mMatrixNight = new float[16];
114
Christine Franks2a125472017-07-26 17:51:16 -0700115 private final float[] mColorTempCoefficients = new float[9];
Christine Franks6418d0b2017-02-13 09:48:00 -0800116
Justin Klaassen911e8892016-06-21 18:24:24 -0700117 private int mCurrentUser = UserHandle.USER_NULL;
Justin Klaassen2696d992016-07-11 21:26:46 -0700118 private ContentObserver mUserSetupObserver;
Justin Klaassen911e8892016-06-21 18:24:24 -0700119 private boolean mBootCompleted;
120
121 private NightDisplayController mController;
Justin Klaassen639214e2016-07-14 21:00:06 -0700122 private ValueAnimator mColorMatrixAnimator;
Justin Klaassen911e8892016-06-21 18:24:24 -0700123 private Boolean mIsActivated;
124 private AutoMode mAutoMode;
125
126 public NightDisplayService(Context context) {
127 super(context);
Justin Klaassen2696d992016-07-11 21:26:46 -0700128 mHandler = new Handler(Looper.getMainLooper());
Christine Franks2a125472017-07-26 17:51:16 -0700129
130 final String[] coefficients = context.getResources().getStringArray(
131 com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients);
132 for (int i = 0; i < 9 && i < coefficients.length; i++) {
133 mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
134 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700135 }
136
137 @Override
138 public void onStart() {
139 // Nothing to publish.
140 }
141
142 @Override
Justin Klaassen2696d992016-07-11 21:26:46 -0700143 public void onBootPhase(int phase) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800144 if (phase >= PHASE_SYSTEM_SERVICES_READY) {
145 final IVrManager vrManager = IVrManager.Stub.asInterface(
146 getBinderService(Context.VR_SERVICE));
Ruben Brunkde003ae2016-08-30 13:14:33 -0700147 if (vrManager != null) {
148 try {
149 vrManager.registerListener(mVrStateCallbacks);
150 } catch (RemoteException e) {
151 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
152 }
153 }
Christine Frankse5bb03e2017-02-10 17:36:10 -0800154 }
155
156 if (phase >= PHASE_BOOT_COMPLETED) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700157 mBootCompleted = true;
158
159 // Register listeners now that boot is complete.
160 if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
161 setUp();
162 }
163 }
164 }
165
166 @Override
Justin Klaassen911e8892016-06-21 18:24:24 -0700167 public void onStartUser(int userHandle) {
168 super.onStartUser(userHandle);
169
Justin Klaassen911e8892016-06-21 18:24:24 -0700170 if (mCurrentUser == UserHandle.USER_NULL) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700171 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700172 }
173 }
174
175 @Override
176 public void onSwitchUser(int userHandle) {
177 super.onSwitchUser(userHandle);
178
Justin Klaassen2696d992016-07-11 21:26:46 -0700179 onUserChanged(userHandle);
Justin Klaassen911e8892016-06-21 18:24:24 -0700180 }
181
182 @Override
183 public void onStopUser(int userHandle) {
184 super.onStopUser(userHandle);
185
Justin Klaassen911e8892016-06-21 18:24:24 -0700186 if (mCurrentUser == userHandle) {
Justin Klaassen2696d992016-07-11 21:26:46 -0700187 onUserChanged(UserHandle.USER_NULL);
Justin Klaassen911e8892016-06-21 18:24:24 -0700188 }
189 }
190
Justin Klaassen2696d992016-07-11 21:26:46 -0700191 private void onUserChanged(int userHandle) {
192 final ContentResolver cr = getContext().getContentResolver();
Justin Klaassen911e8892016-06-21 18:24:24 -0700193
Justin Klaassen2696d992016-07-11 21:26:46 -0700194 if (mCurrentUser != UserHandle.USER_NULL) {
195 if (mUserSetupObserver != null) {
196 cr.unregisterContentObserver(mUserSetupObserver);
197 mUserSetupObserver = null;
198 } else if (mBootCompleted) {
199 tearDown();
200 }
201 }
202
203 mCurrentUser = userHandle;
204
205 if (mCurrentUser != UserHandle.USER_NULL) {
206 if (!isUserSetupCompleted(cr, mCurrentUser)) {
207 mUserSetupObserver = new ContentObserver(mHandler) {
208 @Override
209 public void onChange(boolean selfChange, Uri uri) {
210 if (isUserSetupCompleted(cr, mCurrentUser)) {
211 cr.unregisterContentObserver(this);
212 mUserSetupObserver = null;
213
214 if (mBootCompleted) {
215 setUp();
216 }
217 }
218 }
219 };
220 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
221 false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
222 } else if (mBootCompleted) {
223 setUp();
Justin Klaassen911e8892016-06-21 18:24:24 -0700224 }
225 }
226 }
227
Justin Klaassen2696d992016-07-11 21:26:46 -0700228 private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
229 return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
230 }
231
232 private void setUp() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700233 Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
234
Justin Klaassen911e8892016-06-21 18:24:24 -0700235 // Create a new controller for the current user and start listening for changes.
236 mController = new NightDisplayController(getContext(), mCurrentUser);
237 mController.setListener(this);
238
Christine Franks6418d0b2017-02-13 09:48:00 -0800239 // Prepare color transformation matrix.
240 setMatrix(mController.getColorTemperature(), mMatrixNight);
241
Justin Klaassen911e8892016-06-21 18:24:24 -0700242 // Initialize the current auto mode.
243 onAutoModeChanged(mController.getAutoMode());
244
245 // Force the initialization current activated state.
246 if (mIsActivated == null) {
247 onActivated(mController.isActivated());
248 }
Christine Franks6418d0b2017-02-13 09:48:00 -0800249
250 // Transition the screen to the current temperature.
251 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700252 }
253
Justin Klaassen2696d992016-07-11 21:26:46 -0700254 private void tearDown() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700255 Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
256
Justin Klaassen2696d992016-07-11 21:26:46 -0700257 if (mController != null) {
258 mController.setListener(null);
259 mController = null;
260 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700261
262 if (mAutoMode != null) {
263 mAutoMode.onStop();
264 mAutoMode = null;
265 }
266
Justin Klaassen639214e2016-07-14 21:00:06 -0700267 if (mColorMatrixAnimator != null) {
268 mColorMatrixAnimator.end();
269 mColorMatrixAnimator = null;
270 }
271
Justin Klaassen911e8892016-06-21 18:24:24 -0700272 mIsActivated = null;
Justin Klaassen911e8892016-06-21 18:24:24 -0700273 }
274
275 @Override
276 public void onActivated(boolean activated) {
277 if (mIsActivated == null || mIsActivated != activated) {
278 Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
279
Justin Klaassen908b86c2016-08-08 09:18:42 -0700280 mIsActivated = activated;
281
Christine Frankse5bb03e2017-02-10 17:36:10 -0800282 if (mAutoMode != null) {
283 mAutoMode.onActivated(activated);
284 }
285
Christine Franks6418d0b2017-02-13 09:48:00 -0800286 applyTint(false);
Justin Klaassen911e8892016-06-21 18:24:24 -0700287 }
288 }
289
290 @Override
291 public void onAutoModeChanged(int autoMode) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700292 Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode);
293
Justin Klaassen911e8892016-06-21 18:24:24 -0700294 if (mAutoMode != null) {
295 mAutoMode.onStop();
296 mAutoMode = null;
297 }
298
299 if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
300 mAutoMode = new CustomAutoMode();
301 } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
302 mAutoMode = new TwilightAutoMode();
303 }
304
305 if (mAutoMode != null) {
306 mAutoMode.onStart();
307 }
308 }
309
310 @Override
Christine Franks03213462017-08-25 13:57:26 -0700311 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700312 Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
313
Justin Klaassen911e8892016-06-21 18:24:24 -0700314 if (mAutoMode != null) {
315 mAutoMode.onCustomStartTimeChanged(startTime);
316 }
317 }
318
319 @Override
Christine Franks03213462017-08-25 13:57:26 -0700320 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700321 Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
322
Justin Klaassen911e8892016-06-21 18:24:24 -0700323 if (mAutoMode != null) {
324 mAutoMode.onCustomEndTimeChanged(endTime);
325 }
326 }
327
Christine Franks6418d0b2017-02-13 09:48:00 -0800328 @Override
329 public void onColorTemperatureChanged(int colorTemperature) {
330 setMatrix(colorTemperature, mMatrixNight);
331 applyTint(true);
332 }
333
334 /**
335 * Applies current color temperature matrix, or removes it if deactivated.
336 *
337 * @param immediate {@code true} skips transition animation
338 */
339 private void applyTint(boolean immediate) {
340 // Cancel the old animator if still running.
341 if (mColorMatrixAnimator != null) {
342 mColorMatrixAnimator.cancel();
343 }
344
345 // Don't do any color matrix change animations if we are ignoring them anyway.
346 if (mIgnoreAllColorMatrixChanges.get()) {
347 return;
348 }
349
350 final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
351 final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
352 final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
353
354 if (immediate) {
355 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
356 } else {
357 mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
358 from == null ? MATRIX_IDENTITY : from, to);
359 mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
360 mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
361 getContext(), android.R.interpolator.fast_out_slow_in));
362 mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
363 @Override
364 public void onAnimationUpdate(ValueAnimator animator) {
365 final float[] value = (float[]) animator.getAnimatedValue();
366 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
367 }
368 });
369 mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
370
371 private boolean mIsCancelled;
372
373 @Override
374 public void onAnimationCancel(Animator animator) {
375 mIsCancelled = true;
376 }
377
378 @Override
379 public void onAnimationEnd(Animator animator) {
380 if (!mIsCancelled) {
381 // Ensure final color matrix is set at the end of the animation. If the
382 // animation is cancelled then don't set the final color matrix so the new
383 // animator can pick up from where this one left off.
384 dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
385 }
386 mColorMatrixAnimator = null;
387 }
388 });
389 mColorMatrixAnimator.start();
390 }
391 }
392
393 /**
394 * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
395 *
396 * @param colorTemperature color temperature in Kelvin
Christine Frankse5bb03e2017-02-10 17:36:10 -0800397 * @param outTemp the 4x4 display transformation matrix for that color temperature
Christine Franks6418d0b2017-02-13 09:48:00 -0800398 */
399 private void setMatrix(int colorTemperature, float[] outTemp) {
400 if (outTemp.length != 16) {
401 Slog.d(TAG, "The display transformation matrix must be 4x4");
402 return;
403 }
404
405 Matrix.setIdentityM(mMatrixNight, 0);
406
407 final float squareTemperature = colorTemperature * colorTemperature;
408 final float red = squareTemperature * mColorTempCoefficients[0]
Christine Franks2a125472017-07-26 17:51:16 -0700409 + colorTemperature * mColorTempCoefficients[1] + mColorTempCoefficients[2];
410 final float green = squareTemperature * mColorTempCoefficients[3]
411 + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[5];
412 final float blue = squareTemperature * mColorTempCoefficients[6]
413 + colorTemperature * mColorTempCoefficients[7] + mColorTempCoefficients[8];
Christine Franks6418d0b2017-02-13 09:48:00 -0800414 outTemp[0] = red;
415 outTemp[5] = green;
416 outTemp[10] = blue;
417 }
418
Christine Franks03213462017-08-25 13:57:26 -0700419 /**
420 * Returns the first date time corresponding to the local time that occurs before the
421 * provided date time.
422 *
423 * @param compareTime the LocalDateTime to compare against
424 * @return the prior LocalDateTime corresponding to this local time
425 */
426 public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
427 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
428 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
429
430 // Check if the local time has passed, if so return the same time yesterday.
431 return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
432 }
433
434 /**
435 * Returns the first date time corresponding to this local time that occurs after the
436 * provided date time.
437 *
438 * @param compareTime the LocalDateTime to compare against
439 * @return the next LocalDateTime corresponding to this local time
440 */
441 public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
442 final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
443 compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
444
445 // Check if the local time has passed, if so return the same time tomorrow.
446 return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
447 }
448
Justin Klaassen911e8892016-06-21 18:24:24 -0700449 private abstract class AutoMode implements NightDisplayController.Callback {
450 public abstract void onStart();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800451
Justin Klaassen911e8892016-06-21 18:24:24 -0700452 public abstract void onStop();
453 }
454
455 private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
456
457 private final AlarmManager mAlarmManager;
458 private final BroadcastReceiver mTimeChangedReceiver;
459
Christine Franks03213462017-08-25 13:57:26 -0700460 private LocalTime mStartTime;
461 private LocalTime mEndTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700462
Christine Franks03213462017-08-25 13:57:26 -0700463 private LocalDateTime mLastActivatedTime;
Justin Klaassen911e8892016-06-21 18:24:24 -0700464
Christine Frankse5bb03e2017-02-10 17:36:10 -0800465 CustomAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700466 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
467 mTimeChangedReceiver = new BroadcastReceiver() {
468 @Override
469 public void onReceive(Context context, Intent intent) {
470 updateActivated();
471 }
472 };
473 }
474
475 private void updateActivated() {
Christine Franks03213462017-08-25 13:57:26 -0700476 final LocalDateTime now = LocalDateTime.now();
477 final LocalDateTime start = getDateTimeBefore(mStartTime, now);
478 final LocalDateTime end = getDateTimeAfter(mEndTime, start);
479 boolean activate = now.isBefore(end);
Justin Klaassen911e8892016-06-21 18:24:24 -0700480
Christine Frankse5bb03e2017-02-10 17:36:10 -0800481 if (mLastActivatedTime != null) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800482 // Maintain the existing activated state if within the current period.
Christine Franks03213462017-08-25 13:57:26 -0700483 if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
484 && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800485 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700486 }
487 }
488
Christine Frankse5bb03e2017-02-10 17:36:10 -0800489 if (mIsActivated == null || mIsActivated != activate) {
490 mController.setActivated(activate);
Justin Klaassen911e8892016-06-21 18:24:24 -0700491 }
Christine Franks03213462017-08-25 13:57:26 -0700492
Justin Klaassen4346f632016-08-08 15:01:47 -0700493 updateNextAlarm(mIsActivated, now);
Justin Klaassen911e8892016-06-21 18:24:24 -0700494 }
495
Christine Franks03213462017-08-25 13:57:26 -0700496 private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
Justin Klaassen4346f632016-08-08 15:01:47 -0700497 if (activated != null) {
Christine Franks03213462017-08-25 13:57:26 -0700498 final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
499 : getDateTimeAfter(mStartTime, now);
500 final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
501 mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
Justin Klaassen911e8892016-06-21 18:24:24 -0700502 }
503 }
504
505 @Override
506 public void onStart() {
507 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
508 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
509 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
510
511 mStartTime = mController.getCustomStartTime();
512 mEndTime = mController.getCustomEndTime();
513
Christine Franks1454eae2017-05-31 10:52:22 -0700514 mLastActivatedTime = mController.getLastActivatedTime();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800515
Justin Klaassen911e8892016-06-21 18:24:24 -0700516 // Force an update to initialize state.
517 updateActivated();
518 }
519
520 @Override
521 public void onStop() {
522 getContext().unregisterReceiver(mTimeChangedReceiver);
523
524 mAlarmManager.cancel(this);
525 mLastActivatedTime = null;
526 }
527
528 @Override
529 public void onActivated(boolean activated) {
Christine Franks1454eae2017-05-31 10:52:22 -0700530 mLastActivatedTime = mController.getLastActivatedTime();
Christine Franks03213462017-08-25 13:57:26 -0700531 updateNextAlarm(activated, LocalDateTime.now());
Justin Klaassen911e8892016-06-21 18:24:24 -0700532 }
533
534 @Override
Christine Franks03213462017-08-25 13:57:26 -0700535 public void onCustomStartTimeChanged(LocalTime startTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700536 mStartTime = startTime;
537 mLastActivatedTime = null;
538 updateActivated();
539 }
540
541 @Override
Christine Franks03213462017-08-25 13:57:26 -0700542 public void onCustomEndTimeChanged(LocalTime endTime) {
Justin Klaassen911e8892016-06-21 18:24:24 -0700543 mEndTime = endTime;
544 mLastActivatedTime = null;
545 updateActivated();
546 }
547
548 @Override
549 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700550 Slog.d(TAG, "onAlarm");
Justin Klaassen911e8892016-06-21 18:24:24 -0700551 updateActivated();
552 }
553 }
554
555 private class TwilightAutoMode extends AutoMode implements TwilightListener {
556
557 private final TwilightManager mTwilightManager;
Justin Klaassen911e8892016-06-21 18:24:24 -0700558
Christine Frankse5bb03e2017-02-10 17:36:10 -0800559 TwilightAutoMode() {
Justin Klaassen911e8892016-06-21 18:24:24 -0700560 mTwilightManager = getLocalService(TwilightManager.class);
Justin Klaassen911e8892016-06-21 18:24:24 -0700561 }
562
Justin Klaassen908b86c2016-08-08 09:18:42 -0700563 private void updateActivated(TwilightState state) {
Justin Klaassen3da4c442017-05-05 15:19:33 -0700564 if (state == null) {
565 // If there isn't a valid TwilightState then just keep the current activated
566 // state.
567 return;
568 }
569
570 boolean activate = state.isNight();
Christine Franks03213462017-08-25 13:57:26 -0700571 final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
Justin Klaassen3da4c442017-05-05 15:19:33 -0700572 if (lastActivatedTime != null) {
Christine Franks03213462017-08-25 13:57:26 -0700573 final LocalDateTime now = LocalDateTime.now();
574 final LocalDateTime sunrise = state.sunrise();
575 final LocalDateTime sunset = state.sunset();
Christine Frankse5bb03e2017-02-10 17:36:10 -0800576 // Maintain the existing activated state if within the current period.
Christine Franks03213462017-08-25 13:57:26 -0700577 if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
578 ^ lastActivatedTime.isBefore(sunset))) {
Christine Frankse5bb03e2017-02-10 17:36:10 -0800579 activate = mController.isActivated();
Justin Klaassen911e8892016-06-21 18:24:24 -0700580 }
581 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700582
Christine Frankse5bb03e2017-02-10 17:36:10 -0800583 if (mIsActivated == null || mIsActivated != activate) {
584 mController.setActivated(activate);
Justin Klaassen908b86c2016-08-08 09:18:42 -0700585 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700586 }
587
588 @Override
589 public void onStart() {
590 mTwilightManager.registerListener(this, mHandler);
591
592 // Force an update to initialize state.
Justin Klaassen908b86c2016-08-08 09:18:42 -0700593 updateActivated(mTwilightManager.getLastTwilightState());
Justin Klaassen911e8892016-06-21 18:24:24 -0700594 }
595
596 @Override
597 public void onStop() {
598 mTwilightManager.unregisterListener(this);
599 }
600
601 @Override
Justin Klaassen908b86c2016-08-08 09:18:42 -0700602 public void onActivated(boolean activated) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700603 }
604
605 @Override
606 public void onTwilightStateChanged(@Nullable TwilightState state) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700607 Slog.d(TAG, "onTwilightStateChanged: isNight="
608 + (state == null ? null : state.isNight()));
Justin Klaassen908b86c2016-08-08 09:18:42 -0700609 updateActivated(state);
Justin Klaassen911e8892016-06-21 18:24:24 -0700610 }
611 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700612
613 /**
614 * Interpolates between two 4x4 color transform matrices (in column-major order).
615 */
616 private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
617
618 /**
619 * Result matrix returned by {@link #evaluate(float, float[], float[])}.
620 */
621 private final float[] mResultMatrix = new float[16];
622
623 @Override
624 public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
625 for (int i = 0; i < mResultMatrix.length; i++) {
626 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
627 }
628 return mResultMatrix;
629 }
630 }
Justin Klaassen911e8892016-06-21 18:24:24 -0700631}