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

Commit 4f475b27 authored by Shery Sheng's avatar Shery Sheng Committed by Android (Google) Code Review
Browse files

Merge "[Notification] Show animated replies/actions before regular ones" into main

parents 50ecbee2 370e1f90
Loading
Loading
Loading
Loading
+66 −22
Original line number Original line Diff line number Diff line
@@ -34,6 +34,7 @@ import android.graphics.drawable.Icon
import android.os.Build
import android.os.Build
import android.os.Bundle
import android.os.Bundle
import android.os.SystemClock
import android.os.SystemClock
import android.service.notification.StatusBarNotification
import android.text.Annotation
import android.text.Annotation
import android.text.SpannableStringBuilder
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.Spanned
@@ -46,7 +47,6 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.Button
import android.widget.Button
import android.service.notification.StatusBarNotification
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.content.res.AppCompatResources
import com.android.systemui.Flags
import com.android.systemui.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter
@@ -138,6 +138,42 @@ constructor(
    override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState =
    override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState =
        chooseSmartRepliesAndActions(entry)
        chooseSmartRepliesAndActions(entry)


    private fun Button.getButtonType(): SmartButtonType? {
        return (this.layoutParams as? SmartReplyView.LayoutParams)?.mButtonType
    }

    private fun getSortedButtons(
        smartReplyButtons: List<Button>,
        smartActionButtons: List<Button>
    ): List<Button> {
        val finalCombinedButtons: List<Button>

        if (Flags.notificationAnimatedActionsTreatment()) {
            // Order is Animated Replies -> Animated Actions -> Smart Replies -> Smart Actions

            // Filter buttons based on their type for the animated treatment
            val animatedReplyButtons = smartReplyButtons.filter {
                it.getButtonType() == SmartButtonType.ANIMATED_REPLY
            }
            val regularReplyButtons = smartReplyButtons.filter {
                it.getButtonType() == SmartButtonType.REPLY
            }

            val animatedActionButtons = smartActionButtons.filter {
                it.getButtonType() == SmartButtonType.ANIMATED_ACTION
            }
            val regularActionButtons = smartActionButtons.filter {
                it.getButtonType() == SmartButtonType.ACTION
            }

            finalCombinedButtons =
                animatedReplyButtons + animatedActionButtons + regularReplyButtons + regularActionButtons
        } else {
            // Order is Smart Replies -> Smart Actions
            finalCombinedButtons = smartReplyButtons + smartActionButtons
        }
        return finalCombinedButtons
    }
    override fun inflateSmartReplyViewHolder(
    override fun inflateSmartReplyViewHolder(
        sysuiContext: Context,
        sysuiContext: Context,
        notifPackageContext: Context,
        notifPackageContext: Context,
@@ -147,7 +183,7 @@ constructor(
    ): InflatedSmartReplyViewHolder {
    ): InflatedSmartReplyViewHolder {
        if (!shouldShowSmartReplyView(entry.sbn, newSmartReplyState)) {
        if (!shouldShowSmartReplyView(entry.sbn, newSmartReplyState)) {
            return InflatedSmartReplyViewHolder(
            return InflatedSmartReplyViewHolder(
                null /* smartReplyView */,
                null, /* smartReplyView */
                null, /* smartSuggestionButtons */
                null, /* smartSuggestionButtons */
            )
            )
        }
        }
@@ -174,7 +210,7 @@ constructor(
                        delayOnClickListener,
                        delayOnClickListener,
                    )
                    )
                }
                }
            } ?: emptySequence()
            } ?.toList() ?: emptyList()


        val smartActionButtons =
        val smartActionButtons =
            newSmartReplyState.smartActions?.let { smartActions ->
            newSmartReplyState.smartActions?.let { smartActions ->
@@ -194,14 +230,13 @@ constructor(
                            themedPackageContext,
                            themedPackageContext,
                        )
                        )
                    }
                    }
            } ?: emptySequence()
            }?.toList() ?: emptyList()


        return InflatedSmartReplyViewHolder(
        return InflatedSmartReplyViewHolder(
            smartReplyView,
            smartReplyView,
            (smartReplyButtons + smartActionButtons).toList(),
            getSortedButtons(smartReplyButtons, smartActionButtons)
        )
        )
    }
    }

    /**
    /**
     * Chose what smart replies and smart actions to display. App generated suggestions take
     * Chose what smart replies and smart actions to display. App generated suggestions take
     * precedence. So if the app provides any smart replies, we don't show any replies or actions
     * precedence. So if the app provides any smart replies, we don't show any replies or actions
@@ -448,9 +483,14 @@ constructor(
                        DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
                        DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
                    else onClickListener
                    else onClickListener
                )
                )

                if (isAnimatedAction) {
                    (layoutParams as SmartReplyView.LayoutParams).mButtonType =
                        SmartButtonType.ANIMATED_ACTION
                } else {
                    // Mark this as an Action button
                    // Mark this as an Action button
                (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
                    (layoutParams as SmartReplyView.LayoutParams).mButtonType =
                        SmartButtonType.ACTION
                }
            }
            }
    }
    }


@@ -579,9 +619,13 @@ constructor(
                            info.addAction(action)
                            info.addAction(action)
                        }
                        }
                    }
                    }
                // TODO: probably shouldn't do this here, bad API
                if (enableAnimatedReply) {
                // Mark this as a Reply button
                    (layoutParams as SmartReplyView.LayoutParams).mButtonType =
                (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
                        SmartButtonType.ANIMATED_REPLY
                } else {
                    (layoutParams as SmartReplyView.LayoutParams).mButtonType =
                        SmartButtonType.REPLY
                }
            }
            }
    }
    }


+39 −8
Original line number Original line Diff line number Diff line
package com.android.systemui.statusbar.policy;
package com.android.systemui.statusbar.policy;


import static java.lang.Float.NaN;
import static java.lang.Float.NaN;

import com.android.systemui.Flags;
import android.annotation.ColorInt;
import android.annotation.ColorInt;
import android.app.Notification;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.PendingIntent;
@@ -242,14 +242,42 @@ public class SmartReplyView extends ViewGroup {
                0 /* maxChildHeight */);
                0 /* maxChildHeight */);
        int displayedChildCount = 0;
        int displayedChildCount = 0;


        // Set up a list of suggestions where actions come before replies. Note that the Buttons
        // Determine which smart suggestions (replies and actions of various types)
        // themselves have already been added to the view hierarchy in an order such that Smart
        // can be displayed within the available space.
        // Replies are shown before Smart Actions. The order of the list below determines which
        //
        // suggestions will be shown at all - only the first X elements are shown (where X depends
        // A prioritized list of all potential suggestion buttons (`smartSuggestions`) is
        // on how much space each suggestion button needs).
        // constructed to define the *selection priority* for fitting buttons onto a single line.
        // When `Flags.notificationAnimatedActionsTreatment()` is enabled, this selection
        // priority is:
        //   1. Animated Replies
        //   2. Animated Actions
        //   3. Standard Actions
        //   4. Standard Replies
        // Otherwise, if the flag is disabled, the selection priority is:
        //   1. Standard Actions
        //   2. Standard Replies
        //
        // Buttons are iterated in this selection priority order. If a button fits (possibly after
        // squeezing preceding, lower-priority, single-line buttons that are candidates for
        // squeezing),
        // it's marked for display by setting its `LayoutParams.show = true`.
        //
        // The final left-to-right visual layout of the *displayed* buttons is determined by
        // their initial sequence when added to this ViewGroup via `addPreInflatedButtons`.
        // Refer to that method and the `SmartReplyStateInflater` for how that initial
        // button order is established.
        List<View> smartSuggestions = new ArrayList<>();
        List<View> smartActions = filterActionsOrReplies(SmartButtonType.ACTION);
        List<View> smartActions = filterActionsOrReplies(SmartButtonType.ACTION);
        List<View> smartReplies = filterActionsOrReplies(SmartButtonType.REPLY);
        List<View> smartReplies = filterActionsOrReplies(SmartButtonType.REPLY);
        List<View> smartSuggestions = new ArrayList<>(smartActions);

        if (Flags.notificationAnimatedActionsTreatment()) {
            List<View> animatedReplies = filterActionsOrReplies(SmartButtonType.ANIMATED_REPLY);
            List<View> animatedActions = filterActionsOrReplies(SmartButtonType.ANIMATED_ACTION);
            smartSuggestions.addAll(animatedReplies);
            smartSuggestions.addAll(animatedActions);
        }

        smartSuggestions.addAll(smartActions);
        smartSuggestions.addAll(smartReplies);
        smartSuggestions.addAll(smartReplies);
        List<View> coveredSuggestions = new ArrayList<>();
        List<View> coveredSuggestions = new ArrayList<>();


