Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +114 −17 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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() Loading @@ -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 = Loading Loading @@ -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 } ) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +44 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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()); Loading Loading @@ -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(); Loading Loading @@ -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(); }); Loading @@ -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; Loading Loading @@ -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( Loading @@ -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 ); } } } Loading @@ -631,10 +658,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView || physicalNotificationMovement()); } private void updateAppearRect() { updateAppearRect(ClipSide.BOTTOM); } private void updateAppearAnimationAlpha() { updateAppearAnimationContentAlpha( mAppearAnimationFraction, Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +19 −11 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); Loading @@ -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(); Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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(); } } Loading @@ -325,7 +333,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { @Override public int getOutlineTranslation() { if (mCustomOutline) { if (mHasCustomOutline) { return mOutlineRect.left; } if (mDismissUsingRowTranslationX) { Loading @@ -335,7 +343,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { } public void updateOutline() { if (mCustomOutline) { if (mHasCustomOutline) { return; } boolean hasOutline = needsOutline(); Loading @@ -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); Loading Loading @@ -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); Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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(); } Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +114 −17 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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() Loading @@ -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 = Loading Loading @@ -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 } ) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +44 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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()); Loading Loading @@ -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(); Loading Loading @@ -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(); }); Loading @@ -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; Loading Loading @@ -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( Loading @@ -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 ); } } } Loading @@ -631,10 +658,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView || physicalNotificationMovement()); } private void updateAppearRect() { updateAppearRect(ClipSide.BOTTOM); } private void updateAppearAnimationAlpha() { updateAppearAnimationContentAlpha( mAppearAnimationFraction, Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +19 −11 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); Loading @@ -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(); Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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(); } } Loading @@ -325,7 +333,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { @Override public int getOutlineTranslation() { if (mCustomOutline) { if (mHasCustomOutline) { return mOutlineRect.left; } if (mDismissUsingRowTranslationX) { Loading @@ -335,7 +343,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { } public void updateOutline() { if (mCustomOutline) { if (mHasCustomOutline) { return; } boolean hasOutline = needsOutline(); Loading @@ -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); Loading Loading @@ -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); Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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(); } Loading