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

Commit 887bd1fe authored by Tony's avatar Tony Committed by Tony Wickham
Browse files

Show more shortcuts when last notification is dimissed

We currently only show 2 shortcuts when notifications are
present, but support up to 4 otherwise. With this change,
the hidden shortcuts are added back after dismissing the
notifications, instead of only after closing and reopening
the container.

To ensure the transition is as elegant as possible, we also
separated the shortcuts header from the rest of the shortcuts.
That way we can reveal the new shortcuts without removing the
header (the shortcuts come out from behind the header).

Bug: 38036250
Change-Id: Ie9ab35f9be57cec1d5345e9e70e84e09ea52c9fc
parent 7847d10f
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -22,10 +22,18 @@
    android:elevation="@dimen/deep_shortcuts_elevation">

    <LinearLayout
        android:id="@+id/deep_shortcuts"
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!-- The shortcuts header is added at runtime when necessary. -->

        <LinearLayout
            android:id="@+id/shortcuts"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" />
    </LinearLayout>

</com.android.launcher3.shortcuts.ShortcutsItemView>
+4 −1
Original line number Diff line number Diff line
@@ -21,4 +21,7 @@
    android:layout_height="@dimen/system_shortcut_header_height"
    android:orientation="horizontal"
    android:gravity="end|center_vertical"
    android:background="?attr/popupColorSecondary" />
    android:background="?attr/popupColorSecondary"
    android:elevation="1dp"
    android:outlineProvider="none" />
    <!-- We have elevation so this is drawn on top, but no outline provider to remove shadow -->
+8 −3
Original line number Diff line number Diff line
@@ -86,11 +86,16 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
        return getHeight() - footerHeight;
    }

    public Animator animateHeightRemoval(int heightToRemove) {
    public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
        Rect startRect = new Rect(mPillRect);
        Rect endRect = new Rect(mPillRect);
        if (shouldRemoveFromTop) {
            endRect.top += heightToRemove;
        } else {
            endRect.bottom -= heightToRemove;
        }
        return new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
                mPillRect, endRect, mRoundedCorners).createRevealAnimator(this, false);
                startRect, endRect, mRoundedCorners).createRevealAnimator(this, false);
    }

    public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
+49 −17
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -191,7 +192,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
        // Add dummy views first, and populate with real info when ready.
        PopupPopulator.Item[] itemsToPopulate = PopupPopulator
                .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
        addDummyViews(itemsToPopulate, notificationKeys.size() > 1);
        addDummyViews(itemsToPopulate, notificationKeys.size());

        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
@@ -202,7 +203,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
            mNotificationItemView = null;
            mShortcutsItemView = null;
            itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
            addDummyViews(itemsToPopulate, notificationKeys.size() > 1);
            addDummyViews(itemsToPopulate, notificationKeys.size());

            measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
            orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
@@ -252,8 +253,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
                systemShortcuts, systemShortcutViews));
    }

    private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate,
            boolean notificationFooterHasIcons) {
    private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) {
        final Resources res = getResources();
        final LayoutInflater inflater = mLauncher.getLayoutInflater();

@@ -274,6 +274,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra

            if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
                mNotificationItemView = (NotificationItemView) item;
                boolean notificationFooterHasIcons = numNotifications > 1;
                int footerHeight = notificationFooterHasIcons ?
                        res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
                item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
@@ -316,6 +317,9 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
        int backgroundColor = Themes.getAttrColor(mLauncher, mNotificationItemView == null
                ? R.attr.popupColorPrimary : R.attr.popupColorSecondary);
        mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
        if (numNotifications > 0) {
            mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
        }
    }

    protected PopupItemView getItemViewAt(int index) {
@@ -639,11 +643,22 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
        ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
        BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
        if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
            // There are no more notifications, so create an animation to remove
            // the notifications view and expand the shortcuts view (if possible).
            AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
            int hiddenShortcutsHeight = 0;
            if (mShortcutsItemView != null) {
                hiddenShortcutsHeight = mShortcutsItemView.getHiddenShortcutsHeight();
                int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
                // With notifications gone, all corners of shortcuts item should be rounded.
                mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
                        ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
                removeNotification.play(mShortcutsItemView.showAllShortcuts(mIsAboveIcon));
            }
            final int duration = getResources().getInteger(
                    R.integer.config_removeNotificationViewDuration);
            removeNotification.play(reduceNotificationViewHeight(
                    mNotificationItemView.getHeightMinusFooter(), duration));
            removeNotification.play(adjustItemHeights(mNotificationItemView.getHeightMinusFooter(),
                    hiddenShortcutsHeight, duration));
            Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
                    .setDuration(duration);
            fade.addListener(new AnimatorListenerAdapter() {
@@ -665,12 +680,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
            showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
            removeNotification.playSequentially(hideArrow, showArrow);
            removeNotification.start();
            if (mShortcutsItemView != null) {
                int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
                // With notifications gone, all corners of shortcuts item should be rounded.
                mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
                        ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
            }
            return;
        }
        mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
@@ -689,28 +698,50 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
                mArrow, new PropertyListBuilder().scale(scale).build());
    }

    public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
        return adjustItemHeights(heightToRemove, 0, duration);
    }

    /**
     * Animates the height of the notification item and the translationY of other items accordingly.
     */
    public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
    public Animator adjustItemHeights(int notificationHeightToRemove, int shortcutHeightToAdd,
            int duration) {
        if (mReduceHeightAnimatorSet != null) {
            mReduceHeightAnimatorSet.cancel();
        }
        final int translateYBy = mIsAboveIcon ? heightToRemove : -heightToRemove;
        final int translateYBy = mIsAboveIcon ? notificationHeightToRemove - shortcutHeightToAdd
                : -notificationHeightToRemove;
        mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
        mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(heightToRemove));
        boolean removingNotification =
                notificationHeightToRemove == mNotificationItemView.getHeightMinusFooter();
        boolean shouldRemoveNotificationHeightFromTop = mIsAboveIcon && removingNotification;
        mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(
                notificationHeightToRemove, shouldRemoveNotificationHeightFromTop));
        PropertyResetListener<View, Float> resetTranslationYListener
                = new PropertyResetListener<>(TRANSLATION_Y, 0f);
        boolean itemIsAfterShortcuts = false;
        for (int i = 0; i < getItemCount(); i++) {
            final PopupItemView itemView = getItemViewAt(i);
            if (!mIsAboveIcon && itemView == mNotificationItemView) {
                // The notification view is already in the right place when container is below icon.
            if (itemIsAfterShortcuts) {
                // Every item after the shortcuts item needs to adjust for the new height.
                itemView.setTranslationY(itemView.getTranslationY() - shortcutHeightToAdd);
            }
            if (itemView == mNotificationItemView && (!mIsAboveIcon || removingNotification)) {
                // The notification view is already in the right place.
                continue;
            }
            ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
                    itemView.getTranslationY() + translateYBy).setDuration(duration);
            translateItem.addListener(resetTranslationYListener);
            mReduceHeightAnimatorSet.play(translateItem);
            if (itemView == mShortcutsItemView) {
                itemIsAfterShortcuts = true;
            }
        }
        if (mIsAboveIcon) {
            // We also need to adjust the arrow position to account for the new shortcuts height.
            mArrow.setTranslationY(mArrow.getTranslationY() - shortcutHeightToAdd);
        }
        mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
