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