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

Commit 2546ce3d authored by Coco Duan's avatar Coco Duan
Browse files

Update complication layout engine to be responsive

Enable the layout engine to dynamically update complication
positions and margins using their latest responsive layout params.

Bug: b/378163332
Fixes: b/378163332
Flag: android.service.dreams.dreams_v2
Test: on foldable and tablet
Test: atest ComplicationLayoutEngineTest
Change-Id: I51bc0d37a1edc36fedee7cfda55b37171cd8944c
parent 74040f2e
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import static android.service.dreams.Flags.FLAG_DREAMS_V2;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -165,7 +166,7 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
        mKosmos.getConfigurationRepository().onConfigurationChange(config);
        mKosmos.getTestScope().getTestScheduler().runCurrent();

        verify(mLayoutEngine).updateLayoutEngine(bounds);
        verify(mLayoutEngine).updateLayoutEngine(eq(bounds), anyMap());
    }

    @Test
@@ -183,7 +184,7 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
        mKosmos.getConfigurationRepository().onConfigurationChange(config);
        mKosmos.getTestScope().getTestScheduler().runCurrent();

        verify(mLayoutEngine, never()).updateLayoutEngine(bounds);
        verify(mLayoutEngine, never()).updateLayoutEngine(eq(bounds), anyMap());
    }

    /**
+242 −4
Original line number Diff line number Diff line
@@ -48,7 +48,9 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -64,12 +66,15 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {

    Margins mMargins = new Margins(0, 0, 0, 0);

    int mSpacing = 0;
    ComplicationLayoutEngine createComplicationLayoutEngine() {
        return createComplicationLayoutEngine(0);
    }

    ComplicationLayoutEngine createComplicationLayoutEngine(int spacing) {
        return new ComplicationLayoutEngine(mLayout, spacing, () -> mMargins, mTouchSession, 0, 0);
        mSpacing = spacing;
        return new ComplicationLayoutEngine(
                mLayout, () -> mSpacing, () -> mMargins, mTouchSession, 0, 0);
    }

    @Before
@@ -222,7 +227,12 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
        final int newTopMargin = rand.nextInt();
        final int newEndMargin = rand.nextInt();
        mMargins = new Margins(0, newTopMargin, newEndMargin, 0);
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000));

        final Map<ComplicationId, ComplicationLayoutParams> complicationLayoutParams =
                new HashMap<>();
        complicationLayoutParams.put(firstViewInfo.id, firstViewInfo.lp);
        complicationLayoutParams.put(secondViewInfo.id, secondViewInfo.lp);
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000), complicationLayoutParams);

        // Ensure complication view have new margins
        verifyChange(firstViewInfo, false, lp -> {
@@ -253,7 +263,10 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
        addComplication(engine, viewInfo);
        viewInfo.clearInvocations();

        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000));
        final Map<ComplicationId, ComplicationLayoutParams> complicationLayoutParams =
                new HashMap<>();
        complicationLayoutParams.put(viewInfo.id, viewInfo.lp);
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000), complicationLayoutParams);
        // Views won't be updated.
        verify(viewInfo.view, never()).setLayoutParams(any());
    }
@@ -276,11 +289,236 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
        addComplication(engine, viewInfo);
        viewInfo.clearInvocations();

        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000));
        final Map<ComplicationId, ComplicationLayoutParams> complicationLayoutParams =
                new HashMap<>();
        complicationLayoutParams.put(viewInfo.id, viewInfo.lp);
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000), complicationLayoutParams);
        // Views will be updated.
        verify(viewInfo.view).setLayoutParams(any());
    }

    @Test
    @EnableFlags(Flags.FLAG_DREAMS_V2)
    public void updateLayoutEngine_whenSpacingBetweenComplicationsChanges() {
        final Random rand = new Random();
        final int spacing = rand.nextInt();
        final ComplicationLayoutEngine engine = createComplicationLayoutEngine(spacing);
        final ViewInfo firstViewInfo = new ViewInfo(
                new ComplicationLayoutParams(
                        100,
                        100,
                        ComplicationLayoutParams.POSITION_TOP
                                | ComplicationLayoutParams.POSITION_END,
                        ComplicationLayoutParams.DIRECTION_DOWN,
                        0),
                Complication.CATEGORY_SYSTEM,
                mLayout);

        addComplication(engine, firstViewInfo);

        final ViewInfo secondViewInfo = new ViewInfo(
                new ComplicationLayoutParams(
                        100,
                        100,
                        ComplicationLayoutParams.POSITION_TOP
                                | ComplicationLayoutParams.POSITION_END,
                        ComplicationLayoutParams.DIRECTION_DOWN,
                        0),
                Complication.CATEGORY_SYSTEM,
                mLayout);

        addComplication(engine, secondViewInfo);

        firstViewInfo.clearInvocations();
        secondViewInfo.clearInvocations();

        // Triggers an update to the layout engine with new spacing between complications.
        final int newSpacing = rand.nextInt();
        mSpacing = newSpacing;
        final Map<ComplicationId, ComplicationLayoutParams> complicationLayoutParams =
                new HashMap<>();
        complicationLayoutParams.put(firstViewInfo.id, firstViewInfo.lp);
        complicationLayoutParams.put(secondViewInfo.id, secondViewInfo.lp);
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000), complicationLayoutParams);

        // Ensure complication views have new spacing.
        verifyChange(firstViewInfo, false, lp -> {
            assertThat(lp.topMargin).isEqualTo(mMargins.top);
            assertThat(lp.getMarginEnd()).isEqualTo(mMargins.end);
        });
        verifyChange(secondViewInfo, false, lp -> {
            assertThat(lp.topMargin).isEqualTo(newSpacing);
            assertThat(lp.getMarginEnd()).isEqualTo(mMargins.end);
        });
    }

    @Test
    @EnableFlags(Flags.FLAG_DREAMS_V2)
    public void updateLayoutEngine_repositionComplications_withNewDirection() {
        final Random rand = new Random();
        final int spacing = rand.nextInt();
        mMargins = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(), rand.nextInt());
        final ComplicationLayoutEngine engine = createComplicationLayoutEngine(spacing);
        final ViewInfo firstViewInfo = new ViewInfo(
                new ComplicationLayoutParams(
                        100,
                        100,
                        ComplicationLayoutParams.POSITION_BOTTOM
                                | ComplicationLayoutParams.POSITION_START,
                        ComplicationLayoutParams.DIRECTION_END,
                        1),
                Complication.CATEGORY_SYSTEM,
                mLayout);
        addComplication(engine, firstViewInfo);

        verifyChange(firstViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToStart == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(mMargins.start);
        });

        final ViewInfo secondViewInfo = new ViewInfo(
                new ComplicationLayoutParams(
                        100,
                        100,
                        ComplicationLayoutParams.POSITION_BOTTOM
                                | ComplicationLayoutParams.POSITION_START,
                        ComplicationLayoutParams.DIRECTION_END,
                        0),
                Complication.CATEGORY_SYSTEM,
                mLayout);
        addComplication(engine, secondViewInfo);

        // Second complication is to the right of the first complication.
        verifyChange(secondViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToEnd == firstViewInfo.view.getId()).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(spacing);
        });
        firstViewInfo.clearInvocations();
        secondViewInfo.clearInvocations();

        // Triggers an update with new direction in layout params for each complication.
        final Map<ComplicationId, ComplicationLayoutParams> newParams = new HashMap<>();
        newParams.put(firstViewInfo.id, new ComplicationLayoutParams(
                100,
                100,
                ComplicationLayoutParams.POSITION_BOTTOM
                        | ComplicationLayoutParams.POSITION_START,
                ComplicationLayoutParams.DIRECTION_UP,
                1)
        );
        newParams.put(secondViewInfo.id, new ComplicationLayoutParams(
                100,
                100,
                ComplicationLayoutParams.POSITION_BOTTOM
                        | ComplicationLayoutParams.POSITION_START,
                ComplicationLayoutParams.DIRECTION_UP,
                0)
        );
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000), newParams);

        // Second complication is on top of the first complication.
        verifyChange(firstViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToStart == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(mMargins.start);
        });
        verifyChange(secondViewInfo, false, lp -> {
            assertThat(lp.bottomToTop == firstViewInfo.view.getId()).isTrue();
            assertThat(lp.startToStart == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(spacing);
            assertThat(lp.getMarginStart()).isEqualTo(mMargins.start);
        });
    }

    @Test
    @EnableFlags(Flags.FLAG_DREAMS_V2)
    public void updateLayoutEngine_repositionComplications_withNewWeight() {
        final Random rand = new Random();
        final int spacing = rand.nextInt();
        mMargins = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(), rand.nextInt());
        final ComplicationLayoutEngine engine = createComplicationLayoutEngine(spacing);
        final ViewInfo firstViewInfo = new ViewInfo(
                new ComplicationLayoutParams(
                        100,
                        100,
                        ComplicationLayoutParams.POSITION_BOTTOM
                                | ComplicationLayoutParams.POSITION_START,
                        ComplicationLayoutParams.DIRECTION_END,
                        1),
                Complication.CATEGORY_SYSTEM,
                mLayout);
        addComplication(engine, firstViewInfo);

        verifyChange(firstViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToStart == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(mMargins.start);
        });

        final ViewInfo secondViewInfo = new ViewInfo(
                new ComplicationLayoutParams(
                        100,
                        100,
                        ComplicationLayoutParams.POSITION_BOTTOM
                                | ComplicationLayoutParams.POSITION_START,
                        ComplicationLayoutParams.DIRECTION_END,
                        0),
                Complication.CATEGORY_SYSTEM,
                mLayout);
        addComplication(engine, secondViewInfo);

        // Second complication is after the first complication.
        verifyChange(secondViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToEnd == firstViewInfo.view.getId()).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(spacing);
        });
        firstViewInfo.clearInvocations();
        secondViewInfo.clearInvocations();

        // Triggers an update with new weight in layout params for complications.
        final Map<ComplicationId, ComplicationLayoutParams> newParams = new HashMap<>();
        newParams.put(firstViewInfo.id, new ComplicationLayoutParams(
                100,
                100,
                ComplicationLayoutParams.POSITION_BOTTOM
                        | ComplicationLayoutParams.POSITION_START,
                ComplicationLayoutParams.DIRECTION_END,
                1)
        );
        // Second view has larger weight than the first view.
        newParams.put(secondViewInfo.id, new ComplicationLayoutParams(
                100,
                100,
                ComplicationLayoutParams.POSITION_BOTTOM
                        | ComplicationLayoutParams.POSITION_START,
                ComplicationLayoutParams.DIRECTION_END,
                2)
        );
        engine.updateLayoutEngine(new Rect(0, 0, 800, 1000), newParams);

        // Second complication is in front of the first complication.
        verifyChange(secondViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToStart == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(mMargins.start);
        });
        verifyChange(firstViewInfo, false, lp -> {
            assertThat(lp.bottomToBottom == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
            assertThat(lp.startToEnd == secondViewInfo.view.getId()).isTrue();
            assertThat(lp.bottomMargin).isEqualTo(mMargins.bottom);
            assertThat(lp.getMarginStart()).isEqualTo(spacing);
        });
    }

    /**
     * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
     */