@@ -720,6 +751,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
                    // container itself did not. This means the items would jump back to their
                    // original translation unless we update the container's translationY here.
                    setTranslationY(getTranslationY() + translateYBy);
                    mArrow.setTranslationY(0);
                }
                mReduceHeightAnimatorSet = null;
            }
+11 −14
Original line number Diff line number Diff line
@@ -52,9 +52,9 @@ import java.util.List;
 */
public class PopupPopulator {

    public static final int MAX_ITEMS = 4;
    public static final int MAX_SHORTCUTS = 4;
    @VisibleForTesting static final int NUM_DYNAMIC = 2;
    private static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
    public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;

    public enum Item {
        SHORTCUT(R.layout.deep_shortcut, true),
@@ -77,10 +77,7 @@ public class PopupPopulator {
        boolean hasNotifications = notificationKeys.size() > 0;
        int numNotificationItems = hasNotifications ? 1 : 0;
        int numShortcuts = shortcutIds.size();
        if (hasNotifications && numShortcuts > MAX_SHORTCUTS_IF_NOTIFICATIONS) {
            numShortcuts = MAX_SHORTCUTS_IF_NOTIFICATIONS;
        }
        int numItems = Math.min(MAX_ITEMS, numShortcuts + numNotificationItems)
        int numItems = Math.min(MAX_SHORTCUTS, numShortcuts) + numNotificationItems
                + systemShortcuts.size();
        Item[] items = new Item[numItems];
        for (int i = 0; i < numItems; i++) {
@@ -126,12 +123,12 @@ public class PopupPopulator {
    };

    /**
     * Filters the shortcuts so that only MAX_ITEMS or fewer shortcuts are retained.
     * Filters the shortcuts so that only MAX_SHORTCUTS or fewer shortcuts are retained.
     * We want the filter to include both static and dynamic shortcuts, so we always
     * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present.
     *
     * @param shortcutIdToRemoveFirst An id that should be filtered out first, if any.
     * @return a subset of shortcuts, in sorted order, with size <= MAX_ITEMS.
     * @return a subset of shortcuts, in sorted order, with size <= MAX_SHORTCUTS.
     */
    public static List<ShortcutInfoCompat> sortAndFilterShortcuts(
            List<ShortcutInfoCompat> shortcuts, @Nullable String shortcutIdToRemoveFirst) {
@@ -147,27 +144,27 @@ public class PopupPopulator {
        }

        Collections.sort(shortcuts, SHORTCUT_RANK_COMPARATOR);
        if (shortcuts.size() <= MAX_ITEMS) {
        if (shortcuts.size() <= MAX_SHORTCUTS) {
            return shortcuts;
        }

        // The list of shortcuts is now sorted with static shortcuts followed by dynamic
        // shortcuts. We want to preserve this order, but only keep MAX_ITEMS.
        List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_ITEMS);
        // shortcuts. We want to preserve this order, but only keep MAX_SHORTCUTS.
        List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_SHORTCUTS);
        int numDynamic = 0;
        int size = shortcuts.size();
        for (int i = 0; i < size; i++) {
            ShortcutInfoCompat shortcut = shortcuts.get(i);
            int filteredSize = filteredShortcuts.size();
            if (filteredSize < MAX_ITEMS) {
                // Always add the first MAX_ITEMS to the filtered list.
            if (filteredSize < MAX_SHORTCUTS) {
                // Always add the first MAX_SHORTCUTS to the filtered list.
                filteredShortcuts.add(shortcut);
                if (shortcut.isDynamic()) {
                    numDynamic++;
                }
                continue;
            }
            // At this point, we have MAX_ITEMS already, but they may all be static.
            // At this point, we have MAX_SHORTCUTS already, but they may all be static.
            // If there are dynamic shortcuts, remove static shortcuts to add them.
            if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) {
                numDynamic++;
Loading