diff options
author | Jim Miller <jaggies@google.com> | 2012-02-21 18:57:12 -0800 |
---|---|---|
committer | Jim Miller <jaggies@google.com> | 2012-02-27 18:31:03 -0800 |
commit | 92e66dd6835cd5fefad463c79d0589166e8fd3a7 (patch) | |
tree | 894b51a7b8b1fb103661684d62b621f34420cdc1 /services/java/com/android/server/PowerManagerService.java | |
parent | 79952ee29a8fc67b6d76a8db747bb72dd7d6ecd1 (diff) |
Fix 5797764: don't hold PowerManager lock when changing native brightness
This fixes a bug where the device could see a priority inversion when
updating display brightness. The problem occurs because the code that
manages screen brightness holds the master lock while waiting for the
native method to complete. On some devices, each call can amount to
tens to hundreds of ms, which meant clients using PowerManager APIs
could block for the duration of the call. In some cases, the animation
could block for many seconds because the unfairness of Java locks.
The solution is to handle all brightness updates in a separate thread that
does not hold the master lock while calling native methods.
This also makes the animation more consistent by animating by actual
wall clock time rather than depending on the round-trip from the driver.
Change-Id: Ifad76fb2fb77e7b2a72dd9150440d87e22581b40
Diffstat (limited to 'services/java/com/android/server/PowerManagerService.java')
-rw-r--r-- | services/java/com/android/server/PowerManagerService.java | 341 |
1 files changed, 175 insertions, 166 deletions
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index e953355723b4..5c047c490e0c 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -45,6 +45,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.IPowerManager; import android.os.LocalPowerManager; +import android.os.Message; import android.os.Power; import android.os.PowerManager; import android.os.Process; @@ -57,6 +58,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.view.WindowManagerPolicy; +import static android.view.WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR; import static android.provider.Settings.System.DIM_SCREEN; import static android.provider.Settings.System.SCREEN_BRIGHTNESS; import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; @@ -76,6 +78,7 @@ import java.util.Observer; public class PowerManagerService extends IPowerManager.Stub implements LocalPowerManager, Watchdog.Monitor { + private static final int NOMINAL_FRAME_TIME_MS = 1000/60; private static final String TAG = "PowerManagerService"; static final String PARTIAL_NAME = "PowerManagerService"; @@ -131,6 +134,7 @@ public class PowerManagerService extends IPowerManager.Stub private static final int DEFAULT_SCREEN_BRIGHTNESS = 192; // flags for setPowerState + private static final int ALL_LIGHTS_OFF = 0x00000000; private static final int SCREEN_ON_BIT = 0x00000001; private static final int SCREEN_BRIGHT_BIT = 0x00000002; private static final int BUTTON_BRIGHT_BIT = 0x00000004; @@ -159,9 +163,9 @@ public class PowerManagerService extends IPowerManager.Stub boolean mAnimateScreenLights = true; - static final int ANIM_STEPS = 60/4; + static final int ANIM_STEPS = 60; // nominal # of frames at 60Hz // Slower animation for autobrightness changes - static final int AUTOBRIGHTNESS_ANIM_STEPS = 60; + static final int AUTOBRIGHTNESS_ANIM_STEPS = 2 * ANIM_STEPS; // Number of steps when performing a more immediate brightness change. static final int IMMEDIATE_ANIM_STEPS = 4; @@ -221,12 +225,11 @@ public class PowerManagerService extends IPowerManager.Stub private UnsynchronizedWakeLock mPreventScreenOnPartialLock; private UnsynchronizedWakeLock mProximityPartialLock; private HandlerThread mHandlerThread; - private HandlerThread mScreenOffThread; private Handler mScreenOffHandler; + private Handler mScreenBrightnessHandler; private Handler mHandler; private final TimeoutTask mTimeoutTask = new TimeoutTask(); - private final BrightnessState mScreenBrightness - = new BrightnessState(SCREEN_BRIGHT_BIT); + private ScreenBrightnessAnimator mScreenBrightnessAnimator; private boolean mStillNeedSleepNotification; private boolean mIsPowered = false; private IActivityManager mActivityService; @@ -271,6 +274,7 @@ public class PowerManagerService extends IPowerManager.Stub private int mWarningSpewThrottleCount; private long mWarningSpewThrottleTime; private int mAnimationSetting = ANIM_SETTING_OFF; + private float mWindowScaleAnimation; // Must match with the ISurfaceComposer constants in C++. private static final int ANIM_SETTING_ON = 0x01; @@ -285,7 +289,8 @@ public class PowerManagerService extends IPowerManager.Stub private static final boolean mSpew = false; private static final boolean mDebugProximitySensor = (false || mSpew); private static final boolean mDebugLightSensor = (false || mSpew); - + private static final boolean mDebugLightAnimation = (false || mSpew); + private native void nativeInit(); private native void nativeSetPowerState(boolean screenOn, boolean screenBright); private native void nativeStartSurfaceFlingerAnimation(int mode); @@ -487,10 +492,10 @@ public class PowerManagerService extends IPowerManager.Stub // recalculate everything setScreenOffTimeoutsLocked(); - final float windowScale = getFloat(WINDOW_ANIMATION_SCALE, 1.0f); + mWindowScaleAnimation = getFloat(WINDOW_ANIMATION_SCALE, 1.0f); final float transitionScale = getFloat(TRANSITION_ANIMATION_SCALE, 1.0f); mAnimationSetting = 0; - if (windowScale > 0.5f) { + if (mWindowScaleAnimation > 0.5f) { mAnimationSetting |= ANIM_SETTING_OFF; } if (transitionScale > 0.5f) { @@ -540,28 +545,20 @@ public class PowerManagerService extends IPowerManager.Stub } mInitComplete = false; - mScreenOffThread = new HandlerThread("PowerManagerService.mScreenOffThread") { - @Override - protected void onLooperPrepared() { - mScreenOffHandler = new Handler(); - synchronized (mScreenOffThread) { - mInitComplete = true; - mScreenOffThread.notifyAll(); - } - } - }; - mScreenOffThread.start(); + mScreenBrightnessAnimator = new ScreenBrightnessAnimator("mScreenBrightnessUpdaterThread", + Process.THREAD_PRIORITY_DISPLAY); + mScreenBrightnessAnimator.start(); - synchronized (mScreenOffThread) { + synchronized (mScreenBrightnessAnimator) { while (!mInitComplete) { try { - mScreenOffThread.wait(); + mScreenBrightnessAnimator.wait(); } catch (InterruptedException e) { // Ignore } } } - + mInitComplete = false; mHandlerThread = new HandlerThread("PowerManagerService") { @Override @@ -581,7 +578,7 @@ public class PowerManagerService extends IPowerManager.Stub } } } - + nativeInit(); synchronized (mLocks) { updateNativePowerStateLocked(); @@ -1078,7 +1075,6 @@ public class PowerManagerService extends IPowerManager.Stub int oldPokey = mPokey; int cumulative = 0; - boolean oldAwakeOnSet = mPokeAwakeOnSet; boolean awakeOnSet = false; for (PokeLock p: mPokeLocks.values()) { cumulative |= p.pokey; @@ -1198,7 +1194,7 @@ public class PowerManagerService extends IPowerManager.Stub + " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness); pw.println(" mUseSoftwareAutoBrightness=" + mUseSoftwareAutoBrightness); pw.println(" mAutoBrightessEnabled=" + mAutoBrightessEnabled); - mScreenBrightness.dump(pw, " mScreenBrightness: "); + mScreenBrightnessAnimator.dump(pw, " mScreenBrightnessAnimator: "); int N = mLocks.size(); pw.println(); @@ -1430,7 +1426,7 @@ public class PowerManagerService extends IPowerManager.Stub private WindowManagerPolicy.ScreenOnListener mScreenOnListener = new WindowManagerPolicy.ScreenOnListener() { - @Override public void onScreenOn() { + public void onScreenOn() { synchronized (mLocks) { if (mPreparingForScreenOn) { mPreparingForScreenOn = false; @@ -1719,7 +1715,7 @@ public class PowerManagerService extends IPowerManager.Stub + Integer.toHexString(mPowerState) + " mSkippedScreenOn=" + mSkippedScreenOn); } - mScreenBrightness.forceValueLocked(Power.BRIGHTNESS_OFF); + mScreenBrightnessAnimator.animateTo(Power.BRIGHTNESS_OFF, SCREEN_BRIGHT_BIT, 0); } } int err = Power.setScreenState(on); @@ -1878,7 +1874,7 @@ public class PowerManagerService extends IPowerManager.Stub } mPowerState &= ~SCREEN_ON_BIT; mScreenOffReason = reason; - if (!mScreenBrightness.animating) { + if (!mScreenBrightnessAnimator.isAnimating()) { err = screenOffFinishedAnimatingLocked(reason); } else { err = 0; @@ -1952,11 +1948,11 @@ public class PowerManagerService extends IPowerManager.Stub // If the screen is not currently on, we will want to delay actually // turning the lights on if we are still getting the UI put up. - if ((oldState&SCREEN_ON_BIT) == 0 || mSkippedScreenOn) { + if ((oldState & SCREEN_ON_BIT) == 0 || mSkippedScreenOn) { // Don't turn screen on until we know we are really ready to. // This is to avoid letting the screen go on before things like the // lock screen have been displayed. - if ((mSkippedScreenOn=shouldDeferScreenOnLocked())) { + if ((mSkippedScreenOn = shouldDeferScreenOnLocked())) { newState &= ~(SCREEN_ON_BIT|SCREEN_BRIGHT_BIT); } } @@ -2016,7 +2012,7 @@ public class PowerManagerService extends IPowerManager.Stub case SCREEN_BRIGHT_BIT: default: // not possible - nominalCurrentValue = (int)mScreenBrightness.curValue; + nominalCurrentValue = (int)mScreenBrightnessAnimator.getCurrentBrightness(); break; } } @@ -2066,8 +2062,8 @@ public class PowerManagerService extends IPowerManager.Stub Binder.restoreCallingIdentity(identity); } if (!mSkippedScreenOn) { - mScreenBrightness.setTargetLocked(brightness, steps, - INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue); + int dt = steps * NOMINAL_FRAME_TIME_MS; + mScreenBrightnessAnimator.animateTo(brightness, SCREEN_BRIGHT_BIT, dt); if (DEBUG_SCREEN_ON) { RuntimeException e = new RuntimeException("here"); e.fillInStackTrace(); @@ -2110,154 +2106,165 @@ public class PowerManagerService extends IPowerManager.Stub } } - private void setLightBrightness(int mask, int value) { - int brightnessMode = (mAutoBrightessEnabled - ? LightsService.BRIGHTNESS_MODE_SENSOR - : LightsService.BRIGHTNESS_MODE_USER); - if ((mask & SCREEN_BRIGHT_BIT) != 0) { - if (DEBUG_SCREEN_ON) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Set LCD brightness: " + value, e); - } - mLcdLight.setBrightness(value, brightnessMode); - } - if ((mask & BUTTON_BRIGHT_BIT) != 0) { - mButtonLight.setBrightness(value); - } - if ((mask & KEYBOARD_BRIGHT_BIT) != 0) { - mKeyboardLight.setBrightness(value); + /** + * Note: by design this class does not hold mLocks while calling native methods. + * Nor should it. Ever. + */ + class ScreenBrightnessAnimator extends HandlerThread { + static final int ANIMATE_LIGHTS = 10; + static final int POWER_OFF = 11; + volatile int startValue; + volatile int endValue; + volatile int currentValue; + private int currentMask; + private int duration; + private long startTimeMillis; + private final String prefix; + + public ScreenBrightnessAnimator(String name, int priority) { + super(name, priority); + prefix = name; } - } - class BrightnessState implements Runnable { - final int mask; + @Override + protected void onLooperPrepared() { + mScreenBrightnessHandler = new Handler() { + public void handleMessage(Message msg) { + int brightnessMode = (mAutoBrightessEnabled && !mInitialAnimation + ? LightsService.BRIGHTNESS_MODE_SENSOR + : LightsService.BRIGHTNESS_MODE_USER); + if (msg.what == ANIMATE_LIGHTS) { + final int mask = msg.arg1; + int value = msg.arg2; + long tStart = SystemClock.uptimeMillis(); + if ((mask & SCREEN_BRIGHT_BIT) != 0) { + if (mDebugLightAnimation) Log.v(TAG, "Set brightness: " + value); + mLcdLight.setBrightness(value, brightnessMode); + } + long elapsed = SystemClock.uptimeMillis() - tStart; + if ((mask & BUTTON_BRIGHT_BIT) != 0) { + mButtonLight.setBrightness(value); + } + if ((mask & KEYBOARD_BRIGHT_BIT) != 0) { + mKeyboardLight.setBrightness(value); + } - boolean initialized; - int targetValue; - float curValue; - float delta; - boolean animating; + if (elapsed > 100) { + Log.e(TAG, "Excessive delay setting brightness: " + elapsed + + "ms, mask=" + mask); + } - BrightnessState(int m) { - mask = m; + // Throttle brightness updates to frame refresh rate + int delay = elapsed < NOMINAL_FRAME_TIME_MS ? NOMINAL_FRAME_TIME_MS : 0; + synchronized(this) { + currentValue = value; + } + animateInternal(mask, false, delay); + } else if (msg.what == POWER_OFF) { + if (!mHeadless) { + int mode = msg.arg1; + nativeStartSurfaceFlingerAnimation(mode); + } + } + } + }; + synchronized (this) { + mInitComplete = true; + notifyAll(); + } } - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "animating=" + animating - + " targetValue=" + targetValue - + " curValue=" + curValue - + " delta=" + delta); - } + private void animateInternal(int mask, boolean turningOff, int delay) { + synchronized (this) { + if (currentValue != endValue) { + final long now = SystemClock.elapsedRealtime(); + final int elapsed = (int) (now - startTimeMillis); + int newValue; + if (elapsed < duration) { + int delta = endValue - startValue; + newValue = startValue + delta * elapsed / duration; + newValue = Math.max(Power.BRIGHTNESS_OFF, newValue); + newValue = Math.min(Power.BRIGHTNESS_ON, newValue); + } else { + newValue = endValue; + mInitialAnimation = false; + } - void forceValueLocked(int value) { - targetValue = -1; - curValue = value; - setLightBrightness(mask, value); - if (animating) { - finishAnimationLocked(false, value); - } - } + if (mDebugLightAnimation) { + Log.v(TAG, "Animating light: " + "start:" + startValue + + ", end:" + endValue + ", elapsed:" + elapsed + + ", duration:" + duration + ", current:" + currentValue + + ", delay:" + delay); + } - void setTargetLocked(int target, int stepsToTarget, int initialValue, - int nominalCurrentValue) { - if (!initialized) { - initialized = true; - curValue = (float)initialValue; - } else if (targetValue == target) { - return; - } - targetValue = target; - delta = (targetValue - - (nominalCurrentValue >= 0 ? nominalCurrentValue : curValue)) - / stepsToTarget; - if (mSpew || DEBUG_SCREEN_ON) { - String noticeMe = nominalCurrentValue == curValue ? "" : " ******************"; - Slog.i(TAG, "setTargetLocked mask=" + mask + " curValue=" + curValue - + " target=" + target + " targetValue=" + targetValue + " delta=" + delta - + " nominalCurrentValue=" + nominalCurrentValue - + noticeMe); + if (turningOff) { + int mode = mScreenOffReason == OFF_BECAUSE_OF_PROX_SENSOR + ? 0 : mAnimationSetting; + if (mDebugLightAnimation) Log.v(TAG, "Doing power-off anim, mode=" + mode); + mScreenBrightnessHandler.obtainMessage(POWER_OFF, mode, 0).sendToTarget(); + } + Message msg = mScreenBrightnessHandler + .obtainMessage(ANIMATE_LIGHTS, mask, newValue); + mScreenBrightnessHandler.sendMessageDelayed(msg, delay); + } } - animating = true; + } - if (mSpew) { - Slog.i(TAG, "scheduling light animator"); - } - mScreenOffHandler.removeCallbacks(this); - mScreenOffHandler.post(this); + public void dump(PrintWriter pw, String string) { + pw.println(prefix + "animating: " + "start:" + startValue + ", end:" + endValue + + ", duration:" + duration + ", current:" + currentValue); } - boolean stepLocked() { - if (!animating) return false; - if (false && mSpew) { - Slog.i(TAG, "Step target " + mask + ": cur=" + curValue - + " target=" + targetValue + " delta=" + delta); - } - curValue += delta; - int curIntValue = (int)curValue; - boolean more = true; - if (delta == 0) { - curValue = curIntValue = targetValue; - more = false; - } else if (delta > 0) { - if (curIntValue >= targetValue) { - curValue = curIntValue = targetValue; - more = false; + public void animateTo(int target, int mask, int animationDuration) { + synchronized(this) { + startValue = currentValue; + endValue = target; + currentMask = mask; + duration = (int) (mWindowScaleAnimation * animationDuration); + startTimeMillis = SystemClock.elapsedRealtime(); + mInitialAnimation = currentValue == 0 && target > 0; + + if (mDebugLightAnimation) { + Log.v(TAG, "animateTo(target=" + target + ", mask=" + mask + + ", duration=" + animationDuration +")" + + ", currentValue=" + currentValue + + ", startTime=" + startTimeMillis); } - } else { - if (curIntValue <= targetValue) { - curValue = curIntValue = targetValue; - more = false; + + if (target != currentValue) { + final boolean turningOff = endValue == Power.BRIGHTNESS_OFF; + if (turningOff) { + // Cancel all pending animations since we're turning off + mScreenBrightnessHandler.removeCallbacksAndMessages(null); + screenOffFinishedAnimatingLocked(mScreenOffReason); + duration = 200; // TODO: how long should this be? + } + animateInternal(mask, turningOff, 0); } } - if (mSpew) Slog.d(TAG, "Animating curIntValue=" + curIntValue + ": " + mask); - setLightBrightness(mask, curIntValue); - finishAnimationLocked(more, curIntValue); - return more; } - void jumpToTargetLocked() { - if (mSpew) Slog.d(TAG, "jumpToTargetLocked targetValue=" + targetValue + ": " + mask); - setLightBrightness(mask, targetValue); - final int tv = targetValue; - curValue = tv; - targetValue = -1; - finishAnimationLocked(false, tv); + public int getCurrentBrightness() { + synchronized (this) { + return currentValue; + } } - private void finishAnimationLocked(boolean more, int curIntValue) { - animating = more; - if (!more) { - if (mask == SCREEN_BRIGHT_BIT && curIntValue == Power.BRIGHTNESS_OFF) { - screenOffFinishedAnimatingLocked(mScreenOffReason); - } + public boolean isAnimating() { + synchronized (this) { + return currentValue != endValue; } } - public void run() { - synchronized (mLocks) { - // we're turning off - final boolean turningOff = animating && targetValue == Power.BRIGHTNESS_OFF; - if (mAnimateScreenLights || !turningOff) { - long now = SystemClock.uptimeMillis(); - boolean more = mScreenBrightness.stepLocked(); - if (more) { - mScreenOffHandler.postAtTime(this, now+(1000/60)); - } - } else { - if (!mHeadless) { - // It's pretty scary to hold mLocks for this long, and we should - // redesign this, but it works for now. - nativeStartSurfaceFlingerAnimation( - mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR - ? 0 : mAnimationSetting); - } - mScreenBrightness.jumpToTargetLocked(); - } - } + public void cancelAnimation() { + animateTo(endValue, currentMask, 0); } } + private void setLightBrightness(int mask, int value) { + mScreenBrightnessAnimator.animateTo(value, mask, 0); + } + private int getPreferredBrightness() { if (mScreenBrightnessOverride >= 0) { return mScreenBrightnessOverride; @@ -2325,7 +2332,8 @@ public class PowerManagerService extends IPowerManager.Stub } private boolean isScreenTurningOffLocked() { - return (mScreenBrightness.animating && mScreenBrightness.targetValue == 0); + return (mScreenBrightnessAnimator.isAnimating() + && mScreenBrightnessAnimator.endValue == Power.BRIGHTNESS_OFF); } private boolean shouldLog(long time) { @@ -2346,7 +2354,7 @@ public class PowerManagerService extends IPowerManager.Stub private void forceUserActivityLocked() { if (isScreenTurningOffLocked()) { // cancel animation so userActivity will succeed - mScreenBrightness.animating = false; + mScreenBrightnessAnimator.cancelAnimation(); } boolean savedActivityAllowed = mUserActivityAllowed; mUserActivityAllowed = true; @@ -2525,6 +2533,8 @@ public class PowerManagerService extends IPowerManager.Stub } }; + private boolean mInitialAnimation; // used to prevent lightsensor changes while turning on + private void dockStateChanged(int state) { synchronized (mLocks) { mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED); @@ -2586,10 +2596,11 @@ public class PowerManagerService extends IPowerManager.Stub } if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { - if (!mSkippedScreenOn) { - mScreenBrightness.setTargetLocked(lcdValue, - immediate ? IMMEDIATE_ANIM_STEPS : AUTOBRIGHTNESS_ANIM_STEPS, - INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue); + if (!mSkippedScreenOn && !mInitialAnimation) { + int steps = immediate ? IMMEDIATE_ANIM_STEPS : AUTOBRIGHTNESS_ANIM_STEPS; + mScreenBrightnessAnimator.cancelAnimation(); + mScreenBrightnessAnimator.animateTo(lcdValue, + SCREEN_BRIGHT_BIT, steps * NOMINAL_FRAME_TIME_MS); } } if (mButtonBrightnessOverride < 0) { @@ -2641,7 +2652,7 @@ public class PowerManagerService extends IPowerManager.Stub synchronized (this) { ShutdownThread.reboot(mContext, finalReason, false); } - + } }; // ShutdownThread must run on a looper capable of displaying the UI. @@ -2995,9 +3006,7 @@ public class PowerManagerService extends IPowerManager.Stub } finally { Binder.restoreCallingIdentity(identity); } - - mScreenBrightness.targetValue = brightness; - mScreenBrightness.jumpToTargetLocked(); + mScreenBrightnessAnimator.animateTo(brightness, SCREEN_BRIGHT_BIT, 0); } } |