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