Periodically jitter dream overlay to prevent burn-in.
Test: atest DreamOverlayContainerViewControllerTest Bug: 213907279 Change-Id: Id43d8697c5ef032b07c6f5854445cb16f6c820b7
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user