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

Commit 0fe07395 authored by Selim Cinek's avatar Selim Cinek
Browse files

Implemented rounded corners for notifications

The corners are now rounded on the top and bottom.

Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.jav
Bug: 69168591
Change-Id: I2586ac7a3dfab7fb47644b8c4c582c2a71a36dbe
parent 94529dff
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -901,6 +901,17 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        contentView.setAlpha(contentAlpha);
    }

    @Override
    protected void applyRoundness() {
        super.applyRoundness();
        applyBackgroundRoundness(getBackgroundRadiusTop(), getBackgroundRadiusBottom());
    }

    protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
        mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
        mBackgroundNormal.setRoundness(topRadius, bottomRadius);
    }

    protected abstract View getContentView();

    public int calculateBgColor() {
+5 −0
Original line number Diff line number Diff line
@@ -2332,6 +2332,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
        }
    }

    @Override
    protected boolean needsContentClipping() {
        return true;
    }

    public boolean isShowingAmbient() {
        return mShowAmbient;
    }
+176 −20
Original line number Diff line number Diff line
@@ -18,12 +18,16 @@ package com.android.systemui.statusbar;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;

import com.android.settingslib.Utils;
import com.android.systemui.R;

/**
@@ -35,6 +39,11 @@ public abstract class ExpandableOutlineView extends ExpandableView {
    private boolean mCustomOutline;
    private float mOutlineAlpha = -1f;
    private float mOutlineRadius;
    private boolean mAlwaysRoundBothCorners;
    private Path mTmpPath = new Path();
    private Path mTmpPath2 = new Path();
    private float mTopRoundness;
    private float mBottomRoundNess;

    /**
     * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +54,208 @@ public abstract class ExpandableOutlineView extends ExpandableView {
    private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
            if (!mCustomOutline) {
                outline.setRoundRect(translation,
                        mClipTopAmount,
                        getWidth() + translation,
                        Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
                        mOutlineRadius);
            } else {
                outline.setRoundRect(mOutlineRect, mOutlineRadius);
            Path clipPath = getClipPath();
            if (clipPath != null && clipPath.isConvex()) {
                // The path might not be convex in border cases where the view is small and clipped
                outline.setConvexPath(clipPath);
            }
            outline.setAlpha(mOutlineAlpha);
        }
    };

    private Path getClipPath() {
        int left;
        int top;
        int right;
        int bottom;
        int height;
        Path intersectPath = null;
        if (!mCustomOutline) {
            left = mShouldTranslateContents ? (int) getTranslation() : 0;
            top = mClipTopAmount;
            right = getWidth() + Math.min(left, 0);
            left = Math.max(left, 0);
            bottom = Math.max(getActualHeight(), top);
            int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
            if (bottom != intersectBottom) {
                getRoundedRectPath(left, top, right,
                        intersectBottom, 0.0f,
                        0.0f, mTmpPath2);
                intersectPath = mTmpPath2;
            }
        } else {
            left = mOutlineRect.left;
            top = mOutlineRect.top;
            right = mOutlineRect.right;
            bottom = mOutlineRect.bottom;
        }
        height = bottom - top;
        if (height == 0) {
            return null;
        }
        float topRoundness = mAlwaysRoundBothCorners
                ? mOutlineRadius : mTopRoundness * mOutlineRadius;
        float bottomRoundness = mAlwaysRoundBothCorners
                ? mOutlineRadius : mBottomRoundNess * mOutlineRadius;
        if (topRoundness + bottomRoundness > height) {
            float overShoot = topRoundness + bottomRoundness - height;
            topRoundness -= overShoot * mTopRoundness
                    / (mTopRoundness + mBottomRoundNess);
            bottomRoundness -= overShoot * mBottomRoundNess
                    / (mTopRoundness + mBottomRoundNess);
        }
        getRoundedRectPath(left, top, right, bottom, topRoundness,
                bottomRoundness, mTmpPath);
        Path roundedRectPath = mTmpPath;
        if (intersectPath != null) {
            roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
        }
        return roundedRectPath;
    }

    protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
            float bottomRoundness) {
        getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
                mTmpPath);
        return mTmpPath;
    }

    private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
            float bottomRoundness, Path outPath) {
        outPath.reset();
        int width = right - left;
        float topRoundnessX = topRoundness;
        float bottomRoundnessX = bottomRoundness;
        topRoundnessX = Math.min(width / 2, topRoundnessX);
        bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
        if (topRoundness > 0.0f) {
            outPath.moveTo(left, top + topRoundness);
            outPath.quadTo(left, top, left + topRoundnessX, top);
            outPath.lineTo(right - topRoundnessX, top);
            outPath.quadTo(right, top, right, top + topRoundness);
        } else {
            outPath.moveTo(left, top);
            outPath.lineTo(right, top);
        }
        if (bottomRoundness > 0.0f) {
            outPath.lineTo(right, bottom - bottomRoundness);
            outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
            outPath.lineTo(left + bottomRoundnessX, bottom);
            outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
        } else {
            outPath.lineTo(right, bottom);
            outPath.lineTo(left, bottom);
        }
        outPath.close();
    }

    public ExpandableOutlineView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOutlineProvider(mProvider);
        initDimens();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.save();
        if (needsContentClipping() && (mAlwaysRoundBothCorners || mTopRoundness > 0
                || mBottomRoundNess > 0 || mCustomOutline)) {
            Path clipPath = getCustomClipPath();
            if (clipPath == null) {
                clipPath = getClipPath();
            }
            if (clipPath != null) {
                canvas.clipPath(clipPath);
            }
        }
        super.dispatchDraw(canvas);
        canvas.restore();
    }

    protected boolean needsContentClipping() {
        return false;
    }

    private void initDimens() {
        Resources res = getResources();
        mShouldTranslateContents =
                res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
        mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
        setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
        mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
        if (!mAlwaysRoundBothCorners) {
            mOutlineRadius = res.getDimensionPixelSize(
                    Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
        }
        setClipToOutline(mAlwaysRoundBothCorners);
    }

    public void setTopRoundness(float topRoundness) {
        if (mTopRoundness != topRoundness) {
            mTopRoundness = topRoundness;
            applyRoundness();
        }
    }

    protected void applyRoundness() {
        invalidateOutline();
        invalidate();
    }

    protected float getBackgroundRadiusTop() {
        return mTopRoundness * mOutlineRadius;
    }

    protected float getTopRoundness() {
        return mTopRoundness;
    }

    protected float getBackgroundRadiusBottom() {
        return mBottomRoundNess * mOutlineRadius;
    }

    public void setBottomRoundNess(float bottomRoundness) {
        if (mBottomRoundNess != bottomRoundness) {
            mBottomRoundNess = bottomRoundness;
            applyRoundness();
        }
    }

    public void onDensityOrFontScaleChanged() {
        initDimens();
        invalidateOutline();
        applyRoundness();
    }

    @Override
    public void setActualHeight(int actualHeight, boolean notifyListeners) {
        int previousHeight = getActualHeight();
        super.setActualHeight(actualHeight, notifyListeners);
        invalidateOutline();
        if (previousHeight != actualHeight) {
            applyRoundness();
        }
    }

    @Override
    public void setClipTopAmount(int clipTopAmount) {
        int previousAmount = getClipTopAmount();
        super.setClipTopAmount(clipTopAmount);
        invalidateOutline();
        if (previousAmount != clipTopAmount) {
            applyRoundness();
        }
    }

    @Override
    public void setClipBottomAmount(int clipBottomAmount) {
        int previousAmount = getClipBottomAmount();
        super.setClipBottomAmount(clipBottomAmount);
        invalidateOutline();
        if (previousAmount != clipBottomAmount) {
            applyRoundness();
        }
    }

    protected void setOutlineAlpha(float alpha) {
        if (alpha != mOutlineAlpha) {
            mOutlineAlpha = alpha;
            invalidateOutline();
            applyRoundness();
        }
    }

@@ -113,8 +269,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
            setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
        } else {
            mCustomOutline = false;
            setClipToOutline(false);
            invalidateOutline();
            applyRoundness();
        }
    }

@@ -151,15 +306,16 @@ public abstract class ExpandableOutlineView extends ExpandableView {

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

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

        // Outlines need to be at least 1 dp
        mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
        mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);

        invalidateOutline();
        applyRoundness();
    }

    public Path getCustomClipPath() {
        return null;
    }
}
+37 −1
Original line number Diff line number Diff line
@@ -19,26 +19,35 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;

import com.android.systemui.R;

/**
 * A view that can be used for both the dimmed and normal background of an notification.
 */
