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

Commit c1bda5b3 authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

Scroll NotificationStack in sync with IME when inline replying

Bug: 264506336
Test: atest NotificationStackScrollLayoutTest, manual, i.e. testing inline replies in the shade when it needs to be scrolled
Change-Id: I3fc98c2f976e3a70b4304851186eae01326e9199
parent 629a3fdf
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -109,6 +109,10 @@ object Flags {
    @JvmField
    val NOTIFICATION_ANIMATE_BIG_PICTURE = unreleasedFlag(120, "notification_animate_big_picture")

    @JvmField
    val ANIMATED_NOTIFICATION_SHADE_INSETS =
        unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)

    // 200 - keyguard/lockscreen
    // ** Flag retired **
    // public static final BooleanFlag KEYGUARD_LAYOUT =
+74 −14
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
@@ -199,6 +200,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
    private final boolean mDebugRemoveAnimation;
    private final boolean mSimplifiedAppearFraction;
    private final boolean mUseRoundnessSourceTypes;
    private boolean mAnimatedInsets;

    private int mContentHeight;
    private float mIntrinsicContentHeight;
@@ -207,7 +209,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
    private int mTopPadding;
    private boolean mAnimateNextTopPaddingChange;
    private int mBottomPadding;
    private int mBottomInset = 0;
    @VisibleForTesting
    int mBottomInset = 0;
    private float mQsExpansionFraction;
    private final int mSplitShadeMinContentHeight;

@@ -388,9 +391,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
            }
        }
    };

    private boolean mPulsing;
    private boolean mScrollable;
    private View mForcedScroll;
    private boolean mIsInsetAnimationRunning;

    private final WindowInsetsAnimation.Callback mInsetsCallback =
            new WindowInsetsAnimation.Callback(
                    WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {

                @Override
                public void onPrepare(WindowInsetsAnimation animation) {
                    mIsInsetAnimationRunning = true;
                }

                @Override
                public WindowInsets onProgress(WindowInsets windowInsets,
                        List<WindowInsetsAnimation> list) {
                    updateBottomInset(windowInsets);
                    return windowInsets;
                }

                @Override
                public void onEnd(WindowInsetsAnimation animation) {
                    mIsInsetAnimationRunning = false;
                }
            };

    /**
     * @see #setHideAmount(float, float)
@@ -584,6 +611,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
        mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
        mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
        setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
        mSectionsManager = Dependency.get(NotificationSectionsManager.class);
        mScreenOffAnimationController =
                Dependency.get(ScreenOffAnimationController.class);
@@ -622,6 +650,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
        mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
        mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        if (mAnimatedInsets) {
            setWindowInsetsAnimationCallback(mInsetsCallback);
        }
    }

    /**
@@ -689,6 +720,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
        mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
    }

    @VisibleForTesting
    void setAnimatedInsetsEnabled(boolean enabled) {
        mAnimatedInsets = enabled;
    }

    @VisibleForTesting
    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
    public void updateFooter() {
@@ -1781,8 +1817,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
            return;
        }
        mForcedScroll = v;
        if (mAnimatedInsets) {
            updateForcedScroll();
        } else {
            scrollTo(v);
        }
    }

    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
    public boolean scrollTo(View v) {
@@ -1813,16 +1853,35 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
                + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
    }

    private void updateBottomInset(WindowInsets windowInsets) {
        mBottomInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;

        if (mForcedScroll != null) {
            updateForcedScroll();
        }

        int range = getScrollRange();
        if (mOwnScrollY > range) {
            setOwnScrollY(range);
        }
    }

    @Override
    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if (!mAnimatedInsets) {
            mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
        }
        mWaterfallTopInset = 0;
        final DisplayCutout cutout = insets.getDisplayCutout();
        if (cutout != null) {
            mWaterfallTopInset = cutout.getWaterfallInsets().top;
        }

        if (mAnimatedInsets && !mIsInsetAnimationRunning) {
            // update bottom inset e.g. after rotation
            updateBottomInset(insets);
        }
        if (!mAnimatedInsets) {
            int range = getScrollRange();
            if (mOwnScrollY > range) {
                // HACK: We're repeatedly getting staggered insets here while the IME is
@@ -1834,6 +1893,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
                // to scroll up some more do so now.
                scrollTo(mForcedScroll);
            }
        }
        return insets;
    }

+19 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack;

import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;

import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
@@ -46,6 +47,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.graphics.Insets;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -54,6 +56,8 @@ import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.widget.TextView;

import androidx.test.annotation.UiThreadTest;
@@ -91,6 +95,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.ArrayList;

/**
 * Tests for {@link NotificationStackScrollLayout}.
 */
@@ -843,6 +849,19 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
        verify(mEmptyShadeView).setFooterText(not(0));
    }

    @Test
    public void testWindowInsetAnimationProgress_updatesBottomInset() {
        int bottomImeInset = 100;
        mStackScrollerInternal.setAnimatedInsetsEnabled(true);
        WindowInsets windowInsets = new WindowInsets.Builder()
                .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
        ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
        mStackScrollerInternal
                .dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations);

        assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset);
    }

    private void setBarStateForTest(int state) {
        // Can't inject this through the listener or we end up on the actual implementation
        // rather than the mock because the spy just coppied the anonymous inner /shruggie.