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

Commit 99c69253 authored by Lyn Han's avatar Lyn Han
Browse files

Expand notifications like accordian

Show all notifications in the same section at the same time.

On each invocation of StackScrollAlgorithm
- compute fraction of section showing based on current expansion amount
- apply that fraction to each view's intrinsic height

The notification icon shelf no longer slides down from top of screen,
instead it shows if the notification section before it is showing.

Bug: 172289889

[Test accordian effect]
Test: open shade with single section, no shelf
Test: open shade with single section, silent section, no shelf
Test: open shade with first section overflowing into shelf
Test: open shade with first section, silent section overflowing into shelf
Test: open shade with no notifications (empty shade view)
Test: open shade with shelf, scroll notifications, then close shade
  => accordian effect applies to scrolled state

[Test for regressions]
Test: add/remove delayed notification that arrives while shade opens
  => animation instantly updates for new notifs (while expansion runs)
Test: open shade from heads up notification
Test: open shade from pulsing (incoming notif on aod)
Test: open shade from lockscreen

Change-Id: If3236b9dc202ee75db7cac51a66c49620556ec10
parent 006b746b
Loading
Loading
Loading
Loading
+91 −34
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;

@@ -82,6 +83,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
    private Rect mClipRect = new Rect();
    private int mCutoutHeight;
    private int mGapHeight;
    private int mIndexOfFirstViewInShelf = -1;
    private int mIndexOfFirstViewInOverflowingSection = -1;

    private NotificationShelfController mController;

    public NotificationShelf(Context context, AttributeSet attrs) {
@@ -159,30 +163,49 @@ public class NotificationShelf extends ActivatableNotificationView implements
    }

    /** Update the state of the shelf. */
    public void updateState(AmbientState ambientState) {
    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
            AmbientState ambientState) {
        ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
        ShelfState viewState = (ShelfState) getViewState();
        if (mShowNotificationShelf && lastView != null) {
            float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
                    + ambientState.getStackTranslation();
            ExpandableViewState lastViewState = lastView.getViewState();
            float viewEnd = lastViewState.yTranslation + lastViewState.height;
            viewState.copyFrom(lastViewState);

            viewState.height = getIntrinsicHeight();
            viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
                    getFullyClosedTranslation());
            viewState.zTranslation = ambientState.getBaseZHeight();
            viewState.clipTopAmount = 0;
            viewState.alpha = 1f - ambientState.getHideAmount();
            viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
            viewState.hideSensitive = false;
            viewState.xTranslation = getTranslationX();
            viewState.hasItemsInStableShelf = lastViewState.inShelf;
            viewState.firstViewInShelf = algorithmState.firstViewInShelf;
            viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
            if (mNotGoneIndex != -1) {
                viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
            }
            viewState.hasItemsInStableShelf = lastViewState.inShelf;

            viewState.hidden = !mAmbientState.isShadeExpanded()
                    || mAmbientState.isQsCustomizerShowing();
                    || mAmbientState.isQsCustomizerShowing()
                    || algorithmState.firstViewInShelf == null;

            final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
                    algorithmState.firstViewInShelf);

            if (mAmbientState.isExpansionChanging()
                    && algorithmState.firstViewInShelf != null
                    && indexOfFirstViewInShelf > 0) {

                // Show shelf if section before it is showing.
                final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get(
                        indexOfFirstViewInShelf - 1);
                if (viewBeforeShelf.getViewState().hidden) {
                    viewState.hidden = true;
                }
            }

            final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
            viewState.yTranslation = stackEnd - viewState.height;
        } else {
            viewState.hidden = true;
            viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -199,13 +222,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
        if (!mShowNotificationShelf) {
            return;
        }

        mShelfIcons.resetViewStates();
        float shelfStart = getTranslationY();
        float numViewsInShelf = 0.0f;
        View lastChild = mAmbientState.getLastVisibleBackgroundChild();
        mNotGoneIndex = -1;
        float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
        //  find the first view that doesn't overlap with the shelf
        int notGoneIndex = 0;
        int colorOfViewBeforeLast = NO_COLOR;
@@ -233,22 +254,37 @@ public class NotificationShelf extends ActivatableNotificationView implements
            if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                continue;
            }

            float notificationClipEnd;
            boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
                    || child.isPinned();
            boolean isLastChild = child == lastChild;
            float rowTranslationY = child.getTranslationY();

            final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
                    expandingAnimated, isLastChild);

            final float stackEnd = mAmbientState.getStackY()
                    + mAmbientState.getStackHeight();
            // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
            if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
                notificationClipEnd = shelfStart + getIntrinsicHeight();
                notificationClipEnd = stackEnd;
            } else if (mAmbientState.isExpansionChanging()) {
                if (mIndexOfFirstViewInOverflowingSection != -1
                    && i >= mIndexOfFirstViewInOverflowingSection) {
                    // Clip notifications in (section overflowing into shelf) to shelf start.
                    notificationClipEnd = shelfStart - mPaddingBetweenElements;
                } else {
                    // Clip notifications before the section overflowing into shelf
                    // to stackEnd because we do not show the shelf if the section right before the
                    // shelf is still hidden.
                    notificationClipEnd = stackEnd;
                }
            } else {
                notificationClipEnd = shelfStart - mPaddingBetweenElements;
            }
            int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex);
            clipTopAmount = Math.max(clipTop, clipTopAmount);

            final float inShelfAmount = updateShelfTransformation(child, scrollingFast,
                    expandingAnimated, isLastChild);
            // If the current row is an ExpandableNotificationRow, update its color, roundedness,
            // and icon state.
            if (child instanceof ExpandableNotificationRow) {
@@ -314,19 +350,23 @@ public class NotificationShelf extends ActivatableNotificationView implements
                                distanceToGapTop / mGapHeight);
                        previousAnv.setBottomRoundness(firstElementRoundness,
                                false /* don't animate */);
                        backgroundTop = (int) distanceToGapBottom;
                    }
                }
                previousAnv = anv;
            }
        }

        clipTransientViews();

        setClipTopAmount(clipTopAmount);
        boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight();
        if (mShowNotificationShelf) {

        boolean isHidden = getViewState().hidden
                || clipTopAmount >= getIntrinsicHeight()
                || !mShowNotificationShelf
                || numViewsInShelf < 1f;

        // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
        setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
        }
        setBackgroundTop(backgroundTop);
        setFirstElementRoundness(firstElementRoundness);
        mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
