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

Commit 79d9863e authored by Selim Cinek's avatar Selim Cinek
Browse files

Adapted Shelf algorithm to also use conversation icons

Previously the algorithm would only work with a header
and the icon wouldn't transform into the shelf.
This is now fixed.

Bug: 150905003
Test: add conversation notification, observe normal transition
Change-Id: I533b8f06bee29ee93888d748808b4313fef338e8
parent b98be8b6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -5817,7 +5817,7 @@ public class Notification implements Parcelable
                        PorterDuff.Mode.SRC_ATOP);

            }
            contentView.setInt(R.id.notification_header, "setOriginalIconColor",
            contentView.setInt(R.id.icon, "setOriginalIconColor",
                    colorable ? color : NotificationHeaderView.NO_COLOR);
        }

+1 −7
Original line number Diff line number Diff line
@@ -65,7 +65,6 @@ public class NotificationHeaderView extends ViewGroup {
    private View mMicIcon;
    private View mAppOps;
    private View mAudiblyAlertedIcon;
    private int mIconColor;
    private boolean mExpanded;
    private boolean mShowExpandButtonAtEnd;
    private boolean mShowWorkBadgeAtEnd;
@@ -315,13 +314,8 @@ public class NotificationHeaderView extends ViewGroup {
        updateTouchListener();
    }

    @RemotableViewMethod
    public void setOriginalIconColor(int color) {
        mIconColor = color;
    }

    public int getOriginalIconColor() {
        return mIconColor;
        return mIcon.getOriginalIconColor();
    }

    public int getOriginalNotificationColor() {
+10 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ public class CachingIconView extends ImageView {
    private boolean mForceHidden;
    private int mDesiredVisibility;
    private Consumer<Integer> mOnVisibilityChangedListener;
    private int mIconColor;

    @UnsupportedAppUsage
    public CachingIconView(Context context, @Nullable AttributeSet attrs) {
@@ -209,4 +210,13 @@ public class CachingIconView extends ImageView {
    public void setOnVisibilityChangedListener(Consumer<Integer> listener) {
        mOnVisibilityChangedListener = listener;
    }

    @RemotableViewMethod
    public void setOriginalIconColor(int color) {
        mIconColor = color;
    }

    public int getOriginalIconColor() {
        return mIconColor;
    }
}
+179 −109
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.view.DisplayCutout;
import android.view.View;
import android.view.ViewGroup;
@@ -539,55 +540,39 @@ public class NotificationShelf extends ActivatableNotificationView implements
        // Let calculate how much the view is in the shelf
        float viewStart = view.getTranslationY();
        int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
        float iconTransformDistance = getIntrinsicHeight() * 1.5f;
        iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
        iconTransformDistance = Math.min(iconTransformDistance, fullHeight);
        float iconTransformStart = calculateIconTransformationStart(view);

        float transformDistance = getIntrinsicHeight() * 1.5f;
        transformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
        transformDistance = Math.min(transformDistance, fullHeight);

        // Let's make sure the transform distance is
        // at most to the icon (relevant for conversations)
        transformDistance = Math.min(viewStart + fullHeight - iconTransformStart,
                transformDistance);

        if (isLastChild) {
            fullHeight = Math.min(fullHeight, view.getMinHeight() - getIntrinsicHeight());
            iconTransformDistance = Math.min(iconTransformDistance, view.getMinHeight()
            transformDistance = Math.min(transformDistance, view.getMinHeight()
                    - getIntrinsicHeight());
        }
        float viewEnd = viewStart + fullHeight;
        // TODO: fix this check for anchor scrolling.
        if (iconState != null && expandingAnimated && mAmbientState.getScrollY() == 0
                && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
            // We are expanding animated. Because we switch to a linear interpolation in this case,
            // the last icon may be stuck in between the shelf position and the notification
            // position, which looks pretty bad. We therefore optimize this case by applying a
            // shorter transition such that the icon is either fully in the notification or we clamp
            // it into the shelf if it's close enough.
            // We need to persist this, since after the expansion, the behavior should still be the
            // same.
            float position = mAmbientState.getIntrinsicPadding()
                    + mHostLayout.getPositionInLinearLayout(view);
            int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
            if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart
                    && view.getTranslationY() < position) {
                iconState.isLastExpandIcon = true;
                iconState.customTransformHeight = NO_VALUE;
                // Let's check if we're close enough to snap into the shelf
                boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
                        < getIntrinsicHeight();
                if (!forceInShelf) {
                    // We are overlapping the shelf but not enough, so the icon needs to be
                    // repositioned
                    iconState.customTransformHeight = (int) (mMaxLayoutHeight
                            - getIntrinsicHeight() - position);
                }
            }
        }
        handleCustomTransformHeight(view, expandingAnimated, iconState);

        float fullTransitionAmount;
        float transitionAmount;
        float contentTransformationAmount;
        float shelfStart = getTranslationY();
        if (iconState != null && iconState.hasCustomTransformHeight()) {
            fullHeight = iconState.customTransformHeight;
            iconTransformDistance = iconState.customTransformHeight;
        }
        boolean fullyInOrOut = true;
        if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
                && (mAmbientState.isShadeExpanded()
                        || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
            if (viewStart < shelfStart) {
                if (iconState != null && iconState.hasCustomTransformHeight()) {
                    fullHeight = iconState.customTransformHeight;
                    transformDistance = iconState.customTransformHeight;
                }

                float fullAmount = (shelfStart - viewStart) / fullHeight;
                fullAmount = Math.min(1.0f, fullAmount);
                float interpolatedAmount =  Interpolators.ACCELERATE_DECELERATE.getInterpolation(
@@ -596,46 +581,113 @@ public class NotificationShelf extends ActivatableNotificationView implements
                        interpolatedAmount, fullAmount, expandAmount);
                fullTransitionAmount = 1.0f - interpolatedAmount;

                transitionAmount = (shelfStart - viewStart) / iconTransformDistance;
                transitionAmount = Math.min(1.0f, transitionAmount);
                if (isLastChild) {
                    // If it's the last child we should use all of the notification to transform
                    // instead of just to the icon, since that can be quite low.
                    transitionAmount = (shelfStart - viewStart) / transformDistance;
                } else {
                    transitionAmount = (shelfStart - iconTransformStart) / transformDistance;
                }
                transitionAmount = MathUtils.constrain(transitionAmount, 0.0f, 1.0f);
                transitionAmount = 1.0f - transitionAmount;
                fullyInOrOut = false;
            } else {
                fullTransitionAmount = 1.0f;
                transitionAmount = 1.0f;
            }

            // Transforming the content
            contentTransformationAmount = (shelfStart - viewStart) / transformDistance;
            contentTransformationAmount = Math.min(1.0f, contentTransformationAmount);
            contentTransformationAmount = 1.0f - contentTransformationAmount;
        } else {
            fullTransitionAmount = 0.0f;
            transitionAmount = 0.0f;
            contentTransformationAmount = 0.0f;
        }
        if (iconState != null && fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) {
            iconState.isLastExpandIcon = false;
            iconState.customTransformHeight = NO_VALUE;
        }

        // Update the content transformation amount
        if (view.isAboveShelf() || view.showingPulsing()
                || (!isLastChild && iconState != null && !iconState.translateContent)) {
            contentTransformationAmount = 0.0f;
        }
        view.setContentTransformationAmount(contentTransformationAmount, isLastChild);

        // Update the positioning of the icon
        updateIconPositioning(view, transitionAmount, fullTransitionAmount,
                iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
                transformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);

        return fullTransitionAmount;
    }

    /**
     * @return the location where the transformation into the shelf should start.
     */
    private float calculateIconTransformationStart(ExpandableView view) {
        View target = view.getShelfTransformationTarget();
        if (target == null) {
            return view.getTranslationY();
        }
        float start = view.getTranslationY() + view.getRelativeTopPadding(target);

        // Let's not start the transformation right at the icon but by the padding before it.
        start -= view.getShelfIcon().getTop();
        return start;
    }

    private void handleCustomTransformHeight(ExpandableView view, boolean expandingAnimated,
            NotificationIconContainer.IconState iconState) {
        if (iconState != null && expandingAnimated && mAmbientState.getScrollY() == 0
                && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
            // We are expanding animated. Because we switch to a linear interpolation in this case,
            // the last icon may be stuck in between the shelf position and the notification
            // position, which looks pretty bad. We therefore optimize this case by applying a
            // shorter transition such that the icon is either fully in the notification or we clamp
            // it into the shelf if it's close enough.
            // We need to persist this, since after the expansion, the behavior should still be the
            // same.
            float position = mAmbientState.getIntrinsicPadding()
                    + mHostLayout.getPositionInLinearLayout(view);
            int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
            if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart
                    && view.getTranslationY() < position) {
                iconState.isLastExpandIcon = true;
                iconState.customTransformHeight = NO_VALUE;
                // Let's check if we're close enough to snap into the shelf
                boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
                        < getIntrinsicHeight();
                if (!forceInShelf) {
                    // We are overlapping the shelf but not enough, so the icon needs to be
                    // repositioned
                    iconState.customTransformHeight = (int) (mMaxLayoutHeight
                            - getIntrinsicHeight() - position);
                }
            }
        }
    }

    private void updateIconPositioning(ExpandableView view, float iconTransitionAmount,
            float fullTransitionAmount, float iconTransformDistance, boolean scrolling,
            boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
        StatusBarIconView icon = view.getShelfIcon();
        NotificationIconContainer.IconState iconState = getIconState(icon);
        float contentTransformationAmount;
        if (iconState == null) {
            contentTransformationAmount = iconTransitionAmount;
        } else {
            return;
        }
        boolean forceInShelf =
                iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight();
        float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
            if (clampedAmount == fullTransitionAmount) {
        if (iconTransitionAmount == clampedAmount) {
            iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf;
            iconState.useFullTransitionAmount = iconState.noAnimations
                        || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f
                    || (!ICON_ANMATIONS_WHILE_SCROLLING && iconTransitionAmount == 0.0f
                    && scrolling);
            iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
                        && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
                    && iconTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
            iconState.translateContent = mMaxLayoutHeight - getTranslationY()
                    - getIntrinsicHeight() > 0;
        }
@@ -669,15 +721,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
                ? fullTransitionAmount
                : transitionAmount;
        iconState.clampedAppearAmount = clampedAmount;
            contentTransformationAmount = !view.isAboveShelf() && !view.showingPulsing()
                    && (isLastChild || iconState.translateContent)
                    ? iconTransitionAmount
                    : 0.0f;
        setIconTransformationAmount(view, transitionAmount, iconTransformDistance,
                clampedAmount != transitionAmount, isLastChild);
    }
        view.setContentTransformationAmount(contentTransformationAmount, isLastChild);
    }

    private void setIconTransformationAmount(ExpandableView view,
            float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation,
@@ -689,40 +735,63 @@ public class NotificationShelf extends ActivatableNotificationView implements

        StatusBarIconView icon = row.getShelfIcon();
        NotificationIconContainer.IconState iconState = getIconState(icon);
        View rowIcon = row.getShelfTransformationTarget();

        View rowIcon = row.getNotificationIcon();
        float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
        boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
        if (usingLinearInterpolation && !stayingInShelf) {
            // If we interpolate from the notification position, this might lead to a slightly
            // odd interpolation, since the notification position changes as well. Let's interpolate
            // from a fixed distance. We can only do this if we don't animate and the icon is
            // always in the interpolated positon.
            notificationIconPosition = getTranslationY() - iconTransformDistance;
        }
        // Let's resolve the relative positions of the icons
        float notificationIconSize = 0.0f;
        int iconTopPadding;
        int iconStartPadding;
        if (rowIcon != null) {
            iconTopPadding = row.getRelativeTopPadding(rowIcon);
            iconStartPadding = row.getRelativeStartPadding(rowIcon);
            notificationIconSize = rowIcon.getHeight();
        } else {
            iconTopPadding = mIconAppearTopPadding;
            iconStartPadding = 0;
        }
        notificationIconPosition += iconTopPadding;
        float shelfIconPosition = getTranslationY() + icon.getTop();
        float iconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize;
        shelfIconPosition += (icon.getHeight() - icon.getIconScale() * iconSize) / 2.0f;
        float iconYTranslation = NotificationUtils.interpolate(
                notificationIconPosition - shelfIconPosition,

        float shelfIconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize;
        shelfIconSize = shelfIconSize * icon.getIconScale();

        // Get the icon correctly positioned in Y
        float notificationIconPositionY = row.getTranslationY() + row.getContentTranslation();
        float targetYPosition = 0;
        boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
        if (usingLinearInterpolation && !stayingInShelf) {
            // If we interpolate from the notification position, this might lead to a slightly
            // odd interpolation, since the notification position changes as well.
            // Let's instead interpolate directly to the top left of the notification
            targetYPosition =  NotificationUtils.interpolate(
                    Math.min(notificationIconPositionY + mIconAppearTopPadding
                            - getTranslationY(), 0),
                    0,
                    transitionAmount);
        float shelfIconSize = iconSize * icon.getIconScale();
        }
        notificationIconPositionY += iconTopPadding;
        float shelfIconPositionY = getTranslationY() + icon.getTop();
        shelfIconPositionY += (icon.getHeight() - shelfIconSize) / 2.0f;
        float iconYTranslation = NotificationUtils.interpolate(
                notificationIconPositionY - shelfIconPositionY,
                targetYPosition,
                transitionAmount);

        // Get the icon correctly positioned in X
        // Even in RTL it's the left, since we're inverting the location in post
        float shelfIconPositionX = icon.getLeft();
        shelfIconPositionX += (1.0f - icon.getIconScale()) * icon.getWidth() / 2.0f;
        float iconXTranslation = NotificationUtils.interpolate(
                iconStartPadding - shelfIconPositionX,
                mShelfIcons.getActualPaddingStart(),
                transitionAmount);

        // Let's handle the case that there's no Icon
        float alpha = 1.0f;
        boolean noIcon = !row.isShowingIcon();
        if (noIcon) {
            // The view currently doesn't have an icon, lets transform it in!
            alpha = transitionAmount;
            notificationIconSize = shelfIconSize / 2.0f;
            iconXTranslation = mShelfIcons.getActualPaddingStart();
        }
        // The notification size is different from the size in the shelf / statusbar
        float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
@@ -738,6 +807,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
            }
            iconState.alpha = alpha;
            iconState.yTranslation = iconYTranslation;
            iconState.xTranslation = iconXTranslation;
            if (stayingInShelf) {
                iconState.iconAppearAmount = 1.0f;
                iconState.alpha = 1.0f;
@@ -754,7 +824,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
            int backgroundColor = getBackgroundColorWithoutTint();
            int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor);
            if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
                int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor();
                int iconColor = row.getOriginalIconColor();
                shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor,
                        iconState.iconAppearAmount);
            }
+5 −1
Original line number Diff line number Diff line
@@ -913,7 +913,11 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
    }

    private void updatePivot() {
        if (isLayoutRtl()) {
            setPivotX((1 + mIconScale) / 2.0f * getWidth());
        } else {
            setPivotX((1 - mIconScale) / 2.0f * getWidth());
        }
        setPivotY((getHeight() - mIconScale * getWidth()) / 2.0f);
    }

Loading