blob: 98625161a70679c2641a94cf683fdbafb7bfc756 [file] [log] [blame]
/*
* 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.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 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.prepare(context, mode)) {
mColorFadePrepared = false;
mColorFadeReady = true;
return false;
}
mColorFadePrepared = true;
mColorFadeReady = false;
scheduleColorFadeDraw();
return true;
}
/**
* Dismisses the color fade surface.
*/
public void dismissColorFade() {
mColorFade.dismiss();
mColorFadePrepared = false;
mColorFadeReady = true;
}
/**
* Dismisses the color fade resources.
*/
public void dismissColorFadeResources() {
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);
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);
}
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;
mBacklightChangeInProgress = backlightChanged;
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);
}
}
}
}