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