OSDN Git Service

Burn in protection.
authorFilip Gruszczynski <gruszczy@google.com>
Thu, 19 Feb 2015 21:05:03 +0000 (13:05 -0800)
committerFilip Gruszczynski <gruszczy@google.com>
Wed, 25 Feb 2015 20:22:49 +0000 (12:22 -0800)
Bug: 19373758
Change-Id: I7242f73acadd21bdee03262b4205c8af0734fccb

core/java/android/hardware/display/DisplayManagerInternal.java
core/res/AndroidManifest.xml
core/res/res/values/config.xml
core/res/res/values/symbols.xml
policy/src/com/android/internal/policy/impl/BurnInProtectionHelper.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
services/core/java/com/android/server/display/DisplayManagerService.java
services/core/java/com/android/server/display/LogicalDisplay.java

index bb16215..adab9be 100644 (file)
@@ -132,6 +132,19 @@ public abstract class DisplayManagerInternal {
             float requestedRefreshRate, boolean inTraversal);
 
     /**
+     * Applies an offset to the contents of a display, for example to avoid burn-in.
+     * <p>
+     * TODO: Technically this should be associated with a physical rather than logical
+     * display but this is good enough for now.
+     * </p>
+     *
+     * @param displayId The logical display id to update.
+     * @param x The X offset by which to shift the contents of the display.
+     * @param y The Y offset by which to shift the contents of the display.
+     */
+    public abstract void setDisplayOffsets(int displayId, int x, int y);
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
index ccdb5db..59dafed 100644 (file)
     <protected-broadcast android:name="android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE" />
     <protected-broadcast android:name="android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED" />
 
+    <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
index 17b87d6..15eb095 100755 (executable)
     <!-- Scale factor threshold used by the screen magnifier to determine when to switch from
          panning to scaling the magnification viewport. -->
     <item name="config_screen_magnification_scaling_threshold" format="float" type="dimen">0.3</item>
+
+    <!-- If true, the display will be shifted around in ambient mode. -->
+    <bool name="config_enableBurnInProtection">false</bool>
+
+    <!-- Specifies the maximum burn-in offset displacement from the center. If -1, no maximum value
+         will be used. -->
+    <integer name="config_burnInProtectionMaxRadius">-1</integer>
+
+    <!-- Specifies the minimum burn-in offset horizontally. -->
+    <integer name="config_burnInProtectionMinHorizontalOffset">0</integer>
+
+    <!-- Specifies the maximum burn-in offset horizontally. -->
+    <integer name="config_burnInProtectionMaxHorizontalOffset">0</integer>
+
+    <!-- Specifies the minimum burn-in offset vertically. -->
+    <integer name="config_burnInProtectionMinVerticalOffset">0</integer>
+
+    <!-- Specifies the maximum burn-in offset vertically. -->
+    <integer name="config_burnInProtectionMaxVerticalOffset">0</integer>
 </resources>
index 390661c..1f00dbe 100755 (executable)
   <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
   <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
   <java-symbol type="bool" name="config_enable_puk_unlock_screen" />
+  <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="integer" name="config_wifi_framework_current_network_boost" />
   <java-symbol type="integer" name="config_bluetooth_max_advertisers" />
   <java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
+  <java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
+  <java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />
+  <java-symbol type="integer" name="config_burnInProtectionMinVerticalOffset" />
+  <java-symbol type="integer" name="config_burnInProtectionMaxVerticalOffset" />
+  <java-symbol type="integer" name="config_burnInProtectionMaxRadius" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
diff --git a/policy/src/com/android/internal/policy/impl/BurnInProtectionHelper.java b/policy/src/com/android/internal/policy/impl/BurnInProtectionHelper.java
new file mode 100644 (file)
index 0000000..19fe262
--- /dev/null
@@ -0,0 +1,203 @@
+package com.android.internal.policy.impl;
+
+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.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+public class BurnInProtectionHelper implements DisplayManager.DisplayListener {
+    private static final String TAG = "BurnInProtection";
+
+    // Default value when max burnin radius is not set.
+    public static final int BURN_IN_RADIUS_MAX_DEFAULT = -1;
+
+    private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
+    private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10);
+
+    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 boolean mBurnInProtectionActive;
+
+    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 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) {
+            updateBurnInProtection();
+        }
+    };
+
+    public BurnInProtectionHelper(Context context) {
+        final Resources resources = context.getResources();
+        mMinHorizontalBurnInOffset = resources.getInteger(
+                com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
+        mMaxHorizontalBurnInOffset = resources.getInteger(
+                com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
+        mMinVerticalBurnInOffset = resources.getInteger(
+                com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
+        mMaxVerticalBurnInOffset = resources.getInteger(
+                com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
+        int burnInRadiusMax = resources.getInteger(
+                com.android.internal.R.integer.config_burnInProtectionMaxRadius);
+        if (burnInRadiusMax != BURN_IN_RADIUS_MAX_DEFAULT) {
+            mBurnInRadiusMaxSquared = burnInRadiusMax * burnInRadiusMax;
+        } else {
+            mBurnInRadiusMaxSquared = BURN_IN_RADIUS_MAX_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 */);
+    }
+
+    public void startBurnInProtection() {
+        if (!mBurnInProtectionActive) {
+            mBurnInProtectionActive = true;
+            updateBurnInProtection();
+        }
+    }
+
+    private void updateBurnInProtection() {
+        if (mBurnInProtectionActive) {
+            adjustOffsets();
+            mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(),
+                    mLastBurnInXOffset, mLastBurnInYOffset);
+            // Next adjustment at least ten seconds in the future.
+            long next = SystemClock.elapsedRealtime() + BURNIN_PROTECTION_MINIMAL_INTERVAL_MS;
+            // And aligned to the minute.
+            next = next - next % BURNIN_PROTECTION_WAKEUP_INTERVAL_MS
+                    + BURNIN_PROTECTION_WAKEUP_INTERVAL_MS;
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mBurnInProtectionIntent);
+        } else {
+            mAlarmManager.cancel(mBurnInProtectionIntent);
+            mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), 0, 0);
+        }
+    }
+
+    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_RADIUS_MAX_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();
+            }
+        }
+    }
+}
index b9dba81..ccbef6b 100644 (file)
@@ -252,6 +252,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
     Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
     SearchManager mSearchManager;
     AccessibilityManager mAccessibilityManager;
