Reduce latency of screen on/off and improve how it is synchronized with backlight changes. Screen state changes are no longer posted to vsync which should save time. What's more, the state change occurs on a separate thread so we no longer run the risk of blocking the Looper for a long time while waiting for the screen to turn on or off. Bug: 7382919 Bug: 7139924 Change-Id: I375950d1b07e22fcb94efb82892fd817e2f780dc
415 lines
14 KiB
Java
415 lines
14 KiB
Java
/*
|
|
* 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.power;
|
|
|
|
import com.android.server.LightsService;
|
|
|
|
import android.os.AsyncTask;
|
|
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 java.io.PrintWriter;
|
|
|
|
/**
|
|
* Controls the display power state.
|
|
* <p>
|
|
* This component is similar in nature to a {@link 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
|
|
* {@link PowerManagerService} 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 ElectronBeam mElectronBeam;
|
|
private final DisplayBlanker mDisplayBlanker;
|
|
private final LightsService.Light mBacklight;
|
|
private final PhotonicModulator mPhotonicModulator;
|
|
|
|
private boolean mScreenOn;
|
|
private int mScreenBrightness;
|
|
private boolean mScreenReady;
|
|
private boolean mScreenUpdatePending;
|
|
|
|
private boolean mElectronBeamPrepared;
|
|
private float mElectronBeamLevel;
|
|
private boolean mElectronBeamReady;
|
|
private boolean mElectronBeamDrawPending;
|
|
|
|
private Runnable mCleanListener;
|
|
|
|
public DisplayPowerState(ElectronBeam electronBean,
|
|
DisplayBlanker displayBlanker, LightsService.Light backlight) {
|
|
mHandler = new Handler(true /*async*/);
|
|
mChoreographer = Choreographer.getInstance();
|
|
mElectronBeam = electronBean;
|
|
mDisplayBlanker = displayBlanker;
|
|
mBacklight = backlight;
|
|
mPhotonicModulator = new PhotonicModulator();
|
|
|
|
// 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.
|
|
mScreenOn = true;
|
|
mScreenBrightness = PowerManager.BRIGHTNESS_ON;
|
|
scheduleScreenUpdate();
|
|
|
|
mElectronBeamPrepared = false;
|
|
mElectronBeamLevel = 1.0f;
|
|
mElectronBeamReady = true;
|
|
}
|
|
|
|
public static final FloatProperty<DisplayPowerState> ELECTRON_BEAM_LEVEL =
|
|
new FloatProperty<DisplayPowerState>("electronBeamLevel") {
|
|
@Override
|
|
public void setValue(DisplayPowerState object, float value) {
|
|
object.setElectronBeamLevel(value);
|
|
}
|
|
|
|
@Override
|
|
public Float get(DisplayPowerState object) {
|
|
return object.getElectronBeamLevel();
|
|
}
|
|
};
|
|
|
|
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 or off.
|
|
*/
|
|
public void setScreenOn(boolean on) {
|
|
if (mScreenOn != on) {
|
|
if (DEBUG) {
|
|
Slog.d(TAG, "setScreenOn: on=" + on);
|
|
}
|
|
|
|
mScreenOn = on;
|
|
mScreenReady = false;
|
|
scheduleScreenUpdate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the screen is on.
|
|
*/
|
|
public boolean isScreenOn() {
|
|
return mScreenOn;
|
|
}
|
|
|
|
/**
|
|
* 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 (mScreenOn) {
|
|
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 prepareElectronBeam(int mode) {
|
|
if (!mElectronBeam.prepare(mode)) {
|
|
mElectronBeamPrepared = false;
|
|
mElectronBeamReady = true;
|
|
return false;
|
|
}
|
|
|
|
mElectronBeamPrepared = true;
|
|
mElectronBeamReady = false;
|
|
scheduleElectronBeamDraw();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Dismisses the electron beam surface.
|
|
*/
|
|
public void dismissElectronBeam() {
|
|
mElectronBeam.dismiss();
|
|
mElectronBeamPrepared = false;
|
|
mElectronBeamReady = true;
|
|
}
|
|
|
|
/**
|
|
* 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 setElectronBeamLevel(float level) {
|
|
if (mElectronBeamLevel != level) {
|
|
if (DEBUG) {
|
|
Slog.d(TAG, "setElectronBeamLevel: level=" + level);
|
|
}
|
|
|
|
mElectronBeamLevel = level;
|
|
if (mScreenOn) {
|
|
mScreenReady = false;
|
|
scheduleScreenUpdate(); // update backlight brightness
|
|
}
|
|
if (mElectronBeamPrepared) {
|
|
mElectronBeamReady = false;
|
|
scheduleElectronBeamDraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the level of the electron beam steering current.
|
|
*/
|
|
public float getElectronBeamLevel() {
|
|
return mElectronBeamLevel;
|
|
}
|
|
|
|
/**
|
|
* 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 || !mElectronBeamReady) {
|
|
mCleanListener = listener;
|
|
return false;
|
|
} else {
|
|
mCleanListener = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public void dump(PrintWriter pw) {
|
|
pw.println();
|
|
pw.println("Display Power State:");
|
|
pw.println(" mScreenOn=" + mScreenOn);
|
|
pw.println(" mScreenBrightness=" + mScreenBrightness);
|
|
pw.println(" mScreenReady=" + mScreenReady);
|
|
pw.println(" mScreenUpdatePending=" + mScreenUpdatePending);
|
|
pw.println(" mElectronBeamPrepared=" + mElectronBeamPrepared);
|
|
pw.println(" mElectronBeamLevel=" + mElectronBeamLevel);
|
|
pw.println(" mElectronBeamReady=" + mElectronBeamReady);
|
|
pw.println(" mElectronBeamDrawPending=" + mElectronBeamDrawPending);
|
|
|
|
mPhotonicModulator.dump(pw);
|
|
mElectronBeam.dump(pw);
|
|
}
|
|
|
|
private void scheduleScreenUpdate() {
|
|
if (!mScreenUpdatePending) {
|
|
mScreenUpdatePending = true;
|
|
postScreenUpdateThreadSafe();
|
|
}
|
|
}
|
|
|
|
private void postScreenUpdateThreadSafe() {
|
|
mHandler.removeCallbacks(mScreenUpdateRunnable);
|
|
mHandler.post(mScreenUpdateRunnable);
|
|
}
|
|
|
|
private void scheduleElectronBeamDraw() {
|
|
if (!mElectronBeamDrawPending) {
|
|
mElectronBeamDrawPending = true;
|
|
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
|
|
mElectronBeamDrawRunnable, null);
|
|
}
|
|
}
|
|
|
|
private void invokeCleanListenerIfNeeded() {
|
|
final Runnable listener = mCleanListener;
|
|
if (listener != null && mScreenReady && mElectronBeamReady) {
|
|
mCleanListener = null;
|
|
listener.run();
|
|
}
|
|
}
|
|
|
|
private final Runnable mScreenUpdateRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mScreenUpdatePending = false;
|
|
|
|
if (mPhotonicModulator.setState(mScreenOn,
|
|
mScreenOn ? (int)(mScreenBrightness * mElectronBeamLevel) : 0)) {
|
|
mScreenReady = true;
|
|
invokeCleanListenerIfNeeded();
|
|
}
|
|
}
|
|
};
|
|
|
|
private final Runnable mElectronBeamDrawRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mElectronBeamDrawPending = false;
|
|
|
|
if (mElectronBeamPrepared) {
|
|
mElectronBeam.draw(mElectronBeamLevel);
|
|
}
|
|
|
|
mElectronBeamReady = true;
|
|
invokeCleanListenerIfNeeded();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Updates the state of the screen and backlight asynchronously on a separate thread.
|
|
*/
|
|
private final class PhotonicModulator {
|
|
private static final boolean INITIAL_SCREEN_ON = false; // unknown, assume off
|
|
private static final int INITIAL_BACKLIGHT = -1; // unknown
|
|
|
|
private final Object mLock = new Object();
|
|
|
|
private boolean mPendingOn = INITIAL_SCREEN_ON;
|
|
private int mPendingBacklight = INITIAL_BACKLIGHT;
|
|
private boolean mActualOn = INITIAL_SCREEN_ON;
|
|
private int mActualBacklight = INITIAL_BACKLIGHT;
|
|
private boolean mChangeInProgress;
|
|
|
|
public boolean setState(boolean on, int backlight) {
|
|
synchronized (mLock) {
|
|
if (on != mPendingOn || backlight != mPendingBacklight) {
|
|
if (DEBUG) {
|
|
Slog.d(TAG, "Requesting new screen state: on=" + on
|
|
+ ", backlight=" + backlight);
|
|
}
|
|
|
|
mPendingOn = on;
|
|
mPendingBacklight = backlight;
|
|
|
|
if (!mChangeInProgress) {
|
|
mChangeInProgress = true;
|
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(mTask);
|
|
}
|
|
}
|
|
return mChangeInProgress;
|
|
}
|
|
}
|
|
|
|
public void dump(PrintWriter pw) {
|
|
pw.println();
|
|
pw.println("Photonic Modulator State:");
|
|
pw.println(" mPendingOn=" + mPendingOn);
|
|
pw.println(" mPendingBacklight=" + mPendingBacklight);
|
|
pw.println(" mActualOn=" + mActualOn);
|
|
pw.println(" mActualBacklight=" + mActualBacklight);
|
|
pw.println(" mChangeInProgress=" + mChangeInProgress);
|
|
}
|
|
|
|
private final Runnable mTask = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Apply pending changes until done.
|
|
for (;;) {
|
|
final boolean on;
|
|
final boolean onChanged;
|
|
final int backlight;
|
|
final boolean backlightChanged;
|
|
synchronized (mLock) {
|
|
on = mPendingOn;
|
|
onChanged = (on != mActualOn);
|
|
backlight = mPendingBacklight;
|
|
backlightChanged = (backlight != mActualBacklight);
|
|
if (!onChanged && !backlightChanged) {
|
|
mChangeInProgress = false;
|
|
break;
|
|
}
|
|
mActualOn = on;
|
|
mActualBacklight = backlight;
|
|
}
|
|
|
|
if (DEBUG) {
|
|
Slog.d(TAG, "Updating screen state: on=" + on
|
|
+ ", backlight=" + backlight);
|
|
}
|
|
if (onChanged && on) {
|
|
mDisplayBlanker.unblankAllDisplays();
|
|
}
|
|
if (backlightChanged) {
|
|
mBacklight.setBrightness(backlight);
|
|
}
|
|
if (onChanged && !on) {
|
|
mDisplayBlanker.blankAllDisplays();
|
|
}
|
|
}
|
|
|
|
// Let the outer class know that all changes have been applied.
|
|
postScreenUpdateThreadSafe();
|
|
}
|
|
};
|
|
}
|
|
}
|