Fix a bug with jittering dream overlay.

The first jitter causes all the dream overlay views to jump noticeably.
The issues was that the first jitter happens after the views have
appeared on screen. The solution is to jitter immediately so that the
views are already in their initial jitter position before they become
visible.

Test: atest DreamOverlayContainerViewControllerTest
Bug: 220182090
Change-Id: Ia62a5cf8d314a8617eaa522b51be99f61aef8b99
This commit is contained in:
Will
2022-02-17 17:09:16 -08:00
parent d0cb324a9c
commit 8692fcd08c
4 changed files with 44 additions and 11 deletions

View File

@@ -695,4 +695,7 @@
<!-- How often in milliseconds to jitter the dream overlay in order to avoid burn-in. -->
<integer name="config_dreamOverlayBurnInProtectionUpdateIntervalMillis">500</integer>
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
</resources>

View File

@@ -19,6 +19,7 @@ package com.android.systemui.dreams;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import android.os.Handler;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -56,9 +57,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// The interval in milliseconds between burn-in protection updates.
private final long mBurnInProtectionUpdateInterval;
// Amount of time in milliseconds to linear interpolate toward the final jitter offset. Once
// this time is achieved, the normal jitter algorithm applies in full.
private final long mMillisUntilFullJitter;
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
private final Handler mHandler;
private long mJitterStartTimeMillis;
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
@@ -68,7 +75,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
@Main Handler handler,
@Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval) {
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
@@ -86,6 +94,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mHandler = handler;
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
mMillisUntilFullJitter = millisUntilFullJitter;
}
@Override
@@ -96,6 +105,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
@Override
protected void onViewAttached() {
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
}
@@ -114,13 +124,24 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
}
private void updateBurnInOffsets() {
int burnInOffset = mMaxBurnInOffset;
// Make sure the offset starts at zero, to avoid a big jump in the overlay when it first
// appears.
long millisSinceStart = System.currentTimeMillis() - mJitterStartTimeMillis;
if (millisSinceStart < mMillisUntilFullJitter) {
float lerpAmount = (float) millisSinceStart / (float) mMillisUntilFullJitter;
burnInOffset = Math.round(MathUtils.lerp(0f, burnInOffset, lerpAmount));
}
// 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);
int burnInOffsetX = getBurnInOffset(burnInOffset * 2, true)
- burnInOffset;
int burnInOffsetY = getBurnInOffset(burnInOffset * 2, false)
- burnInOffset;
mView.setTranslationX(burnInOffsetX);
mView.setTranslationY(burnInOffsetY);
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
}

View File

@@ -46,6 +46,7 @@ public abstract class DreamOverlayModule {
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";
public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
/** */
@Provides
@@ -106,6 +107,13 @@ public abstract class DreamOverlayModule {
R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis);
}
/** */
@Provides
@Named(MILLIS_UNTIL_FULL_JITTER)
static long providesMillisUntilFullJitter(@Main Resources resources) {
return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter);
}
@Provides
@DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {

View File

@@ -18,7 +18,6 @@ package com.android.systemui.dreams;
import static org.junit.Assert.assertEquals;
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;
@@ -49,6 +48,7 @@ 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;
private static final long MILLIS_UNTIL_FULL_JITTER = 240 * 1000;
@Mock
Resources mResources;
@@ -100,7 +100,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mDreamOverlayStatusBarViewController,
mHandler,
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL);
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER);
}
@Test
@@ -129,14 +130,14 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
}
@Test
public void testBurnInProtectionUpdatesPeriodically() {
public void testBurnInProtectionOffsetsStartAtZero() {
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());
verify(mDreamOverlayContainerView).setTranslationX(0.f);
verify(mDreamOverlayContainerView).setTranslationY(0.f);
}
@Test