@@ -764,7 +792,10 @@ public class SmartReplyView extends ViewGroup {


    enum SmartButtonType {
    enum SmartButtonType {
        REPLY,
        REPLY,
        ACTION
        ACTION,
        ANIMATED_ACTION,
        ANIMATED_REPLY

    }
    }


    @VisibleForTesting
    @VisibleForTesting
+342 −87
Original line number Original line Diff line number Diff line
@@ -15,7 +15,6 @@
package com.android.systemui.statusbar.policy;
package com.android.systemui.statusbar.policy;


import static android.view.View.MeasureSpec;
import static android.view.View.MeasureSpec;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertNull;
@@ -117,13 +116,20 @@ public class SmartReplyViewTest extends SysuiTestCase {
    private SmartActionInflaterImpl mSmartActionInflater;
    private SmartActionInflaterImpl mSmartActionInflater;
    private KeyguardDismissUtil mKeyguardDismissUtil;
    private KeyguardDismissUtil mKeyguardDismissUtil;


    @Mock private SmartReplyConstants mConstants;
    @Mock
    @Mock private ActivityStarter mActivityStarter;
    private SmartReplyConstants mConstants;
    @Mock private HeadsUpManager mHeadsUpManager;
    @Mock
    @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
    private ActivityStarter mActivityStarter;
    @Mock private SmartReplyController mSmartReplyController;
    @Mock
    @Mock private  KeyguardStateController mKeyguardStateController;
    private HeadsUpManager mHeadsUpManager;
    @Mock private  SysuiStatusBarStateController mStatusBarStateController;
    @Mock
    private NotificationRemoteInputManager mNotificationRemoteInputManager;
    @Mock
    private SmartReplyController mSmartReplyController;
    @Mock
    private KeyguardStateController mKeyguardStateController;
    @Mock
    private SysuiStatusBarStateController mStatusBarStateController;


    @Before
    @Before
    public void setUp() {
    public void setUp() {
@@ -207,7 +213,9 @@ public class SmartReplyViewTest extends SysuiTestCase {
        mKeyguardDismissUtil = new KeyguardDismissUtil(
        mKeyguardDismissUtil = new KeyguardDismissUtil(
                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
                    boolean requiresShadeOpen, boolean afterKeyguardGone) { }};
                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
            }
        };
        mSmartReplyInflater = new SmartReplyInflaterImpl(
        mSmartReplyInflater = new SmartReplyInflaterImpl(
                mConstants,
                mConstants,
                mKeyguardDismissUtil,
                mKeyguardDismissUtil,
@@ -623,12 +631,12 @@ public class SmartReplyViewTest extends SysuiTestCase {


    private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
    private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
        setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */,
        setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */,
                true /* useDelayedOnClickListener */);
                true /* useDelayedOnClickListener */, false /* animatedAction */);
    }
    }


    private void setSmartRepliesAndActions(
    private void setSmartRepliesAndActions(
            CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
            CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
            boolean useDelayedOnClickListener) {
            boolean useDelayedOnClickListener, boolean animatedAction) {
        mView.resetSmartSuggestions(mContainer);
        mView.resetSmartSuggestions(mContainer);
        Sequence<Button> inflatedReplies = SequencesKt.asSequence(
        Sequence<Button> inflatedReplies = SequencesKt.asSequence(
                inflateSmartReplies(choices, fromAssistant, useDelayedOnClickListener)
                inflateSmartReplies(choices, fromAssistant, useDelayedOnClickListener)
@@ -637,15 +645,37 @@ public class SmartReplyViewTest extends SysuiTestCase {
                createActions(actionTitles), fromAssistant);
                createActions(actionTitles), fromAssistant);
        Sequence<Button> inflatedSmartActions = SequencesKt.asSequence(
        Sequence<Button> inflatedSmartActions = SequencesKt.asSequence(
                IntStream.range(0, smartActions.actions.size())
                IntStream.range(0, smartActions.actions.size())
                        .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
                        .mapToObj(idx -> {
                            Button actionButton = mSmartActionInflater.inflateActionButton(
                                    mView,
                                    mView,
                                    mEntry,
                                    mEntry,
                                    smartActions,
                                    smartActions,
                                    idx,
                                    idx,
                                    smartActions.actions.get(idx),
                                    smartActions.actions.get(idx),
                                    useDelayedOnClickListener,
                                    useDelayedOnClickListener,
                                getContext()))
                                    getContext());

                            if (actionButton != null && animatedAction) {
                                ViewGroup.LayoutParams params = actionButton.getLayoutParams();
                                SmartReplyView.LayoutParams srvLayoutParams;

                                if (params instanceof SmartReplyView.LayoutParams) {
                                    srvLayoutParams = (SmartReplyView.LayoutParams) params;
                                } else if (params != null) {
                                    srvLayoutParams = (SmartReplyView.LayoutParams) mView.generateLayoutParams(params);
                                    actionButton.setLayoutParams(srvLayoutParams);
                                } else {
                                    srvLayoutParams = (SmartReplyView.LayoutParams) mView.generateDefaultLayoutParams();
                                    actionButton.setLayoutParams(srvLayoutParams); // Apply the new LayoutParams
                                }
                                // Set the button type to make it animated action
                                srvLayoutParams.mButtonType = SmartReplyView.SmartButtonType.ANIMATED_ACTION;
                            }

                            return actionButton;
                        })
                        .iterator());
                        .iterator());

        mView.addPreInflatedButtons(
        mView.addPreInflatedButtons(
                SequencesKt.toList(SequencesKt.plus(inflatedReplies, inflatedSmartActions)));
                SequencesKt.toList(SequencesKt.plus(inflatedReplies, inflatedSmartActions)));
        mView.setSmartRepliesGeneratedByAssistant(fromAssistant);
        mView.setSmartRepliesGeneratedByAssistant(fromAssistant);
@@ -728,6 +758,14 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertEqualPadding(expected, actual);
        assertEqualPadding(expected, actual);
    }
    }


    private static void assertAnimatedActionButtonShownWithEqualMeasures(
            View expected, View actual, int index) {
        assertReplyButtonShownWithEqualMeasures(expected, actual);
        SmartReplyView.LayoutParams params =
                (SmartReplyView.LayoutParams) actual.getLayoutParams();
        assertEquals("Button " + index + " should be of type ANIMATED_ACTION",
                SmartReplyView.SmartButtonType.ANIMATED_ACTION, params.mButtonType);
    }
    private static void assertReplyButtonShown(View view) {
    private static void assertReplyButtonShown(View view) {
        assertTrue(((SmartReplyView.LayoutParams) view.getLayoutParams()).isShown());
        assertTrue(((SmartReplyView.LayoutParams) view.getLayoutParams()).isShown());
    }
    }
@@ -1205,7 +1243,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);


        setSmartRepliesAndActions(
        setSmartRepliesAndActions(
                choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
                choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */,
                false /* not animated action */);
        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);


        // 395, 168
        // 395, 168
