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

Commit fc0d9e19 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Test and refactors for custom outline gap fix" into main

parents 215fb816 bc2e5e41
Loading
Loading
Loading
Loading
+114 −17
Original line number Diff line number Diff line
@@ -17,10 +17,14 @@ package com.android.systemui.statusbar.notification.row

import android.annotation.ColorInt
import android.graphics.Color
import android.graphics.Outline
import android.graphics.Path
import android.graphics.RectF
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
@@ -32,6 +36,8 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.kotlin.argThat

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -40,12 +46,15 @@ class ActivatableNotificationViewTest : SysuiTestCase() {
    private val mContentView: View = mock()
    private lateinit var mView: ActivatableNotificationView

    @ColorInt
    private var mNormalColor = 0
    @ColorInt private var mNormalColor = 0

    private val finalWidth = 1000
    private val finalHeight = 200

    @Before
    fun setUp() {
        mView = object : ActivatableNotificationView(mContext, null) {
        mView =
            object : ActivatableNotificationView(mContext, null) {

                init {
                    onFinishInflate()
@@ -55,11 +64,13 @@ class ActivatableNotificationViewTest : SysuiTestCase() {
                    return mContentView
                }

            override fun <T : View> findViewTraversal(id: Int): T? = when (id) {
                override fun <T : View> findViewTraversal(id: Int): T? =
                    when (id) {
                        R.id.backgroundNormal -> mock<NotificationBackgroundView>()
                        R.id.fake_shadow -> mock<FakeShadowView>()
                        else -> null
            } as T?
                    }
                        as T?
            }

        mNormalColor =
@@ -109,5 +120,91 @@ class ActivatableNotificationViewTest : SysuiTestCase() {

        mView.clipBottomAmount = 10
        assertThat(mView.backgroundBottom).isEqualTo(90)

    }

    @Test
    fun updateAppearRect_forClipSideBottom_atAnimationStart_setsLocalZeroHeightOutline() {
        // Set state for start of animation
        mView.mTargetPoint = null
        mView.appearAnimationFraction = 0.0f
        mView.setCurrentAppearInterpolator(Interpolators.LINEAR)

        // Call method under test
        mView.updateAppearRect(ExpandableView.ClipSide.BOTTOM, finalWidth, finalHeight)

        // Assert that outline is zero-height rect at local top
        val outline = mock<Outline>()
        mView.outlineProvider.getOutline(mView, outline)

        verify(outline).setPath(argThat { pathArgument: Path -> pathArgument.isEmpty })
    }

    @Test
    fun updateAppearRect_forClipSideBottom_midAnimation_setsLocalPartialHeightOutline() {
        // Set state for mid animation
        val fraction = 0.5f
        mView.mTargetPoint = null
        mView.appearAnimationFraction = fraction
        mView.setCurrentAppearInterpolator(Interpolators.LINEAR)

        // Call method under test
        mView.updateAppearRect(ExpandableView.ClipSide.BOTTOM, finalWidth, finalHeight)

        // Assert that outline has a partial height based on the fraction.
        val outline = mock<Outline>()
        mView.outlineProvider.getOutline(mView, outline)

        verifyOutline(
            outline,
            expectedLeft = 0f,
            expectedTop = 0f,
            expectedRight = finalWidth.toFloat(),
            expectedBottom = finalHeight * fraction,
        )
    }

    @Test
    fun updateAppearRect_forClipSideBottom_atAnimationEnd_setsLocalFullHeightOutline() {
        // Set state for end of animation
        mView.mTargetPoint = null
        mView.appearAnimationFraction = 1.0f
        mView.setCurrentAppearInterpolator(Interpolators.LINEAR)

        // Call method under test
        mView.updateAppearRect(ExpandableView.ClipSide.BOTTOM, finalWidth, finalHeight)

        // Assert that outline has the full final height
        val outline = mock<Outline>()
        mView.outlineProvider.getOutline(mView, outline)

        verifyOutline(
            outline,
            expectedLeft = 0f,
            expectedTop = 0f,
            expectedRight = finalWidth.toFloat(),
            expectedBottom = finalHeight.toFloat(),
        )
    }

    /** Helper to verify the bounds of a Path set on an Outline. */
    private fun verifyOutline(
        outline: Outline,
        expectedLeft: Float,
        expectedTop: Float,
        expectedRight: Float,
        expectedBottom: Float,
    ) {
        verify(outline)
            .setPath(
                argThat { pathArgument: Path ->
                    val bounds = RectF()
                    pathArgument.computeBounds(bounds, /* exact= */ true)
                    bounds.left == expectedLeft &&
                        bounds.top == expectedTop &&
                        bounds.right == expectedRight &&
                        bounds.bottom == expectedBottom
                }
            )
    }
}
+44 −21
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;

import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -118,7 +119,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
    private boolean mShadowHidden;
    private boolean mIsHeadsUpAnimation;
    private boolean mIsHeadsUpCycling;
    /* In order to track headsup longpress coorindate. */
    /* In order to track headsup longpress coordindate. */
    protected Point mTargetPoint;
    private boolean mDismissed;
    private boolean mRefocusOnDismiss;
@@ -137,6 +138,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        updateColors();
    }

    /**
     * @return Fraction of ongoing appear animation.
     */
    public float getAppearAnimationFraction() {
        return mAppearAnimationFraction;
    }

    @VisibleForTesting
    public void setAppearAnimationFraction(float fraction) {
        mAppearAnimationFraction = fraction;
    }

    protected void updateColors() {
        if (notificationRowTransparency()) {
            mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
@@ -179,6 +192,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        mBackgroundNormal.setActualWidth(width);
    }

    @VisibleForTesting
    public void setCurrentAppearInterpolator(Interpolator interpolator) {
        mCurrentAppearInterpolator = interpolator;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
@@ -473,9 +491,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
            updateAppearAnimationAlpha();
            if (NotificationHeadsUpCycling.isEnabled()) {
                // For cycling out, we want the HUN to be clipped from the top.
                updateAppearRect(clipSide);
                updateAppearRect(clipSide, getWidth(), getActualHeight());
            } else {
                updateAppearRect();
                updateAppearRect(clipSide.BOTTOM, getWidth(), getActualHeight());
            }
            invalidate();
        });
@@ -485,9 +503,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        // we need to apply the initial state already to avoid drawn frames in the wrong state
        updateAppearAnimationAlpha();
        if (NotificationHeadsUpCycling.isEnabled()) {
            updateAppearRect(clipSide);
            updateAppearRect(clipSide, getWidth(), getActualHeight());
        } else {
            updateAppearRect();
            updateAppearRect(clipSide.BOTTOM, getWidth(), getActualHeight());
        }
        mAppearAnimator.addListener(new AnimatorListenerAdapter() {
            private boolean mRunWithoutInterruptions;
@@ -591,8 +609,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
    /**
     * Update the View's Rect clipping to fit the appear animation
     * @param clipSide Which side if view we want to clip from
     * @param fullWidth The width of the view.
     * @param fullHeight The actualHeight of the view.
     */
    private void updateAppearRect(ClipSide clipSide) {
    @VisibleForTesting
    public void updateAppearRect(ClipSide clipSide, int fullWidth, int fullHeight) {
        float interpolatedFraction;
        if (useNonLinearAnimation()) {
            interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
@@ -600,28 +621,34 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        } else {
            interpolatedFraction = mAppearAnimationFraction;
        }
        // mAppearAnimationTranslation is used in dispatchDraw to translate the canvas
        mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
        final int fullHeight = getActualHeight();
        float height = fullHeight * interpolatedFraction;
        final float animatingHeight = fullHeight * interpolatedFraction;

        if (mTargetPoint != null) {
            int width = getWidth();
            float fraction = 1 - mAppearAnimationFraction;

            setOutlineRect(mTargetPoint.x * fraction,
                    mAnimationTranslationY
            setOutlineRect(
                    /* left= */ mTargetPoint.x * fraction,
                    /* top= */  mAnimationTranslationY
                            + (mAnimationTranslationY - mTargetPoint.y) * fraction,
                    width - (width - mTargetPoint.x) * fraction,
                    fullHeight - (fullHeight - mTargetPoint.y) * fraction);
                    /* right= */  fullWidth - (fullWidth - mTargetPoint.x) * fraction,
                    /* bottom= */ fullHeight - (fullHeight - mTargetPoint.y) * fraction);
        } else {
            if (clipSide == TOP) {
                setOutlineRect(
                        0,
                        /* top= */ fullHeight - height,
                        getWidth(),
                        /* left= */ 0,
                        /* top= */ fullHeight - animatingHeight,
                        /* right= */ fullWidth,
                        /* bottom= */ fullHeight
                );
            } else if (clipSide == BOTTOM) {
                setOutlineRect(0, 0, getWidth(), height);
                setOutlineRect(
                        /* left= */ 0,
                        /* top= */ 0,
                        /* right= */ fullWidth,
                        /* bottom= */ animatingHeight
                );
            }
        }
    }
@@ -631,10 +658,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
                || physicalNotificationMovement());
    }

    private void updateAppearRect() {
        updateAppearRect(ClipSide.BOTTOM);
    }

    private void updateAppearAnimationAlpha() {
        updateAppearAnimationContentAlpha(
                mAppearAnimationFraction,
+19 −11
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
    private RoundableState mRoundableState;
    private static final Path EMPTY_PATH = new Path();
    private final Rect mOutlineRect = new Rect();
    private boolean mCustomOutline;
    private boolean mHasCustomOutline;
    private float mOutlineAlpha = -1f;
    private boolean mAlwaysRoundBothCorners;
    private Path mTmpPath = new Path();
@@ -59,7 +59,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
    private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            if (!mCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) {
            if (!mHasCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) {
                // Only when translating just the contents, does the outline need to be shifted.
                int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
                int left = Math.max(translation, 0);
@@ -85,15 +85,23 @@ public abstract class ExpandableOutlineView extends ExpandableView {

    @Override
    public int getClipHeight() {
        if (mCustomOutline) {
        if (mHasCustomOutline) {
            return mOutlineRect.height();
        }

        return super.getClipHeight();
    }

    public boolean hasCustomOutline() {
        return mHasCustomOutline;
    }

    public Rect getOutlineRect() {
        return mOutlineRect;
    }

    public int getBackgroundBottom() {
        if (mCustomOutline) {
        if (mHasCustomOutline) {
            return mOutlineRect.bottom;
        }
        return getActualHeight() - getClipBottomAmount();
@@ -106,7 +114,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
        int bottom;
        int height;
        float topRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
        if (!mCustomOutline) {
        if (!mHasCustomOutline) {
            // The outline just needs to be shifted if we're translating the contents. Otherwise
            // it's already in the right place.
            int translation = !mDismissUsingRowTranslationX && !ignoreTranslation
@@ -215,7 +223,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
        // When translating the contents instead of the overall view, we need to make sure we clip
        // rounded to the contents.
        boolean forTranslation = getTranslation() != 0 && !mDismissUsingRowTranslationX;
        return mAlwaysRoundBothCorners || mCustomOutline || forTranslation;
        return mAlwaysRoundBothCorners || mHasCustomOutline || forTranslation;
    }

    private void initDimens() {
@@ -306,7 +314,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
        if (rect != null) {
            setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
        } else {
            mCustomOutline = false;
            mHasCustomOutline = false;
            applyRoundnessAndInvalidate();
        }
    }
@@ -325,7 +333,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {

    @Override
    public int getOutlineTranslation() {
        if (mCustomOutline) {
        if (mHasCustomOutline) {
            return mOutlineRect.left;
        }
        if (mDismissUsingRowTranslationX) {
@@ -335,7 +343,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
    }

    public void updateOutline() {
        if (mCustomOutline) {
        if (mHasCustomOutline) {
            return;
        }
        boolean hasOutline = needsOutline();
@@ -361,7 +369,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
    }

    protected void setOutlineRect(float left, float top, float right, float bottom) {
        mCustomOutline = true;
        mHasCustomOutline = true;

        mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);

@@ -389,7 +397,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {

    protected void dumpCustomOutline(IndentingPrintWriter pw, String[] args) {
        pw.print("CustomOutline: ");
        pw.print("mCustomOutline", mCustomOutline);
        pw.print("mHasCustomOutline", mHasCustomOutline);
        pw.print("mOutlineRect", mOutlineRect);
        pw.print("mOutlineAlpha", mOutlineAlpha);
        pw.print("mAlwaysRoundBothCorners", mAlwaysRoundBothCorners);
+36 −0
Original line number Diff line number Diff line
@@ -153,6 +153,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -5668,6 +5669,39 @@ public class NotificationStackScrollLayout
        mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
    }

    /**
     * Dumps debug info for ActivatableNotificationView appearing with invalid outline
     */
    private void verifyOutline(IndentingPrintWriter pw, ExpandableView ev) {
        if (!(ev instanceof ActivatableNotificationView anv)) {
            return;
        }
        if (!anv.isDrawingAppearAnimation()) {
            return;
        }
        boolean hasInvalidOutline = false;
        StringBuilder detailStr = new StringBuilder();

        if (anv.hasCustomOutline()) {
            Rect or = anv.getOutlineRect();
            if (or.top < 0 || or.bottom < 0 || or.bottom <= or.top) {
                hasInvalidOutline = true;
                detailStr.append(" invalidOutline:(").append(or.top).append(",")
                        .append(or.bottom).append(")");
            }
        }
        if (hasInvalidOutline) {
            String rowKey = (anv instanceof ExpandableNotificationRow)
                    ? ((ExpandableNotificationRow) anv).getKey()
                    : ev.toString();
            pw.print(" [!] Animating INVALID OUTLINE: " + rowKey);
            pw.print(" appearFraction: " + String.format(Locale.US, "%.3f",
                    anv.getAppearAnimationFraction()));
            pw.print(detailStr);
            pw.println();
        }
    }

    public void dump(PrintWriter pwOriginal, String[] args) {
        IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
        final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -5733,6 +5767,8 @@ public class NotificationStackScrollLayout

                    for (int i = 0; i < childCount; i++) {
                        ExpandableView child = getChildAtIndex(i);
                        pw.println();
                        verifyOutline(pw, child);
                        child.dump(pw, args);
                        pw.println();
                    }