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

Commit 9a52b474 authored by Lyn's avatar Lyn
Browse files

Short shelf on lockscreen that animates width with transition to shade

NotificationIconContainer provides the shortest shelf width based on
3 icons + 1 overflow dot.

Propagate fraction-to-shade from LockscreenShadeTransitionController
=> NSSLC => NSSL => NotificationShelf, which uses it to calculates shelf width

NSSL requests children update,
then NotificationShelf propagates shelf width
=> NotificationBackgroundView for corner roundness positioning
=> NotificationIconContainer to limit icons to new width

NotificationShelf ensures that only the visible part of the shelf is tappable.

Bug: 213480466
Test: NotificationShelfTest
Test: swipe down on lockscreen notifications, let go
	=> short shelf expands, retracts width
Test: swipe down on lockscreen notifications to go to unlocked shade
	=> short shelf expands to full width
Test: expand notification on lockscreen
	=> short shelf expands to full width as shade opens
Test: tap hidden/visible part of shelf
	=> only visible part of shelf launches full shade
Test: the above in LTR/RTL

Change-Id: I1d3f2a9077e6058d4459bb11602601c035b8043a
parent 9aaf48a2
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -337,11 +337,11 @@ class LockscreenShadeTransitionController @Inject constructor(
            if (field != value || forceApplyAmount) {
                field = value
                if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
                    nsslController.setTransitionToFullShadeAmount(field)
                    notificationPanelController.setTransitionToFullShadeAmount(field,
                            false /* animate */, 0 /* delay */)
                    qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
                    nsslController.setTransitionToFullShadeAmount(field, qSDragProgress)
                    qS.setTransitionToFullShadeAmount(field, qSDragProgress)
                    notificationPanelController.setTransitionToFullShadeAmount(field,
                            false /* animate */, 0 /* delay */)
                    // TODO: appear media also in split shade
                    val mediaAmount = if (useSplitShade) 0f else field
                    mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
+110 −6
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.statusbar;

import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -32,6 +34,7 @@ import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -45,6 +48,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.util.Utils;

/**
 * A notification shelf view that is placed inside the notification scroller. It manages the
@@ -81,6 +85,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
    private int mIndexOfFirstViewInShelf = -1;
    private float mCornerAnimationDistance;
    private NotificationShelfController mController;
    private int mActualWidth = -1;
    private boolean mUseSplitShade;

    /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
    private float mFractionToShade;

    public NotificationShelf(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -122,13 +131,16 @@ public class NotificationShelf extends ActivatableNotificationView implements
        layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
        setLayoutParams(layoutParams);

        int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
        final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
        mShelfIcons.setPadding(padding, 0, padding, 0);
        mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
        mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
        mCornerAnimationDistance = res.getDimensionPixelSize(
                R.dimen.notification_corner_animation_distance);

        // TODO(b/213480466)  enable short shelf on split shade
        mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources());

        mShelfIcons.setInNotificationIconShelf(true);
        if (!mShowNotificationShelf) {
            setVisibility(GONE);
@@ -203,6 +215,10 @@ public class NotificationShelf extends ActivatableNotificationView implements

            final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
            viewState.yTranslation = stackEnd - viewState.height;

            final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN);
            final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade);
            updateStateWidth(viewState, fraction, shortestWidth);
        } else {
            viewState.hidden = true;
            viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -210,6 +226,77 @@ public class NotificationShelf extends ActivatableNotificationView implements
        }
    }

    /**
     * @param shelfState View state for NotificationShelf
     * @param fraction Fraction of lockscreen to shade transition
     * @param shortestWidth Shortest width to use for lockscreen shelf
     */
    @VisibleForTesting
    public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) {
        shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard()
                ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction)
                : getWidth();
    }

    /**
     * @param fractionToShade Fraction of lockscreen to shade transition
     */
    public void setFractionToShade(float fractionToShade) {
        mFractionToShade = fractionToShade;
    }

    /**
     * @return Actual width of shelf, accounting for possible ongoing width animation
     */
    public int getActualWidth() {
        return mActualWidth > -1 ? mActualWidth : getWidth();
    }

    /**
     * @param localX Click x from left of screen
     * @param slop Margin of error within which we count x for valid click
     * @param left Left of shelf, from left of screen
     * @param right Right of shelf, from left of screen
     * @return Whether click x was in view
     */
    @VisibleForTesting
    public boolean isXInView(float localX, float slop, float left, float right) {
        return (left - slop) <= localX && localX < (right + slop);
    }

    /**
     * @param localY Click y from top of shelf
     * @param slop Margin of error within which we count y for valid click
     * @param top Top of shelf
     * @param bottom Height of shelf
     * @return Whether click y was in view
     */
    @VisibleForTesting
    public boolean isYInView(float localY, float slop, float top, float bottom) {
        return (top - slop) <= localY && localY < (bottom + slop);
    }

    /**
     * @param localX Click x
     * @param localY Click y
     * @param slop Margin of error for valid click
     * @return Whether this click was on the visible (non-clipped) part of the shelf
     */
    @Override
    public boolean pointInView(float localX, float localY, float slop) {
        final float containerWidth = getWidth();
        final float shelfWidth = getActualWidth();

        final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
        final float right = isLayoutRtl() ? containerWidth : shelfWidth;

        final float top = mClipTopAmount;
        final float bottom = getActualHeight();

        return isXInView(localX, slop, left, right)
                && isYInView(localY, slop, top, bottom);
    }

    /**
     * Update the shelf appearance based on the other notifications around it. This transforms
     * the icons from the notification area into the shelf.
@@ -732,11 +819,15 @@ public class NotificationShelf extends ActivatableNotificationView implements
        // we always want to clip to our sides, such that nothing can draw outside of these bounds
        int height = getResources().getDisplayMetrics().heightPixels;
        mClipRect.set(0, -height, getWidth(), height);
        if (mShelfIcons != null) {
            mShelfIcons.setClipBounds(mClipRect);
        }
    }

    private void updateRelativeOffset() {
        if (mCollapsedIcons != null) {
            mCollapsedIcons.getLocationOnScreen(mTmp);
        }
        getLocationOnScreen(mTmp);
    }

@@ -831,9 +922,20 @@ public class NotificationShelf extends ActivatableNotificationView implements
        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
    }

    private class ShelfState extends ExpandableViewState {
    public class ShelfState extends ExpandableViewState {
        private boolean hasItemsInStableShelf;
        private ExpandableView firstViewInShelf;
        public int actualWidth = -1;

        private void updateShelfWidth(View view) {
            if (actualWidth < 0) {
                return;
            }
            mActualWidth = actualWidth;
            ActivatableNotificationView anv = (ActivatableNotificationView) view;
            anv.getBackgroundNormal().setActualWidth(actualWidth);
            mShelfIcons.setActualLayoutWidth(actualWidth);
        }

        @Override
        public void applyToView(View view) {
@@ -846,19 +948,21 @@ public class NotificationShelf extends ActivatableNotificationView implements
            updateAppearance();
            setHasItemsInStableShelf(hasItemsInStableShelf);
            mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
            updateShelfWidth(view);
        }

        @Override
        public void animateTo(View child, AnimationProperties properties) {
        public void animateTo(View view, AnimationProperties properties) {
            if (!mShowNotificationShelf) {
                return;
            }

            super.animateTo(child, properties);
            super.animateTo(view, properties);
            setIndexOfFirstViewInShelf(firstViewInShelf);
            updateAppearance();
            setHasItemsInStableShelf(hasItemsInStableShelf);
            mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
            updateShelfWidth(view);
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -162,6 +162,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        updateBackgroundTint();
    }

    /**
     * @return The background of this view.
     */
    public NotificationBackgroundView getBackgroundNormal() {
        return mBackgroundNormal;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
+42 −14
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.view.View;

import com.android.internal.util.ArrayUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;

/**
@@ -39,15 +40,17 @@ 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 final float[] mCornerRadii = new float[8];
    private boolean mBottomIsRounded;
    private int mBackgroundTop;
    private boolean mBottomAmountClips = true;
    private int mActualHeight = -1;
    private int mActualWidth = -1;
    private boolean mExpandAnimationRunning;
    private float mActualWidth;
    private int mExpandAnimationWidth = -1;
    private int mExpandAnimationHeight = -1;
    private int mDrawableAlpha = 255;
    private boolean mIsPressedAllowed;

@@ -59,11 +62,12 @@ public class NotificationBackgroundView extends View {

    @Override
    protected void onDraw(Canvas canvas) {
        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
        if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
                || mExpandAnimationRunning) {
            canvas.save();
            if (!mExpandAnimationRunning) {
                canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
                canvas.clipRect(0, mClipTopAmount, getWidth(),
                        getActualHeight() - mClipBottomAmount);
            }
            draw(canvas, mBackground);
            canvas.restore();
@@ -73,17 +77,23 @@ public class NotificationBackgroundView extends View {
    private void draw(Canvas canvas, Drawable drawable) {
        if (drawable != null) {
            int top = mBackgroundTop;
            int bottom = mActualHeight;
            int bottom = getActualHeight();
            if (mBottomIsRounded
                    && mBottomAmountClips
                    && !mExpandAnimationRunning) {
                bottom -= mClipBottomAmount;
            }
            int left = 0;
            int right = getWidth();
            final boolean isRtl = isLayoutRtl();
            final int width = getWidth();
            final int actualWidth = getActualWidth();

            int left = isRtl ? width - actualWidth : 0;
            int right = isRtl ? width : actualWidth;

            if (mExpandAnimationRunning) {
                left = (int) ((getWidth() - mActualWidth) / 2.0f);
                right = (int) (left + mActualWidth);
                // Horizontally center this background view inside of the container
                left = (int) ((width - actualWidth) / 2.0f);
                right = (int) (left + actualWidth);
            }
            drawable.setBounds(left, top, right, bottom);
            drawable.draw(canvas);
@@ -152,9 +162,27 @@ public class NotificationBackgroundView extends View {
        invalidate();
    }

    public int getActualHeight() {
    private int getActualHeight() {
        if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
            return mExpandAnimationHeight;
        } else if (mActualHeight > -1) {
            return mActualHeight;
        }
        return getHeight();
    }

    public void setActualWidth(int actualWidth) {
        mActualWidth = actualWidth;
    }

    private int getActualWidth() {
        if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
            return mExpandAnimationWidth;
        } else if (mActualWidth > -1) {
            return mActualWidth;
        }
        return getWidth();
    }

    public void setClipTopAmount(int clipTopAmount) {
        mClipTopAmount = clipTopAmount;
@@ -241,9 +269,9 @@ public class NotificationBackgroundView extends View {
    }

    /** Set the current expand animation size. */
    public void setExpandAnimationSize(int actualWidth, int actualHeight) {
        mActualHeight = actualHeight;
        mActualWidth = actualWidth;
    public void setExpandAnimationSize(int width, int height) {
        mExpandAnimationHeight = width;
        mExpandAnimationWidth = height;
        invalidate();
    }

+11 −0
Original line number Diff line number Diff line
@@ -5442,6 +5442,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
        requestChildrenUpdate();
    }

    /**
     * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states.
     *                 Once the lockscreen to shade transition completes and the shade is 100% open
     *                 LockscreenShadeTransitionController resets fraction to 0
     *                 where it remains until the next lockscreen-to-shade transition.
     */
    public void setFractionToShade(float fraction) {
        mShelf.setFractionToShade(fraction);
        requestChildrenUpdate();
    }

    /**
     * Set a listener to when scrolling changes.
     */
Loading