Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4597428a authored by William Leshner's avatar William Leshner Committed by Android (Google) Code Review
Browse files

Merge "Periodically jitter dream overlay to prevent burn-in."

parents da31876b 86240155
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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>
+35 −1
Original line number Diff line number Diff line
@@ -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);
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+47 −2
Original line number Diff line number Diff line
@@ -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);
    }
}