@@ -339,11 +379,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
                continue;
            }
            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
            updateIconClipAmount(row);
            updateContinuousClipping(row);
        }
        boolean hideBackground = numViewsInShelf < 1.0f;
        setHideBackground(hideBackground || backgroundForceHidden);
        boolean hideBackground = isHidden;
        setHideBackground(hideBackground);
        if (mNotGoneIndex == -1) {
            mNotGoneIndex = notGoneIndex;
        }
@@ -476,10 +515,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
    /**
     * @return the amount how much this notification is in the shelf
     */
    private float updateShelfTransformation(ExpandableView view, boolean scrollingFast,
    private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
            boolean expandingAnimated, boolean isLastChild) {

        // Let calculate how much the view is in the shelf
        // Let's calculate how much the view is in the shelf
        float viewStart = view.getTranslationY();
        int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
        float iconTransformStart = calculateIconTransformationStart(view);
@@ -496,12 +535,18 @@ public class NotificationShelf extends ActivatableNotificationView implements
                    transformDistance,
                    view.getMinHeight() - getIntrinsicHeight());
        }

        float viewEnd = viewStart + fullHeight;
        float fullTransitionAmount = 0.0f;
        float iconTransitionAmount = 0.0f;
        float shelfStart = getTranslationY();

        if (viewEnd >= shelfStart
        if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
            // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
            if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
                fullTransitionAmount = 1f;
                iconTransitionAmount = 1f;
            }
        } else if (viewEnd >= shelfStart
                && (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
                && (mAmbientState.isShadeExpanded()
                || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
@@ -572,7 +617,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
                    && !mNoAnimationsInThisFrame;
        }
        iconState.clampedAppearAmount = clampedAmount;
        setIconTransformationAmount(view, transitionAmount, isLastChild);
        setIconTransformationAmount(view, transitionAmount);
    }

    private boolean isTargetClipped(ExpandableView view) {
@@ -585,12 +630,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
                + view.getContentTranslation()
                + view.getRelativeTopPadding(target)
                + target.getHeight();

        return endOfTarget >= getTranslationY() - mPaddingBetweenElements;
    }

    private void setIconTransformationAmount(ExpandableView view, float transitionAmount,
            boolean isLastChild) {
    private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
        if (!(view instanceof ExpandableNotificationRow)) {
            return;
        }
@@ -601,7 +644,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
            return;
        }
        iconState.alpha = transitionAmount;

        boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
        iconState.hidden = isAppearing
                || (view instanceof ExpandableNotificationRow
@@ -610,8 +652,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
                || (transitionAmount == 0.0f && !iconState.isAnimating(icon))
                || row.isAboveShelf()
                || row.showingPulsing()
                || (!row.isInShelf() && isLastChild)
                || row.getTranslationZ() > mAmbientState.getBaseZHeight();

        iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;

        // Fade in icons at shelf start
@@ -790,8 +832,19 @@ public class NotificationShelf extends ActivatableNotificationView implements
        mController = notificationShelfController;
    }

    public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
    }

    public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
        mIndexOfFirstViewInOverflowingSection =
                mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
    }

    private class ShelfState extends ExpandableViewState {
        private boolean hasItemsInStableShelf;
        private ExpandableView firstViewInShelf;
        private ExpandableView firstViewInOverflowSection;

        @Override
        public void applyToView(View view) {
@@ -800,6 +853,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
            }

            super.applyToView(view);
            setIndexOfFirstViewInShelf(firstViewInShelf);
            setFirstViewInOverflowingSection(firstViewInOverflowSection);
            updateAppearance();
            setHasItemsInStableShelf(hasItemsInStableShelf);
            mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -812,6 +867,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
            }

            super.animateTo(child, properties);
            setIndexOfFirstViewInShelf(firstViewInShelf);
            setFirstViewInOverflowingSection(firstViewInOverflowSection);
            updateAppearance();
            setHasItemsInStableShelf(hasItemsInStableShelf);
            mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+4 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
