content: /*
   * Copyright (C) 2012 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.display;
  
  import android.content.Context;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.PowerManager;
  import android.os.Trace;
  import android.util.FloatProperty;
  import android.util.IntProperty;
  import android.util.Slog;
  import android.view.Choreographer;
  import android.view.Display;
  
  import java.io.PrintWriter;
  
  /**
   * Controls the display power state.
   * <p>
   * This component is similar in nature to a {@link android.view.View} except that it
   * describes the properties of a display.  When properties are changed, the component
   * invalidates itself and posts a callback to apply the changes in a consistent order.
   * This mechanism enables multiple properties of the display power state to be animated
   * together smoothly by the animation framework.  Some of the work to blank or unblank
   * the display is done on a separate thread to avoid blocking the looper.
   * </p><p>
   * This component must only be created or accessed by the {@link Looper} thread
   * that belongs to the {@link DisplayPowerController}.
   * </p><p>
   * We don't need to worry about holding a suspend blocker here because the
   * power manager does that for us whenever there is a change in progress.
   * </p>
   */
  final class DisplayPowerState {
      private static final String TAG = "DisplayPowerState";
  
      private static boolean DEBUG = false;
      private static String COUNTER_COLOR_FADE = "ColorFadeLevel";
  
      private final Handler mHandler;
      private final Choreographer mChoreographer;
      private final DisplayBlanker mBlanker;
      private final ColorFade mColorFade;
      private final PhotonicModulator mPhotonicModulator;
  
      private int mScreenState;
      private int mScreenBrightness;
      private boolean mScreenReady;
      private boolean mScreenUpdatePending;
  
      private boolean mColorFadePrepared;
      private float mColorFadeLevel;
      private boolean mColorFadeReady;
      private boolean mColorFadeDrawPending;
  
      private Runnable mCleanListener;
  
      public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) {
          mHandler = new Handler(true /*async*/);
          mChoreographer = Choreographer.getInstance();
          mBlanker = blanker;
          mColorFade = colorFade;
          mPhotonicModulator = new PhotonicModulator();
          mPhotonicModulator.start();
  
          // At boot time, we know that the screen is on and the electron beam
          // animation is not playing.  We don't know the screen's brightness though,
          // so prepare to set it to a known state when the state is next applied.
          // Although we set the brightness to full on here, the display power controller
          // will reset the brightness to a new level immediately before the changes
          // actually have a chance to be applied.
          mScreenState = Display.STATE_ON;
          mScreenBrightness = PowerManager.BRIGHTNESS_ON;
          scheduleScreenUpdate();
  
          mColorFadePrepared = false;
          mColorFadeLevel = 1.0f;
          mColorFadeReady = true;
      }
  
      public static final FloatProperty<DisplayPowerState> COLOR_FADE_LEVEL =
              new FloatProperty<DisplayPowerState>("electronBeamLevel") {
          @Override
          public void setValue(DisplayPowerState object, float value) {
              object.setColorFadeLevel(value);
          }
  
          @Override
          public Float get(DisplayPowerState object) {
              return object.getColorFadeLevel();
          }
      };
  
      public static final IntProperty<DisplayPowerState> SCREEN_BRIGHTNESS =
              new IntProperty<DisplayPowerState>("screenBrightness") {
          @Override
          public void setValue(DisplayPowerState object, int value) {
              object.setScreenBrightness(value);
          }
  
          @Override
          public Integer get(DisplayPowerState object) {
              return object.getScreenBrightness();
          }
      };
  
      /**
       * Sets whether the screen is on, off, or dozing.
       */
      public void setScreenState(int state) {
          if (mScreenState != state) {
              if (DEBUG) {
                  Slog.d(TAG, "setScreenState: state=" + state);
              }
  
              mScreenState = state;
              mScreenReady = false;
              scheduleScreenUpdate();
          }
      }
  
      /**
       * Gets the desired screen state.
       */
      public int getScreenState() {
          return mScreenState;
      }
  
      /**
       * Sets the display brightness.
       *
       * @param brightness The brightness, ranges from 0 (minimum / off) to 255 (brightest).
       */
      public void setScreenBrightness(int brightness) {
          if (mScreenBrightness != brightness) {
              if (DEBUG) {
                  Slog.d(TAG, "setScreenBrightness: brightness=" + brightness);
              }
  
              mScreenBrightness = brightness;
              if (mScreenState != Display.STATE_OFF) {
                  mScreenReady = false;
                  scheduleScreenUpdate();
              }
          }
      }
  
      /**
       * Gets the screen brightness.
       */
      public int getScreenBrightness() {
          return mScreenBrightness;
      }
  
      /**
       * Prepares the electron beam to turn on or off.
       * This method should be called before starting an animation because it
       * can take a fair amount of time to prepare the electron beam surface.
       *
       * @param mode The electron beam animation mode to prepare.
       * @return True if the electron beam was prepared.
       */
      public boolean prepareColorFade(Context context, int mode) {
          if (mColorFade == null || !mColorFade.prepare(context, mode)) {
              mColorFadePrepared = false;
              mColorFadeReady = true;
              return false;
          }
  
          mColorFadePrepared = true;
          mColorFadeReady = false;
          scheduleColorFadeDraw();
          return true;
      }
  
      /**
       * Dismisses the color fade surface.
       */
      public void dismissColorFade() {
          Trace.traceCounter(Trace.TRACE_TAG_POWER, COUNTER_COLOR_FADE, 100);
          if (mColorFade != null) mColorFade.dismiss();
          mColorFadePrepared = false;
          mColorFadeReady = true;
      }
  
     /**
       * Dismisses the color fade resources.
       */
      public void dismissColorFadeResources() {
          if (mColorFade != null) mColorFade.dismissResources();
      }
  
      /**
       * Sets the level of the electron beam steering current.
       *
       * The display is blanked when the level is 0.0.  In normal use, the electron
       * beam should have a value of 1.0.  The electron beam is unstable in between
       * these states and the picture quality may be compromised.  For best effect,
       * the electron beam should be warmed up or cooled off slowly.
       *
       * Warning: Electron beam emits harmful radiation.  Avoid direct exposure to
       * skin or eyes.
       *
       * @param level The level, ranges from 0.0 (full off) to 1.0 (full on).
       */
      public void setColorFadeLevel(float level) {
          if (mColorFadeLevel != level) {
              if (DEBUG) {
                  Slog.d(TAG, "setColorFadeLevel: level=" + level);
              }
  
              mColorFadeLevel = level;
              if (mScreenState != Display.STATE_OFF) {
                  mScreenReady = false;
                  scheduleScreenUpdate(); // update backlight brightness
              }
              if (mColorFadePrepared) {
                  mColorFadeReady = false;
                  scheduleColorFadeDraw();
              }
          }
      }
  
      /**
       * Gets the level of the electron beam steering current.
       */
      public float getColorFadeLevel() {
          return mColorFadeLevel;
      }
  
      /**
       * Returns true if no properties have been invalidated.
       * Otherwise, returns false and promises to invoke the specified listener
       * when the properties have all been applied.
       * The listener always overrides any previously set listener.
       */
      public boolean waitUntilClean(Runnable listener) {
          if (!mScreenReady || !mColorFadeReady) {
              mCleanListener = listener;
              return false;
          } else {
              mCleanListener = null;
              return true;
          }
      }
  
      public void dump(PrintWriter pw) {
          pw.println();
          pw.println("Display Power State:");
          pw.println("  mScreenState=" + Display.stateToString(mScreenState));
          pw.println("  mScreenBrightness=" + mScreenBrightness);
          pw.println("  mScreenReady=" + mScreenReady);
          pw.println("  mScreenUpdatePending=" + mScreenUpdatePending);
          pw.println("  mColorFadePrepared=" + mColorFadePrepared);
          pw.println("  mColorFadeLevel=" + mColorFadeLevel);
          pw.println("  mColorFadeReady=" + mColorFadeReady);
          pw.println("  mColorFadeDrawPending=" + mColorFadeDrawPending);
  
          mPhotonicModulator.dump(pw);
          if (mColorFade != null) mColorFade.dump(pw);
      }
  
      private void scheduleScreenUpdate() {
          if (!mScreenUpdatePending) {
              mScreenUpdatePending = true;
              postScreenUpdateThreadSafe();
          }
      }
  
      private void postScreenUpdateThreadSafe() {
          mHandler.removeCallbacks(mScreenUpdateRunnable);
          mHandler.post(mScreenUpdateRunnable);
      }
  
      private void scheduleColorFadeDraw() {
          if (!mColorFadeDrawPending) {
              mColorFadeDrawPending = true;
              mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
                      mColorFadeDrawRunnable, null);
          }
      }
  
      private void invokeCleanListenerIfNeeded() {
          final Runnable listener = mCleanListener;
          if (listener != null && mScreenReady && mColorFadeReady) {
              mCleanListener = null;
              listener.run();
          }
      }
  
      private final Runnable mScreenUpdateRunnable = new Runnable() {
          @Override
          public void run() {
              mScreenUpdatePending = false;
  
              int brightness = mScreenState != Display.STATE_OFF
                      && mColorFadeLevel > 0f ? mScreenBrightness : 0;
              if (mPhotonicModulator.setState(mScreenState, brightness)) {
                  if (DEBUG) {
                      Slog.d(TAG, "Screen ready");
                  }
                  mScreenReady = true;
                  invokeCleanListenerIfNeeded();
              } else {
                  if (DEBUG) {
                      Slog.d(TAG, "Screen not ready");
                  }
              }
          }
      };
  
      private final Runnable mColorFadeDrawRunnable = new Runnable() {
          @Override
          public void run() {
              mColorFadeDrawPending = false;
  
              if (mColorFadePrepared) {
                  mColorFade.draw(mColorFadeLevel);
                  Trace.traceCounter(Trace.TRACE_TAG_POWER,
                          COUNTER_COLOR_FADE, Math.round(mColorFadeLevel * 100));
              }
  
              mColorFadeReady = true;
              invokeCleanListenerIfNeeded();
          }
      };
  
      /**
       * Updates the state of the screen and backlight asynchronously on a separate thread.
       */
      private final class PhotonicModulator extends Thread {
          private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off
          private static final int INITIAL_BACKLIGHT = -1; // unknown
  
          private final Object mLock = new Object();
  
          private int mPendingState = INITIAL_SCREEN_STATE;
          private int mPendingBacklight = INITIAL_BACKLIGHT;
          private int mActualState = INITIAL_SCREEN_STATE;
          private int mActualBacklight = INITIAL_BACKLIGHT;
          private boolean mStateChangeInProgress;
          private boolean mBacklightChangeInProgress;
  
          public PhotonicModulator() {
              super("PhotonicModulator");
          }
  
          public boolean setState(int state, int backlight) {
              synchronized (mLock) {
                  boolean stateChanged = state != mPendingState;
                  boolean backlightChanged = backlight != mPendingBacklight;
                  if (stateChanged || backlightChanged) {
                      if (DEBUG) {
                          Slog.d(TAG, "Requesting new screen state: state="
                                  + Display.stateToString(state) + ", backlight=" + backlight);
                      }
  
                      mPendingState = state;
                      mPendingBacklight = backlight;
  
                      boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress;
                      mStateChangeInProgress = stateChanged || mStateChangeInProgress;
                      mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress;
  
                      if (!changeInProgress) {
                          mLock.notifyAll();
                      }
                  }
                  return !mStateChangeInProgress;
              }
          }
  
          public void dump(PrintWriter pw) {
              synchronized (mLock) {
                  pw.println();
                  pw.println("Photonic Modulator State:");
                  pw.println("  mPendingState=" + Display.stateToString(mPendingState));
                  pw.println("  mPendingBacklight=" + mPendingBacklight);
                  pw.println("  mActualState=" + Display.stateToString(mActualState));
                  pw.println("  mActualBacklight=" + mActualBacklight);
                  pw.println("  mStateChangeInProgress=" + mStateChangeInProgress);
                  pw.println("  mBacklightChangeInProgress=" + mBacklightChangeInProgress);
              }
          }
  
          @Override
          public void run() {
              for (;;) {
                  // Get pending change.
                  final int state;
                  final boolean stateChanged;
                  final int backlight;
                  final boolean backlightChanged;
                  synchronized (mLock) {
                      state = mPendingState;
                      stateChanged = (state != mActualState);
                      backlight = mPendingBacklight;
                      backlightChanged = (backlight != mActualBacklight);
                      if (!stateChanged) {
                          // State changed applied, notify outer class.
                          postScreenUpdateThreadSafe();
                          mStateChangeInProgress = false;
                      }
                      if (!backlightChanged) {
                          mBacklightChangeInProgress = false;
                      }
                      if (!stateChanged && !backlightChanged) {
                          try {
                              mLock.wait();
                          } catch (InterruptedException ex) { }
                          continue;
                      }
                      mActualState = state;
                      mActualBacklight = backlight;
                  }
  
                  // Apply pending change.
                  if (DEBUG) {
                      Slog.d(TAG, "Updating screen state: state="
                              + Display.stateToString(state) + ", backlight=" + backlight);
                  }
                  mBlanker.requestDisplayState(state, backlight);
              }
          }
      }
  }
  