+3 −0
Original line number Diff line number Diff line
@@ -2070,6 +2070,9 @@

    <!-- The margin applied between complications -->
    <dimen name="dream_overlay_complication_margin">0dp</dimen>
    <dimen name="dream_overlay_complication_small_margin">1dp</dimen>
    <dimen name="dream_overlay_complication_medium_margin">9dp</dimen>
    <dimen name="dream_overlay_complication_large_margin">24dp</dimen>

    <dimen name="dream_overlay_y_offset">80dp</dimen>
    <dimen name="dream_overlay_entry_y_offset">40dp</dimen>
+13 −0
Original line number Diff line number Diff line
@@ -42,4 +42,17 @@ object WindowSizeUtils {
        val width = metrics.bounds.width()
        return width / metrics.density < COMPACT_WIDTH.value
    }

    /** Whether the window size reflects most tablet sizes. */
    @JvmStatic
    fun isTabletWindowSize(context: Context): Boolean {
        val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
        val width = metrics.bounds.width() / metrics.density
        val height = metrics.bounds.height() / metrics.density
        return height >= EXPANDED_HEIGHT.value ||
            (width >= MEDIUM_WIDTH.value &&
                height in COMPACT_HEIGHT.value..EXPANDED_HEIGHT.value &&
                // aspect ratio to exclude unfolded screen size
                width / height >= 1.5f)
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import kotlinx.coroutines.CoroutineDispatcher;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.inject.Inject;
@@ -100,7 +101,7 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
            collectFlow(
                    view,
                    configurationInteractor.getMaxBounds(),
                    mLayoutEngine::updateLayoutEngine,
                    this::updateLayoutEngine,
                    mainDispatcher
            );
        }
@@ -125,6 +126,14 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
        return region;
    }

    private void updateLayoutEngine(Rect bounds) {
        // Get the latest screen responsive layoutParams for each complication
        final Map<ComplicationId, ComplicationLayoutParams> latestComplicationLayoutParams =
                mComplications.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
                        entry -> entry.getValue().getLayoutParams()));
        mLayoutEngine.updateLayoutEngine(bounds, latestComplicationLayoutParams);
    }

    private void updateComplications(Collection<ComplicationViewModel> complications) {
        if (DEBUG) {
            Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25));
Loading