@@ -1231,7 +1270,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);


        setSmartRepliesAndActions(
        setSmartRepliesAndActions(
                choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
                choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */,
                false /* not animated action */);
        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);


        assertEqualMeasures(expectedView, mView);
        assertEqualMeasures(expectedView, mView);
@@ -1261,7 +1301,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);


        setSmartRepliesAndActions(
        setSmartRepliesAndActions(
                choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
                choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */,
                false /* not animated action */);
        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);


        assertEqualMeasures(expectedView, mView);
        assertEqualMeasures(expectedView, mView);
@@ -1332,4 +1373,218 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertReplyButtonShownWithEqualMeasures(
        assertReplyButtonShownWithEqualMeasures(
                expectedView.getChildAt(3), mView.getChildAt(3)); // a4
                expectedView.getChildAt(3), mView.getChildAt(3)); // a4
    }
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT)
    public void testMeasure_animatedReplies_areShownAndMeasuredCorrectly() {

        // Define 3 animated replies
        CharSequence animatedChoice0 = addAnnotationToChoice("animated_reply0", true, "attr0");
        CharSequence animatedChoice1 = addAnnotationToChoice("animated_reply1", true, "attr1");
        CharSequence animatedChoice2 = addAnnotationToChoice("animated_reply2", true, "attr2");

        CharSequence[] choicesToDisplayAndExpect =
                new CharSequence[]{animatedChoice0, animatedChoice1, animatedChoice2};
        String[] actions = new String[]{};

        ViewGroup expectedView = buildExpectedView(choicesToDisplayAndExpect, 1 /* lineCount */);
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        setSmartRepliesAndActions(
                choicesToDisplayAndExpect,
                actions,
                true /* fromAssistant */,
                true /* useDelayedOnClickListener */,
                false /* not animated action */);

        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        assertEqualLayouts(expectedView, mView);
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT)
    public void testMeasure_animatedActions_areShownAndMeasuredCorrectly() {

        // Define 3 animated actions
        String animatedAction0 = "animated_action0";
        String animatedAction1 = "animated_action1";
        String animatedAction2 = "animated_action2";

        String[] actions = new String[]{animatedAction0, animatedAction1, animatedAction2};
        CharSequence[] choices = new CharSequence[]{};

        ViewGroup expectedView = buildExpectedView(choices, 1 /* lineCount */,
                createActions(actions));
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        setSmartRepliesAndActions(
                choices,
                actions,
                true /* fromAssistant */,
                true /* useDelayedOnClickListener */,
                true /* animated action */);

        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        assertEqualMeasures(expectedView, mView);
        assertEqualLayouts(expectedView, mView);
        assertAnimatedActionButtonShownWithEqualMeasures(
                expectedView.getChildAt(0), mView.getChildAt(0), 0);
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));

        assertAnimatedActionButtonShownWithEqualMeasures(
                expectedView.getChildAt(1), mView.getChildAt(1), 1);
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));

        assertAnimatedActionButtonShownWithEqualMeasures(
                expectedView.getChildAt(2), mView.getChildAt(2), 2);
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));

    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT)
    public void testMeasure_animatedRepliesAndActions_showAllAnimatedRepliesAndHideActions() {

        // Define 2 animated replies
        CharSequence animatedChoice0 = addAnnotationToChoice("animated_reply0", true, "attr0");
        CharSequence animatedChoice1 = addAnnotationToChoice("animated_reply1", true, "attr1");

        CharSequence[] animatedChoices = new CharSequence[]{animatedChoice0, animatedChoice1};
        CharSequence[] allChoices =
                new CharSequence[]{"A Normal Smart Reply", animatedChoice0, animatedChoice1};
        String[] actions = new String[]{"action1", "action2", "action3"};

        // We expect to show all animated replies and no actions
        ViewGroup expectedView = buildExpectedView(animatedChoices, 1 /* lineCount */,
                createActions(new String[]{"action1"}));
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        setSmartRepliesAndActions(
                allChoices,
                actions,
                true /* fromAssistant */,
                true /* useDelayedOnClickListener */,
                false /* not animated action */);

        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        assertEqualLayouts(expectedView, mView);
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(0));
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT)
    public void testMeasure_animatedRepliesAndAnimatedActions_prioritizeAnimatedReplies() {

        // Define 2 animated replies
        CharSequence animatedChoice0 = addAnnotationToChoice("animated_reply0", true, "attr0");
        CharSequence animatedChoice1 = addAnnotationToChoice("animated_reply1", true, "attr1");

        CharSequence[] animatedChoices = new CharSequence[]{animatedChoice0, animatedChoice1};
        CharSequence[] allChoices =
                new CharSequence[]{"A Normal Smart Reply", animatedChoice0, animatedChoice1};
        String[] actions = new String[]{"action1", "action2", "action3"};

        // We expect to show all animated replies and no actions
        ViewGroup expectedView = buildExpectedView(animatedChoices, 1 /* lineCount */,
                createActions(new String[]{"action1"}));
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        setSmartRepliesAndActions(
                allChoices,
                actions,
                true /* fromAssistant */,
                true /* useDelayedOnClickListener */,
                true /* animated action */);

        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        assertEqualLayouts(expectedView, mView);
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(3));
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT)
    public void testMeasure_animatedRepliesAndActions_notEnoughSpace_showOnlyAnimatedReplies() {

        // Define 3 animated replies
        CharSequence animatedChoice0 = addAnnotationToChoice("animated_reply0", true, "attr0");
        CharSequence animatedChoice1 = addAnnotationToChoice("animated_reply1", true, "attr1");
        CharSequence animatedChoice2 = addAnnotationToChoice("animated_reply2", true, "attr2");

        CharSequence[] allChoices =
                new CharSequence[]{"A Normal Smart Reply", animatedChoice0, animatedChoice1,
                        animatedChoice2};
        String[] actions = new String[]{"action1", "action2", "action3"};

        // We expect to show only 2 animated replies
        CharSequence[] expectedAnimatedChoices =
                new CharSequence[]{animatedChoice0, animatedChoice1,
                        animatedChoice2};
        ViewGroup expectedView = buildExpectedView(expectedAnimatedChoices, 1 /* lineCount */);
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        setSmartRepliesAndActions(
                allChoices,
                actions,
                true /* fromAssistant */,
                true /* useDelayedOnClickListener */,
                false /* not animated action */);

        mView.measure(
                MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
                MeasureSpec.UNSPECIFIED);

        assertEqualLayouts(expectedView, mView);
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT)
    public void testMeasure_animatedActionsAndSmartReplies_showAllAnimatedActionsAndHideReplies() {

        // Define 3 animated actions
        String animatedAction0 = "animated_action0";
        String animatedAction1 = "animated_action1";
        String animatedAction2 = "animated_action1";

        String[] actions = new String[]{animatedAction0, animatedAction1, animatedAction2};
        CharSequence[] choices = new CharSequence[]{"reply1", "reply2", "reply3"};


        // We expect to show all animated actions and no replies
        ViewGroup expectedView = buildExpectedView(new CharSequence[]{}, 1 /* lineCount */,
                createActions(actions));
        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        setSmartRepliesAndActions(
                choices,
                actions,
                true /* fromAssistant */,
                true /* useDelayedOnClickListener */,
                true /* animated action */);

        mView.measure(
                MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
                MeasureSpec.UNSPECIFIED);

        assertEqualMeasures(expectedView, mView);
        assertEqualLayouts(expectedView, mView);

        // Verify that all replies are hidden
        assertReplyButtonHidden(mView.getChildAt(0));
        assertReplyButtonHidden(mView.getChildAt(1));
        assertReplyButtonHidden(mView.getChildAt(2));
    }
}
}