public class NotificationBackgroundView extends View {

    private final boolean mDontModifyCorners;
    private Drawable mBackground;
    private int mClipTopAmount;
    private int mActualHeight;
    private int mClipBottomAmount;
    private int mTintColor;
    private float mTopRoundness;
    private float mBottomRoundness;
    private float[] mCornerRadii = new float[8];

    public NotificationBackgroundView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mDontModifyCorners = getResources().getBoolean(
                R.bool.config_clipNotificationsToOutline);
    }

    @Override
@@ -87,6 +96,7 @@ public class NotificationBackgroundView extends View {
            unscheduleDrawable(mBackground);
        }
        mBackground = background;
        mBackground.mutate();
        if (mBackground != null) {
            mBackground.setCallback(this);
            setTint(mTintColor);
@@ -94,6 +104,7 @@ public class NotificationBackgroundView extends View {
        if (mBackground instanceof RippleDrawable) {
            ((RippleDrawable) mBackground).setForceSoftware(true);
        }
        updateBackgroundRadii();
        invalidate();
    }

@@ -152,4 +163,29 @@ public class NotificationBackgroundView extends View {
    public void setDrawableAlpha(int drawableAlpha) {
        mBackground.setAlpha(drawableAlpha);
    }

    public void setRoundness(float topRoundness, float bottomRoundNess) {
        mCornerRadii[0] = topRoundness;
        mCornerRadii[1] = topRoundness;
        mCornerRadii[2] = topRoundness;
        mCornerRadii[3] = topRoundness;
        mCornerRadii[4] = bottomRoundNess;
        mCornerRadii[5] = bottomRoundNess;
        mCornerRadii[6] = bottomRoundNess;
        mCornerRadii[7] = bottomRoundNess;
        updateBackgroundRadii();
    }


    private void updateBackgroundRadii() {
        if (mDontModifyCorners) {
            return;
        }
        if (mBackground instanceof LayerDrawable) {
            GradientDrawable gradientDrawable =
                    (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
            gradientDrawable.setCornerRadii(mCornerRadii);
        }
    }

}
+39 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.systemui.statusbar.phone.NotificationIconContainer.OVE
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.AttributeSet;
@@ -85,6 +86,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
    private boolean mVibrationOnAnimation;
    private boolean mUserTouchingScreen;
    private boolean mTouchActive;
    private boolean mContentNeedsClipping;
    private int mCustomClipTop;
    private float mFirstElementTopRoundness;

    public NotificationShelf(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -107,6 +111,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
        mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                NotificationPanelView.DOZE_ANIMATION_DURATION);
        mShelfState = new ShelfState();
        setBottomRoundNess(1.0f);
        initDimens();
    }

@@ -252,6 +257,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
        boolean expandingAnimated = mAmbientState.isExpansionChanging()
                && !mAmbientState.isPanelTracking();
        int baseZHeight = mAmbientState.getBaseZHeight();
        boolean contentNeedsClipping = false;
        while (notificationIndex < mHostLayout.getChildCount()) {
            ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
            notificationIndex++;
@@ -302,9 +308,20 @@ public class NotificationShelf extends ActivatableNotificationView implements
            if (notGoneIndex != 0 || !aboveShelf) {
                row.setAboveShelf(false);
            }
            if (notGoneIndex == 0) {
                StatusBarIconView icon = row.getEntry().expandedIcon;
                NotificationIconContainer.IconState iconState = getIconState(icon);
                if (iconState.clampedAppearAmount == 1.0f) {
                    // only if the first icon is fully in the shelf we want to clip to it!
                    mCustomClipTop = (int) (row.getTranslationY() - getTranslationY());
                    mFirstElementTopRoundness = row.getBackgroundRadiusTop();
                    contentNeedsClipping = true;
                }
            }
            notGoneIndex++;
            previousColor = ownColorUntinted;
        }
        setContentNeedsClipping(contentNeedsClipping);
        mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
        mShelfIcons.calculateIconTranslations();
        mShelfIcons.applyIconStates();
@@ -325,6 +342,28 @@ public class NotificationShelf extends ActivatableNotificationView implements
        }
    }

    private void setContentNeedsClipping(boolean contentNeedsClipping) {
        boolean changed = mContentNeedsClipping != contentNeedsClipping;
        mContentNeedsClipping = contentNeedsClipping;
        if (changed || contentNeedsClipping) {
            invalidate();
        }
    }

    @Override
    public Path getCustomClipPath() {
        if (!mContentNeedsClipping) {
            return null;
        }
        return getRoundedRectPath(0, mCustomClipTop, getWidth(), getHeight(),
                mFirstElementTopRoundness, getBackgroundRadiusBottom());
    }

    @Override
    protected boolean needsContentClipping() {
        return mContentNeedsClipping;
    }

    private void updateIconClipAmount(ExpandableNotificationRow row) {
        float maxTop = row.getTranslationY();
        StatusBarIconView icon = row.getEntry().expandedIcon;
Loading