diff options
| author | 2022-01-18 19:39:55 +0000 | |
|---|---|---|
| committer | 2022-01-18 19:39:55 +0000 | |
| commit | 4597428acbb2b288748367520ef75450437d0214 (patch) | |
| tree | 0c3c65418a55e276dd9b6c1f83ebf68892e66e56 | |
| parent | da31876b9a41facf73310a1229731eaa0f62e980 (diff) | |
| parent | 8624015554c37c48d8611be2af7141750275e335 (diff) | |
Merge "Periodically jitter dream overlay to prevent burn-in."
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); + } } |