Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 92e66dd6 authored by Jim Miller's avatar Jim Miller
Browse files

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
parent 79952ee2
Loading
Loading
Loading
Loading
+175 −166
Original line number Diff line number Diff line
@@ -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,6 +289,7 @@ 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);
@@ -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,22 +545,14 @@ 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
                }
@@ -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;
@@ -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
    /**
     * 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;
        }

        @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 (DEBUG_SCREEN_ON) {
                RuntimeException e = new RuntimeException("here");
                e.fillInStackTrace();
                Slog.i(TAG, "Set LCD brightness: " + value, e);
            }
                            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);
                        }
    }

    class BrightnessState implements Runnable {
        final int mask;

        boolean initialized;
        int targetValue;
        float curValue;
        float delta;
        boolean animating;

        BrightnessState(int m) {
            mask = m;
                        if (elapsed > 100) {
                            Log.e(TAG, "Excessive delay setting brightness: " + elapsed
                                    + "ms, mask=" + mask);
                        }

        public void dump(PrintWriter pw, String prefix) {
            pw.println(prefix + "animating=" + animating
                    + " targetValue=" + targetValue
                    + " curValue=" + curValue
                    + " delta=" + delta);
                        // Throttle brightness updates to frame refresh rate
                        int delay = elapsed < NOMINAL_FRAME_TIME_MS ? NOMINAL_FRAME_TIME_MS : 0;
                        synchronized(this) {
                            currentValue = value;
                        }

        void forceValueLocked(int value) {
            targetValue = -1;
            curValue = value;
            setLightBrightness(mask, value);
            if (animating) {
                finishAnimationLocked(false, value);
                        animateInternal(mask, false, delay);
                    } else if (msg.what == POWER_OFF) {
                        if (!mHeadless) {
                            int mode = msg.arg1;
                            nativeStartSurfaceFlingerAnimation(mode);
                        }
                    }

        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);
            };
            synchronized (this) {
                mInitComplete = true;
                notifyAll();
            }
        }
            animating = true;

            if (mSpew) {
                Slog.i(TAG, "scheduling light animator");
            }
            mScreenOffHandler.removeCallbacks(this);
            mScreenOffHandler.post(this);
        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;
                    }

        boolean stepLocked() {
            if (!animating) return false;
            if (false && mSpew) {
                Slog.i(TAG, "Step target " + mask + ": cur=" + curValue
                        + " target=" + targetValue + " delta=" + delta);
                    if (mDebugLightAnimation) {
                        Log.v(TAG, "Animating light: " + "start:" + startValue
                                + ", end:" + endValue + ", elapsed:" + elapsed
                                + ", duration:" + duration + ", current:" + currentValue
                                + ", delay:" + delay);
                    }
            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;

                    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();
                    }
            } else {
                if (curIntValue <= targetValue) {
                    curValue = curIntValue = targetValue;
                    more = false;
                    Message msg = mScreenBrightnessHandler
                            .obtainMessage(ANIMATE_LIGHTS, mask, newValue);
                    mScreenBrightnessHandler.sendMessageDelayed(msg, delay);
                }
            }
            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 void dump(PrintWriter pw, String string) {
            pw.println(prefix + "animating: " + "start:" + startValue + ", end:" + endValue
                    + ", duration:" + duration + ", current:" + currentValue);
        }

        private void finishAnimationLocked(boolean more, int curIntValue) {
            animating = more;
            if (!more) {
                if (mask == SCREEN_BRIGHT_BIT && curIntValue == Power.BRIGHTNESS_OFF) {
        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);
                }

                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);
                }
            }
        }

        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));
        public int getCurrentBrightness() {
            synchronized (this) {
                return currentValue;
            }
                } 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 boolean isAnimating() {
            synchronized (this) {
                return currentValue != endValue;
            }
        }

        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) {
@@ -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);
        }
    }