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.util.FloatProperty;
     24 import android.util.IntProperty;
     25 import android.util.Slog;
     26 import android.view.Choreographer;
     27 import android.view.Display;
     28 
     29 import 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  */
     48 final 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