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