1 /*
2  * Copyright (C) 2012 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.content.Context;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.PowerManager;
23 import android.os.Trace;
24 import android.util.FloatProperty;
25 import android.util.Slog;
26 import android.view.Choreographer;
27 import android.view.Display;
28 
29 import java.io.PrintWriter;
30 
31 /**
32  * Controls the display power state.
33  * <p>
34  * This component is similar in nature to a {@link android.view.View} except that it
35  * describes the properties of a display.  When properties are changed, the component
36  * invalidates itself and posts a callback to apply the changes in a consistent order.
37  * This mechanism enables multiple properties of the display power state to be animated
38  * together smoothly by the animation framework.  Some of the work to blank or unblank
39  * the display is done on a separate thread to avoid blocking the looper.
40  * </p><p>
41  * This component must only be created or accessed by the {@link Looper} thread
42  * that belongs to the {@link DisplayPowerController}.
43  * </p><p>
44  * We don't need to worry about holding a suspend blocker here because the
45  * power manager does that for us whenever there is a change in progress.
46  * </p>
47  */
48 final class DisplayPowerState {
49     private static final String TAG = "DisplayPowerState";
50 
51     private static boolean DEBUG = false;
52     private static String COUNTER_COLOR_FADE = "ColorFadeLevel";
53 
54     private final Handler mHandler;
55     private final Choreographer mChoreographer;
56     private final DisplayBlanker mBlanker;
57     private final ColorFade mColorFade;
58     private final PhotonicModulator mPhotonicModulator;
59     private final int mDisplayId;
60 
61     private int mScreenState;
62     private float mScreenBrightness;
63     private float mSdrScreenBrightness;
64     private boolean mScreenReady;
65     private boolean mScreenUpdatePending;
66 
67     private boolean mColorFadePrepared;
68     private float mColorFadeLevel;
69     private boolean mColorFadeReady;
70     private boolean mColorFadeDrawPending;
71 
72     private Runnable mCleanListener;
73 
74     private volatile boolean mStopped;
75 
DisplayPowerState( DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState)76     DisplayPowerState(
77             DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
78         mHandler = new Handler(true /*async*/);
79         mChoreographer = Choreographer.getInstance();
80         mBlanker = blanker;
81         mColorFade = colorFade;
82         mPhotonicModulator = new PhotonicModulator();
83         mPhotonicModulator.start();
84         mDisplayId = displayId;
85 
86         // At boot time, we don't know the screen's brightness,
87         // so prepare to set it to a known state when the state is next applied.
88         // Although we set the brightness here, the display power controller
89         // will reset the brightness to a new level immediately before the changes
90         // actually have a chance to be applied.
91         mScreenState = displayState;
92         mScreenBrightness = (displayState != Display.STATE_OFF) ? PowerManager.BRIGHTNESS_MAX
93                 : PowerManager.BRIGHTNESS_OFF_FLOAT;
94         mSdrScreenBrightness = mScreenBrightness;
95         scheduleScreenUpdate();
96 
97         mColorFadePrepared = false;
98         mColorFadeLevel = 1.0f;
99         mColorFadeReady = true;
100     }
101 
102     public static final FloatProperty<DisplayPowerState> COLOR_FADE_LEVEL =
103             new FloatProperty<DisplayPowerState>("electronBeamLevel") {
104         @Override
105         public void setValue(DisplayPowerState object, float value) {
106             object.setColorFadeLevel(value);
107         }
108 
109         @Override
110         public Float get(DisplayPowerState object) {
111             return object.getColorFadeLevel();
112         }
113     };
114 
115 
116     public static final FloatProperty<DisplayPowerState> SCREEN_BRIGHTNESS_FLOAT =
117             new FloatProperty<DisplayPowerState>("screenBrightnessFloat") {
118                 @Override
119                 public void setValue(DisplayPowerState object, float value) {
120                     object.setScreenBrightness(value);
121                 }
122 
123                 @Override
124                 public Float get(DisplayPowerState object) {
125                     return object.getScreenBrightness();
126                 }
127             };
128 
129     public static final FloatProperty<DisplayPowerState> SCREEN_SDR_BRIGHTNESS_FLOAT =
130             new FloatProperty<DisplayPowerState>("sdrScreenBrightnessFloat") {
131                 @Override
132                 public void setValue(DisplayPowerState object, float value) {
133                     object.setSdrScreenBrightness(value);
134                 }
135 
136                 @Override
137                 public Float get(DisplayPowerState object) {
138                     return object.getSdrScreenBrightness();
139                 }
140             };
141 
142     /**
143      * Sets whether the screen is on, off, or dozing.
144      */
setScreenState(int state)145     public void setScreenState(int state) {
146         if (mScreenState != state) {
147             if (DEBUG) {
148                 Slog.d(TAG, "setScreenState: state=" + state);
149             }
150 
151             mScreenState = state;
152             mScreenReady = false;
153             scheduleScreenUpdate();
154         }
155     }
156 
157     /**
158      * Gets the desired screen state.
159      */
getScreenState()160     public int getScreenState() {
161         return mScreenState;
162     }
163 
164     /**
165      * Sets the display's SDR brightness.
166      *
167      * @param brightness The brightness, ranges from 0.0f (minimum) to 1.0f (brightest), or is -1f
168      *                   (off).
169      */
setSdrScreenBrightness(float brightness)170     public void setSdrScreenBrightness(float brightness) {
171         if (mSdrScreenBrightness != brightness) {
172             if (DEBUG) {
173                 Slog.d(TAG, "setSdrScreenBrightness: brightness=" + brightness);
174             }
175 
176             mSdrScreenBrightness = brightness;
177             if (mScreenState != Display.STATE_OFF) {
178                 mScreenReady = false;
179                 scheduleScreenUpdate();
180             }
181         }
182     }
183 
184     /**
185      * Gets the screen SDR brightness.
186      */
getSdrScreenBrightness()187     public float getSdrScreenBrightness() {
188         return mSdrScreenBrightness;
189     }
190 
191     /**
192      * Sets the display brightness.
193      *
194      * @param brightness The brightness, ranges from 0.0f (minimum) to 1.0f (brightest), or is -1f
195      *                   (off).
196      */
setScreenBrightness(float brightness)197     public void setScreenBrightness(float brightness) {
198         if (mScreenBrightness != brightness) {
199             if (DEBUG) {
200                 Slog.d(TAG, "setScreenBrightness: brightness=" + brightness);
201             }
202 
203             mScreenBrightness = brightness;
204             if (mScreenState != Display.STATE_OFF) {
205                 mScreenReady = false;
206                 scheduleScreenUpdate();
207             }
208         }
209     }
210 
211     /**
212      * Gets the screen brightness.
213      */
getScreenBrightness()214     public float getScreenBrightness() {
215         return mScreenBrightness;
216     }
217 
218     /**
219      * Prepares the electron beam to turn on or off.
220      * This method should be called before starting an animation because it
221      * can take a fair amount of time to prepare the electron beam surface.
222      *
223      * @param mode The electron beam animation mode to prepare.
224      * @return True if the electron beam was prepared.
225      */
prepareColorFade(Context context, int mode)226     public boolean prepareColorFade(Context context, int mode) {
227         if (mColorFade == null || !mColorFade.prepare(context, mode)) {
228             mColorFadePrepared = false;
229             mColorFadeReady = true;
230             return false;
231         }
232 
233         mColorFadePrepared = true;
234         mColorFadeReady = false;
235         scheduleColorFadeDraw();
236         return true;
237     }
238 
239     /**
240      * Dismisses the color fade surface.
241      */
dismissColorFade()242     public void dismissColorFade() {
243         Trace.traceCounter(Trace.TRACE_TAG_POWER, COUNTER_COLOR_FADE, 100);
244         if (mColorFade != null) mColorFade.dismiss();
245         mColorFadePrepared = false;
246         mColorFadeReady = true;
247     }
248 
249    /**
250      * Dismisses the color fade resources.
251      */
dismissColorFadeResources()252     public void dismissColorFadeResources() {
253         if (mColorFade != null) mColorFade.dismissResources();
254     }
255 
256     /**
257      * Sets the level of the electron beam steering current.
258      *
259      * The display is blanked when the level is 0.0.  In normal use, the electron
260      * beam should have a value of 1.0.  The electron beam is unstable in between
261      * these states and the picture quality may be compromised.  For best effect,
262      * the electron beam should be warmed up or cooled off slowly.
263      *
264      * Warning: Electron beam emits harmful radiation.  Avoid direct exposure to
265      * skin or eyes.
266      *
267      * @param level The level, ranges from 0.0 (full off) to 1.0 (full on).
268      */
setColorFadeLevel(float level)269     public void setColorFadeLevel(float level) {
270         if (mColorFadeLevel != level) {
271             if (DEBUG) {
272                 Slog.d(TAG, "setColorFadeLevel: level=" + level);
273             }
274 
275             mColorFadeLevel = level;
276             if (mScreenState != Display.STATE_OFF) {
277                 mScreenReady = false;
278                 scheduleScreenUpdate(); // update backlight brightness
279             }
280             if (mColorFadePrepared) {
281                 mColorFadeReady = false;
282                 scheduleColorFadeDraw();
283             }
284         }
285     }
286 
287     /**
288      * Gets the level of the electron beam steering current.
289      */
getColorFadeLevel()290     public float getColorFadeLevel() {
291         return mColorFadeLevel;
292     }
293 
294     /**
295      * Returns true if no properties have been invalidated.
296      * Otherwise, returns false and promises to invoke the specified listener
297      * when the properties have all been applied.
298      * The listener always overrides any previously set listener.
299      */
waitUntilClean(Runnable listener)300     public boolean waitUntilClean(Runnable listener) {
301         if (!mScreenReady || !mColorFadeReady) {
302             mCleanListener = listener;
303             return false;
304         } else {
305             mCleanListener = null;
306             return true;
307         }
308     }
309 
310     /**
311      * Interrupts all running threads; halting future work.
312      *
313      * This method should be called when the DisplayPowerState is no longer in use; i.e. when
314      * the {@link #mDisplayId display} has been removed.
315      */
stop()316     public void stop() {
317         mStopped = true;
318         mPhotonicModulator.interrupt();
319         dismissColorFade();
320         mCleanListener = null;
321         mHandler.removeCallbacksAndMessages(null);
322     }
323 
dump(PrintWriter pw)324     public void dump(PrintWriter pw) {
325         pw.println();
326         pw.println("Display Power State:");
327         pw.println("  mStopped=" + mStopped);
328         pw.println("  mScreenState=" + Display.stateToString(mScreenState));
329         pw.println("  mScreenBrightness=" + mScreenBrightness);
330         pw.println("  mSdrScreenBrightness=" + mSdrScreenBrightness);
331         pw.println("  mScreenReady=" + mScreenReady);
332         pw.println("  mScreenUpdatePending=" + mScreenUpdatePending);
333         pw.println("  mColorFadePrepared=" + mColorFadePrepared);
334         pw.println("  mColorFadeLevel=" + mColorFadeLevel);
335         pw.println("  mColorFadeReady=" + mColorFadeReady);
336         pw.println("  mColorFadeDrawPending=" + mColorFadeDrawPending);
337 
338         mPhotonicModulator.dump(pw);
339         if (mColorFade != null) mColorFade.dump(pw);
340     }
341 
scheduleScreenUpdate()342     private void scheduleScreenUpdate() {
343         if (!mScreenUpdatePending) {
344             mScreenUpdatePending = true;
345             postScreenUpdateThreadSafe();
346         }
347     }
348 
postScreenUpdateThreadSafe()349     private void postScreenUpdateThreadSafe() {
350         mHandler.removeCallbacks(mScreenUpdateRunnable);
351         mHandler.post(mScreenUpdateRunnable);
352     }
353 
scheduleColorFadeDraw()354     private void scheduleColorFadeDraw() {
355         if (!mColorFadeDrawPending) {
356             mColorFadeDrawPending = true;
357             mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
358                     mColorFadeDrawRunnable, null);
359         }
360     }
361 
invokeCleanListenerIfNeeded()362     private void invokeCleanListenerIfNeeded() {
363         final Runnable listener = mCleanListener;
364         if (listener != null && mScreenReady && mColorFadeReady) {
365             mCleanListener = null;
366             listener.run();
367         }
368     }
369 
370     private final Runnable mScreenUpdateRunnable = new Runnable() {
371         @Override
372         public void run() {
373             mScreenUpdatePending = false;
374 
375             float brightnessState = mScreenState != Display.STATE_OFF
376                     && mColorFadeLevel > 0f ? mScreenBrightness : PowerManager.BRIGHTNESS_OFF_FLOAT;
377             float sdrBrightnessState = mScreenState != Display.STATE_OFF
378                     && mColorFadeLevel > 0f
379                             ? mSdrScreenBrightness : PowerManager.BRIGHTNESS_OFF_FLOAT;
380             if (mPhotonicModulator.setState(mScreenState, brightnessState, sdrBrightnessState)) {
381                 if (DEBUG) {
382                     Slog.d(TAG, "Screen ready");
383                 }
384                 mScreenReady = true;
385                 invokeCleanListenerIfNeeded();
386             } else {
387                 if (DEBUG) {
388                     Slog.d(TAG, "Screen not ready");
389                 }
390             }
391         }
392     };
393 
394     private final Runnable mColorFadeDrawRunnable = new Runnable() {
395         @Override
396         public void run() {
397             mColorFadeDrawPending = false;
398 
399             if (mColorFadePrepared) {
400                 mColorFade.draw(mColorFadeLevel);
401                 Trace.traceCounter(Trace.TRACE_TAG_POWER,
402                         COUNTER_COLOR_FADE, Math.round(mColorFadeLevel * 100));
403             }
404 
405             mColorFadeReady = true;
406             invokeCleanListenerIfNeeded();
407         }
408     };
409 
410     /**
411      * Updates the state of the screen and backlight asynchronously on a separate thread.
412      */
413     private final class PhotonicModulator extends Thread {
414         private static final int INITIAL_SCREEN_STATE = Display.STATE_UNKNOWN;
415         private static final float INITIAL_BACKLIGHT_FLOAT = PowerManager.BRIGHTNESS_INVALID_FLOAT;
416 
417         private final Object mLock = new Object();
418 
419         private int mPendingState = INITIAL_SCREEN_STATE;
420         private float mPendingBacklight = INITIAL_BACKLIGHT_FLOAT;
421         private float mPendingSdrBacklight = INITIAL_BACKLIGHT_FLOAT;
422         private int mActualState = INITIAL_SCREEN_STATE;
423         private float mActualBacklight = INITIAL_BACKLIGHT_FLOAT;
424         private float mActualSdrBacklight = INITIAL_BACKLIGHT_FLOAT;
425         private boolean mStateChangeInProgress;
426         private boolean mBacklightChangeInProgress;
427 
PhotonicModulator()428         public PhotonicModulator() {
429             super("PhotonicModulator");
430         }
431 
setState(int state, float brightnessState, float sdrBrightnessState)432         public boolean setState(int state, float brightnessState, float sdrBrightnessState) {
433             synchronized (mLock) {
434                 boolean stateChanged = state != mPendingState;
435                 boolean backlightChanged = brightnessState != mPendingBacklight
436                         || sdrBrightnessState != mPendingSdrBacklight;
437                 if (stateChanged || backlightChanged) {
438                     if (DEBUG) {
439                         Slog.d(TAG, "Requesting new screen state: state="
440                                 + Display.stateToString(state) + ", backlight=" + brightnessState);
441                     }
442 
443                     mPendingState = state;
444                     mPendingBacklight = brightnessState;
445                     mPendingSdrBacklight = sdrBrightnessState;
446                     boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress;
447                     mStateChangeInProgress = stateChanged || mStateChangeInProgress;
448                     mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress;
449 
450                     if (!changeInProgress) {
451                         mLock.notifyAll();
452                     }
453                 }
454                 return !mStateChangeInProgress;
455             }
456         }
457 
dump(PrintWriter pw)458         public void dump(PrintWriter pw) {
459             synchronized (mLock) {
460                 pw.println();
461                 pw.println("Photonic Modulator State:");
462                 pw.println("  mPendingState=" + Display.stateToString(mPendingState));
463                 pw.println("  mPendingBacklight=" + mPendingBacklight);
464                 pw.println("  mPendingSdrBacklight=" + mPendingSdrBacklight);
465                 pw.println("  mActualState=" + Display.stateToString(mActualState));
466                 pw.println("  mActualBacklight=" + mActualBacklight);
467                 pw.println("  mActualSdrBacklight=" + mActualSdrBacklight);
468                 pw.println("  mStateChangeInProgress=" + mStateChangeInProgress);
469                 pw.println("  mBacklightChangeInProgress=" + mBacklightChangeInProgress);
470             }
471         }
472 
473         @Override
run()474         public void run() {
475             for (;;) {
476                 // Get pending change.
477                 final int state;
478                 final boolean stateChanged;
479                 final float brightnessState;
480                 final float sdrBrightnessState;
481                 final boolean backlightChanged;
482                 synchronized (mLock) {
483                     state = mPendingState;
484                     stateChanged = (state != mActualState);
485                     brightnessState = mPendingBacklight;
486                     sdrBrightnessState = mPendingSdrBacklight;
487                     backlightChanged = brightnessState != mActualBacklight
488                             || sdrBrightnessState != mActualSdrBacklight;
489                     if (!stateChanged) {
490                         // State changed applied, notify outer class.
491                         postScreenUpdateThreadSafe();
492                         mStateChangeInProgress = false;
493                     }
494                     if (!backlightChanged) {
495                         mBacklightChangeInProgress = false;
496                     }
497                     boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
498                     boolean changed = stateChanged || backlightChanged;
499                     if (!valid || !changed) {
500                         try {
501                             mLock.wait();
502                         } catch (InterruptedException ex) {
503                             if (mStopped) {
504                                 return;
505                             }
506                         }
507                         continue;
508                     }
509                     mActualState = state;
510                     mActualBacklight = brightnessState;
511                     mActualSdrBacklight = sdrBrightnessState;
512                 }
513 
514                 // Apply pending change.
515                 if (DEBUG) {
516                     Slog.d(TAG, "Updating screen state: id=" + mDisplayId +  ", state="
517                             + Display.stateToString(state) + ", backlight=" + brightnessState
518                             + ", sdrBacklight=" + sdrBrightnessState);
519                 }
520                 mBlanker.requestDisplayState(mDisplayId, state, brightnessState,
521                         sdrBrightnessState);
522             }
523         }
524     }
525 }
526