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