From 95222c3b3f3b60dc805d498bd160fb0da51bc3fa Mon Sep 17 00:00:00 2001 From: JagravNaik Date: Sat, 30 Sep 2017 15:16:06 -0400 Subject: [PATCH] Should be enough to enable burnin protection derived from OmniROM --- BurnInProtectionHelper.java | 294 ++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 BurnInProtectionHelper.java diff --git a/BurnInProtectionHelper.java b/BurnInProtectionHelper.java new file mode 100644 index 00000000000..92729dc08b6 --- /dev/null +++ b/BurnInProtectionHelper.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2015 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.policy; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Display; +import android.view.animation.LinearInterpolator; + +import com.android.server.LocalServices; + +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; + +public class BurnInProtectionHelper implements DisplayManager.DisplayListener, + Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { + private static final String TAG = "BurnInProtection"; + + // Default value when max burnin radius is not set. + public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1; + + private static final long BURNIN_PROTECTION_FIRST_WAKEUP_INTERVAL_MS = + TimeUnit.MINUTES.toMillis(1); + private static final long BURNIN_PROTECTION_SUBSEQUENT_WAKEUP_INTERVAL_MS = + TimeUnit.MINUTES.toMillis(2); + private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10); + + private static final boolean DEBUG = false; + + private static final String ACTION_BURN_IN_PROTECTION = + "android.internal.policy.action.BURN_IN_PROTECTION"; + + private static final int BURN_IN_SHIFT_STEP = 2; + private static final long CENTERING_ANIMATION_DURATION_MS = 100; + private final ValueAnimator mCenteringAnimator; + + private boolean mBurnInProtectionActive; + private boolean mFirstUpdate; + + private final int mMinHorizontalBurnInOffset; + private final int mMaxHorizontalBurnInOffset; + private final int mMinVerticalBurnInOffset; + private final int mMaxVerticalBurnInOffset; + + private final int mBurnInRadiusMaxSquared; + + private int mLastBurnInXOffset = 0; + /* 1 means increasing, -1 means decreasing */ + private int mXOffsetDirection = 1; + private int mLastBurnInYOffset = 0; + /* 1 means increasing, -1 means decreasing */ + private int mYOffsetDirection = 1; + + private int mAppliedBurnInXOffset = 0; + private int mAppliedBurnInYOffset = 0; + + private final AlarmManager mAlarmManager; + private final PendingIntent mBurnInProtectionIntent; + private final DisplayManagerInternal mDisplayManagerInternal; + private final Display mDisplay; + + private BroadcastReceiver mBurnInProtectionReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "onReceive " + intent); + } + updateBurnInProtection(); + } + }; + + public BurnInProtectionHelper(Context context, int minHorizontalOffset, + int maxHorizontalOffset, int minVerticalOffset, int maxVerticalOffset, + int maxOffsetRadius) { + mMinHorizontalBurnInOffset = minHorizontalOffset; + mMaxHorizontalBurnInOffset = maxHorizontalOffset; + mMinVerticalBurnInOffset = minVerticalOffset; + mMaxVerticalBurnInOffset = maxVerticalOffset; + if (maxOffsetRadius != BURN_IN_MAX_RADIUS_DEFAULT) { + mBurnInRadiusMaxSquared = maxOffsetRadius * maxOffsetRadius; + } else { + mBurnInRadiusMaxSquared = BURN_IN_MAX_RADIUS_DEFAULT; + } + + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + context.registerReceiver(mBurnInProtectionReceiver, + new IntentFilter(ACTION_BURN_IN_PROTECTION)); + Intent intent = new Intent(ACTION_BURN_IN_PROTECTION); + intent.setPackage(context.getPackageName()); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mBurnInProtectionIntent = PendingIntent.getBroadcast(context, 0, + intent, PendingIntent.FLAG_UPDATE_CURRENT); + DisplayManager displayManager = + (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + mDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + displayManager.registerDisplayListener(this, null /* handler */); + + mCenteringAnimator = ValueAnimator.ofFloat(1f, 0f); + mCenteringAnimator.setDuration(CENTERING_ANIMATION_DURATION_MS); + mCenteringAnimator.setInterpolator(new LinearInterpolator()); + mCenteringAnimator.addListener(this); + mCenteringAnimator.addUpdateListener(this); + } + + public void startBurnInProtection() { + if (!mBurnInProtectionActive) { + mBurnInProtectionActive = true; + mFirstUpdate = true; + mCenteringAnimator.cancel(); + updateBurnInProtection(); + } + } + + private void updateBurnInProtection() { + if (mBurnInProtectionActive) { + // We don't want to adjust offsets immediately after the device goes into ambient mode. + // Instead, we want to wait until it's more likely that the user is not observing the + // screen anymore. + final long interval = mFirstUpdate + ? BURNIN_PROTECTION_FIRST_WAKEUP_INTERVAL_MS + : BURNIN_PROTECTION_SUBSEQUENT_WAKEUP_INTERVAL_MS; + if (mFirstUpdate) { + mFirstUpdate = false; + } else { + adjustOffsets(); + mAppliedBurnInXOffset = mLastBurnInXOffset; + mAppliedBurnInYOffset = mLastBurnInYOffset; + mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), + mLastBurnInXOffset, mLastBurnInYOffset); + } + // We use currentTimeMillis to compute the next wakeup time since we want to wake up at + // the same time as we wake up to update ambient mode to minimize power consumption. + // However, we use elapsedRealtime to schedule the alarm so that setting the time can't + // disable burn-in protection for extended periods. + final long nowWall = System.currentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + // Next adjustment at least ten seconds in the future. + long nextWall = nowWall + BURNIN_PROTECTION_MINIMAL_INTERVAL_MS; + // And aligned to the minute. + nextWall = (nextWall - (nextWall % interval)) + interval; + // Use elapsed real time that is adjusted to full minute on wall clock. + final long nextElapsed = nowElapsed + (nextWall - nowWall); + if (DEBUG) { + Slog.d(TAG, "scheduling next wake-up, now wall time " + nowWall + + ", next wall: " + nextWall + ", now elapsed: " + nowElapsed + + ", next elapsed: " + nextElapsed); + } + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextElapsed, + mBurnInProtectionIntent); + } else { + mAlarmManager.cancel(mBurnInProtectionIntent); + mCenteringAnimator.start(); + } + } + + public void cancelBurnInProtection() { + if (mBurnInProtectionActive) { + mBurnInProtectionActive = false; + updateBurnInProtection(); + } + } + + /** + * Gently shifts current burn-in offsets, minimizing the change for the user. + * + * Shifts are applied in following fashion: + * 1) shift horizontally from minimum to the maximum; + * 2) shift vertically by one from minimum to the maximum; + * 3) shift horizontally from maximum to the minimum; + * 4) shift vertically by one from minimum to the maximum. + * 5) if you reach the maximum vertically, start shifting back by one from maximum to minimum. + * + * On top of that, stay within specified radius. If the shift distance from the center is + * higher than the radius, skip these values and go the next position that is within the radius. + */ + private void adjustOffsets() { + do { + // By default, let's just shift the X offset. + final int xChange = mXOffsetDirection * BURN_IN_SHIFT_STEP; + mLastBurnInXOffset += xChange; + if (mLastBurnInXOffset > mMaxHorizontalBurnInOffset + || mLastBurnInXOffset < mMinHorizontalBurnInOffset) { + // Whoops, we went too far horizontally. Let's retract.. + mLastBurnInXOffset -= xChange; + // change horizontal direction.. + mXOffsetDirection *= -1; + // and let's shift the Y offset. + final int yChange = mYOffsetDirection * BURN_IN_SHIFT_STEP; + mLastBurnInYOffset += yChange; + if (mLastBurnInYOffset > mMaxVerticalBurnInOffset + || mLastBurnInYOffset < mMinVerticalBurnInOffset) { + // Whoops, we went to far vertically. Let's retract.. + mLastBurnInYOffset -= yChange; + // and change vertical direction. + mYOffsetDirection *= -1; + } + } + // If we are outside of the radius, let's try again. + } while (mBurnInRadiusMaxSquared != BURN_IN_MAX_RADIUS_DEFAULT + && mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset + > mBurnInRadiusMaxSquared); + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mBurnInProtectionActive=" + mBurnInProtectionActive); + pw.println(prefix + "mHorizontalBurnInOffsetsBounds=(" + mMinHorizontalBurnInOffset + ", " + + mMaxHorizontalBurnInOffset + ")"); + pw.println(prefix + "mVerticalBurnInOffsetsBounds=(" + mMinVerticalBurnInOffset + ", " + + mMaxVerticalBurnInOffset + ")"); + pw.println(prefix + "mBurnInRadiusMaxSquared=" + mBurnInRadiusMaxSquared); + pw.println(prefix + "mLastBurnInOffset=(" + mLastBurnInXOffset + ", " + + mLastBurnInYOffset + ")"); + pw.println(prefix + "mOfsetChangeDirections=(" + mXOffsetDirection + ", " + + mYOffsetDirection + ")"); + } + + @Override + public void onDisplayAdded(int i) { + } + + @Override + public void onDisplayRemoved(int i) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDisplay.getDisplayId()) { + if (mDisplay.getState() == Display.STATE_DOZE + || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) { + startBurnInProtection(); + } else { + cancelBurnInProtection(); + } + } + } + + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationEnd(Animator animator) { + if (animator == mCenteringAnimator && !mBurnInProtectionActive) { + mAppliedBurnInXOffset = 0; + mAppliedBurnInYOffset = 0; + // No matter how the animation finishes, we want to zero the offsets. + mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), 0, 0); + } + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + if (!mBurnInProtectionActive) { + final float value = (Float) valueAnimator.getAnimatedValue(); + mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), + (int) (mAppliedBurnInXOffset * value), (int) (mAppliedBurnInYOffset * value)); + } + } +}