Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +66 −22 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading @@ -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 */ ) ) } } Loading @@ -174,7 +210,7 @@ constructor( delayOnClickListener, delayOnClickListener, ) ) } } } ?: emptySequence() } ?.toList() ?: emptyList() val smartActionButtons = val smartActionButtons = newSmartReplyState.smartActions?.let { smartActions -> newSmartReplyState.smartActions?.let { smartActions -> Loading @@ -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 Loading Loading @@ -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 } } } } } Loading Loading @@ -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 } } } } } Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +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; Loading Loading @@ -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<>(); Loading Loading @@ -764,7 +792,10 @@ public class SmartReplyView extends ViewGroup { enum SmartButtonType { enum SmartButtonType { REPLY, REPLY, ACTION ACTION, ANIMATED_ACTION, ANIMATED_REPLY } } @VisibleForTesting @VisibleForTesting Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +342 −87 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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, Loading Loading @@ -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) Loading @@ -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); Loading Loading @@ -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()); } } Loading Loading @@ -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 Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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)); } } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +66 −22 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading @@ -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 */ ) ) } } Loading @@ -174,7 +210,7 @@ constructor( delayOnClickListener, delayOnClickListener, ) ) } } } ?: emptySequence() } ?.toList() ?: emptyList() val smartActionButtons = val smartActionButtons = newSmartReplyState.smartActions?.let { smartActions -> newSmartReplyState.smartActions?.let { smartActions -> Loading @@ -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 Loading Loading @@ -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 } } } } } Loading Loading @@ -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 } } } } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +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; Loading Loading @@ -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<>(); Loading Loading @@ -764,7 +792,10 @@ public class SmartReplyView extends ViewGroup { enum SmartButtonType { enum SmartButtonType { REPLY, REPLY, ACTION ACTION, ANIMATED_ACTION, ANIMATED_REPLY } } @VisibleForTesting @VisibleForTesting Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +342 −87 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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, Loading Loading @@ -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) Loading @@ -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); Loading Loading @@ -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()); } } Loading Loading @@ -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 Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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)); } } }