+    BurnInProtectionHelper mBurnInProtectionHelper;
 
     // Vibrator pattern for haptic feedback of a long press.
     long[] mLongPressVibePattern;
@@ -1183,6 +1184,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
         mWindowManagerFuncs = windowManagerFuncs;
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
+        if (context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableBurnInProtection)){
+            mBurnInProtectionHelper = new BurnInProtectionHelper(context);
+        }
 
         mHandler = new PolicyHandler();
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
@@ -6398,5 +6403,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
         if (mOrientationListener != null) {
             mOrientationListener.dump(pw, prefix);
         }
+        if (mBurnInProtectionHelper != null) {
+            mBurnInProtectionHelper.dump(prefix, pw);
+        }
     }
 }
index 09dc477..5f0ad9f 100644 (file)
@@ -46,7 +46,6 @@ import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -832,6 +831,24 @@ public final class DisplayManagerService extends SystemService {
         }
     }
 
+    private void setDisplayOffsetsInternal(int displayId, int x, int y) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            if (display.getDisplayOffsetXLocked() != x
+                    || display.getDisplayOffsetYLocked() != y) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Display " + displayId + " burn-in offset set to ("
+                            + x + ", " + y + ")");
+                }
+                display.setDisplayOffsetsLocked(x, y);
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
     private void clearViewportsLocked() {
         mDefaultViewport.valid = false;
         mExternalTouchViewport.valid = false;
@@ -1513,5 +1530,10 @@ public final class DisplayManagerService extends SystemService {
                 float requestedRefreshRate, boolean inTraversal) {
             setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate, inTraversal);
         }
+
+        @Override
+        public void setDisplayOffsets(int displayId, int x, int y) {
+            setDisplayOffsetsInternal(displayId, x, y);
+        }
     }
 }
index 6c57eec..3bb7818 100644 (file)
@@ -76,6 +76,10 @@ final class LogicalDisplay {
     // The pending requested refresh rate. 0 if no request is pending.
     private float mRequestedRefreshRate;
 
+    // The display offsets to apply to the display projection.
+    private int mDisplayOffsetX;
+    private int mDisplayOffsetY;
+
     // Temporary rectangle used when needed.
     private final Rect mTempLayerStackRect = new Rect();
     private final Rect mTempDisplayRect = new Rect();
@@ -313,6 +317,10 @@ final class LogicalDisplay {
         mTempDisplayRect.set(displayRectLeft, displayRectTop,
                 displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);
 
+        mTempDisplayRect.left += mDisplayOffsetX;
+        mTempDisplayRect.right += mDisplayOffsetX;
+        mTempDisplayRect.top += mDisplayOffsetY;
+        mTempDisplayRect.bottom += mDisplayOffsetY;
         device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect);
     }
 
@@ -356,10 +364,34 @@ final class LogicalDisplay {
         return mRequestedRefreshRate;
     }
 
+    /**
+     * Gets the burn-in offset in X.
+     */
+    public int getDisplayOffsetXLocked() {
+        return mDisplayOffsetX;
+    }
+
+    /**
+     * Gets the burn-in offset in Y.
+     */
+    public int getDisplayOffsetYLocked() {
+        return mDisplayOffsetY;
+    }
+
+    /**
+     * Sets the burn-in offsets.
+     */
+    public void setDisplayOffsetsLocked(int x, int y) {
+        mDisplayOffsetX = x;
+        mDisplayOffsetY = y;
+    }
+
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
+        pw.println("mRequestedRefreshRate=" + mRequestedRefreshRate);
+        pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
         pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
                 mPrimaryDisplayDevice.getNameLocked() : "null"));
         pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);