summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author William Leshner <wleshner@google.com> 2022-01-18 19:39:55 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-01-18 19:39:55 +0000
commit4597428acbb2b288748367520ef75450437d0214 (patch)
tree0c3c65418a55e276dd9b6c1f83ebf68892e66e56
parentda31876b9a41facf73310a1229731eaa0f62e980 (diff)
parent8624015554c37c48d8611be2af7141750275e335 (diff)
Merge "Periodically jitter dream overlay to prevent burn-in."
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java49
4 files changed, 104 insertions, 3 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9c35fea6438d..65f22b805d4e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -738,4 +738,6 @@
<!-- Class for the communal source connector to be used -->
<string name="config_communalSourceConnector" translatable="false"></string>
+ <!-- How often in milliseconds to jitter the dream overlay in order to avoid burn-in. -->
+ <integer name="config_dreamOverlayBurnInProtectionUpdateIntervalMillis">500</integer>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 572bb4467c97..5b46079c87cc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -16,8 +16,11 @@
package com.android.systemui.dreams;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -26,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.util.ViewController;
@@ -47,6 +51,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// the space into which widgets are placed.
private final ViewGroup mDreamOverlayContentView;
+ // The maximum translation offset to apply to the overlay container to avoid screen burn-in.
+ private final int mMaxBurnInOffset;
+
+ // The interval in milliseconds between burn-in protection updates.
+ private final long mBurnInProtectionUpdateInterval;
+
+ // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
+ private final Handler mHandler;
+
// A hook into the internal inset calculation where we declare the overlays as the only
// touchable regions.
private final ViewTreeObserver.OnComputeInternalInsetsListener
@@ -81,13 +94,21 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
- DreamOverlayStatusBarViewController statusBarViewController) {
+ DreamOverlayStatusBarViewController statusBarViewController,
+ @Main Handler handler,
+ @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
+ @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
+ burnInProtectionUpdateInterval) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
mDreamOverlayNotificationsDragAreaHeight =
mView.getResources().getDimensionPixelSize(
R.dimen.dream_overlay_notifications_drag_area_height);
+
+ mHandler = handler;
+ mMaxBurnInOffset = maxBurnInOffset;
+ mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
}
@Override
@@ -99,10 +120,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
protected void onViewAttached() {
mView.getViewTreeObserver()
.addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+ mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
}
@Override
protected void onViewDetached() {
+ mHandler.removeCallbacks(this::updateBurnInOffsets);
mView.getViewTreeObserver()
.removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
}
@@ -123,4 +146,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
int getDreamOverlayNotificationsDragAreaHeight() {
return mDreamOverlayNotificationsDragAreaHeight;
}
+
+ private void updateBurnInOffsets() {
+ // These translation values change slowly, and the set translation methods are idempotent,
+ // so no translation occurs when the values don't change.
+ mView.setTranslationX(getBurnInOffset(mMaxBurnInOffset * 2, true)
+ - mMaxBurnInOffset);
+ mView.setTranslationY(getBurnInOffset(mMaxBurnInOffset * 2, false)
+ - mMaxBurnInOffset);
+
+ mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 5b588a9d9023..d2912032ed39 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.dagger;
import android.content.ContentResolver;
+import android.content.res.Resources;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -45,6 +46,9 @@ public abstract class DreamOverlayModule {
public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
"dream_overlay_battery_controller";
public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
+ public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
+ public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
+ "burn_in_protection_update_interval";
/** */
@Provides
@@ -104,4 +108,20 @@ public abstract class DreamOverlayModule {
contentResolver,
batteryController);
}
+
+ /** */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ @Named(MAX_BURN_IN_OFFSET)
+ static int providesMaxBurnInOffset(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset);
+ }
+
+ /** */
+ @Provides
+ @Named(BURN_IN_PROTECTION_UPDATE_INTERVAL)
+ static long providesBurnInProtectionUpdateInterval(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index cf53ccffcdb0..7c5f57fe0b39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -19,10 +19,13 @@ package com.android.systemui.dreams;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.ViewGroup;
@@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
private static final int DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT = 100;
+ private static final int MAX_BURN_IN_OFFSET = 20;
+ private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10;
@Mock
Resources mResources;
@@ -61,6 +66,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
@Mock
ViewGroup mDreamOverlayContentView;
+ @Mock
+ Handler mHandler;
+
DreamOverlayContainerViewController mController;
@Before
@@ -74,8 +82,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
mController = new DreamOverlayContainerViewController(
- mDreamOverlayContainerView, mDreamOverlayContentView,
- mDreamOverlayStatusBarViewController);
+ mDreamOverlayContainerView,
+ mDreamOverlayContentView,
+ mDreamOverlayStatusBarViewController,
+ mHandler,
+ MAX_BURN_IN_OFFSET,
+ BURN_IN_PROTECTION_UPDATE_INTERVAL);
}
@Test
@@ -129,4 +141,37 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
computeInsetsListenerCapture.getValue().onComputeInternalInsets(info);
assertNotNull(info.touchableRegion);
}
+
+ @Test
+ public void testBurnInProtectionStartsWhenContentViewAttached() {
+ mController.onViewAttached();
+ verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL));
+ }
+
+ @Test
+ public void testBurnInProtectionStopsWhenContentViewDetached() {
+ mController.onViewDetached();
+ verify(mHandler).removeCallbacks(any(Runnable.class));
+ }
+
+ @Test
+ public void testBurnInProtectionUpdatesPeriodically() {
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mController.onViewAttached();
+ verify(mHandler).postDelayed(
+ runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL));
+ runnableCaptor.getValue().run();
+ verify(mDreamOverlayContainerView).setTranslationX(anyFloat());
+ verify(mDreamOverlayContainerView).setTranslationY(anyFloat());
+ }
+
+ @Test
+ public void testBurnInProtectionReschedulesUpdate() {
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mController.onViewAttached();
+ verify(mHandler).postDelayed(
+ runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL));
+ runnableCaptor.getValue().run();
+ verify(mHandler).postDelayed(runnableCaptor.getValue(), BURN_IN_PROTECTION_UPDATE_INTERVAL);
+ }
}