BurnInProtectionHelper.java revision c045208fef869e71622f7a1596314766138a13c4
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server.policy; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.res.Resources; 26import android.hardware.display.DisplayManager; 27import android.hardware.display.DisplayManagerInternal; 28import android.os.SystemClock; 29import android.view.Display; 30 31import com.android.server.LocalServices; 32 33import java.io.PrintWriter; 34import java.util.concurrent.TimeUnit; 35 36public class BurnInProtectionHelper implements DisplayManager.DisplayListener { 37 private static final String TAG = "BurnInProtection"; 38 39 // Default value when max burnin radius is not set. 40 public static final int BURN_IN_RADIUS_MAX_DEFAULT = -1; 41 42 private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1); 43 private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10); 44 45 private static final String ACTION_BURN_IN_PROTECTION = 46 "android.internal.policy.action.BURN_IN_PROTECTION"; 47 48 private static final int BURN_IN_SHIFT_STEP = 2; 49 50 private boolean mBurnInProtectionActive; 51 52 private final int mMinHorizontalBurnInOffset; 53 private final int mMaxHorizontalBurnInOffset; 54 private final int mMinVerticalBurnInOffset; 55 private final int mMaxVerticalBurnInOffset; 56 57 private final int mBurnInRadiusMaxSquared; 58 59 private int mLastBurnInXOffset = 0; 60 /* 1 means increasing, -1 means decreasing */ 61 private int mXOffsetDirection = 1; 62 private int mLastBurnInYOffset = 0; 63 /* 1 means increasing, -1 means decreasing */ 64 private int mYOffsetDirection = 1; 65 66 private final AlarmManager mAlarmManager; 67 private final PendingIntent mBurnInProtectionIntent; 68 private final DisplayManagerInternal mDisplayManagerInternal; 69 private final Display mDisplay; 70 71 private BroadcastReceiver mBurnInProtectionReceiver = new BroadcastReceiver() { 72 @Override 73 public void onReceive(Context context, Intent intent) { 74 updateBurnInProtection(); 75 } 76 }; 77 78 public BurnInProtectionHelper(Context context) { 79 final Resources resources = context.getResources(); 80 mMinHorizontalBurnInOffset = resources.getInteger( 81 com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset); 82 mMaxHorizontalBurnInOffset = resources.getInteger( 83 com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset); 84 mMinVerticalBurnInOffset = resources.getInteger( 85 com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset); 86 mMaxVerticalBurnInOffset = resources.getInteger( 87 com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset); 88 int burnInRadiusMax = resources.getInteger( 89 com.android.internal.R.integer.config_burnInProtectionMaxRadius); 90 if (burnInRadiusMax != BURN_IN_RADIUS_MAX_DEFAULT) { 91 mBurnInRadiusMaxSquared = burnInRadiusMax * burnInRadiusMax; 92 } else { 93 mBurnInRadiusMaxSquared = BURN_IN_RADIUS_MAX_DEFAULT; 94 } 95 96 mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); 97 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 98 context.registerReceiver(mBurnInProtectionReceiver, 99 new IntentFilter(ACTION_BURN_IN_PROTECTION)); 100 Intent intent = new Intent(ACTION_BURN_IN_PROTECTION); 101 intent.setPackage(context.getPackageName()); 102 intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 103 mBurnInProtectionIntent = PendingIntent.getBroadcast(context, 0, 104 intent, PendingIntent.FLAG_UPDATE_CURRENT); 105 DisplayManager displayManager = 106 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); 107 mDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 108 displayManager.registerDisplayListener(this, null /* handler */); 109 } 110 111 public void startBurnInProtection() { 112 if (!mBurnInProtectionActive) { 113 mBurnInProtectionActive = true; 114 updateBurnInProtection(); 115 } 116 } 117 118 private void updateBurnInProtection() { 119 if (mBurnInProtectionActive) { 120 adjustOffsets(); 121 mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), 122 mLastBurnInXOffset, mLastBurnInYOffset); 123 // Next adjustment at least ten seconds in the future. 124 long next = SystemClock.elapsedRealtime() + BURNIN_PROTECTION_MINIMAL_INTERVAL_MS; 125 // And aligned to the minute. 126 next = next - next % BURNIN_PROTECTION_WAKEUP_INTERVAL_MS 127 + BURNIN_PROTECTION_WAKEUP_INTERVAL_MS; 128 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mBurnInProtectionIntent); 129 } else { 130 mAlarmManager.cancel(mBurnInProtectionIntent); 131 mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), 0, 0); 132 } 133 } 134 135 public void cancelBurnInProtection() { 136 if (mBurnInProtectionActive) { 137 mBurnInProtectionActive = false; 138 updateBurnInProtection(); 139 } 140 } 141 142 /** 143 * Gently shifts current burn-in offsets, minimizing the change for the user. 144 * 145 * Shifts are applied in following fashion: 146 * 1) shift horizontally from minimum to the maximum; 147 * 2) shift vertically by one from minimum to the maximum; 148 * 3) shift horizontally from maximum to the minimum; 149 * 4) shift vertically by one from minimum to the maximum. 150 * 5) if you reach the maximum vertically, start shifting back by one from maximum to minimum. 151 * 152 * On top of that, stay within specified radius. If the shift distance from the center is 153 * higher than the radius, skip these values and go the next position that is within the radius. 154 */ 155 private void adjustOffsets() { 156 do { 157 // By default, let's just shift the X offset. 158 final int xChange = mXOffsetDirection * BURN_IN_SHIFT_STEP; 159 mLastBurnInXOffset += xChange; 160 if (mLastBurnInXOffset > mMaxHorizontalBurnInOffset 161 || mLastBurnInXOffset < mMinHorizontalBurnInOffset) { 162 // Whoops, we went too far horizontally. Let's retract.. 163 mLastBurnInXOffset -= xChange; 164 // change horizontal direction.. 165 mXOffsetDirection *= -1; 166 // and let's shift the Y offset. 167 final int yChange = mYOffsetDirection * BURN_IN_SHIFT_STEP; 168 mLastBurnInYOffset += yChange; 169 if (mLastBurnInYOffset > mMaxVerticalBurnInOffset 170 || mLastBurnInYOffset < mMinVerticalBurnInOffset) { 171 // Whoops, we went to far vertically. Let's retract.. 172 mLastBurnInYOffset -= yChange; 173 // and change vertical direction. 174 mYOffsetDirection *= -1; 175 } 176 } 177 // If we are outside of the radius, let's try again. 178 } while (mBurnInRadiusMaxSquared != BURN_IN_RADIUS_MAX_DEFAULT 179 && mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset 180 > mBurnInRadiusMaxSquared); 181 } 182 183 public void dump(String prefix, PrintWriter pw) { 184 pw.println(prefix + TAG); 185 prefix += " "; 186 pw.println(prefix + "mBurnInProtectionActive=" + mBurnInProtectionActive); 187 pw.println(prefix + "mHorizontalBurnInOffsetsBounds=(" + mMinHorizontalBurnInOffset + ", " 188 + mMaxHorizontalBurnInOffset + ")"); 189 pw.println(prefix + "mVerticalBurnInOffsetsBounds=(" + mMinVerticalBurnInOffset + ", " 190 + mMaxVerticalBurnInOffset + ")"); 191 pw.println(prefix + "mBurnInRadiusMaxSquared=" + mBurnInRadiusMaxSquared); 192 pw.println(prefix + "mLastBurnInOffset=(" + mLastBurnInXOffset + ", " 193 + mLastBurnInYOffset + ")"); 194 pw.println(prefix + "mOfsetChangeDirections=(" + mXOffsetDirection + ", " 195 + mYOffsetDirection + ")"); 196 } 197 198 @Override 199 public void onDisplayAdded(int i) { 200 } 201 202 @Override 203 public void onDisplayRemoved(int i) { 204 } 205 206 @Override 207 public void onDisplayChanged(int displayId) { 208 if (displayId == mDisplay.getDisplayId()) { 209 if (mDisplay.getState() == Display.STATE_DOZE 210 || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) { 211 startBurnInProtection(); 212 } else { 213 cancelBurnInProtection(); 214 } 215 } 216 } 217} 218