OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / services / core / java / com / android / server / display / NightDisplayService.java
1 /*
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
17 package com.android.server.display;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TypeEvaluator;
22 import android.animation.ValueAnimator;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.AlarmManager;
26 import android.content.BroadcastReceiver;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.database.ContentObserver;
32 import android.net.Uri;
33 import android.opengl.Matrix;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.UserHandle;
37 import android.provider.Settings.Secure;
38 import android.util.MathUtils;
39 import android.util.Slog;
40 import android.view.animation.AnimationUtils;
41
42 import com.android.internal.app.NightDisplayController;
43 import com.android.server.SystemService;
44 import com.android.server.twilight.TwilightListener;
45 import com.android.server.twilight.TwilightManager;
46 import com.android.server.twilight.TwilightState;
47
48 import java.util.Calendar;
49 import java.util.TimeZone;
50
51 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
52
53 /**
54  * Tints the display at night.
55  */
56 public final class NightDisplayService extends SystemService
57         implements NightDisplayController.Callback {
58
59     private static final String TAG = "NightDisplayService";
60     private static final boolean DEBUG = false;
61
62     /**
63      * Night display ~= 3400 K.
64      */
65     private static final float[] MATRIX_NIGHT = new float[] {
66         1,      0,      0, 0,
67         0, 0.754f,      0, 0,
68         0,      0, 0.516f, 0,
69         0,      0,      0, 1
70     };
71
72     /**
73      * 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
85     private final Handler mHandler;
86
87     private int mCurrentUser = UserHandle.USER_NULL;
88     private ContentObserver mUserSetupObserver;
89     private boolean mBootCompleted;
90
91     private NightDisplayController mController;
92     private ValueAnimator mColorMatrixAnimator;
93     private Boolean mIsActivated;
94     private AutoMode mAutoMode;
95
96     public NightDisplayService(Context context) {
97         super(context);
98         mHandler = new Handler(Looper.getMainLooper());
99     }
100
101     @Override
102     public void onStart() {
103         // Nothing to publish.
104     }
105
106     @Override
107     public void onBootPhase(int phase) {
108         if (phase == PHASE_BOOT_COMPLETED) {
109             mBootCompleted = true;
110
111             // Register listeners now that boot is complete.
112             if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
113                 setUp();
114             }
115         }
116     }
117
118     @Override
119     public void onStartUser(int userHandle) {
120         super.onStartUser(userHandle);
121
122         if (mCurrentUser == UserHandle.USER_NULL) {
123             onUserChanged(userHandle);
124         }
125     }
126
127     @Override
128     public void onSwitchUser(int userHandle) {
129         super.onSwitchUser(userHandle);
130
131         onUserChanged(userHandle);
132     }
133
134     @Override
135     public void onStopUser(int userHandle) {
136         super.onStopUser(userHandle);
137
138         if (mCurrentUser == userHandle) {
139             onUserChanged(UserHandle.USER_NULL);
140         }
141     }
142
143     private void onUserChanged(int userHandle) {
144         final ContentResolver cr = getContext().getContentResolver();
145
146         if (mCurrentUser != UserHandle.USER_NULL) {
147             if (mUserSetupObserver != null) {
148                 cr.unregisterContentObserver(mUserSetupObserver);
149                 mUserSetupObserver = null;
150             } else if (mBootCompleted) {
151                 tearDown();
152             }
153         }
154
155         mCurrentUser = userHandle;
156
157         if (mCurrentUser != UserHandle.USER_NULL) {
158             if (!isUserSetupCompleted(cr, mCurrentUser)) {
159                 mUserSetupObserver = new ContentObserver(mHandler) {
160                     @Override
161                     public void onChange(boolean selfChange, Uri uri) {
162                         if (isUserSetupCompleted(cr, mCurrentUser)) {
163                             cr.unregisterContentObserver(this);
164                             mUserSetupObserver = null;
165
166                             if (mBootCompleted) {
167                                 setUp();
168                             }
169                         }
170                     }
171                 };
172                 cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
173                         false /* notifyForDescendents */, mUserSetupObserver, mCurrentUser);
174             } else if (mBootCompleted) {
175                 setUp();
176             }
177         }
178     }
179
180     private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
181         return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
182     }
183
184     private void setUp() {
185         // Create a new controller for the current user and start listening for changes.
186         mController = new NightDisplayController(getContext(), mCurrentUser);
187         mController.setListener(this);
188
189         // Initialize the current auto mode.
190         onAutoModeChanged(mController.getAutoMode());
191
192         // Force the initialization current activated state.
193         if (mIsActivated == null) {
194             onActivated(mController.isActivated());
195         }
196     }
197
198     private void tearDown() {
199         if (mController != null) {
200             mController.setListener(null);
201             mController = null;
202         }
203
204         if (mAutoMode != null) {
205             mAutoMode.onStop();
206             mAutoMode = null;
207         }
208
209         if (mColorMatrixAnimator != null) {
210             mColorMatrixAnimator.end();
211             mColorMatrixAnimator = null;
212         }
213
214         mIsActivated = null;
215     }
216
217     @Override
218     public void onActivated(boolean activated) {
219         if (mIsActivated == null || mIsActivated != activated) {
220             Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
221
222             if (mAutoMode != null) {
223                 mAutoMode.onActivated(activated);
224             }
225
226             mIsActivated = activated;
227
228             // Cancel the old animator if still running.
229             if (mColorMatrixAnimator != null) {
230                 mColorMatrixAnimator.cancel();
231             }
232
233             final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
234             final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
235             final float[] to = mIsActivated ? MATRIX_NIGHT : null;
236
237             mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
238                     from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
239             mColorMatrixAnimator.setDuration(getContext().getResources()
240                     .getInteger(android.R.integer.config_longAnimTime));
241             mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
242                     getContext(), android.R.interpolator.fast_out_slow_in));
243             mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
244                 @Override
245                 public void onAnimationUpdate(ValueAnimator animator) {
246                     final float[] value = (float[]) animator.getAnimatedValue();
247                     dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
248                 }
249             });
250             mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
251
252                 private boolean mIsCancelled;
253
254                 @Override
255                 public void onAnimationCancel(Animator animator) {
256                     mIsCancelled = true;
257                 }
258
259                 @Override
260                 public void onAnimationEnd(Animator animator) {
261                     if (!mIsCancelled) {
262                         // Ensure final color matrix is set at the end of the animation. If the
263                         // animation is cancelled then don't set the final color matrix so the new
264                         // animator can pick up from where this one left off.
265                         dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
266                     }
267                     mColorMatrixAnimator = null;
268                 }
269             });
270             mColorMatrixAnimator.start();
271         }
272     }
273
274     @Override
275     public void onAutoModeChanged(int autoMode) {
276         if (mAutoMode != null) {
277             mAutoMode.onStop();
278             mAutoMode = null;
279         }
280
281         if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
282             mAutoMode = new CustomAutoMode();
283         } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
284             mAutoMode = new TwilightAutoMode();
285         }
286
287         if (mAutoMode != null) {
288             mAutoMode.onStart();
289         }
290     }
291
292     @Override
293     public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
294         if (mAutoMode != null) {
295             mAutoMode.onCustomStartTimeChanged(startTime);
296         }
297     }
298
299     @Override
300     public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
301         if (mAutoMode != null) {
302             mAutoMode.onCustomEndTimeChanged(endTime);
303         }
304     }
305
306     private abstract class AutoMode implements NightDisplayController.Callback {
307         public abstract void onStart();
308         public abstract void onStop();
309     }
310
311     private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
312
313         private final AlarmManager mAlarmManager;
314         private final BroadcastReceiver mTimeChangedReceiver;
315
316         private NightDisplayController.LocalTime mStartTime;
317         private NightDisplayController.LocalTime mEndTime;
318
319         private Calendar mLastActivatedTime;
320
321         public CustomAutoMode() {
322             mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
323             mTimeChangedReceiver = new BroadcastReceiver() {
324                 @Override
325                 public void onReceive(Context context, Intent intent) {
326                     updateActivated();
327                 }
328             };
329         }
330
331         private void updateActivated() {
332             final Calendar now = Calendar.getInstance();
333             final Calendar startTime = mStartTime.getDateTimeBefore(now);
334             final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
335             final boolean activated = now.before(endTime);
336
337             boolean setActivated = mIsActivated == null || mLastActivatedTime == null;
338             if (!setActivated && mIsActivated != activated) {
339                 final TimeZone currentTimeZone = now.getTimeZone();
340                 if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
341                     final int year = mLastActivatedTime.get(Calendar.YEAR);
342                     final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
343                     final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
344                     final int minute = mLastActivatedTime.get(Calendar.MINUTE);
345
346                     mLastActivatedTime.setTimeZone(currentTimeZone);
347                     mLastActivatedTime.set(Calendar.YEAR, year);
348                     mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
349                     mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
350                     mLastActivatedTime.set(Calendar.MINUTE, minute);
351                 }
352
353                 if (mIsActivated) {
354                     setActivated = now.before(mStartTime.getDateTimeBefore(mLastActivatedTime))
355                             || now.after(mEndTime.getDateTimeAfter(mLastActivatedTime));
356                 } else {
357                     setActivated = now.before(mEndTime.getDateTimeBefore(mLastActivatedTime))
358                             || now.after(mStartTime.getDateTimeAfter(mLastActivatedTime));
359                 }
360             }
361
362             if (setActivated) {
363                 mController.setActivated(activated);
364             }
365             updateNextAlarm(mIsActivated, now);
366         }
367
368         private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
369             if (activated != null) {
370                 final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
371                         : mStartTime.getDateTimeAfter(now);
372                 mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
373             }
374         }
375
376         @Override
377         public void onStart() {
378             final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
379             intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
380             getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
381
382             mStartTime = mController.getCustomStartTime();
383             mEndTime = mController.getCustomEndTime();
384
385             // Force an update to initialize state.
386             updateActivated();
387         }
388
389         @Override
390         public void onStop() {
391             getContext().unregisterReceiver(mTimeChangedReceiver);
392
393             mAlarmManager.cancel(this);
394             mLastActivatedTime = null;
395         }
396
397         @Override
398         public void onActivated(boolean activated) {
399             final Calendar now = Calendar.getInstance();
400             if (mIsActivated != null) {
401                 mLastActivatedTime = now;
402             }
403             updateNextAlarm(activated, now);
404         }
405
406         @Override
407         public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
408             mStartTime = startTime;
409             mLastActivatedTime = null;
410             updateActivated();
411         }
412
413         @Override
414         public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
415             mEndTime = endTime;
416             mLastActivatedTime = null;
417             updateActivated();
418         }
419
420         @Override
421         public void onAlarm() {
422             if (DEBUG) Slog.d(TAG, "onAlarm");
423             updateActivated();
424         }
425     }
426
427     private class TwilightAutoMode extends AutoMode implements TwilightListener {
428
429         private final TwilightManager mTwilightManager;
430
431         private Calendar mLastActivatedTime;
432
433         public TwilightAutoMode() {
434             mTwilightManager = getLocalService(TwilightManager.class);
435         }
436
437         private void updateActivated(TwilightState state) {
438             final boolean isNight = state != null && state.isNight();
439             boolean setActivated = mIsActivated == null || mIsActivated != isNight;
440             if (setActivated && state != null && mLastActivatedTime != null) {
441                 final Calendar sunrise = state.sunrise();
442                 final Calendar sunset = state.sunset();
443                 if (sunrise.before(sunset)) {
444                     setActivated = mLastActivatedTime.before(sunrise)
445                             || mLastActivatedTime.after(sunset);
446                 } else {
447                     setActivated = mLastActivatedTime.before(sunset)
448                             || mLastActivatedTime.after(sunrise);
449                 }
450             }
451
452             if (setActivated) {
453                 mController.setActivated(isNight);
454             }
455         }
456
457         @Override
458         public void onStart() {
459             mTwilightManager.registerListener(this, mHandler);
460
461             // Force an update to initialize state.
462             updateActivated(mTwilightManager.getLastTwilightState());
463         }
464
465         @Override
466         public void onStop() {
467             mTwilightManager.unregisterListener(this);
468             mLastActivatedTime = null;
469         }
470
471         @Override
472         public void onActivated(boolean activated) {
473             if (mIsActivated != null) {
474                 mLastActivatedTime = Calendar.getInstance();
475             }
476         }
477
478         @Override
479         public void onTwilightStateChanged(@Nullable TwilightState state) {
480             if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
481             updateActivated(state);
482         }
483     }
484
485     /**
486      * Interpolates between two 4x4 color transform matrices (in column-major order).
487      */
488     private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
489
490         /**
491          * Result matrix returned by {@link #evaluate(float, float[], float[])}.
492          */
493         private final float[] mResultMatrix = new float[16];
494
495         @Override
496         public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
497             for (int i = 0; i < mResultMatrix.length; i++) {
498                 mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
499             }
500             return mResultMatrix;
501         }
502     }
503 }