@@ -103,9 +104,10 @@ public class NotificationShelfController {
        return mView.getHeight();
    }

    public void updateState(AmbientState ambientState) {
    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
            AmbientState ambientState) {
        mAmbientState = ambientState;
        mView.updateState(ambientState);
        mView.updateState(algorithmState, ambientState);
    }

    public int getIntrinsicHeight() {
+78 −5
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ public class AmbientState {
    private NotificationShelf mShelf;
    private int mZDistanceBetweenElements;
    private int mBaseZHeight;
    private int mMaxLayoutHeight;
    private int mContentHeight;
    private ExpandableView mLastVisibleBackgroundChild;
    private float mCurrentScrollVelocity;
    private int mStatusBarState;
@@ -84,6 +84,75 @@ public class AmbientState {
    private boolean mIsShadeOpening;
    private float mSectionPadding;

    /** Distance of top of notifications panel from top of screen. */
    private float mStackY = 0;

    /** Height of notifications panel. */
    private float mStackHeight = 0;

    /** Fraction of shade expansion. */
    private float mExpansionFraction;

    /** Height of the notifications panel without top padding when expansion completes. */
    private float mStackEndHeight;

    /**
     * @return Height of the notifications panel without top padding when expansion completes.
     */
    public float getStackEndHeight() {
        return mStackEndHeight;
    }

    /**
     * @param stackEndHeight Height of the notifications panel without top padding
     *                       when expansion completes.
     */
    public void setStackEndHeight(float stackEndHeight) {
        mStackEndHeight = stackEndHeight;
    }

    /**
     * @param stackY Distance of top of notifications panel from top of screen.
     */
    public void setStackY(float stackY) {
        mStackY = stackY;
    }

    /**
     * @return Distance of top of notifications panel from top of screen.
     */
    public float getStackY() {
        return mStackY;
    }

    /**
     * @param expansionFraction Fraction of shade expansion.
     */
    public void setExpansionFraction(float expansionFraction) {
        mExpansionFraction = expansionFraction;
    }

    /**
     * @return Fraction of shade expansion.
     */
    public float getExpansionFraction() {
        return mExpansionFraction;
    }

    /**
     * @param stackHeight Height of notifications panel.
     */
    public void setStackHeight(float stackHeight) {
        mStackHeight = stackHeight;
    }

    /**
     * @return Height of notifications panel.
     */
    public float getStackHeight() {
        return mStackHeight;
    }

    /** Tracks the state from AlertingNotificationManager#hasNotifications() */
    private boolean mHasAlertEntries;

@@ -263,8 +332,8 @@ public class AmbientState {
        if (mDozeAmount == 1.0f && !isPulseExpanding()) {
            return mShelf.getHeight();
        }
        int height = Math.max(mLayoutMinHeight,
                Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding);
        int height = (int) Math.max(mLayoutMinHeight,
                Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
        if (ignorePulseHeight) {
            return height;
        }
@@ -313,8 +382,12 @@ public class AmbientState {
        return mShelf;
    }

    public void setLayoutMaxHeight(int maxLayoutHeight) {
        mMaxLayoutHeight = maxLayoutHeight;
    public void setContentHeight(int contentHeight) {
        mContentHeight = contentHeight;
    }

    public float getContentHeight() {
        return mContentHeight;
    }

    /**
+29 −1
Original line number Diff line number Diff line
@@ -658,6 +658,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
            y = getHeight() - getEmptyBottomMargin();
            mDebugPaint.setColor(Color.GREEN);
            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);

            y = (int) (mAmbientState.getStackY());
            mDebugPaint.setColor(Color.CYAN);
            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);

            y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
            mDebugPaint.setColor(Color.BLUE);
            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
        }
    }

@@ -1123,11 +1131,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
                mTopPaddingNeedsAnimation = true;
                mNeedsAnimation = true;
            }
            updateStackPosition();
            requestChildrenUpdate();
            notifyHeightChangeListener(null, animate);
        }
    }

    /**
     * Apply expansion fraction to the y position and height of the notifications panel.
     */
    private void updateStackPosition() {
        // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
        mAmbientState.setStackY(
                MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction()));
        final float shadeBottom = getHeight() - getEmptyBottomMargin();
        mAmbientState.setStackEndHeight(shadeBottom - mTopPadding);
        mAmbientState.setStackHeight(
                MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction()));
    }

    /**
     * Update the height of the panel.
     *
@@ -1135,6 +1157,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
     */
    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
    public void setExpandedHeight(float height) {
        final float shadeBottom = getHeight() - getEmptyBottomMargin();
        final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f);
        mAmbientState.setExpansionFraction(expansionFraction);
        updateStackPosition();

        mExpandedHeight = height;
        setIsExpanded(height > 0);
        int minExpansionHeight = getMinExpansionHeight();
@@ -2067,7 +2094,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
        mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
        updateScrollability();
        clampScrollPosition();
        mAmbientState.setLayoutMaxHeight(mContentHeight);
        updateStackPosition();
        mAmbientState.setContentHeight(mContentHeight);
    }

    /**
+4 −0
Original line number Diff line number Diff line
@@ -783,6 +783,10 @@ public class NotificationStackScrollLayoutController {
        return mView.getTranslationX();
    }

    public int indexOfChild(View view) {
        return mView.indexOfChild(view);
    }

    public void setOnHeightChangedListener(
            ExpandableView.OnHeightChangedListener listener) {
        mView.setOnHeightChangedListener(listener);
Loading