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