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

Commit 6a74879f authored by András Kurucz's avatar András Kurucz
Browse files

[Flexiglass] Handle remoteInput scrolling for nested notifications

The previous code was not accounting for children inside notification
groups, so the calculated scroll offset was not enough for these
notifications, because it was only the y translation of the row related
to its parent.

Bug: 431275624
Test: click the remoteInput of a nested notification -> stack scrolls to
bring the IME to a visible position
Flag: com.android.systemui.scene_container

Change-Id: I2ac6a5006e727874cac483c269e42fed3d94fa58
parent 71331759
Loading
Loading
Loading
Loading
+65 −0
Original line number Diff line number Diff line
@@ -2056,6 +2056,69 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
        assertThat(mStackScroller.getOwnScrollY()).isEqualTo(100 - childHeight);
    }

    @Test
    @EnableSceneContainer
    public void testRequestScrollToRemoteInput_singleRow() {
        final Consumer<Float> scroller = mock(FloatConsumer.class);
        mStackScroller.setRemoteInputRowBottomBoundConsumer(scroller);

        // GIVEN a non-grouped notification row with specific dimensions and offsets
        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
        when(row.getTranslationY()).thenReturn(100f);
        when(row.getActualHeight()).thenReturn(200);
        when(row.getRemoteInputActionsContainerExpandedOffset()).thenReturn(50f);
        mTestableResources.addOverride(
                com.android.internal.R.dimen.notification_content_margin, 10);

        // WHEN requestScrollToRemoteInput is called
        mStackScroller.requestScrollToRemoteInput(row);

        // THEN the correct bottom bound is sent
        // expected = translationY + height + remoteInputOffset + contentMargin
        // expected = 100 + 200 + 50 + 10 = 360
        verify(scroller).accept(360f);

        // WHEN requestScrollToRemoteInput is called with null
        mStackScroller.requestScrollToRemoteInput(null);

        // THEN null is sent
        verify(scroller).accept(null);
    }

    @Test
    @EnableSceneContainer
    public void testRequestScrollToRemoteInput_childInGroup() {
        final Consumer<Float> scroller = mock(FloatConsumer.class);
        mStackScroller.setRemoteInputRowBottomBoundConsumer(scroller);

        // GIVEN a grouped notification row
        ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
        when(row.getEntryAdapter()).thenReturn(mock(EntryAdapter.class));
        when(mGroupMembershipManger.isChildInGroup(row.getEntryAdapter())).thenReturn(true);
        when(row.getTranslationY()).thenReturn(100f);
        when(row.getActualHeight()).thenReturn(200);
        when(row.getRemoteInputActionsContainerExpandedOffset()).thenReturn(50f);
        when(parent.getTranslationY()).thenReturn(50f);
        when(row.getNotificationParent()).thenReturn(parent);
        mTestableResources.addOverride(
                com.android.internal.R.dimen.notification_content_margin, 10);

        // WHEN requestScrollToRemoteInput is called for the grouped row
        mStackScroller.requestScrollToRemoteInput(row);

        // THEN the correct bottom bound is sent, including the parent's translation
        // expected = parentTranslationY + translationY + height + remoteInputOffset + contentMargin
        // expected = 50 + 100 + 200 + 50 + 10 = 410
        verify(scroller).accept(410f);

        // WHEN requestScrollToRemoteInput is called with null
        mStackScroller.requestScrollToRemoteInput(null);

        // THEN null is sent
        verify(scroller).accept(null);
    }

    private MotionEvent captureTouchSentToSceneFramework() {
        ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
        verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
@@ -2150,6 +2213,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {

    private abstract static class BooleanConsumer implements Consumer<Boolean> { }

    private abstract static class FloatConsumer implements Consumer<Float> { }

    private ShadeScrimShape createScrimShape(int left, int top, int right, int bottom) {
        ShadeScrimBounds bounds = new ShadeScrimBounds(left, top, right, bottom);
        return new ShadeScrimShape(bounds, 0, 0);
+31 −5
Original line number Diff line number Diff line
@@ -760,15 +760,41 @@ public class NotificationStackScrollLayout
        mSectionsManager.reinflateViews();
    }

    void sendRemoteInputRowBottomBound(Float bottom) {
        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
        if (bottom != null) {
            bottom += getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.notification_content_margin);
    /**
     * Requests to scroll to the RemoteInput view of the given row.
     * @param row currently holding the active RemoteInput, or null if the RemoteInput is inactive.
     */
    void requestScrollToRemoteInput(@Nullable ExpandableNotificationRow row) {
        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
            return;
        }
        // When SceneContainer is enabled, the NSSL doesn't handle its own scrolling.
        // Instead, it calculates the required scroll amount and emits it as a "synthetic scroll"
        // event. The Compose-based UI (`NotificationScrollingStack.kt`) consumes this event
        // and performs the actual scroll, synchronizing it with the IME animation.
        Float bottom = (row != null) ? getRemoteInputViewBottom(row) : null;
        mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
    }

    /**
     * Calculates the Y position of the bottom of the remoteInput view, relative to the NSSL.
     *
     * @param row with an active remoteInput
     * @return The bottom Y position of the view relative to this layout.
     */
    private float getRemoteInputViewBottom(ExpandableNotificationRow row) {
        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
        // Calculate the base top position
        float topPosition = row.getTranslationY()
                // For nested notifications, add the parent's translation.
                + (isChildInGroup(row) ? row.getNotificationParent().getTranslationY() : 0f);
        int height = row.getActualHeight();
        float remoteInputOffset = row.getRemoteInputActionsContainerExpandedOffset();
        float contentMargin = getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.notification_content_margin);
        return topPosition + height + remoteInputOffset + contentMargin;
    }

    void updateBgColor() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
+6 −7
Original line number Diff line number Diff line
@@ -1670,28 +1670,27 @@ public class NotificationStackScrollLayoutController implements Dumpable {
            public void setRemoteInputActive(RemoteInputEntryAdapter entry,
                    boolean remoteInputActive) {
                if (SceneContainerFlag.isEnabled()) {
                    sendRemoteInputRowBottomBound(entry, remoteInputActive);
                    setRemoteInputActiveRow(entry, remoteInputActive);
                }
                entry.setRemoteInputActive(mHeadsUpManager, remoteInputActive);
                entry.notifyHeightChanged(true /* needsAnimation */);
            }

            public void lockScrollTo(ExpandableNotificationRow row) {
                if (!SceneContainerFlag.isEnabled()) {
                    mView.lockScrollTo(row);
                }
            }

            public void requestDisallowLongPressAndDismiss() {
                mView.requestDisallowLongPress();
                mView.requestDisallowDismiss();
            }

            private void sendRemoteInputRowBottomBound(RemoteInputEntryAdapter entry,
            private void setRemoteInputActiveRow(RemoteInputEntryAdapter entry,
                    boolean remoteInputActive) {
                ExpandableNotificationRow row = entry.getRow();
                float top = row.getTranslationY();
                int height = row.getActualHeight();
                float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset();
                mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null);
                mView.requestScrollToRemoteInput(remoteInputActive ? row : null);
            }
        };
    }