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

Commit 6cedcd25 authored by Bryce Lee's avatar Bryce Lee
Browse files

Update max window bounds based on display changes.

This changelist updates the TouchMonitor logic to only query the
display's max window bounds on changes. This prevents unnecessary
operations on each motion event.

Test: atest TouchMonitorTest#testDisplayListenerUpdatesBounds
test: atest TouchMonitorTest#testDisplayListenerUnregisters
Fixes: 330906135
Flag: ACONFIG com.android.systemui.ambient_touch_monitor_listen_to_display_changes DISABLED
Change-Id: I52ad200ff27a88dfb305137c097e9605a66dae80
parent 232bd75e
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -828,3 +828,13 @@ flag {
    description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS."
    bug: "329205638"
}

flag {
  name: "ambient_touch_monitor_listen_to_display_changes"
  namespace: "systemui"
  description: "listen to display changes and cache window metrics"
  bug: "330906135"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+23 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.ambient.touch;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

import static com.android.systemui.shared.Flags.bouncerAreaExclusion;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;

import android.graphics.Rect;
import android.graphics.Region;
@@ -37,7 +38,9 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;

import com.android.systemui.Flags;
import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -69,6 +72,9 @@ public class TouchMonitor {
    public String TAG = "DreamOverlayTouchMonitor";
    private final Executor mMainExecutor;
    private final Executor mBackgroundExecutor;

    private final ConfigurationInteractor mConfigurationInteractor;

    private final Lifecycle mLifecycle;
    private Rect mExclusionRect = null;

@@ -82,6 +88,8 @@ public class TouchMonitor {
                }
            };

    private Consumer<Rect> mMaxBoundsConsumer = rect -> mMaxBounds = rect;


    /**
     * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
@@ -262,6 +270,7 @@ public class TouchMonitor {
     */
    private void startMonitoring() {
        stopMonitoring(true);

        if (bouncerAreaExclusion()) {
            mBackgroundExecutor.execute(() -> {
                try {
@@ -340,8 +349,13 @@ public class TouchMonitor {
                            if (!handler.isEnabled()) {
                                continue;
                            }
                            final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),

                            final Rect maxBounds =
                                    Flags.ambientTouchMonitorListenToDisplayChanges()
                                            ? mMaxBounds
                                            : mDisplayHelper.getMaxBounds(ev.getDisplayId(),
                                                    TYPE_APPLICATION_OVERLAY);

                            final Region initiationRegion = Region.obtain();
                            Rect exclusionRect = null;
                            if (bouncerAreaExclusion()) {
@@ -478,6 +492,8 @@ public class TouchMonitor {
    private final int mDisplayId;
    private final IWindowManager mWindowManagerService;

    private Rect mMaxBounds;


    /**
     * Designated constructor for {@link TouchMonitor}
@@ -500,6 +516,7 @@ public class TouchMonitor {
            Lifecycle lifecycle,
            InputSessionComponent.Factory inputSessionFactory,
            DisplayHelper displayHelper,
            ConfigurationInteractor configurationInteractor,
            Set<TouchHandler> handlers,
            IWindowManager windowManagerService,
            @DisplayId int displayId) {
@@ -511,6 +528,7 @@ public class TouchMonitor {
        mLifecycle = lifecycle;
        mDisplayHelper = displayHelper;
        mWindowManagerService = windowManagerService;
        mConfigurationInteractor = configurationInteractor;
    }

    /**
@@ -518,6 +536,9 @@ public class TouchMonitor {
     */
    public void init() {
        mLifecycle.addObserver(mLifecycleObserver);
        if (Flags.ambientTouchMonitorListenToDisplayChanges()) {
            collectFlow(mLifecycle, mConfigurationInteractor.getMaxBounds(), mMaxBoundsConsumer);
        }
    }

    private void isolate(Set<TouchSessionImpl> sessions) {
+6 −0
Original line number Diff line number Diff line
@@ -48,6 +48,12 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config
            }
        }

    /** Returns the unadjusted screen size. */
    val maxBounds: Flow<Rect> =
        repository.configurationValues
            .map { Rect(it.windowConfiguration.maxBounds) }
            .distinctUntilChanged()

    /**
     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+98 −49
Original line number Diff line number Diff line
@@ -24,26 +24,35 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.testing.TestableLooper;
import android.view.GestureDetector;
import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowMetrics;

import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.test.filters.SmallTest;

import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.display.DisplayHelper;
@@ -67,31 +76,58 @@ import java.util.stream.Stream;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class TouchMonitorTest extends SysuiTestCase {
    private KosmosJavaAdapter mKosmos;
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mKosmos = new KosmosJavaAdapter(this);
    }

    private static class SimpleLifecycleOwner implements LifecycleOwner {
        LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
        @NonNull
        @Override
        public Lifecycle getLifecycle() {
            return mLifecycle;
        }

        public void setState(Lifecycle.State state) {
            mLifecycle.setCurrentState(state);
        }
    }

    private static class Environment {
        private final InputSessionComponent.Factory mInputFactory;
        private final InputSession mInputSession;
        private final Lifecycle mLifecycle;
        private final LifecycleOwner mLifecycleOwner;
        private final SimpleLifecycleOwner mLifecycleOwner;

        private final LifecycleRegistry mLifecycleRegistry;
        private final TouchMonitor mMonitor;
        private final DefaultLifecycleObserver mLifecycleObserver;
        private final InputChannelCompat.InputEventListener mEventListener;
        private final GestureDetector.OnGestureListener mGestureListener;
        private final DisplayHelper mDisplayHelper;
        private final DisplayManager mDisplayManager;
        private final WindowManager mWindowManager;
        private final WindowMetrics mWindowMetrics;
        private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
        private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());

        private final Rect mDisplayBounds = Mockito.mock(Rect.class);
        private final IWindowManager mIWindowManager;

        Environment(Set<TouchHandler> handlers) {
            mLifecycle = Mockito.mock(Lifecycle.class);
            mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
        private final KosmosJavaAdapter mKosmos;


        Environment(Set<TouchHandler> handlers, KosmosJavaAdapter kosmos) {
            mLifecycleOwner = new SimpleLifecycleOwner();
            mLifecycleRegistry = spy(new LifecycleRegistry(mLifecycleOwner));

            mIWindowManager = Mockito.mock(IWindowManager.class);
            mDisplayManager = Mockito.mock(DisplayManager.class);
            mWindowManager = Mockito.mock(WindowManager.class);
            mKosmos = kosmos;

            mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
            final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
@@ -104,18 +140,16 @@ public class TouchMonitorTest extends SysuiTestCase {
            mDisplayHelper = Mockito.mock(DisplayHelper.class);
            when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
                    .thenReturn(mDisplayBounds);
            mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor,
                    mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
            mMonitor.init();

            final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor =
                    ArgumentCaptor.forClass(LifecycleObserver.class);
            verify(mLifecycle).addObserver(lifecycleObserverCaptor.capture());
            assertThat(lifecycleObserverCaptor.getValue() instanceof DefaultLifecycleObserver)
                    .isTrue();
            mLifecycleObserver = (DefaultLifecycleObserver) lifecycleObserverCaptor.getValue();
            mWindowMetrics = Mockito.mock(WindowMetrics.class);
            when(mWindowMetrics.getBounds()).thenReturn(mDisplayBounds);
            when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics);
            mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycleRegistry,
                    mInputFactory, mDisplayHelper, mKosmos.getConfigurationInteractor(),
                    handlers, mIWindowManager,  0);
            mMonitor.init();

            updateLifecycle(observer -> observer.first.onResume(observer.second));
            updateLifecycle(Lifecycle.State.RESUMED);

            // Capture creation request.
            final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
@@ -145,8 +179,8 @@ public class TouchMonitorTest extends SysuiTestCase {
            listenerConsumer.accept(mGestureListener);
        }

        void updateLifecycle(Consumer<Pair<DefaultLifecycleObserver, LifecycleOwner>> consumer) {
            consumer.accept(Pair.create(mLifecycleObserver, mLifecycleOwner));
        void updateLifecycle(Lifecycle.State state) {
            mLifecycleRegistry.setCurrentState(state);
        }

        void verifyInputSessionDispose() {
@@ -156,10 +190,33 @@ public class TouchMonitorTest extends SysuiTestCase {
    }

    @Test
    @EnableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES)
    public void testConfigurationListenerUpdatesBounds() {
        final TouchHandler touchHandler = createTouchHandler();
        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);
        ArgumentCaptor<DisplayManager.DisplayListener> listenerCaptor =
                ArgumentCaptor.forClass(DisplayManager.DisplayListener.class);
        final Rect testRect = new Rect(0, 0, 2, 2);
        final Configuration configuration = new Configuration();
        configuration.windowConfiguration.setMaxBounds(testRect);

        mKosmos.getConfigurationRepository().onConfigurationChange(configuration);
        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
        when(initialEvent.getX()).thenReturn(0.0f);
        when(initialEvent.getY()).thenReturn(0.0f);
        environment.publishInputEvent(initialEvent);

        // Verify display bounds passed into TouchHandler#getTouchInitiationRegion
        verify(touchHandler).getTouchInitiationRegion(eq(testRect), any(), any());
    }

    @Test
    @DisableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES)
    public void testReportedDisplayBounds() {
        final TouchHandler touchHandler = createTouchHandler();
        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
        when(initialEvent.getX()).thenReturn(0.0f);
@@ -190,7 +247,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        // Ensure touch outside specified region is not delivered.
        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
@@ -219,7 +276,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());

        final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        // Ensure touch outside specified region is delivered to unzoned touch handler.
        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
@@ -261,7 +318,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        when(touchHandler.isEnabled()).thenReturn(false);

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);
        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
        when(initialEvent.getX()).thenReturn(5.0f);
        when(initialEvent.getY()).thenReturn(5.0f);
@@ -277,7 +334,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -297,7 +354,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -321,7 +378,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -340,7 +397,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -365,7 +422,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        when(touchHandler2.isEnabled()).thenReturn(true);

        final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -389,7 +446,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -435,7 +492,7 @@ public class TouchMonitorTest extends SysuiTestCase {
                Mockito.mock(TouchHandler.TouchSession.Callback.class);

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -453,11 +510,9 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        environment.updateLifecycle(observerOwnerPair -> {
            observerOwnerPair.first.onPause(observerOwnerPair.second);
        });
        environment.updateLifecycle(Lifecycle.State.STARTED);

        environment.verifyInputSessionDispose();
    }
@@ -467,7 +522,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -486,9 +541,7 @@ public class TouchMonitorTest extends SysuiTestCase {

        verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());

        environment.updateLifecycle(observerOwnerPair -> {
            observerOwnerPair.first.onPause(observerOwnerPair.second);
        });
        environment.updateLifecycle(Lifecycle.State.STARTED);

        verify(environment.mInputSession, never()).dispose();

@@ -505,7 +558,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler = createTouchHandler();

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -524,9 +577,7 @@ public class TouchMonitorTest extends SysuiTestCase {

        verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());

        environment.updateLifecycle(observerOwnerPair -> {
            observerOwnerPair.first.onDestroy(observerOwnerPair.second);
        });
        environment.updateLifecycle(Lifecycle.State.DESTROYED);

        // Check to make sure the input session is now disposed.
        environment.verifyInputSessionDispose();
@@ -538,7 +589,7 @@ public class TouchMonitorTest extends SysuiTestCase {
        final TouchHandler touchHandler1 = createTouchHandler();
        final TouchHandler touchHandler2 = createTouchHandler();
        final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -574,7 +625,7 @@ public class TouchMonitorTest extends SysuiTestCase {
                Mockito.mock(TouchHandler.TouchSession.Callback.class);

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));
                .collect(Collectors.toCollection(HashSet::new)), mKosmos);

        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
        environment.publishInputEvent(initialEvent);
@@ -584,9 +635,7 @@ public class TouchMonitorTest extends SysuiTestCase {

        environment.executeAll();

        environment.updateLifecycle(observerOwnerPair -> {
            observerOwnerPair.first.onDestroy(observerOwnerPair.second);
        });
        environment.updateLifecycle(Lifecycle.State.DESTROYED);

        environment.executeAll();

+45 −3
Original line number Diff line number Diff line
@@ -91,7 +91,7 @@ class ConfigurationInteractorTest : SysuiTestCase() {
    @Test
    fun maxBoundsChange_emitsMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.naturalMaxBounds)
            val values by collectValues(underTest.maxBounds)

            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
            runCurrent()
@@ -109,7 +109,7 @@ class ConfigurationInteractorTest : SysuiTestCase() {
    @Test
    fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.naturalMaxBounds)
            val values by collectValues(underTest.maxBounds)

            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
            runCurrent()
@@ -121,6 +121,48 @@ class ConfigurationInteractorTest : SysuiTestCase() {

    @Test
    fun firstMaxBoundsChange_emitsMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.maxBounds)

            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
            runCurrent()

            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
        }

    @Test
    fun maxBoundsChange_emitsNaturalMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.naturalMaxBounds)

            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
            runCurrent()
            updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
            runCurrent()

            assertThat(values)
                .containsExactly(
                    Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
                    Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
                )
                .inOrder()
        }

    @Test
    fun maxBoundsSameOnConfigChange_doesNotEmitNaturalMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.naturalMaxBounds)

            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
            runCurrent()
            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
            runCurrent()

            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
        }

    @Test
    fun firstMaxBoundsChange_emitsNaturalMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.naturalMaxBounds)

@@ -131,7 +173,7 @@ class ConfigurationInteractorTest : SysuiTestCase() {
        }

    @Test
    fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
    fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewNaturalMaxBoundsChange() =
        testScope.runTest {
            val values by collectValues(underTest.naturalMaxBounds)