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