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