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