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 color fade surface.
191     */
192    public void dismissColorFade() {
193        mColorFade.dismiss();
194        mColorFadePrepared = false;
195        mColorFadeReady = true;
196    }
197
198   /**
199     * Dismisses the color fade resources.
200     */
201    public void dismissColorFadeResources() {
202        mColorFade.dismissResources();
203    }
204
205    /**
206     * Sets the level of the electron beam steering current.
207     *
208     * The display is blanked when the level is 0.0.  In normal use, the electron
209     * beam should have a value of 1.0.  The electron beam is unstable in between
210     * these states and the picture quality may be compromised.  For best effect,
211     * the electron beam should be warmed up or cooled off slowly.
212     *
213     * Warning: Electron beam emits harmful radiation.  Avoid direct exposure to
214     * skin or eyes.
215     *
216     * @param level The level, ranges from 0.0 (full off) to 1.0 (full on).
217     */
218    public void setColorFadeLevel(float level) {
219        if (mColorFadeLevel != level) {
220            if (DEBUG) {
221                Slog.d(TAG, "setColorFadeLevel: level=" + level);
222            }
223
224            mColorFadeLevel = level;
225            if (mScreenState != Display.STATE_OFF) {
226                mScreenReady = false;
227                scheduleScreenUpdate(); // update backlight brightness
228            }
229            if (mColorFadePrepared) {
230                mColorFadeReady = false;
231                scheduleColorFadeDraw();
232            }
233        }
234    }
235
236    /**
237     * Gets the level of the electron beam steering current.
238     */
239    public float getColorFadeLevel() {
240        return mColorFadeLevel;
241    }
242
243    /**
244     * Returns true if no properties have been invalidated.
245     * Otherwise, returns false and promises to invoke the specified listener
246     * when the properties have all been applied.
247     * The listener always overrides any previously set listener.
248     */
249    public boolean waitUntilClean(Runnable listener) {
250        if (!mScreenReady || !mColorFadeReady) {
251            mCleanListener = listener;
252            return false;
253        } else {
254            mCleanListener = null;
255            return true;
256        }
257    }
258
259    public void dump(PrintWriter pw) {
260        pw.println();
261        pw.println("Display Power State:");
262        pw.println("  mScreenState=" + Display.stateToString(mScreenState));
263        pw.println("  mScreenBrightness=" + mScreenBrightness);
264        pw.println("  mScreenReady=" + mScreenReady);
265        pw.println("  mScreenUpdatePending=" + mScreenUpdatePending);
266        pw.println("  mColorFadePrepared=" + mColorFadePrepared);
267        pw.println("  mColorFadeLevel=" + mColorFadeLevel);
268        pw.println("  mColorFadeReady=" + mColorFadeReady);
269        pw.println("  mColorFadeDrawPending=" + mColorFadeDrawPending);
270
271        mPhotonicModulator.dump(pw);
272        mColorFade.dump(pw);
273    }
274
275    private void scheduleScreenUpdate() {
276        if (!mScreenUpdatePending) {
277            mScreenUpdatePending = true;
278            postScreenUpdateThreadSafe();
279        }
280    }
281
282    private void postScreenUpdateThreadSafe() {
283        mHandler.removeCallbacks(mScreenUpdateRunnable);
284        mHandler.post(mScreenUpdateRunnable);
285    }
286
287    private void scheduleColorFadeDraw() {
288        if (!mColorFadeDrawPending) {
289            mColorFadeDrawPending = true;
290            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
291                    mColorFadeDrawRunnable, null);
292        }
293    }
294
295    private void invokeCleanListenerIfNeeded() {
296        final Runnable listener = mCleanListener;
297        if (listener != null && mScreenReady && mColorFadeReady) {
298            mCleanListener = null;
299            listener.run();
300        }
301    }
302
303    private final Runnable mScreenUpdateRunnable = new Runnable() {
304        @Override
305        public void run() {
306            mScreenUpdatePending = false;
307
308            int brightness = mScreenState != Display.STATE_OFF
309                    && mColorFadeLevel > 0f ? mScreenBrightness : 0;
310            if (mPhotonicModulator.setState(mScreenState, brightness)) {
311                if (DEBUG) {
312                    Slog.d(TAG, "Screen ready");
313                }
314                mScreenReady = true;
315                invokeCleanListenerIfNeeded();
316            } else {
317                if (DEBUG) {
318                    Slog.d(TAG, "Screen not ready");
319                }
320            }
321        }
322    };
323
324    private final Runnable mColorFadeDrawRunnable = new Runnable() {
325        @Override
326        public void run() {
327            mColorFadeDrawPending = false;
328
329            if (mColorFadePrepared) {
330                mColorFade.draw(mColorFadeLevel);
331            }
332
333            mColorFadeReady = true;
334            invokeCleanListenerIfNeeded();
335        }
336    };
337
338    /**
339     * Updates the state of the screen and backlight asynchronously on a separate thread.
340     */
341    private final class PhotonicModulator extends Thread {
342        private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off
343        private static final int INITIAL_BACKLIGHT = -1; // unknown
344
345        private final Object mLock = new Object();
346
347        private int mPendingState = INITIAL_SCREEN_STATE;
348        private int mPendingBacklight = INITIAL_BACKLIGHT;
349        private int mActualState = INITIAL_SCREEN_STATE;
350        private int mActualBacklight = INITIAL_BACKLIGHT;
351        private boolean mStateChangeInProgress;
352        private boolean mBacklightChangeInProgress;
353
354        public PhotonicModulator() {
355            super("PhotonicModulator");
356        }
357
358        public boolean setState(int state, int backlight) {
359            synchronized (mLock) {
360                boolean stateChanged = state != mPendingState;
361                boolean backlightChanged = backlight != mPendingBacklight;
362                if (stateChanged || backlightChanged) {
363                    if (DEBUG) {
364                        Slog.d(TAG, "Requesting new screen state: state="
365                                + Display.stateToString(state) + ", backlight=" + backlight);
366                    }
367
368                    mPendingState = state;
369                    mPendingBacklight = backlight;
370
371                    boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress;
372                    mStateChangeInProgress = stateChanged || mStateChangeInProgress;
373                    mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress;
374
375                    if (!changeInProgress) {
376                        mLock.notifyAll();
377                    }
378                }
379                return !mStateChangeInProgress;
380            }
381        }
382
383        public void dump(PrintWriter pw) {
384            synchronized (mLock) {
385                pw.println();
386                pw.println("Photonic Modulator State:");
387                pw.println("  mPendingState=" + Display.stateToString(mPendingState));
388                pw.println("  mPendingBacklight=" + mPendingBacklight);
389                pw.println("  mActualState=" + Display.stateToString(mActualState));
390                pw.println("  mActualBacklight=" + mActualBacklight);
391                pw.println("  mStateChangeInProgress=" + mStateChangeInProgress);
392                pw.println("  mBacklightChangeInProgress=" + mBacklightChangeInProgress);
393            }
394        }
395
396        @Override
397        public void run() {
398            for (;;) {
399                // Get pending change.
400                final int state;
401                final boolean stateChanged;
402                final int backlight;
403                final boolean backlightChanged;
404                synchronized (mLock) {
405                    state = mPendingState;
406                    stateChanged = (state != mActualState);
407                    backlight = mPendingBacklight;
408                    backlightChanged = (backlight != mActualBacklight);
409                    if (!stateChanged) {
410                        // State changed applied, notify outer class.
411                        postScreenUpdateThreadSafe();
412                        mStateChangeInProgress = false;
413                    }
414                    if (!backlightChanged) {
415                        mBacklightChangeInProgress = false;
416                    }
417                    if (!stateChanged && !backlightChanged) {
418                        try {
419                            mLock.wait();
420                        } catch (InterruptedException ex) { }
421                        continue;
422                    }
423                    mActualState = state;
424                    mActualBacklight = backlight;
425                }
426
427                // Apply pending change.
428                if (DEBUG) {
429                    Slog.d(TAG, "Updating screen state: state="
430                            + Display.stateToString(state) + ", backlight=" + backlight);
431                }
432                mBlanker.requestDisplayState(state, backlight);
433            }
434        }
435    }
436}
437